Complete Dentistry commercial suite with PMS depth.
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:
isaacclad
2026-07-18 17:11:15 +00:00
co-authored by Cursor
parent e227f8705f
commit ce782382c5
37 changed files with 2458 additions and 204 deletions
+127
View File
@@ -266,4 +266,131 @@ class CareDentistrySuiteTest extends TestCase
->assertSee('Odontogram (FDI)')
->assertSee('Afia Darko');
}
public function test_plan_item_cancel_and_reject(): void
{
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.plan-items', $this->visit), [
'procedure_code' => 'den.fill',
'tooth_code' => '16',
'priority' => 10,
])
->assertRedirect();
$plan = DentalTreatmentPlan::query()->where('patient_id', $this->patient->id)->firstOrFail();
$item = $plan->items()->firstOrFail();
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.plan-items.cancel', [$this->visit, $item]))
->assertRedirect();
$this->assertSame(DentalPlanItem::STATUS_CANCELLED, $item->fresh()->status);
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.plan-items', $this->visit), [
'procedure_code' => 'den.cleaning',
'priority' => 20,
])
->assertRedirect();
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.plan.reject', $this->visit), [
'rejection_reason' => 'Patient declined',
])
->assertRedirect();
$this->assertSame(DentalTreatmentPlan::STATUS_REJECTED, $plan->fresh()->status);
}
public function test_visit_stage_advance_and_primary_dentition(): void
{
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.stage', $this->visit), [
'stage' => 'chair',
])
->assertRedirect();
$this->assertSame('chair', $this->visit->fresh()->specialty_stage);
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.dentition', $this->visit), [
'dentition' => 'primary',
])
->assertRedirect();
$chart = DentalChart::query()->where('patient_id', $this->patient->id)->firstOrFail();
$this->assertSame('primary', $chart->dentition);
$this->assertTrue($chart->teeth()->where('tooth_code', '55')->exists());
}
public function test_imaging_void_perio_lab_and_recall(): void
{
Storage::fake('public');
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.images', $this->visit), [
'attachment' => UploadedFile::fake()->image('xray.jpg'),
'modality' => 'bw',
'tooth_codes' => ['16'],
'add_fee' => 0,
])
->assertRedirect();
$image = \App\Models\DentalImage::query()->where('visit_id', $this->visit->id)->firstOrFail();
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.images.void', [$this->visit, $image]))
->assertRedirect();
$this->assertSoftDeleted('care_dental_images', ['id' => $image->id]);
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.perio', $this->visit), [
'sites' => [
['tooth_code' => '16', 'surface' => 'B', 'probing_mm' => 4, 'bleeding' => 1],
],
'notes' => 'Perio demo',
])
->assertRedirect();
$this->assertDatabaseHas('care_dental_perio_exams', [
'patient_id' => $this->patient->id,
'visit_id' => $this->visit->id,
]);
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.lab-cases', $this->visit), [
'case_type' => 'crown',
'lab_name' => 'City Lab',
'tooth_codes' => ['16'],
'status' => 'ordered',
])
->assertRedirect();
$case = \App\Models\DentalLabCase::query()->where('patient_id', $this->patient->id)->firstOrFail();
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.lab-cases.status', [$this->visit, $case]), [
'status' => 'received',
])
->assertRedirect();
$this->assertSame('received', $case->fresh()->status);
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.recalls', $this->visit), [
'due_on' => now()->addMonths(3)->toDateString(),
'reason' => 'Check-up',
])
->assertRedirect();
$recall = \App\Models\DentalRecall::query()->where('patient_id', $this->patient->id)->firstOrFail();
$this->actingAs($this->owner)
->post(route('care.specialty.dentistry.recalls.complete', [$this->visit, $recall]))
->assertRedirect();
$this->assertSame('completed', $recall->fresh()->status);
$this->actingAs($this->owner)
->get(route('care.specialty.dentistry.reports'))
->assertOk()
->assertSee('Revenue by procedure');
}
}
+1 -63
View File
@@ -165,7 +165,7 @@ class CareSpecialtyClinicalTest extends TestCase
->assertSee('ABC compromise');
}
public function test_blood_bank_request_and_dentistry_odontogram_save(): void
public function test_blood_bank_request_save(): void
{
[$bbVisit] = $this->activateWithVisit('blood_bank', 'blood_bank');
@@ -188,68 +188,6 @@ class CareSpecialtyClinicalTest extends TestCase
'module_key' => 'blood_bank',
'record_type' => 'blood_request',
]);
app(SpecialtyModuleService::class)->activate(
$this->organization->fresh(),
$this->owner->public_id,
'dentistry',
);
$dentalDept = Department::query()
->where('branch_id', $this->branch->id)
->where('type', 'dental')
->firstOrFail();
$patient = Patient::create([
'owner_ref' => $this->owner->public_id,
'organization_id' => $this->organization->id,
'branch_id' => $this->branch->id,
'patient_number' => 'P-CL-2',
'first_name' => 'Ama',
'last_name' => 'Boateng',
'gender' => 'female',
'date_of_birth' => '1990-01-01',
]);
$denVisit = Visit::create([
'owner_ref' => $this->owner->public_id,
'organization_id' => $this->organization->id,
'branch_id' => $this->branch->id,
'patient_id' => $patient->id,
'status' => Visit::STATUS_OPEN,
'checked_in_at' => now(),
]);
Appointment::create([
'owner_ref' => $this->owner->public_id,
'organization_id' => $this->organization->id,
'branch_id' => $this->branch->id,
'patient_id' => $patient->id,
'department_id' => $dentalDept->id,
'visit_id' => $denVisit->id,
'type' => Appointment::TYPE_WALK_IN,
'status' => Appointment::STATUS_WAITING,
'scheduled_at' => now(),
'waiting_at' => now(),
]);
$this->actingAs($this->owner)
->post(route('care.specialty.clinical.save', ['module' => 'dentistry', 'visit' => $denVisit]), [
'tab' => 'odontogram',
'payload' => [
'teeth_affected' => '16, 26',
'findings' => 'Caries on occlusal surfaces',
'planned_procedures' => 'Fillings',
'anesthesia' => 'Local',
],
])
->assertRedirect();
$this->assertDatabaseHas('care_specialty_clinical_records', [
'visit_id' => $denVisit->id,
'module_key' => 'dentistry',
'record_type' => 'odontogram',
]);
}
public function test_ophthalmology_uses_eye_exam_form_not_generic_notes(): void