*/ 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, 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(); } }