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>
101 lines
3.2 KiB
PHP
101 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Dentistry;
|
|
|
|
use App\Models\DentalRecall;
|
|
use App\Models\Organization;
|
|
use App\Models\Patient;
|
|
use App\Models\Visit;
|
|
use App\Services\Care\AuditLogger;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class DentalRecallService
|
|
{
|
|
/**
|
|
* @return Collection<int, DentalRecall>
|
|
*/
|
|
public function recallsForPatient(Organization $organization, Patient $patient, string $ownerRef, int $limit = 30): Collection
|
|
{
|
|
return DentalRecall::owned($ownerRef)
|
|
->where('organization_id', $organization->id)
|
|
->where('patient_id', $patient->id)
|
|
->orderBy('due_on')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* @param array{due_on: string, reason?: ?string, notes?: ?string} $data
|
|
*/
|
|
public function schedule(
|
|
Organization $organization,
|
|
Patient $patient,
|
|
string $ownerRef,
|
|
array $data,
|
|
?string $actorRef = null,
|
|
): DentalRecall {
|
|
$dueOn = (string) ($data['due_on'] ?? '');
|
|
if ($dueOn === '') {
|
|
throw new \InvalidArgumentException('Recall due date is required.');
|
|
}
|
|
|
|
$status = DentalRecall::STATUS_SCHEDULED;
|
|
if (now()->startOfDay()->gte(\Carbon\Carbon::parse($dueOn)->startOfDay())) {
|
|
$status = DentalRecall::STATUS_DUE;
|
|
}
|
|
|
|
$recall = DentalRecall::create([
|
|
'owner_ref' => $ownerRef,
|
|
'organization_id' => $organization->id,
|
|
'patient_id' => $patient->id,
|
|
'due_on' => $dueOn,
|
|
'reason' => $data['reason'] ?? null,
|
|
'status' => $status,
|
|
'notes' => $data['notes'] ?? null,
|
|
'created_by' => $actorRef ?? $ownerRef,
|
|
]);
|
|
|
|
AuditLogger::record($ownerRef, 'dental.recall.scheduled', $organization->id, $actorRef, DentalRecall::class, $recall->id);
|
|
|
|
return $recall;
|
|
}
|
|
|
|
public function complete(
|
|
DentalRecall $recall,
|
|
string $ownerRef,
|
|
?Visit $visit = null,
|
|
?string $actorRef = null,
|
|
): DentalRecall {
|
|
$recall->update([
|
|
'status' => DentalRecall::STATUS_COMPLETED,
|
|
'linked_visit_id' => $visit?->id ?? $recall->linked_visit_id,
|
|
]);
|
|
|
|
AuditLogger::record($ownerRef, 'dental.recall.completed', $recall->organization_id, $actorRef, DentalRecall::class, $recall->id);
|
|
|
|
return $recall->fresh();
|
|
}
|
|
|
|
public function cancel(DentalRecall $recall, string $ownerRef, ?string $actorRef = null): DentalRecall
|
|
{
|
|
$recall->update(['status' => DentalRecall::STATUS_CANCELLED]);
|
|
|
|
AuditLogger::record($ownerRef, 'dental.recall.cancelled', $recall->organization_id, $actorRef, DentalRecall::class, $recall->id);
|
|
|
|
return $recall->fresh();
|
|
}
|
|
|
|
/**
|
|
* @return Collection<int, DentalRecall>
|
|
*/
|
|
public function openForPatient(Organization $organization, Patient $patient, string $ownerRef): Collection
|
|
{
|
|
return DentalRecall::owned($ownerRef)
|
|
->where('organization_id', $organization->id)
|
|
->where('patient_id', $patient->id)
|
|
->whereIn('status', [DentalRecall::STATUS_SCHEDULED, DentalRecall::STATUS_DUE])
|
|
->orderBy('due_on')
|
|
->get();
|
|
}
|
|
}
|