Laravel fetch sum of collection with leftJoin between two dates - php

I want to list the revenue of all businesses. All businesses are meant to be listed whether they have revenue or not. The revenue can also be sorted by date (within a date range). Meaning that I can check how much each of the business has made between certain dates.
Here is my implementation below:
return Business::
leftJoin('invoice as i', 'i.business_id', '=', 'businesses.id')
->leftJoin('departments as d', 'businesses.department_id', '=', 'd.id')
->where('i.created_at', '>', $from)
->where('i.created_at', '<', $to)
->select('businesses.id','businesses.name', 'd.name as department', 'd.id as deparment_id', DB::raw('coalesce(sum(i.total_amount_paid),0) as received'),
DB::raw('coalesce(sum(i.invoice_amount),0) as expected'))
->groupBy('businesses.id', 'businesses.name')
->get();
This works but it doesn't return all the businesses. I need all business returned irrespective of whether there are invoices during the set date duration or not.
It only returns all businesses when the snippet below is not within the query builder and that way I cannot sort by date:
->where('i.created_at', '>', $from)
->where('i.created_at', '<', $to)

You should add the date filtration into the join condition as following:
return Business::
->leftJoin('invoice as i', function($join)
{
$join->on('i.business_id', '=', 'businesses.id'));
$join->on('i.created_at', '>', $from);
$join->on('i.created_at', '<', $to);
})
->leftJoin('departments as d', 'businesses.department_id', '=', 'd.id')
->select('businesses.id','businesses.name', 'd.name as department', 'd.id as deparment_id', DB::raw('coalesce(sum(i.total_amount_paid),0) as received'),
DB::raw('coalesce(sum(i.invoice_amount),0) as expected'))
->groupBy('businesses.id', 'businesses.name')
->get();

Related

Find Expiry date of Deal in relation table

I have a category table in which I have different categories of deals. Each of them consists of many deals along with its expiry date. I want to access only those deals with their categories whose expiry date is not over, but I am getting an issue that if any deal of category exists in time range all of its deals arrive whether it's expired or not. Here is my code:
$deals = DealCategory::where('name', '!=', 'Today Deal')
->whereRelation('deals','start_date', '<=', date('Y-m-d'))
->whereRelation('deals', 'expiry_date',">=", date('Y-m-d'))
->with('deals', 'deals.deal_images', 'deals.deal_products', 'deals.deal_products.product', 'deals.rating')->latest()->Paginate(12);
return response()->json(['Deals' => $deals, 'Date' => Carbon::now(), 'status' => 'success'], 200);
When you eagerly load relations using with, you can pass additional criteria to tell Eloquent which records to load:
DealCategory::where('name', '!=', 'Today Deal')
->whereRelation('deals','start_date', '<=', date('Y-m-d'))
->whereRelation('deals', 'expiry_date',">=", date('Y-m-d'))
->with(['deals' => function ($query) {
$query->where('start_date', '<=', date('Y-m-d'));
$query->where('expiry_date',">=", date('Y-m-d'));
$query->with('deal_images', 'deal_products', 'deal_products.product', 'rating');
}])
->latest()->Paginate(12);
Latest versions of Laravel even include a dedicated withWhereHas method to check for the existence of a relationship while simultaneously loading the relationship based on the same conditions:
DealCategory::where('name', '!=', 'Today Deal')
->withWhereHas('deals', function ($query) {
$query->where('start_date', '<=', date('Y-m-d'));
$query->where('expiry_date',">=", date('Y-m-d'));
$query->with('deal_images', 'deal_products', 'deal_products.product', 'rating');
})
->latest()->Paginate(12);
Either option should do what you need.

Laravel Postgres Formatted Date Month Year Error Ordering Dates

