complete story 6.2 with qa tests
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\UserType;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AnalyticsService
|
||||
{
|
||||
/**
|
||||
* Get translated month labels for chart X-axis.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getMonthLabels(Carbon $startDate, int $months): array
|
||||
{
|
||||
return collect(range(0, $months - 1))
|
||||
->map(fn ($i) => $startDate->copy()->addMonths($i)->translatedFormat('M Y'))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly new client counts for chart data.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public function getMonthlyNewClients(Carbon $startDate, int $months): array
|
||||
{
|
||||
$endDate = $startDate->copy()->addMonths($months)->endOfMonth();
|
||||
|
||||
$data = User::query()
|
||||
->whereIn('user_type', [UserType::Individual, UserType::Company])
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->get()
|
||||
->groupBy(fn ($user) => $user->created_at->format('Y-m'))
|
||||
->map(fn ($group) => $group->count());
|
||||
|
||||
return $this->fillMonthlyData($startDate, $months, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly consultation counts for chart data.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public function getMonthlyConsultations(Carbon $startDate, int $months): array
|
||||
{
|
||||
$endDate = $startDate->copy()->addMonths($months)->endOfMonth();
|
||||
|
||||
$data = Consultation::query()
|
||||
->whereBetween('booking_date', [$startDate, $endDate])
|
||||
->get()
|
||||
->groupBy(fn ($consultation) => $consultation->booking_date->format('Y-m'))
|
||||
->map(fn ($group) => $group->count());
|
||||
|
||||
return $this->fillMonthlyData($startDate, $months, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get consultation type breakdown (free vs paid).
|
||||
*
|
||||
* @return array{free: int, paid: int}
|
||||
*/
|
||||
public function getConsultationTypeBreakdown(Carbon $startDate, int $months): array
|
||||
{
|
||||
$endDate = $startDate->copy()->addMonths($months)->endOfMonth();
|
||||
|
||||
return [
|
||||
'free' => Consultation::query()
|
||||
->whereBetween('booking_date', [$startDate, $endDate])
|
||||
->where('consultation_type', 'free')
|
||||
->count(),
|
||||
'paid' => Consultation::query()
|
||||
->whereBetween('booking_date', [$startDate, $endDate])
|
||||
->where('consultation_type', 'paid')
|
||||
->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly no-show rates as percentages.
|
||||
*
|
||||
* @return array<float>
|
||||
*/
|
||||
public function getMonthlyNoShowRates(Carbon $startDate, int $months): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
for ($i = 0; $i < $months; $i++) {
|
||||
$monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
|
||||
$monthEnd = $monthStart->copy()->endOfMonth();
|
||||
|
||||
$total = Consultation::query()
|
||||
->whereBetween('booking_date', [$monthStart, $monthEnd])
|
||||
->whereIn('status', [ConsultationStatus::Completed, ConsultationStatus::NoShow])
|
||||
->count();
|
||||
|
||||
$noShows = Consultation::query()
|
||||
->whereBetween('booking_date', [$monthStart, $monthEnd])
|
||||
->where('status', ConsultationStatus::NoShow)
|
||||
->count();
|
||||
|
||||
$results[] = $total > 0 ? round(($noShows / $total) * 100, 1) : 0;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill monthly data array ensuring all months have values.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
private function fillMonthlyData(Carbon $startDate, int $months, Collection $data): array
|
||||
{
|
||||
return collect(range(0, $months - 1))
|
||||
->map(fn ($i) => $data->get($startDate->copy()->addMonths($i)->format('Y-m'), 0))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user