Laravel recursively get all offsprings of a taxonomy - laravel

I have a table called taxonomies which stores all the taxonomies of products for my ecommerce site. This is how the table looks like:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(20) | NO | UNI | NULL | |
| parent_id | int(10) unsigned | YES | MUL | NULL | |
| num_products | smallint(6) | NO | | 0 | |
+--------------+------------------+------+-----+---------+----------------+
Now given a taxonomy id, how can I recursively get all its offspring ids? My effort so far:
public function getAllTaxonomyOffspringIds($parent_id, $offspring_ids = array()) {
$taxonomy = Taxonomy::find($parent_id);
if (!is_null($taxonomy)) {
$first_generation_ids = $taxonomy->subtaxonomies()->lists('id');
$offspring_ids = array_merge($offspring_ids, $first_generation_ids);
if (count($first_generation_ids) > 0) {
foreach ($first_generation_ids as $child_id) {
self::getAllTaxonomyOffspringIds($child_id, $offspring_ids);
}
// foreach ($first_generation_ids as $child_id) {
// $child_taxonomy = Taxonomy::find($child_id);
// if (!is_null($child_taxonomy)) {
// $second_generation_ids = $child_taxonomy->subtaxonomies()->lists('id');
// $offspring_ids = array_merge($offspring_ids, $second_generation_ids);
// if (count($second_generation_ids)) {
// }
// }
// }
}
}
return $offspring_ids;
}
But it gives me only the ids of first generation children...

This is actually a width first tree traversal algorithm, and here's the code I've managed to figure out how to:
public function getTaxonomyChildren($id) {
$taxonomy = Taxonomy::find($id);
$child_ids = array();
if (!is_null($taxonomy)) {
$child_ids = $taxonomy->subtaxonomies()->lists('id');
}
return $child_ids;
}
public function getAllTaxonomyOffspringIds($parent_id, $offspring_ids = array()) {
$child_ids = self::getTaxonomyChildren($parent_id);
$offspring_ids = array_merge($offspring_ids, $child_ids);
while (count($child_ids) > 0) {
$temp_ids = array();
foreach ($child_ids as $child_id) {
$temp_ids = array_merge($temp_ids, self::getTaxonomyChildren($child_id));
}
$child_ids = $temp_ids;
$offspring_ids = array_merge($offspring_ids, $child_ids);
}
return $offspring_ids;
}

Related

LINQ select duplicate object values

