Laravel whereHas behaving unexpectedly - laravel

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();

Related

Eloquent return elements only when last related element is older than 30 days?

I'm new to programming world and I use laravel. I have have Post model, every user can have more posts. For for all posts I have hasMany relation but this is related to posts, and I need inverse logic.
I don't know how can I get only users which last post is older then 30 days? I need them for email notification.
Can somebody give me some inputs please?
https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
So it should be something like this:
User::whereHas('posts', function ($query) {
return $query->where('created_at', '<=', now()->subDays(30);
})->get()

Eloquent sum of foreign key column

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();

Eloquent select with() based on foreign key

I have a table with user data (users) and a table with prices (prices).
My prices table can contain multiple prices pr. user since I want to keep historical data.
I've defined my relation as a one-to-one
$this->hasOne("App\Model\Price","userid","id")->orderBy("id","desc")->take(1);
to allow me to see the users current price.
What I want to do now, is to select every user that has a current price of 100, but how do I do this? I know I could go for a left join, but as I read the documentation, it should be possible without a left join.
I've built a pseudo-query to explain what I'm after;
User::with("price")->where("prices.price","100")->get();
I've read through the documentation (Eloquent: Querying relationships), but that doesn't seem to be useful to my question.
I've also read several questions here on SO but unfortunately to no avail.
You may try this:
$currentPrice = 100;
$users = User::whereHas('price', function($query) use ($currentPrice) {
$query->where('price', $currentPrice); // price is the field name
})
->with("price")->get();
Since you have more than a single price for per user then you may also declare another relationship method to get all the price models instead of one and you may do it using something like this:
// In User model
public function prices()
{
return $this->hasMany("App\Model\Price", "userid", "id");
}
In this case, with::price will give you the last single record and with::prices will give you all the related prices. So, if you want then you may write something like the following to get all users with their all related prices who has the (latest/current) price of 100:
$currentPrice = 100;
$users = User::whereHas('price', function($query) use($currentPrice) {
$query->where('price', $currentPrice); // price is the field name
})
->with("prices") // with all prices
->get();
You can use the combination of whereHas() and with() as:
$users = User::whereHas("price", function($q) use ($currentPrice) {
$q->where("price", $currentPrice);
})
->with(["price" => function ($q) {
$query->where("price", $currentPrice);
})
->get();

Laravel 5.1 - Trending Posts - Query on relationship count with timestamp condition of pivot

I am using laravel 5.1 for a CMS development. I have a simple structure of posts, users and users can like posts.
I want to list the trending posts (posts with most likes in last 1 week in desc order). Posts and Users have many-to many relationship and use a pivot table for relationship.
Posts Model has
public function likedby()
{
return $this->belongsToMany('App\Models\User','user_like_post')
->withTimestamps();
}
User Model has
public function likes(){
return $this->belongsToMany('App\Models\Post','user_like_post')
->withTimestamps();
}
How can I write eloquent query so I receive the trending post. I need to use the timestamp of the pivot table which I find difficult to use.
This is what I tried but it used 'created_at' of post table and not of pivot table.
$trending = Post::with('likedby')->get()
->sortByDesc(function ($post){
$one_week_ago = Carbon::now()->subWeeks(1);
return $post->likedby
->where('created_at','>=',$one_week_ago)
->count();
}
);
You can constrain an eager load within the with method, however this would still load all the posts but only eager load the likedby relationship when it is less than one week old. This is probably not the behaviour you want, but worth mentioning.
Post::with(['likedby' => function($query) {
$query->where('created_at', '>=', Carbon::now()->subWeeks(1));
}])->get();
To only load posts that have been liked within the last week, you would be best served by the whereHas method. This will provide you with a list of posts that have had likes placed on them within the last week.
Post::whereHas('likedby', function ($query) {
$query->where('created_at', '>=', Carbon::now()->subWeeks(1));
})->get();

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