Laravel multi nested withCount() - laravel

I have an Appointment Table which can have many appointments for a Property table.
Property has many appointments with specific type. Then a property can have many structures and each structure can have many payments.
I need to find sum of the payments for each structure. it works for the first nested relation.
Here is my code
$appointments = Appointment::whereUserId(auth()->id())
->where('appointment_type','evaluation')
->whereHas('property.structures', function ($qq) {
$qq->where('production_milestones','post_closing');
})
->with([
'property',
'property.structures',
'property.structures.payments',
'property.structures.estimates' => function ($query) {
return $query->where('is_signed', 1);
},
'property.structures.estimates.customizeProposal',
'property.structures.structureAssessment'
])
->withCount([
'payments as sum' => function($query) {
$query->select(DB::raw('SUM(amount)'));
}
])
->get();

Related

Computing with nested values from subqueries in Laravel

I'm struggling for days now with the following issue in Laravel:
I've got three models:
Equipment (has many batches)
Batch (belongs to equipment, and a location, and has many transactions)
Transaction (belongs to a batch)
In the batch model I have a scope to calculate the current quantity in that batch based on the transactions since the last transaction with the type "COUNT" (there are three types, COUNT, IN and OTU):
public function scopeWithCurrentQt($query)
{
$query->addSelect(['current_qt' => Transaction::selectRaw('SUM(hospital_transactions.qt) AS qt_sum')
->whereColumn('batch_id', '=', 'hospital_batches.id')
->where('issued_at','>=',function ($subquery) {
$subquery->select('hospital_transactions.issued_at')
->from('hospital_transactions')
->whereColumn('batch_id', '=', 'hospital_batches.id')
->where('hospital_transactions.type','=','COUNT')
->latest()
->take(1);
})
])->withCasts(['current_qt' => 'integer']);
}
Now I want the Equipment model to calculate the total quantities based on the current quantities from the batches. In the code you also see a division in various locations, that's because there should be a distinction between those as well.
Now this can be achieved pretty easily by appending an attribute to the model:
public function getQtActualAttribute(): object
{
$locations = $this->getHospitalStandardLocations();
$qt_inventory = $this->batches->whereNotIn('location_id',$locations->raft)
->whereNotIn('location_id',$locations->fak)
->sum('current_qt');
$qt_fak = $this->batches->whereIn('location_id',$locations->fak)
->sum('current_qt');
$qt_raft = $this->batches->whereIn('location_id',$locations->raft)
->sum('current_qt');
return (object) [
'check' => $locations->ignore_fak ? $qt_inventory : $qt_inventory + $qt_fak,
'inventory' => $qt_inventory,
'fak' => $qt_fak,
'raft' => $qt_raft,
'total' => $qt_inventory + $qt_fak + $qt_raft
];
}
But the difficulty is, I can't filter on these values, unless I'm loading all the models, which is not preferable. So I came to thing, let's add a scope with subqueries as well.
I have tried A LOT of options. The latest version I have is this:
public function scopeWithQts($query){
$locations = $this->getHospitalStandardLocations();
$query->addSelect([
'qt_inventory' => function ($query) use ($locations) {
$query->selectRaw('sum(current_qt)')
->from('hospital_batches')
->whereNotIn('location_id', $locations->raft)
->whereNotIn('location_id', $locations->fak)
->whereColumn('equipment_id', 'hospital_equipment.id');
},
'qt_fak' => function ($query) use ($locations) {
$query->selectRaw('sum(current_qt)')
->from('hospital_batches')
->whereIn('location_id', $locations->fak)
->whereColumn('equipment_id', 'hospital_equipment.id');
},
'qt_raft' => function ($query) use ($locations) {
$query->selectRaw('sum(current_qt)')
->from('hospital_batches')
->whereIn('location_id', $locations->raft)
->whereColumn('equipment_id', 'hospital_equipment.id');
}
]);
return $query;
}
But basically, all are resulting in the following error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'current_qt' in 'field list'
The function from the controller is the following:
$equipment = Equipment::with(['batches','batches.location'])
->orderByCode()
->withExpiryDates()
->withQts()
->when($expiryDates, function ($query) use ($expiringBefore){
$query->whereHas('batches', function($query) use ($expiringBefore){
$query->expiringBefore($expiringBefore);
});
});
Anybody a suggestion how to solve this? Thanks!

Laravel query orderBy nested relationship

