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>
95 lines
3.3 KiB
PHP
95 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Dentistry;
|
|
|
|
use App\Models\DentalPerioExam;
|
|
use App\Models\DentalPerioSite;
|
|
use App\Models\Organization;
|
|
use App\Models\Patient;
|
|
use App\Models\Visit;
|
|
use App\Services\Care\AuditLogger;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class DentalPerioService
|
|
{
|
|
/**
|
|
* @return Collection<int, DentalPerioExam>
|
|
*/
|
|
public function examsForPatient(Organization $organization, Patient $patient, string $ownerRef, int $limit = 10): Collection
|
|
{
|
|
return DentalPerioExam::owned($ownerRef)
|
|
->where('organization_id', $organization->id)
|
|
->where('patient_id', $patient->id)
|
|
->with('sites')
|
|
->latest('id')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
|
|
public function latestForPatient(Organization $organization, Patient $patient, string $ownerRef): ?DentalPerioExam
|
|
{
|
|
return $this->examsForPatient($organization, $patient, $ownerRef, 1)->first();
|
|
}
|
|
|
|
/**
|
|
* @param list<array{tooth_code: string, surface: string, probing_mm?: ?int, bleeding?: bool, plaque?: bool, recession_mm?: ?int}> $sites
|
|
*/
|
|
public function saveExam(
|
|
Organization $organization,
|
|
Patient $patient,
|
|
string $ownerRef,
|
|
array $sites,
|
|
?Visit $visit = null,
|
|
?string $notes = null,
|
|
?string $actorRef = null,
|
|
): DentalPerioExam {
|
|
return DB::transaction(function () use ($organization, $patient, $ownerRef, $sites, $visit, $notes, $actorRef) {
|
|
$exam = DentalPerioExam::create([
|
|
'owner_ref' => $ownerRef,
|
|
'organization_id' => $organization->id,
|
|
'patient_id' => $patient->id,
|
|
'visit_id' => $visit?->id,
|
|
'exam_date' => now()->toDateString(),
|
|
'examiner' => $actorRef ?? $ownerRef,
|
|
'notes' => $notes,
|
|
]);
|
|
|
|
foreach ($sites as $row) {
|
|
$tooth = (string) ($row['tooth_code'] ?? '');
|
|
$surface = strtoupper((string) ($row['surface'] ?? ''));
|
|
if (! DentalCatalog::isValidToothCode($tooth) || ! in_array($surface, DentalCatalog::perioSurfaces(), true)) {
|
|
continue;
|
|
}
|
|
if (($row['probing_mm'] ?? null) === null
|
|
&& empty($row['bleeding'])
|
|
&& empty($row['plaque'])
|
|
&& ($row['recession_mm'] ?? null) === null) {
|
|
continue;
|
|
}
|
|
|
|
DentalPerioSite::create([
|
|
'perio_exam_id' => $exam->id,
|
|
'tooth_code' => $tooth,
|
|
'surface' => $surface,
|
|
'probing_mm' => isset($row['probing_mm']) ? (int) $row['probing_mm'] : null,
|
|
'bleeding' => (bool) ($row['bleeding'] ?? false),
|
|
'plaque' => (bool) ($row['plaque'] ?? false),
|
|
'recession_mm' => isset($row['recession_mm']) ? (int) $row['recession_mm'] : null,
|
|
]);
|
|
}
|
|
|
|
AuditLogger::record(
|
|
$ownerRef,
|
|
'dental.perio.saved',
|
|
$organization->id,
|
|
$actorRef,
|
|
DentalPerioExam::class,
|
|
$exam->id,
|
|
);
|
|
|
|
return $exam->fresh('sites');
|
|
});
|
|
}
|
|
}
|