I have a database that looks like this :
I want to make a graph of total costs across a specified period based on different statuses. My query is returning month/year and the total cost at that time.
Despite using order by to display data in an ascending order, my graph is quite unordered. This is how my query looks :
$delivery_failure = DB::table('partners_sms')->select(DB::raw('month, sum(sum) as y'))
->whereBetween('created_at', [new Carbon($start_date), new Carbon($end_date)])
->where('failure_reason', '=', 'DeliveryFailure')
->groupBy('month')
->orderBy('month', 'DESC')
->get();
I have also tried this approach with no luck:
$successNonPartner = SMSData::query()->selectRaw("to_char(created_at::timestamp, 'MONTH-YYYY') as month")
->whereBetween('created_at', [new Carbon($start_date), new Carbon($end_date)])
->where('status', '=', 'Success')
->groupBy('created_at')
->orderBy('created_at', 'DESC')
->get();
Any advise or recommendations on an ideal approach to format my date in month/year and order the dates will be appreciated. I am using laravel and postgres.
to_char(created_at,'YYYY-MONTH') will be of help but it returns a string, probably why order_by doesn't work. You can try returning dates, order by will work and then convert the date to a format of choice in the graph
$successNonPartner = SMSData::selectRaw("month, CEIL(sum(sum)) as y")
->whereBetween('created_at', [new Carbon($start_date), new Carbon($end_date)])
->where('status', '=', 'Success')
->groupBy('month')
->orderBy('month', 'DESC')
->get();

Laravel sum from relationship with condition

I have the following query where I have a conditional query with relation between two models. I need sum of column hours from the attendance where the conditions are met. I have tried the following, but it's not working.
$users = User::with('attendance')
->whereHas('attendance', function (Builder $query) use ($end, $start) {
$query->whereBetween('date', [$start, $end])
->where('status', 2)
->select(DB::raw('SUM(hours) as h'));
})->orderBy('name')
->where('status', 0)
->get();
In my blade
#foreach($users as $user)
{{ $user->h }}
#endforeach
Please help
User::whereHas('attendance')
->withSum(['attendance' => function ($query) use ($start, $end){
$query->whereBetween('date', [$start, $end])
->where('status', 2);
}], 'hours')
->get();
you can access sum hours using attendance_sum_hours property
({relation}_{function}_{column})
$user->attendance_sum_hours
Tip:
one more thing; $query->whereBetween('date', [$start, $end]) be carefull when using whereBetween on datetime column because will compare also the time, so the results won't be favorable
use whereBetweenColumn('date(date)', [$start, $end])
I do not think you can do this with whereHas, but this is how you would do it using joins. This would return all users that have an attendance between 2 dates and then give you the count.
$users = User::with('attendances')
->selectRaw('users.*, SUM(attendances.hours) as hours_sum')
->leftJoin('attendances', 'users.id', 'attendances.user_id')
->whereBetween('attendances.date', [$startDate, $endDate])
->groupBy('users.id')
->get();
However, if you want to return all users but if they have no attendance it will return 0 you can do the following
$users = User::with('attendances')
->selectRaw('users.*, COALESCE(SUM(attendances.hours), 0) as hours_sum')
->leftJoin('attendances', function (JoinClause $joinClause) {
$joinClause->on('users.id', 'attendances.user_id')
->whereBetween('attendances.date', [$startDate, $endDate]);
})
->groupBy('users.id')
->get();

Laravel Sort Model based on relationship values