I have a table:
Name | account info| AccountNumber
-----| ------------| ------
abc | IT | 3000
bdc | Desk | 2000
sed | Kitchen | 3000
afh | work | 4000
hsfs | home | 2000
I want to achieve something like this:
Name | account info| DisguiseInfo
-----| ------------| ------
abc | IT | Acc1
bdc | Desk | Acc2
sed | Kitchen | Acc1
afh | work | Acc3
hsfs | home | Acc2
I tried doing this:
int count = 1;
var disguise = listResults.GroupBy(x => x.ID).Select(y =>
y.First()).Distinct();
foreach (var i in disguise)
{
i.DisguiseName = "Acc " + count;
count++;
}
Which gives a results like this (very close to what I want):
Name | account info| DisguiseInfo
-----| ------------| ------
abc | IT | Acc1
bdc | Desk | Acc2
sed | Kitchen |
afh | work | Acc3
hsfs | home |
The problem with that is that, it doesn't give the ability to add the same string value 'Acc1' to the same duplicate value in the list, (the rest of the table comes blank only the fist values gets replaced), So how do I replace the entire value with matching IDs?
//EDIT
the data is being populated using sqlcommand in a class called SQLQuery, in this class there's a method called Account which execute like this:
SqlDataReader reader = command.ExecuteReader();
List<ViewModel> returnList = new List<ViewModel>();
if (reader.HasRows)
{
while (reader.Read())
{
ViewModel vm = new ViewModel();
vm.Name = reader.GetString(2);
vm.AccountInfo= reader.GetString(3);
vm.AccountNumber = reader.GetInt32(4);
returnList.Add(vm)
}
}
so this method return the first table above no issues.
In my controller action, is where I want to perhaps copy the SQLQuery return list into another list to filter so I'm doing (in the action method):
public async Task<IActionResult> DisguiseAction(string accNum)
{
List<ViewModel> executeSQL = new List<ViewModel>();
SQLQuery getQuery = new SQLQuery();
executeSQL = getQuery.Account(accNum); //at this point the sql
//gets executed with the correct value. Now I need to disguise the
//value. which I did
int count = 1;
var disguise = listResults.GroupBy(x => x.ID).Select(y =>
y.First()).Distinct();
foreach (var i in disguise)
{
i.DisguiseName = "Acc " + count;
count++;
}
}
Your problem is the .Distinct() call, that only takes the first element out of each group. Due to the fact, that you need to memoize all already seen values, it is easier to use a dictionary to hold the already mapped values. One possibility could be:
var accounts = new List<Account>
{
new Account { Name = "abc", Department = "IT", AccountInfo = 3000 },
new Account { Name = "bdc", Department = "Desk", AccountInfo = 2000 },
new Account { Name = "sed", Department = "Kitchen", AccountInfo = 3000 },
new Account { Name = "afh", Department = "work", AccountInfo = 4000 },
new Account { Name = "hsfs", Department = "home", AccountInfo = 2000 },
};
var mappings = new Dictionary<int, string>();
var summary = accounts
.Select(acc => new AccountSummary
{
Name = acc.Name,
Department = acc.Department,
DisguiseInfo = GetOrAddMapping(acc.AccountInfo, mappings)
})
.ToList();
foreach (var item in summary)
{
Console.WriteLine(JsonSerializer.Serialize(item));
}
And the helper method would in this case be:
private static string GetOrAddMapping(int accountInfo, Dictionary<int, string> mappings)
{
if (!mappings.TryGetValue(accountInfo, out var value))
{
value = $"Acc{mappings.Count + 1}";
mappings.Add(accountInfo, value);
}
return value;
}
using System;
using System.Collections.Generic;
public class Ent
{
public Ent(string a, string b, string c)
{
name = a;
location = b;
id = c;
}
public string name;
public string location;
public string id;
public override string ToString()
{
return $"{name} | {location} | {id}";
}
}
public class Program
{
public static void Main()
{
var input = new List<Ent>
{
new Ent("abc", "IT", "3000"),
new Ent("bcd", "Desk", "2000"),
new Ent("sed", "Kitchen", "3000"),
new Ent("afh", "work", "4000"),
new Ent("hsf", "home", "2000"),
};
var output = input
.GroupBy(x => x.id) // x is of type Ent
.SelectMany(y => // y is of type IGrouping<string, IEnumerable<Ent>>
y.Select(z => // z is of type Ent
new Ent(z.name, z.location, "Acc" + y.Key.Substring(0, 1))));
foreach(var line in output)
Console.WriteLine(line);
}
}
Gives an output that looks like:
abc | IT | Acc3
sed | Kitchen | Acc3
bcd | Desk | Acc2
hsf | home | Acc2
afh | work | Acc4
This code works using GroupBy on the id, then unroll the groups using SelectMany, but now we have the Key for each group. So when unrolling, re-create each line, but replace the id with a transformed value of the Key.
After grouping by AccountInfo, you could take advantage of the .SelectMany() overload that provides an indexer for the source element (i.e. an indexer for the AccountInfo values).
In the following example, I am assuming that you have two separate classes for the original (identifiable) accounts and the disguised accounts, e.g.:
public class BasicAccount
{
public string Name { get; set; }
public string AccountType { get; set; }
}
public class Account : BasicAccount
{
public int AccountInfo { get; set; }
}
public class DisguisedAccount : BasicAccount
{
public string DisguisedInfo { get; set; }
}
If your original accounts are collected in a variable List<Account> accounts as such:
List<Account> accounts = new()
{
new() { Name = "abc", AccountType = "IT", AccountInfo = 3000 },
new() { Name = "bdc", AccountType = "Desk", AccountInfo = 2000 },
new() { Name = "sed", AccountType = "Kitchen", AccountInfo = 3000 },
new() { Name = "afh", AccountType = "work", AccountInfo = 4000 },
new() { Name = "hsfs",AccountType = "home", AccountInfo = 2000 }
};
, your disguised accounts could be produced as follows:
IEnumerable<DisguisedAccount> disguisedAccounts = accounts
.GroupBy(a => a.AccountInfo)
.SelectMany(( accountsByInfo, counter ) => accountsByInfo
.Select(account => new DisguisedAccount
{
Name = account.Name,
AccountType = account.AccountType,
DisguisedInfo = $"Acc{counter}"
}));
Note: Using this approach, you lose the ordering given by the original accounts collection. The resulting collection is:
Name
AccountType
DisguisedInfo
abc
IT
Acc1
sed
Kitchen
Acc1
bdc
Desk
Acc2
hsfs
home
Acc2
afh
work
Acc3
Example fiddle here.

