Complete Dentistry commercial suite with PMS depth.
Deploy Ladill Care / deploy (push) Successful in 31s
Deploy Ladill Care / deploy (push) Successful in 31s
Add plan lifecycle, visit stages with chair/recovery points, primary dentition charting, imaging void, revenue reports, perio exams, lab cases, and recalls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,9 +7,11 @@ use App\Models\BillLineItem;
|
||||
use App\Models\DentalImage;
|
||||
use App\Models\DentalPlanItem;
|
||||
use App\Models\DentalProcedure;
|
||||
use App\Models\DentalRecall;
|
||||
use App\Models\DentalTreatmentPlan;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Patient;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class DentalAnalyticsService
|
||||
@@ -21,16 +23,21 @@ class DentalAnalyticsService
|
||||
* revenue_by_code: Collection,
|
||||
* unfinished_items: int,
|
||||
* avg_chair_minutes: ?float,
|
||||
* imaging_by_modality: Collection
|
||||
* imaging_by_modality: Collection,
|
||||
* from: string,
|
||||
* to: string
|
||||
* }
|
||||
*/
|
||||
public function report(
|
||||
Organization $organization,
|
||||
string $ownerRef,
|
||||
?int $branchId = null,
|
||||
?string $from = null,
|
||||
?string $to = null,
|
||||
): array {
|
||||
$todayStart = now()->startOfDay();
|
||||
$monthStart = now()->startOfMonth();
|
||||
$rangeFrom = $from ? Carbon::parse($from)->startOfDay() : now()->startOfMonth();
|
||||
$rangeTo = $to ? Carbon::parse($to)->endOfDay() : now()->endOfDay();
|
||||
|
||||
$procBase = DentalProcedure::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
@@ -44,7 +51,7 @@ class DentalAnalyticsService
|
||||
->get();
|
||||
|
||||
$proceduresMonth = (clone $procBase)
|
||||
->where('performed_at', '>=', $monthStart)
|
||||
->whereBetween('performed_at', [$rangeFrom, $rangeTo])
|
||||
->selectRaw('procedure_code, label, count(*) as total, sum(amount_minor) as amount_minor')
|
||||
->groupBy('procedure_code', 'label')
|
||||
->orderByDesc('total')
|
||||
@@ -53,7 +60,7 @@ class DentalAnalyticsService
|
||||
$revenueByCode = BillLineItem::query()
|
||||
->where('owner_ref', $ownerRef)
|
||||
->where('source_type', 'dental_procedure')
|
||||
->where('created_at', '>=', $monthStart)
|
||||
->whereBetween('created_at', [$rangeFrom, $rangeTo])
|
||||
->selectRaw('description, sum(total_minor) as amount_minor, count(*) as total')
|
||||
->groupBy('description')
|
||||
->orderByDesc('amount_minor')
|
||||
@@ -77,7 +84,7 @@ class DentalAnalyticsService
|
||||
->where('status', Appointment::STATUS_COMPLETED)
|
||||
->whereNotNull('started_at')
|
||||
->whereNotNull('completed_at')
|
||||
->where('completed_at', '>=', $monthStart)
|
||||
->whereBetween('completed_at', [$rangeFrom, $rangeTo])
|
||||
->when($branchId, fn ($q) => $q->where('branch_id', $branchId));
|
||||
|
||||
$avgChair = null;
|
||||
@@ -88,7 +95,7 @@ class DentalAnalyticsService
|
||||
|
||||
$imaging = DentalImage::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('captured_at', '>=', $monthStart)
|
||||
->whereBetween('captured_at', [$rangeFrom, $rangeTo])
|
||||
->selectRaw('modality, count(*) as total')
|
||||
->groupBy('modality')
|
||||
->orderByDesc('total')
|
||||
@@ -101,6 +108,8 @@ class DentalAnalyticsService
|
||||
'unfinished_items' => $unfinished,
|
||||
'avg_chair_minutes' => $avgChair,
|
||||
'imaging_by_modality' => $imaging,
|
||||
'from' => $rangeFrom->toDateString(),
|
||||
'to' => $rangeTo->toDateString(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -150,6 +159,24 @@ class DentalAnalyticsService
|
||||
}
|
||||
}
|
||||
|
||||
$overdue = DentalRecall::owned($ownerRef)
|
||||
->where('organization_id', $organization->id)
|
||||
->where('patient_id', $patient->id)
|
||||
->whereIn('status', [DentalRecall::STATUS_SCHEDULED, DentalRecall::STATUS_DUE])
|
||||
->where('due_on', '<', now()->toDateString())
|
||||
->orderBy('due_on')
|
||||
->limit(3)
|
||||
->get();
|
||||
|
||||
foreach ($overdue as $recall) {
|
||||
$reason = $recall->reason ?: 'Recall';
|
||||
$alerts[] = [
|
||||
'code' => 'dental.recall.overdue',
|
||||
'severity' => 'warning',
|
||||
'message' => "Overdue recall ({$reason}) due {$recall->due_on->format('d M Y')}.",
|
||||
];
|
||||
}
|
||||
|
||||
return $alerts;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user