I have a model called Business, a business can have several services they provide. I have another model called Payments. Payment holds a record of what service people pay for. A service can have many Payments. I intend to fetch top 10 and worst 10 businesses based on payments received. The code below works fine, but it is very inefficient. I have to loop through the whole data to retrieve the information I need. Any more efficient way of achieving this?
$businesses = Business::with(['services'])->get();
foreach($businesses as $business){
$id = $business->id;
$name = $business->display_name;
$services = $business->services;
$businessRevenue = 0;
if(count($services)>0){
foreach($services as $service){
$serviceId = $service->id;
$totalAmount = PaymentTransaction::whereHas('invoice', function($query) use ($serviceId){
$query->where('product_code_id', $serviceId);
})->where('amount_paid', ">", 0)->sum('amount_paid');
$businessRevenue= $businessRevenue + $totalAmount;
}
}
$businessArray = (object) array('id'=> $id, 'name'=> $name, 'revenue'=> $businessRevenue);
array_push($transformedBusiness, $businessArray);
}
$topBusiness = $bottomBusiness = $transformedBusiness;
usort($bottomBusiness, function($a, $b) {return strcmp($a->revenue, $b->revenue);});
usort($topBusiness, function($a, $b) {return strcmp($b->revenue, $a->revenue);});
$topBusiness = array_slice($topBusiness, 0, 10);
$bottomBusiness = array_slice($bottomBusiness, 0, 10);
return view('report.department_performance', compact('topBusiness', 'bottomBusiness'));
I guess you could use a join query to get top and lowest 10 businesses directly from database instead of looping all business records and manually calculate their revenue
For top 10 business you can use inner joins for rest of the related tables
$topBusinesses = DB::query()
->select('b.id', 'b.display_name', DB::raw('sum(p.amount_paid) as revenue')
->from('business as b')
->join('service as s', 'b.id', '=', 's.business_id')
->join('invoice as i', 's.id', '=', 'i.product_code_id')
->join('payment_transaction as p', function ($join) {
$join->on('p.id', '=', 'i.payment_transaction')
->where('p.amount_paid', '>', 0);
})
->groupBy('b.id', 'b.display_name')
->orderByDesc('revenue')
->limit(10)
->get();
For lowest 10 business use left joins for invoice and payment_transaction so that if there are no records in these table for a business you will still get these business records
$lowestBusinesses = DB::query()
->select('b.id', 'b.display_name', DB::raw('coalesce(sum(p.amount_paid),0) as revenue')
->from('business as b')
->join('service as s', 'b.id', '=', 's.business_id')
->leftJoin('invoice as i', 's.id', '=', 'i.product_code_id')
->leftJoin('payment_transaction as p', function ($join) {
$join->on('p.id', '=', 'i.payment_transaction')
->where('p.amount_paid', '>', 0);
})
->groupBy('b.id', 'b.display_name')
->orderBy('revenue')
->limit(10)
->get();
I have used MySQL coalesce function to show 0 value in case sum() returns null, If you are using any other database you can use an alternate function.

Laravel Eloquent date range between range

I am building a website where i have a booking mechanism.
The user can book a hotel room for X days if the room is available.
I am using:
Laravel 5.4
MySql
The room is unavailable if:
It is already booked by another user
The admin has set it as unavailable
The room capacity is less or equal to the number of travellers
If have 3 tables to store those data:
Rent: Contains the booking infos, such as rentStartDate and rentEndDate (as DateTime) and other fields (userId, rentalId, ...)
Unavailabilities: When the admin set a room as unavailable, it's stored here. I have the fields unavailabilityStartDate, unavailabilityEndDate (as DateTime) and rentalId
Rentals: This table contain all the infos regarding the room (capacity ,name, location, ...)
I am struggling to build a Eloquent query to check if the room is available before processing the user payment. Here is what i have for now:
public function isAvailableAtDates($rentalId, $startDate, $endDate, $capacity) {
$from = min($startDate, $endDate);
$till = max($startDate, $endDate);
$result = DB::table('rentals')
->where([
['rentals.rentalId', '=', $rentalId],
['rentals.rentalCapacity', '>=', $capacity]
])
->whereNotIn('rentals.rentalId', function($query) use ($from, $till, $rentalId) {
$query->select('unavailabilities.rentalId')
->from('unavailabilities')
->where([
['unavailabilities.rentalId', '=', $rentalId],
['unavailabilityStartDate', '>=', $from],
['unavailabilityEndDate', '<=', $till],
]);
})
->whereNotIn('rentals.rentalId', function($query) use ($from, $till, $rentalId) {
$query->select('rent.rentalId')
->from('rent')
->where([
['rent.rentalId', '=', $rentalId],
['rentStartDate', '>=', $from],
['rentEndDate', '<=', $till]
]);
})
->select('rentals.rentalId')
->get();
return count($result) == 1;
}
Let's say I have a row inside Unavailabilities with:
unavailabilityStartDate = 2017-04-26 00:00:00
unavailabilityEndDate = 2017-04-30 00:00:00
When calling the method with some dates outside of the range stored in Unavailabilities, i'm getting the expected result. When calling it with the exact same dates, i'm getting no result (which is what i want).
So far so good!
The problem is if i'm calling it with a start date between 26 of April and 30th and a end date later in May, i am still getting a result even tho i shouldn't.
Could anyone help me with that?
This is not a laravel, nor a mysql issue.
try this:
->whereNotIn('rentals.rentalId', function($query) use ($from, $till, $rentalId) {
$query->select('unavailabilities.rentalId')
->from('unavailabilities')
->where([
['unavailabilities.rentalId', '=', $rentalId],
['unavailabilityStartDate', '<=', $till],
['unavailabilityEndDate', '>=', $from],
]);
})
->whereNotIn('rentals.rentalId', function($query) use ($from, $till, $rentalId) {
$query->select('rent.rentalId')
->from('rent')
->where([
['rent.rentalId', '=', $rentalId],
['rentStartDate', '<=', $till],
['rentEndDate', '>=', $from]
]);
})
You need all rent and unavailabilities records that had been started before $till and hadn't been ended before $from date.
Try to draw a time diagram.

Categories