Eloquent sum of foreign key column - laravel

The models I have are : Invoice - Credit_note - Payment
An Invoice hasMany Credit_notes
Each Credit_note has a single Payment
A Payment is not directly related to a Credit_note
What I need to do is to calculate the sum of the Payments(amount) of the Credit_notes of an Invoice.
With the relations I currently have in place, I can load all the Credit_notes (with their Payments) of an Invoice.
Credit_note::with(['payment'])->where('invoice_id', $i->id)
I could then sum the amounts by hand... but, is there a way to directly return the sum with the query ?
I tried to add a ->sum('payment.amount') or something similar, with no luck.
Thanks.

$sum = Credit_note::with([
'payments' => function ($query) {
$query->selectRaw('payment_id, SUM(payments.amount) AS cn_total')->groupBy('payment_id');
},
])
->where('invoice_id', $i->id)->get()->pluck('payments.cn_total')->sum();

Solution :
$sum = Credit_note::with(['payment:id,amount'])
->where('invoice_id', $i->id)
->get()
->pluck('payment.amount')
->sum();

Inside Credit_note model make this function
public function sum_of_payment(){
$result=0;
$paiments=$this->payment()->get();
foreach($paiments as $paiment){
$result+=$paiment->amount;
}
return $result;
}
then you can get SUM like this
$sum=Credit_note::with(['payment'])->where('invoice_id', $i->id)->first()->sum_of_payment();

Related

Sort the results returned by model relation laravel

I have 2 table:
products: id, name
auctions: id, product_id, expired_time
Model product
function getAuctions(){
return $this->hasOne('App\Models\Auction', 'product_id', 'id');
}
How can I do the same as below(auctions.expired_time not working):
Controller
function getProductAuctions(){
return \App\Models\Product::with('getAuctions')->whereHas('getAuctions', function(){})->orderBy('auctions.expired_time', 'asc');
}
Thanks everyone.
You can do it with sortBy() method. That method applies to collections only.
function getProductAuctions(){
$products = \App\Models\Product::all();
return $products->sortBy(function ($product, $key) {
return $product->getAuctions->expired_time;
})
}
This should now sort your products collection by the auctions expired time, if you want reversed order, you can just use sortByDesc() method, rest is the same. Also, in your product model, if you are having hasOne() relationship, rename the function name to getAuction() or even better auction() (singular).
you can use one of these to get sort your result
in your controller
function getProductAuctions(){
return \App\Models\Product::with('getAuctions')->whereHas('getAuctions', function($query){
$query->orderBy('expired_time', 'asc');
})->get();
}
and if you want to sort in your model then just use this
function getAuctions(){
return $this->hasOne('App\Models\Auction', 'product_id', 'id')->orderBy('expired_time', 'asc');
}
and thats all no need to do any thing...your result is already sorted
Thank everyone. I found the answer here and I find it correct: https://laracasts.com/discuss/channels/eloquent/order-by-on-relationship
"When you use the with method the Eloquent makes another query in database to retrieve the related data using whereIn. This is why we can't order by a relation field."

Sum of Items field in collection of collections