Mybatis mapper returns null value. in spring -rest-mybatis-mysql-gradle

{
"code": 1000,
"message": "Success",
"timestamp": 1596860735157,
"data": [
{
"id": 1,
"contentName": null,
"createdTime": 0,
"contentPath": null
},
{
"id": 2,
"contentName": null,
"createdTime": 0,
"contentPath": null
},
{
"id": 3,
"contentName": null,
"createdTime": 0,
"contentPath": null
}
]
}
here on t_content_library=>
id:bigint,content_name:varchar(450),content_path:varchar(500),created_time:bigint.
mysql> select * from t_content_library;
+----+----------------------------------------------------+----------------------------------------------------------------------------------+---------------+
| id | content_name | content_path | created_time |
+----+----------------------------------------------------+----------------------------------------------------------------------------------+---------------+
| 1 | pexels-jack-redgate-3014019.jpg | C:\Users\DELL\Desktop\content\pexels-jack-redgate-3014019.jpg | 1596783953545 |
| 2 | pexels-thiago-matos-2335275.jpg | C:\Users\DELL\Desktop\content\pexels-thiago-matos-2335275.jpg | 1596784207089 |
| 3 | louis-hansel-shotsoflouis-2gwghEzGp4g-unsplash.jpg | C:\Users\DELL\Desktop\content\louis-hansel-shotsoflouis-2gwghEzGp4g-unsplash.jpg | 1596784491699 |
| 4 | jessica-delp-p1P_e86R2DI-unsplash.jpg | C:\Users\DELL\Desktop\content\jessica-delp-p1P_e86R2DI-unsplash.jpg | 1596784579313 |
| 5 | patrick-untersee-OrT5-yC95j0-unsplash.jpg | C:\Users\DELL\Desktop\content\patrick-untersee-OrT5-yC95j0-unsplash.jpg | 1596784602268 |
| 6 | jun-zhao-XWQ15ixxRjE-unsplash.jpg | C:\Users\DELL\Desktop\content\jun-zhao-XWQ15ixxRjE-unsplash.jpg | 1596784616456 |
| 7 | jo-jo-mPM-x0zPhok-unsplash.jpg | C:\Users\DELL\Desktop\content\jo-jo-mPM-x0zPhok-unsplash.jpg | 1596784632238 |
| 8 | melnychuk-nataliya-8J6uuvsdj-4-unsplash.jpg | C:\Users\DELL\Desktop\content\melnychuk-nataliya-8J6uuvsdj-4-unsplash.jpg | 1596784644961 |
| 9 | nathan-anderson-UhagOo7IOyc-unsplash.jpg | C:\Users\DELL\Desktop\content\nathan-anderson-UhagOo7IOyc-unsplash.jpg | 1596784877626 |
| 10 | gayathri-sri-ptbKY_b1ROc-unsplash.jpg | C:\Users\DELL\Desktop\content\gayathri-sri-ptbKY_b1ROc-unsplash.jpg | 1596784887985 |
| 11 | Depositphotos_95439918_xl-2015_1920x1920.jpg | C:\Users\DELL\Desktop\content\Depositphotos_95439918_xl-2015_1920x1920.jpg | 1596907309367 |
+----+----------------------------------------------------+----------------------------------------------------------------------------------+---------------+
model
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#ToString
public class ContentLibrary {
int id;
String contentName;
long createdTime;
String contentPath;
}
controller
#RequestMapping(path = "content/library/")
#RestController
public class ContentLibraryController {
#Autowired
ContentLibraryService contentLibraryService;
#PostMapping("")
public Response readLibrary() {
List<ContentLibrary> contentLibraries =
contentLibraryService.readContentLibrary();
return ResponseBuilder.buildSuccessResponse(contentLibraries);
}
}
service
#Service
public class ContentLibraryService {
#Autowired
ContentLibraryMapper contentLibraryMapper;
ContentLibrary content = new ContentLibrary();
public List<ContentLibrary> readContentLibrary() {
return contentLibraryMapper.readAllLibraryContent();
}
}
mapper
#Repository
#Mapper
public interface ContentLibraryMapper {
#Select("SELECT
* FROM
t_content_library")
List<ContentLibrary> readAllLibraryContent();
}
I got a solution which is not the exact solution to implement a long list of columns. It is nothing but by adding a result set to map the result which is.
#Results({
#Result(property ="id",column = "id"),
#Result(property ="contentName",column = "content_name"),
#Result(property ="contentPath",column = "content_path"),
#Result(property ="createdTime",column = "created_time"),
})
public List<ContentLibrary> readAllLibraryContent();
You need to enable the MyBatis function that maps underscores to camel case.
If you are using Spring Boot, add the following to application.properties:
mybatis.configuration.map-underscore-to-camel-case=true

