125 lines
3.8 KiB
PHP
125 lines
3.8 KiB
PHP
<?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();
|
|
}
|
|
}
|