I'm trying to see if it's possible, but I would like to get the sum of a field in an item in a collection of collections.
I have the following in my controller:
$prefiltered_contacts = Contact::with(['donations' => function ($query) use ($request) {
$query->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])->get();
$contacts = $prefiltered_contacts ->filter(function ($contact) {
return $contact->donations->isNotEmpty();
});
My donation class has the following:
public function monetary_donations(){
return $this->hasMany('App\Payments_Distribution', 'donation_id','id');
}
Now the last part of this is that in the Payments_Distribution class, there is a field titled amount.
If I was coming directly from the Donation model, I would access the sum of the monetary donations as $donation->monetary_donations->sum('amount'); and I would receive the sum. But how would I go about doing this from the Contact model? Or is that even possible given that it would need to go through a collection of donations to get to the collection of monetary_donations? I'm trying to get a report of all Contacts donations (and monetary donations) and output a subtotal of the monetary_donations for that specific period.
Sum accepts a closure as the argument. So you could do something like this:
$sum = $donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
Or 1 level higher (from $contacts):
$sum = $contacts->sum(function ($contact) {
return $contact->donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
});
Edit:
I would also recommend eager loading your relationships and filtering out contacts without donations with SQL rather than collections:
$contacts = Contact::with(['donations' => function ($query) use ($request) {
$query
->with('monetary_donations')
->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])
->whereHas('donations') // Filter out those without donations with SQL
->get();

Laravel whereHas behaving unexpectedly

I have the following relationships set up in my Laravel 5.6 application.
Purchase belongs to many Invoice
Invoice belongs to many Purchase
Invoice belongs to many Payment
Payment belongs to many Invoice
These relationships are made using pivot tables.
I want to find only purchases that have, via invoices, payments that are 0.
In my test I have one purchase, I have attached two invoices to that purchase, and I have attached one payment to each of those invoices. One payment has the amount 100, the other has the amount 0.
For my test to pass, the query should return no results, however, it isn't doing so, it is consistently returning the purchase I have created.
This is the query i've written:
Purchase::whereHas('invoices.payments', function ($q) {
return $q->where('amount', '<=', 0);
})->get();
I have also tried:
Purchase::whereDoesntHave('invoices.payments', function ($q) {
return $q->where('amount', '>', 0);
})->get();
Am I doing something wrong here? Am I misunderstanding the capabilities of WhereHas?
Any help would be greatly appreciated, thanks.
Your second approach is correct, but whereDoesntHave() doesn't work properly with nested relationships in Laravel 5.6. This bug has been fixed in Laravel 5.7.
You can use this workaround:
Purchase::whereDoesntHave('invoices', function ($q) {
$q->whereHas('payments', function ($q) {
$q->where('amount', '>', 0);
});
})->get();
I suppose, You should use both of methods.
Purchase::whereHas('invoices.payments', function ($q) {
return $q->where('amount', 0);
})
->whereDoesntHave('invoices.payments', function ($q) {
return $q->where('amount', '>', 0);
})
->get();
whereHas() take all purchases with payment amount = 0, whereDoesntHave() throw away purchases that have payments > 0.
Perhaps its me not fully understanding your requirements / the problem here but this is what I think I understand...
You created 1 Purchase.
It has 2 Invoices.
Each Invoice has 1 Payment (1 of 100, 1 of 0 <- technically not a payment).
In your question you say
I want to find only purchases that have, via invoices, payments that
are 0.
But then you are complaining that you got a result... When you DO have a purchase with a zero payment.
To make the test a bit more "real", I would create many Purchases with many Invoices and Payments etc to really get a feel for whats going on.
UPDATE
Have you considered a 'hasManyThrough' relationship on your Purchase model to the Payment? like this
public function payment()
{
return $this->hasManyThrough(Payment::class, Invoice::class);
}
Then perhaps you could do this query.
Purchase::whereDoesntHave('payment')->get();

Eager loading sum from two pivot tables with Eloquent

I have a Part entity that has a many-to-many relationship with Order and Project. The pivot tables contains a quantity field which might have a positive or negative value, and by adding the sums of both pivot tables, for a specific part, I get the current stock.
I am able to do this, but get the N+1 problem. And I am having a hard time figuring out how I can do it with eager loading. I've read this post, but I don't understand how to adapt it to my needs.
So I am looking for a way to provide a eager loadable stock property on the Part model, suggestions to how I can accomplish this?
I was able to figure it out with the help of this post. This is what I did:
Part model
Create two relationship orderPartsCount and projectPartsCount, add attribute calculatedStock to sum them and provide an easy way of retrieving.
public function orderPartsCount()
{
$a = $this->orders();
$a1 = $a->selectRaw($a->getForeignKey() . ', sum(count) as stock')
->where('done', '>', 0)
->groupBy($a->getForeignKey()
);
return $a1;
}
public function projectPartsCount()
{
$b = $this->projects();
$b1 = $b->selectRaw($b->getForeignKey() . ', sum(count) as stock')
->where('status', '>', 0)
->groupBy($b->getForeignKey()
);
return $b1;
}
public function getCalculatedStockAttribute()
{
$orders = $this->orderPartsCount->first() ? $this->orderPartsCount->first()->stock : 0;
$projects = $this->projectPartsCount->first() ? $this->projectPartsCount->first()->stock : 0;
// invert project parts, since they are listed as positive counts but shall reduce the stock
return $orders + ( $projects * -1);
}
Part controller
Eager load orderPartsCount and projectPartsCount in the controller.
public function index()
{
return View::make('parts.index', [
'parts' => Part::with('category', 'location', 'orderPartsCount', 'projectPartsCount')
->orderBy('description_no')
->paginate(100)
]);
}

How to do scope with subquery

I have a model Partner_deal which has lots of fields but the only one you really need to know about is called quantity which is an integer that specifies how many times the deal can be redeemed.
I then have another model Partner_deal_redemption to keep track of the redemptions. This has a partner_deal_id column and a user_id column to record which users have redeemed which deal.
I want to create a scope in my Partner_deal model so that it will only return deals where the number of redemptions is less than the quantity field.
I know how to do this in MySql by doing a subquery that counts how many redemptions each deal has had and uses a HAVING clause to filter out the ones where the number of redemptions = quantity.
I have no idea where to begin doing this in eloquent, this is my best attempt:
function scopeNotRunOut($query)
{
return $query->having('quantity', '>', function($q)
{
$q->from('partner_deal_redemptions')
->selectRaw('count(*)')
->where('partner_deal_id', '=', 'id');
});
}
You can probably use the has() function of Eloquent:
http://laravel.com/docs/5.0/eloquent#querying-relations
function scopeNotRunOut($query)
{
return $query->has('redemptions', '<', DB::raw('quantity'));
}
To use this function you need to define the redemptions function in your Partner_deal model, which will represent the relation between the Partner_deal and the Partner_deal_redemption models.
MrShibby was correct but then I realised I needed to check if quantity was null as well so I modified his solution slightly to the following:
function scopeNotRunOut($query)
{
return $query->where(function($q) {
$q->orHas('redemptions', '<', DB::raw('quantity'))
->orWhere('quantity', '=', null);
});
}

Resources