I have the following relationship:
Unit (HasMany)-> Users -> (BelongsTo) -> Position
I am trying to return an array of units with users, where users are sorted by their position. The property in the position model is 'order' that I would like to use as the sort field. I have attempted the following:
return Unit::query()->ordered()->with(['users' => function($query) {
$query->with(['position' => function($query) {
$query->orderBy('order');
}]);
}])->get();
You can not order by nested relationship just using with() method. You need to join the relation first. So the code should be:
return Unit::query()->ordered()->with([
'users' => function ($query) {
$query->join('positions', 'positions.id', '=', 'users.position_id');
$query->orderBy('positions.order');
}
])->get();
or another way is order using laravel collection sortBy
$ordered_units = Unit::query()->ordered()->with(['users' => function($query) {
$query->with(['position' => function($query) {
$query->orderBy('order');
}]);
}])->get();
return $ordered_units->sortBy('users.position.order');

Spatie Query Builder filter nested relations

Using Spatie Query Builder I would like to extend the filters on nested relationships
The following code fetches locations with the relation jobs counts which works fine. I would like to extend the filter on job relation to can query relation job.level which is a many to many. How would I go for?
the filter would look like /options/?filter[job.level]=2
Germany - 12,
Italy - 34
$locations = QueryBuilder::for(JobLocation::class)
->select('id as value', 'title', 'country_id')
->orderBy('title')
->withCount(['job as counts' => function ($q) {
$q->whereNull('deleted_at');
}])
->whereHas('country', function ($q) {
$q->where('id', '=', 80);
})
->allowedAppends(
'job',
)
->allowedIncludes('job', 'job.level',)
->allowedFilters([
AllowedFilter::exact('job.language_id'),
AllowedFilter::exact('job.level', 'job.level.id')
])->get();
I think you are looking for the whereHas function. See the docs for querying relation existence here. But you are already doing this for the country relationship, this is not any different.
So I think yours would look something like:
$locations = QueryBuilder::for(JobLocation::class)
->select('id as value', 'title', 'country_id')
->orderBy('title')
->withCount(['job as counts' => function ($q) {
$q->whereNull('deleted_at');
}])
->whereHas('country', function ($q) {
$q->where('id', '=', 80);
})
//New query constraint here
->whereHas('job', function ($q) {
$q->where('level', '=', 2);
})
->allowedAppends(
'job',
)
->allowedIncludes('job', 'job.level',)
->allowedFilters([
AllowedFilter::exact('job.language_id'),
AllowedFilter::exact('job.level', 'job.level.id')
])->get();

how to select specific fields from the tables in laravel

This works but it gives all the fields of account table but only few are required.
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account'
])
->get();
This gives account: null
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account'=> function ($query) {
$query->addSelect('id', 'account'); // if this line is commented, all the fields are returned but I just need few fields.
}
])
->get();
Member model:
public function account()
{
return $this->hasOne(Account::class);
}
Account model:
public function member()
{
return $this->belongsTo(Member::class);
}
How can I get only the required fields of account table?
You can define the relationship and set it to give you specific columns like this.
on Member model
public function account()
{
return $this->hasOne(Account::class)->select(['id', 'account']);
}
You can try to define required fields in with:
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account:id,number'
])
->get();
Your are close you just need to select the foreign key column that points to your account model , I guess it would be account_id, So you basically need to select 3 columns from your table which are select(['id', 'name','account_id'])
$data = Loan::with(['member' => function ($query) {
$query->select(['id', 'name','account_id'])
->with(['account'=> function($query){
$query->select(['id','name']);
}]);
}])
->get();
So if you have another nested relation account -> type and you need specific columns from type so you need to select the foreign key column from accounts models as
with(['account'=> function($query){
$query->select(['id','name','type_id'])
->with(['type'=> function($query){
$query->select(['id','name']);
}]);
}]);

Laravel filter relation's data by other relations data

I am trying to filter my tickets.tips.drawDates relation's data by other relation's column (results.draw_date) in an eager-loading query. Does anybody have any advice on how to accomplish that?
$products = Product::with([
'results' => function ($query) use ($drawDates) {
return $query->whereBetween('draw_date', $drawDates);
},
'tickets' => function ($query) use ($drawDateFrom) {
return $query->whereDate('valid_until', '>=', $drawDateFrom)->where('status', 'pending');
},
'tickets.tips',
'tickets.tips.drawDates' => function($query) {
return $query->whereNull('status')->whereDate('draw_date', 'HERE SHOULD BE draw_date COLUMN FROM results RELATION');
},
'prizes'
])->get();
You could try with the whereColumn() function, it is used to verify that two columns are equal, something like that:
return $query->whereNull('status')->whereColumn('draw_date', 'results.draw_date');

Resources