Search in belongsToMany-relation for item by pivot value - laravel

I got these models:
Order
Invoice
An Order can have many Invoices and an Invoice can belong to many Orders.
Example:
Orders:
Order #1
Order #2
Order #3
Invoices:
Invoice #1 -> belongs To Order #1 and Order #2 (so this Invoice contains both Orders)
Invoice #2 -> belongsTo Order #2 and Order #3
Each belongsTo-relation saves a date as pivot, which defines the last date this order has been invoiced.
Now I'd like to create a isInvoiceable() getter for my Order-Model, which returns, whether the order has been invoiced within the last month or not. If the latest date is older than one month, return true, as this Order should create a new invoice.
So now I need your help: I need to check all invoices, whether there's an Order (belongsToMany) with the order_id of the current Order, and a date-pivot that's older than now - one month. I need to check the order_id because the Invoice could possibly contain information about other Orders, too.
And this is the missing link for me: How can I check the belongsToMany-relation in Invoice for date and order_id?

Asuming you have defined your relation as 'invoices', you may do something like this:
class Order extends Model {
...
public function isInvoiceable(){
$lastInvoiceDate = (date('Y-m-d', $this->invoices()->latest()->created_at));
$today = new Carbon(date('Y-m-d', strtotime(Input::get('startdate'))));
$diff_months = $lastInvoiceDate->diff($today)->months;
return $diff_months < 1;
}

There is a wherePivot method, which should do what you want.
From the docs:
Filtering Relationships Via Intermediate Table Columns:
You can also filter the results returned by belongsToMany using the
wherePivot, wherePivotIn, and wherePivotNotIn methods when defining
the relationship:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
return $this->belongsToMany('App\Role')->wherePivotNotIn('priority', [1, 2]);
I have not tested it, but you should be able to do something like this:
public function isInvoiceable()
{
// replace 'invoices()' with your relationship
if ($this->invoices()->wherePivot('date', '>=', Carbon::now()->subMonth())->count() > 0) {
return false;
}
return true;
}

you have two choices:
1- like Remul said in his answer: using wherePivot();
2- using newPivotQuery()
witch give you more space to work with ...
when you use this method, the query will be on pivot table directly ...
class Order extends Model {
public function isInvoiceable()
{
return $this->invoices()-> newPivotQuery()->firstWhere('date', '>=', Carbon::now()->subMonth())!=null;
}
the advantage of using newPivotQuery() that you can insert,update, make overall queries ...

Related

How to sum of column in laravel with relationship

How to sum of duration column where my course id or section id is same
Try this code:
$res = Model::groupBy('course_id', 'section_id')
->selectRaw('sum(duration) as total_duration, course_id, section_id') // do the sum query here
->pluck('total_duration','course_id', 'section_id'); // get the related query column here
Source answer
You have to use hasMany relation on this table with course and section table.
For hasMany relation see this docs. Here
Then, you can use foreach to generate the sum.
$summation = $yourtablenames->sum(function ($yourtablename) {
return $yourtablename->your_relation_name->sum('duration');
});

Laravel Get previous records by date (optimization)

I have a bookings table with fields:
name | String
from | Datetime
to | Datetime
I select some of those bookings and display them as a list. I also check if the previous booking is less than 30 days apart.
I do this by querying for each booking the previous booking:
#if ($booking->previousBooking()) // Simplified version but you get the idea
The underlying code:
public function previousBooking()
{
return Booking::where('from', '<', $this->from)
->orderByDesc('from')
->first();
}
You might have guessed it already: it adds a query for each booking.
The best scenario would be to kind of eager load the "previous booking" (with) so that it is accessible like:
$booking->previous_booking->from
Is there any possible way to do it like this?
Constraints:
I can't query all bookings, order them by "from" and then just get the previous index
Take a look at this article by Jonathan Reinink. You can apply his solution by simulating a previous_booking_id on your Booking model. Then add the previousBooking() relationship and query that using with('previousBooking'). Let me know if you need any help on the implementation.
Update
Add the following scope to your Booking model:
public function previousBooking()
{
return $this->belongsTo(Booking::class);
}
public function scopeWithPreviousBooking($query)
{
$query->addSelect(['previous_booking_id' => Booking::select('id')
->whereColumn('previous_booking.from', '<', 'bookings.to')
->orderByDesc('from')
->take(1)
])->with('previousBooking');
}
You can now get all your bookings with their previous booking id included using this:
$bookings = Booking::withPreviousBooking()->get();
All bookings should now have a previous_booking_id. We add the with('previousBooking') to the end of the scope as if we query a relation of the booking. Eloquent does not care whether that previous_booking_id is in the database or not, as long as it's available in the model (which we've done by the addSelect() method).
You should now be able to use your requested solution in your view:
$booking->previousBooking->from
Note: you can only access the previousBooking relation if you've applied the scope. If not, the previous_booking_id is not available, and the relation will therefore be null. If you always want to load the previous booking, consider to add it as a global scope. However, I recommend to just apply the scope where it's needed.

Laravel adding custom attribute to where clause

I have a mutator that calculate the product existing quantity by:
summing the product credit and minus it from the sold credit.
public function getReportTotalQuantityAttribute() {
// GET IMPORT INVOICE SUM
$import_invoices_sum = $this -> credits -> sum('quantity');
// GET EXPORT INVOICE SUM
$export_invoices_sum = $this -> sold -> sum('quantity');
// CALCULATE THE SUM
return $import_invoices_sum - $export_invoices_sum;
}
This mutator works fine and return the actually product quantity as report_total_quantity attribute whenever I call the model.
What I am trying to do:
I am trying to get the product where('report_total_quantity', '>', 0).
What I have tried:
$condition_queries = Product::withCreditsAndWarehouses() -> where('report_total_quantity', '>', 0);
But I am getting error say Unknown column 'report_total_quantity'. And this is logical since I don't have this column but I append it using $append and mutator.
What I searched and found:
I have found something called filters but I don't think it is good solution since I am using paginate(10) and like that I will return 10 values and filter them.
Also I have many other conditions and filter is not working good for me.
If you want to use WHERE with attribute create by mutator You must first create Collection then use Where statement.
$condition_queries = Product::withCreditsAndWarehouses()->get()->where('report_total_quantity', '>', 0);
But after ->get() you can not use paginate. Instead you must use Collection paginate method forPage. Check this

Is it possible to calculate the average in one query with Eloquent with two hasMany Relationships

My current approach looks like this:
public function overallAverage()
{
$ids = $this->reviews()->pluck('id')->all();
return Rating::where('rating_type_id', 0)
->whereIn('offer_review_id', $ids)
->avg('rating_value');
}
But I don't like it that I first need to pluck all ids in order to calculate the averages. To understand it better here is a diagram of my tables:
So when I have an Offer, I want to calculate all the averages of ratings that have the rating_type_id of 0.
1 Offer has many Offer Reviews. One Offer Review has many Ratings (like overall, taste rating, value rating etc).
With reviews() I get the has Many relationship
public function reviews(){
return $this->hasMany(OfferReview::class);
}
Yes, you can get average of values by using laravel collection. Laravel provide us number of collection helper functions that provide us flexibility to calculate values.
Below is the avg collect helper function uses
https://laravel.com/docs/5.7/collections#method-average
public function overallAverage()
{
$reviewTableName = $this->reviews()->getTable();
$query = "select id from $reviewTableName";
$avg = Rating::where('rating_type_id', 0)
->whereRaw("offer_review_id in ($query)")
->pluck('rating_value')
->avg();
return $avg;
}
I think this will help you.

"Order by" when querying relation

I have a Project table and a pivot table "like_project" which has 2 columns : user_id and project_id, so users can like Projects
I'm trying to list all the Projects and order them by number of likes, by using the "has" method, like this :
$projects = Project::has('likes')->paginate(10);
the issue is that I don't know how to order by number of likes, I have a function on my Project model to count the number of likes for a project :
public function getTotalLikes(){
return sizeof($this->likes()->getRelatedIds()); //I could use $this->likes()->count()
}
Unless you want to write out a long SQL query to run an GROUP/ORDER BY command I'd just run the eloquent collection's sort method once you get the projects back.
$projects->sort(function($project) {
return $project->likes->count();
});
It depends on how your likes are stored exactly, but most straightforward way is simple join (suppose MySQL):
$projects = Project::join('like_project as lp', 'lp.project_id', '=', 'projects.id')
->groupBy('projects.id')
->orderByRaw('count(lp.project_id) desc')
->select('projects.*') // and if needed: DB::raw('count(lp.project_id) as likesCount')
->paginate(10);

Resources