Can spock mock dynamic objects

As you can see, I tried to mock response dynamically.
underTest.getByTaskIds(taskIds) will pass a list of taskIds, inside the method I will invoke underTest.getByTaskId(taskId, channelId, false) separately.
But this won't work.
when:
def actual = underTest.getByTaskIds(taskIds)
then:
taskIds.forEach({ taskId ->
underTest.getByTaskId(taskId, channelId, false) >> mockResp.get(taskId)
})
actual.size() == expectedResultSize
where:
taskIds | mockResp | expectedResultSize
[] as Set<UUID> | [key: value] | 1
[UUID.randomUUID()] as Set<UUID> | [key: value] | 1
[UUID.randomUUID(), UUID.randomUUID()] as Set<UUID> | [key: value] | 2
Example
class UnderTest:
void taskId(id){
do something
}
void taskIds(ids)->
{
this.taskId(id)
}

Laravel handling race conditions

I am trying to implement a scheduling system of sort. So I have a database design of TimeBlock with intervals of 15 minutes per row. The actual database will be more complicated and the table below is just to illustrate my case.
AvailableTime Table
id | date | start_time | end_time | doctor_id | status
TimeBlock Table
id | appointment_id | available_time_id | start_time | end_time | status
Eg of AvailableTime
id | date | start_time | end_time | doctor_id | status
1 | 2018-06-18 | 08:00:00 | 09:00:00 | 1 | Active
Eg of TimeBlock
id | appointment_id | available_time_id | start_time | end_time | status
1 | null | 1 | 08:00:00 | 08:15:00 | Active
2 | null | 1 | 08:15:00 | 08:30:00 | Active
So when a user wants to book a time, the system will check table based on the start_time and the status and if the column appointment_id is null. If the conditions are met, then the update should occur.
My problem occurs when two users tries to pick the same time concurrently. My validation will pass and then either one of the user's entry will override the other user's entry. How do I handle this problem? I've tried using laravel's pessimistic locking (sharedLock and lockForUpdate) but to no avail. Not sure I'm using it wrongly or what.
public function create() {
if (request()->has('member_id')) {
$member_id = request('member_id');
} else {
$member_id = get_member_id();
}
try {
DB::beginTransaction();
$member = Member::find($member_id);
$member_treatment = DB::table('Member_Treatment')->where('id', request('member_treatment_id'))->first();
$treatment = Treatment::find($member_treatment->treatment_id);
$date = Carbon::parse(request('date'));
$start_time = Carbon::parse(request('time'));
$end_time = $start_time->copy()->addMinutes($treatment->durationRequired);
$member_package_id = request('member_package_id');
$doctor = Doctor::find(request('doctor_id'));
$available_time = AvailableTime::available($doctor, $treatment, $date, $start_time)->first();
// if (!$this->validate($date, $start_time, $doctor->id)) {
// DB::rollback();
// return false;
// }
$data = [
'doctor_id' => request('doctor_id'),
'member_id' => $member_id,
'treatment_id' => $member_treatment->treatment_id,
'member_treatment_id' => $member_treatment->id,
'member_package_id' => request('member_package_id'),
'available_time_id' => $available_time->id,
'date' => $date->format('Y-m-d'),
'start_time' => $start_time->format('H:i:s'),
'end_time' => $end_time->format('H:i:s'),
'status' => Appointment::$pending,
'created_by' => Auth::id() ?: $member->user_id
];
if (request()->has('remarks'))
$data['remark'] = request('remarks');
if (request()->has('admin_remark'))
$data['admin_remark'] = request('admin_remark');
$appointment = Appointment::create($data);
$this->update_timeblocks($appointment);
$this->update_member_package($appointment, false);
DB::commit();
return $appointment;
} catch (\Exception $e){
DB::rollback();
dd($e);
}
}
protected function update_timeblocks(Appointment $appointment) {
$start_time = Carbon::parse($appointment->start_time);
// add buffer
$end_time = Carbon::parse($appointment->end_time)
->addMinutes(Appointment::$buffer);
// subtract interval to get the previous timeblock
$end_time->subMinutes(TimeBlock::$interval);
// retrieve all
$timeblocks = DB::table('TimeBlockMaster')
->where('available_time_id', $appointment->available_time_id)->get();
foreach ($timeblocks as $key => $timeblock) {
$time = Carbon::parse($timeblock->start_time);
$block_starttime = Carbon::parse($timeblock->start_time);
$block_endtime = $start_time->copy()->addMinutes($appointment->treatment->duration);
if ($time >= $start_time && $time < $end_time) {
$timeblock->appointment_id = $appointment->id;
$timeblock->end_time = $block_endtime;
$timeblock->available_duration = $block_starttime->diffInMinutes($block_endtime);
$timeblock->status = TimeBlock::$reserved;
}
if ($time == $end_time) {
$timeblock->appointment_id = $appointment->id;
$timeblock->end_time = $block_endtime;
$timeblock->available_duration = $block_starttime->diffInMinutes($block_endtime);
$timeblock->status = TimeBlock::$buffer;
}
}
$index_range = [];
$start = 0;
$end = 0;
// get the index of timeblocks that have appointment
for ($i = 0; $i < count($timeblocks); $i++) {
if (($i+1) < count($timeblocks)) {
$current = $timeblocks[$i]->appointment_id;
$next = $timeblocks[$i+1]->appointment_id;
if ($current != $next) {
$end = $i;
$index_range[] = ['start' => $start, 'end' => $end];
$start = $i+1;
}
} else {
$index_range[] = ['start' => $start, 'end' => $i];
}
}
$index = 0;
foreach ($index_range as $range) {
$endtime = Carbon::parse($timeblocks[$range['end']]->start_time)->addMinutes(TimeBlock::$interval);
$index = $range['start'];
for ($i = $index; $i <= $range['end']; $i++ ) {
$starttime = Carbon::parse($timeblocks[$i]->start_time);
DB::table('TimeBlockMaster')->where('id', $timeblocks[$i]->id)
->update([
'appointment_id' => $timeblocks[$i]->appointment_id,
'available_duration' => $starttime->diffInMinutes($endtime),
'start_time' => $timeblocks[$i]->start_time,
'end_time' => $endtime->format('H:i:s'),
'status' => $timeblocks[$i]->status
]);
}
}
}
I would try adding something like this to "reserve" the appointment
$date = Carbon::parse(request('date'));
$start_time = Carbon::parse(request('time'));
$available_time = AvailableTime::join('time_block', 'available_time.id', '=', 'time_block.available_time_id')
->where('available_time.date', $date)
->where('time_block.start_time', $start_time)
->where('available_time.doctor_id', request('doctor_id') )
->value('time_block.id');
if (empty($available_time) || blank($available_time) ) {
return false; // Add your return
}
$time_block= TimeBlock::find($available_time);
$time_block->update([
'appointment_id' => 0
]);
If you add this to start (change where needed) you should resolve most of your problem as this will effectively reserve the time for the first person to reserve it and reduces the time that you can have another person book the same appointment time.

