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.5 KiB
PHP
101 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Dentistry;
|
|
|
|
use App\Models\DentalLabCase;
|
|
use App\Models\Organization;
|
|
use App\Models\Patient;
|
|
use App\Models\Visit;
|
|
use App\Services\Care\AuditLogger;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class DentalLabCaseService
|
|
{
|
|
/**
|
|
* @return Collection<int, DentalLabCase>
|
|
*/
|
|
public function casesForPatient(Organization $organization, Patient $patient, string $ownerRef, int $limit = 30): Collection
|
|
{
|
|
return DentalLabCase::owned($ownerRef)
|
|
->where('organization_id', $organization->id)
|
|
->where('patient_id', $patient->id)
|
|
->latest('id')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* @param array{case_type: string, lab_name?: ?string, tooth_codes?: list<string>, plan_item_id?: ?int, due_at?: ?string, notes?: ?string, status?: string} $data
|
|
*/
|
|
public function create(
|
|
Organization $organization,
|
|
Patient $patient,
|
|
string $ownerRef,
|
|
array $data,
|
|
?Visit $visit = null,
|
|
?string $actorRef = null,
|
|
): DentalLabCase {
|
|
$type = (string) ($data['case_type'] ?? '');
|
|
if (! array_key_exists($type, DentalCatalog::labCaseTypes())) {
|
|
throw new \InvalidArgumentException("Invalid lab case type [{$type}].");
|
|
}
|
|
|
|
$status = (string) ($data['status'] ?? DentalLabCase::STATUS_DRAFT);
|
|
if (! array_key_exists($status, DentalCatalog::labCaseStatuses())) {
|
|
throw new \InvalidArgumentException("Invalid lab case status [{$status}].");
|
|
}
|
|
|
|
$toothCodes = array_values(array_filter(
|
|
$data['tooth_codes'] ?? [],
|
|
fn ($c) => DentalCatalog::isValidToothCode((string) $c),
|
|
));
|
|
|
|
$case = DentalLabCase::create([
|
|
'owner_ref' => $ownerRef,
|
|
'organization_id' => $organization->id,
|
|
'patient_id' => $patient->id,
|
|
'visit_id' => $visit?->id,
|
|
'plan_item_id' => $data['plan_item_id'] ?? null,
|
|
'tooth_codes' => $toothCodes,
|
|
'case_type' => $type,
|
|
'lab_name' => $data['lab_name'] ?? null,
|
|
'status' => $status,
|
|
'ordered_at' => in_array($status, [DentalLabCase::STATUS_ORDERED, DentalLabCase::STATUS_SENT], true) ? now() : null,
|
|
'due_at' => $data['due_at'] ?? null,
|
|
'notes' => $data['notes'] ?? null,
|
|
'created_by' => $actorRef ?? $ownerRef,
|
|
]);
|
|
|
|
AuditLogger::record($ownerRef, 'dental.lab.created', $organization->id, $actorRef, DentalLabCase::class, $case->id);
|
|
|
|
return $case;
|
|
}
|
|
|
|
public function transition(
|
|
DentalLabCase $case,
|
|
string $status,
|
|
string $ownerRef,
|
|
?string $actorRef = null,
|
|
): DentalLabCase {
|
|
if (! array_key_exists($status, DentalCatalog::labCaseStatuses())) {
|
|
throw new \InvalidArgumentException("Invalid lab case status [{$status}].");
|
|
}
|
|
|
|
$updates = ['status' => $status];
|
|
if (in_array($status, [DentalLabCase::STATUS_ORDERED, DentalLabCase::STATUS_SENT], true) && ! $case->ordered_at) {
|
|
$updates['ordered_at'] = now();
|
|
}
|
|
if (in_array($status, [DentalLabCase::STATUS_RECEIVED, DentalLabCase::STATUS_FITTED], true) && ! $case->received_at) {
|
|
$updates['received_at'] = now();
|
|
}
|
|
|
|
$case->update($updates);
|
|
|
|
AuditLogger::record($ownerRef, 'dental.lab.transitioned', $case->organization_id, $actorRef, DentalLabCase::class, $case->id, [
|
|
'status' => $status,
|
|
]);
|
|
|
|
return $case->fresh();
|
|
}
|
|
}
|