Healthcare management app: patients, appointments, consultations, lab, pharmacy inventory, billing, and reports at care.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Care;
|
||||
|
||||
use App\Models\Appointment;
|
||||
use App\Models\Bill;
|
||||
use App\Models\Diagnosis;
|
||||
use App\Models\InvestigationRequest;
|
||||
use App\Models\Patient;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Visit;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ReportService
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function dashboardStats(string $ownerRef, int $organizationId, ?int $branchId = null): array
|
||||
{
|
||||
$today = now()->startOfDay();
|
||||
|
||||
$patientsToday = Patient::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereDate('created_at', $today)
|
||||
->count();
|
||||
|
||||
$appointmentsToday = Appointment::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereDate('scheduled_at', $today)
|
||||
->count();
|
||||
|
||||
$openBills = Bill::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL])
|
||||
->count();
|
||||
|
||||
$revenueToday = Payment::owned($ownerRef)
|
||||
->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) {
|
||||
$q->where('organization_id', $organizationId);
|
||||
if ($branchId) {
|
||||
$q->where('branch_id', $branchId);
|
||||
}
|
||||
})
|
||||
->whereDate('paid_at', $today)
|
||||
->sum('amount_minor');
|
||||
|
||||
$pendingLab = InvestigationRequest::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereIn('status', InvestigationRequest::activeStatuses())
|
||||
->count();
|
||||
|
||||
return [
|
||||
'patients_today' => $patientsToday,
|
||||
'appointments_today' => $appointmentsToday,
|
||||
'open_bills' => $openBills,
|
||||
'revenue_today_minor' => (int) $revenueToday,
|
||||
'pending_lab' => $pendingLab,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function patientsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
|
||||
{
|
||||
$newPatients = Patient::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('created_at', [$from, $to])
|
||||
->count();
|
||||
|
||||
$returningPatients = Visit::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('checked_in_at', [$from, $to])
|
||||
->distinct('patient_id')
|
||||
->count('patient_id');
|
||||
|
||||
$visits = Visit::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('checked_in_at', [$from, $to])
|
||||
->count();
|
||||
|
||||
return [
|
||||
'new_patients' => $newPatients,
|
||||
'returning_patients' => $returningPatients,
|
||||
'total_visits' => $visits,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function appointmentsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
|
||||
{
|
||||
$base = Appointment::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('scheduled_at', [$from, $to]);
|
||||
|
||||
return [
|
||||
'total' => (clone $base)->count(),
|
||||
'completed' => (clone $base)->where('status', Appointment::STATUS_COMPLETED)->count(),
|
||||
'cancelled' => (clone $base)->where('status', Appointment::STATUS_CANCELLED)->count(),
|
||||
'no_show' => (clone $base)->where('status', Appointment::STATUS_NO_SHOW)->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function laboratoryReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
|
||||
{
|
||||
$base = InvestigationRequest::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('created_at', [$from, $to]);
|
||||
|
||||
$completed = (clone $base)
|
||||
->whereIn('status', [InvestigationRequest::STATUS_COMPLETED, InvestigationRequest::STATUS_DELIVERED])
|
||||
->get();
|
||||
|
||||
$turnaroundHours = $completed
|
||||
->filter(fn ($r) => $r->completed_at && $r->created_at)
|
||||
->map(fn ($r) => $r->created_at->diffInHours($r->completed_at))
|
||||
->avg();
|
||||
|
||||
return [
|
||||
'requested' => (clone $base)->count(),
|
||||
'completed' => $completed->count(),
|
||||
'pending' => (clone $base)->whereIn('status', InvestigationRequest::activeStatuses())->count(),
|
||||
'avg_turnaround_hours' => $turnaroundHours ? round($turnaroundHours, 1) : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function financeReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
|
||||
{
|
||||
$billed = Bill::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereBetween('created_at', [$from, $to])
|
||||
->where('status', '!=', Bill::STATUS_VOID);
|
||||
|
||||
$payments = Payment::owned($ownerRef)
|
||||
->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) {
|
||||
$q->where('organization_id', $organizationId);
|
||||
if ($branchId) {
|
||||
$q->where('branch_id', $branchId);
|
||||
}
|
||||
})
|
||||
->whereBetween('paid_at', [$from, $to]);
|
||||
|
||||
return [
|
||||
'invoiced_minor' => (int) (clone $billed)->sum('total_minor'),
|
||||
'collected_minor' => (int) (clone $payments)->sum('amount_minor'),
|
||||
'outstanding_minor' => (int) Bill::owned($ownerRef)
|
||||
->where('organization_id', $organizationId)
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
|
||||
->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL])
|
||||
->sum('balance_minor'),
|
||||
'invoice_count' => (clone $billed)->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, object>
|
||||
*/
|
||||
public function clinicalReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to): Collection
|
||||
{
|
||||
return Diagnosis::owned($ownerRef)
|
||||
->whereHas('consultation.visit', function (Builder $q) use ($organizationId, $from, $to) {
|
||||
$q->where('organization_id', $organizationId)
|
||||
->whereBetween('checked_in_at', [$from, $to]);
|
||||
})
|
||||
->select('description', DB::raw('count(*) as total'))
|
||||
->groupBy('description')
|
||||
->orderByDesc('total')
|
||||
->limit(20)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user