Linq group by year, month and employeeid then sum total working hours

I'm using this code to group the rows by year, month and employeeId.
var dtrSummary = from dtr in db.DTRs
group dtr by new { dtr.Date.Year, dtr.Date.Month, dtr.EmployeeId } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
NoOfDays = g.Count(),
FullName = db.EmployeeRates.Where(e => e.Id == g.Key.EmployeeId).Select(e => e.FullName).FirstOrDefault(),
Time1 = // Get total hours between two datetime
// DateTime timein = new DateTime(year, month, day, tihh1, timm1, 0);
// DateTime timeout = new DateTime(year, month, day, tohh1, tomm1, 0);
// Timespan totalTime1 = timeout - timein;
};
But i don't know how to get total hours using linq group by like you see above.
This is my table:
+----------------------------------------------------------------------+
| Date | Timeinhh | Timeinmm | Timeouthh | Timeoutmm | EmployeeId |
+----------------------------------------------------------------------+
| 10/1/2015 | 9 | 0 | 18 | 0 | 1 |
| 10/2/2015 | 9 | 0 | 18 | 0 | 2 |
| 10/3/2015 | 9 | 0 | 18 | 0 | 1 |
| 10/4/2015 | 9 | 0 | 18 | 0 | 2 |
+----------------------------------------------------------------------+
You might want to calculate each hours before grouping DTRs.
var DTRs2 = db.DTRs.ToList().Select(d => new
{
Date = d.Date,
Hours = new DateTime(d.Date.Year, d.Date.Month, d.Date.Day, d.Timeouthh, d.Timeoutmm, 0)
.Subtract(new DateTime(d.Date.Year, d.Date.Month, d.Date.Day, d.Timeinhh, d.Timeinmm, 0)).TotalHours,
EmployeeId = d.EmployeeId
}).ToList();
var dtrSummary =
(from dtr in DTRs2
group dtr by new { dtr.Date.Year, dtr.Date.Month, dtr.EmployeeId } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
NoOfDays = g.Count(),
EmpId = g.Key.EmployeeId,
FullName = db.EmployeeRates.Where(e => e.Id == g.Key.EmployeeId).Select(e => e.FullName).FirstOrDefault(),
Time1 = g.Sum(dtr => dtr.Hours) // Get total hours between two datetime
});
I also recommend to define another class/table like this rather than using anonymous type every time:
public class DTRDetails
{
public DTR DTR { get; set; }
public EmployeeRate EmployeeRate { get; set; }
public string FullName
{
get { return EmployeeRate.FullName; }
}
public int Hours
{
get
{
return (int)(new DateTime(DTR.Date.Year, DTR.Date.Month, DTR.Date.Day, DTR.Timeinhh, DTR.Timeinmm, 0)
.Subtract(new DateTime(DTR.Date.Year, DTR.Date.Month, DTR.Date.Day, DTR.Timeouthh, DTR.Timeoutmm, 0))).TotalHours;
}
}
public int Hours2
{
get
{
// return something
}
}
public int Hours3
{
get { return Hours - Hours2; }
}
}

Resources