Laravel filter nested relationship - php

So I have three models: Volunteer, Task and Payment. A Volunteer can have many (has many relationship) tasks and a task can have many (another has many relationship) payments.
class Volunteer
public function tasks()
{
return $this->hasMany(Task::class);
}
class Task
public function volunteer()
{
return $this->belongsTo(Volunteer::class);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
class Payment
public function task() {
return $this->belongsTo(Task::class);
}
Now I want to query all volunteers with unpaid/ partially paid tasks. So, basically I want to filter a volunteer's tasks where each task's amount should equal the sum of all payments linked to that particular task.
I tried using whereHas and with but I don't seem to be able to filter the tasks properly.
I've managed to do it with joins but was wondering if it's possible to with whereHas or with. Below is the code:
Volunteer::select('volunteers.id', 'volunteers.name', 'tasks.amount', DB::raw('SUM(payments.amount) as amount_paid'))
->join('tasks', 'tasks.volunteer_id', '=', 'volunteers.id')
->leftJoin('payments', 'payments.task_id', '=', 'tasks.id')
->groupBy('volunteers.id', 'volunteers.name', 'tasks.amount')
->havingRaw('amount_paid >= tasks.amount')
->get();
Any help would be appreciated!

I would like to suggest something else which is adding a column in tasks table that indicates if the task is [paid, unpaid or partially paid] in your tasks migration like so
$table->unsignedTinyInteger('paid_status')->default(0); // 0 -> unpaid, 1 -> partially paid, 2 -> paid
then each time the volunteer makes a payments you will do a simple check to update tasks.paid_status something like checking the total paid_amount and task amount
then using Laravel hasManyThrough in Volunteer model like so
public function payments()
{
return $this->hasManyThrough(
'App\Payment',
'App\Task'
);
}
now to get your data you will do so
// unpaid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '0')->get();
// partially paid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '1')->get();
// paid tasks
Volunteer::query()->first()->payments()->where('tasks.paid_status', '2')->get();
you can read more about HasManyThrough Here

You can handle this using the eloquent powers as well. Extends the Task model with a local scope method
class Task extends Model
{
public function volunteer()
{
return $this->belongsTo(Volunteer::class);
}
public function payments()
{
return $this->hasMany(Payment::class);
}
public function scopeIncompletePayments($query)
{
return $query->select('tasks.*')->leftJoin('payments', 'tasks.id', '=', 'payments.task_id')
->having('tasks.amount', '>', DB::raw('SUM(payments.amount)'))
->groupBy('tasks.id')
->with(['volunteer', 'payments']);
}
}
Which allows you to run the following code to get the tasks where the sum of the related payments is less than the amount of the task. With the payments and Volunteer eagerly loaded
Task::incompletePayments()->get()

Related

Laravel relationships: How do I retrieve all courses to which a collection of assessment tests belong?

Am not a pro in neither PHP nor Laravel and I think I am cornered. In my assessment app, I have courses, lessons and assessment tests. An assessment_test has many to one relationship with lesson. lesson also has many to one relationship with course.
Now here is the problem, How do I retrieve all courses to which a collection of assessment_tests belong?
As shown in the code snippets provides, I tried to get around this by manually looping through the collection and saving the courses to an array but I got some weird error.
Trying to get property 'course' of non-object
Here is my problematic function
public function index()
{
$attempts=AssessmentAttempt::all();
$i=0;
$courses=array();
foreach ($attempts as $attempt) {
// dd($attempt->lesson->course);
$courses[$i++]=$attempt->lesson->course;
}
dd(array_unique($courses));
return view('achievements.index', ['attempts'=>$attempts, 'courses'=>$courses]);
}
AssessmentAttempt model
public function lesson()
{
return $this->belongsTo(Lesson::class);
}
Lesson model
public function course()
{
return $this->belongsTo(Course::class);
}
public function assessmentAttempts()
{
return $this->hasMany(AssessmentAttempt::class);
}
in the assesment_modal define the relation ship with course and make the course id column as a foreign_key in the assesment_model table
return $this->hasMany(Course::class, 'foreign_key');
also design the relation in course model
return $this->belongsTo(Assesment_Model::class);

Laravel Eloquent Polymorphic Scope latest first item

Good day,
I'm a bit stuck here with fetching latest item using Laravel scopes and Eloquent Polymorphic One-to-Many relationship.
Given:
I'm using latest version of Laravel 6.x.
I have two models: Website and Status.
Status model is reusable and can be used with other models.
Each website has multiple statuses.
Every time status is changed a new record is created in DB.
Active website status is the latest one in the DB.
Websites model:
class Website extends Model
{
public function statuses()
{
return $this->morphMany(Statuses::class, 'stateable');
}
public function scopePending($query)
{
return $query->whereHas('statuses', function ($query) {
$query->getByStatusCode('pending');
});
}
}
Statuses model:
class Statuses extends Model
{
public function stateable()
{
return $this->morphTo();
}
public function scopeGetByStatusCode($query, $statusCode)
{
return $query->where('status_code', $statusCode)->latest()->limit(1);
}
}
The problem is that when I call:
Website::pending()->get();
The pending() scope will return all websites that have ever got a pending status assigned to them, and not the websites that have currently active pending status (eg. latest status).
Here is the query that is returned with DB::getQueryLog()
select * from `websites`
where exists
(
select * from `statuses`
where `websites`.`id` = `statuses`.`stateable_id`
and `statuses`.`stateable_type` = "App\\Models\\Website"
and `status_code` = 'pending'
order by `created_at` desc limit 1
)
and `websites`.`deleted_at` is null
What is the right way of obtaining pending websites using scope with polymorphic one-to-many relation?
Similar issue is descried here: https://laracasts.com/discuss/channels/eloquent/polymorphic-relations-and-scope
Thanks.
Okay, after doing some research, I have stumbled across this article: https://nullthoughts.com/development/2019/10/08/dynamic-scope-on-latest-relationship-in-laravel/
Solution turned out to be quite eloquent (sorry for bad pun):
protected function scopePending($query)
{
return $query->whereHas('statuses', function ($query) {
$query->where('id', function ($sub) {
$sub->from('statuses')
->selectRaw('max(id)')
->whereColumn('statuses.stateable_id', 'websites.id');
})->where('status_code', 'pending');
});
}

How to fetch customer information through an eloquent model

I have three models: Payment, Booking and Customer. A payment relation belongs to the Booking model and the Booking model belongs to the Customer model.
I need to make a customer relation inside the Payment model, however, they are not related, the only relation between them is through the Booking model.
My code:
public function booking(){
return $this->belongsTo(Booking::class , 'payable_id' , 'id')->with('customer');
}
// what i need , i need to consume that booking function to get customer info
// something like this
public function customer(){
// consume the booking relation
// return customer info
}
Could you please advise me on the right approach?
The answer to that is a Has One Through or Has Many Through relationship, depending on your business model.
Class Payment {
public function customer()
{
return $this->hasOneThrough('Customer', 'Booking');
}
// or:
public function customers()
{
return $this->hasManyThrough('Customer', 'Booking');
}
}
Hope it helps.
Based on your question you want to get customer information through "booking" let's say it is your lookup table where a customer id and payment id fields exist.
It should look like this
ERD of table image
In your Booking Model you have should two functions
public function customer() {
return $this->belongsTo(Customer::class, 'cust_id', 'id');
}
public function payment() {
return $this->belongsTo(Payment::class, 'payable_id', 'id');
}
In both your Customer and Payment Models add
public function bookings() {
return $this->hasMany(Booking::class, '(cust_id or payable_id respectively)', 'id');
}
in case you want to access booking via customer/payment models as well.
Now with those relationships you can access both payment and customer by accessing booking.
e.g.
$customers = Booking::all()->with('customer');
$payments = Booking::all()->with('payment');
Now you can manipulate all other data for display and/or editing or whatever you need to do with it.
You can also now access a customer's data with all of his/her bookings and payments associated. Below code results a collection of payments based on c
$customer = Customer::find(1)->with('bookings')->first();
$payments = $customer->bookings->payment;
For more questions feel free to delve in to the Official Laravel documentations.
In answering your question I based it on this.
https://laravel.com/docs/5.8/eloquent-relationships
Hope I helped and cheers!

Laravel whereHas getting the relationship

I'm doing a Laravel application to manage events. An Attendant and and Event have a belongsToMany relationship. An attendant has multiple events and viceversa.
The pivot column has an extra field called uuid, this is like the ticket id to the event.
I need to validate the entrance of an Attendant to an Event. So the Attendant scans a QR code with the uuid of the pivot table.
So I have this function
public function validateTicket(Request $request, $ticket) {
$event = Event::fromTicket($ticket);
\Log::info($event);
return new EventResource($event);
}
And the static method fromTicket is:
public static function fromTicket($ticket) {
return static::whereHas('attendants', function($q) use ($ticket) {
$q->where('uuid', $ticket);
})->first();
}
The problem is that I need the relationship too, this because I need to register the hour of entrance of an Attendant, and this entrance could happen many times.
Is there a way to return the relationship too during the whereHas query?
Thanks in advance.
No because it needs the original models relationship to get the pivot table, fear not there is a solution that is very close to the original. Instead include all attendants and do a condition in the include and only the attendent you need will be present.
public static function fromTicket($ticket) {
return static::whereHas('attendants',
function($query) use ($ticket) {
$query->where('uuid', $ticket);
})->with(['attendants' => function ($query) use ($ticket) {
$query->where('uuid', $ticket);
}])->first();
}
This will include the attendants, but only the one with the correct ticket, so now you can do $event->attendants->first(), this is two queries still quite optimized but not optimal, my easiest solution for how you portrayed your problem.

Laravel Eloquent Relationship Filter by Column of Relationship

I have 3 Tables
Product:[Table]
Person:[Table]
Payment:[Table]
Many To Many Relationship Between Product and Person
One To Many Relationship Between Product and Payment (One Product Has Many Payments)
One To Many Relationship Between Person and Payment (One Person Has Many Payments)
Payment:[Table]
id
person_id
product_id
amount
The thing is that i am trying to get All persons with products and Product payments filtered by person_id.
Reason is that i dont want to have any other persons record in payments.
This is actually the query i am running yeah i know its wrong cuz i cant filter it by person_id.
$query = $person::with('product', 'payment')->where('is_active', 1);
I want to achieve something like this..
$query = $person::with(array('product', 'payment' => function ($query) {
$query->where('person_id', '=', 'person.id');
}))->where('is_active', 1);
If you setup your relations like:
class Person extends Model
{
public function payments()
{
return $this->hasMany(Payment::class);
}
}
class Payment extends Model
{
public function product()
{
return $this->belongsTo(Product::class);
}
}
Then you should be able to do:
$person = Person::with('payments.product')->where('id',$personId)->where('is_active',1)->first();
Which will return a Person with all the relations loaded and you can access those like:
#foreach($person->payments as $payment)
{{$person->name}} bought {{$payment->product->name}} for {{$payment->amount}}
#endforeach

Categories