organization($request); abort_unless($modules->memberCanAccess($organization, $this->member($request), 'dentistry'), 403); } protected function assertVisit(Request $request, Visit $visit): void { abort_unless($visit->organization_id === $this->organization($request)->id, 404); $this->authorizeBranch($request, (int) $visit->branch_id); } public function updateTooth( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalChartService $charts, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ 'tooth_code' => ['required', 'string', 'max:8'], 'status' => ['required', 'string', 'in:present,missing,extracted,implant'], 'surfaces' => ['nullable', 'array'], 'surfaces.*' => ['string', 'max:2'], 'conditions' => ['nullable', 'array'], 'conditions.*' => ['string', 'max:32'], 'notes' => ['nullable', 'string', 'max:2000'], ]); $organization = $this->organization($request); $owner = $this->ownerRef($request); $visit->loadMissing('patient'); $chart = $charts->chartForPatient($organization, $visit->patient, $owner); try { $charts->updateTooth( $chart, $validated['tooth_code'], $validated, $owner, $this->actorRef($request), $visit, ); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'odontogram']) ->with('success', 'Tooth '.$validated['tooth_code'].' updated.'); } public function addPlanItem( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalTreatmentPlanService $plans, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ 'procedure_code' => ['required', 'string', 'max:64'], 'tooth_code' => ['nullable', 'string', 'max:8'], 'surfaces' => ['nullable', 'array'], 'surfaces.*' => ['string', 'max:2'], 'priority' => ['nullable', 'integer', 'min:1', 'max:100'], 'notes' => ['nullable', 'string', 'max:2000'], ]); $organization = $this->organization($request); $owner = $this->ownerRef($request); $visit->loadMissing('patient'); $plan = $plans->ensurePlan($organization, $visit->patient, $owner, $visit, $this->actorRef($request)); try { $plans->addItem($plan, $organization, $validated, $owner, $this->actorRef($request)); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'plan']) ->with('success', 'Added to treatment plan.'); } public function acceptPlan( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalTreatmentPlanService $plans, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $organization = $this->organization($request); $owner = $this->ownerRef($request); $visit->loadMissing('patient'); $plan = $plans->activePlanForPatient($organization, $visit->patient, $owner); if (! $plan) { return back()->with('error', 'No open treatment plan.'); } $plans->accept($plan, $owner, $this->actorRef($request)); return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'plan']) ->with('success', 'Treatment plan accepted.'); } public function completeProcedure( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalProcedureService $procedures, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ 'procedure_code' => ['required', 'string', 'max:64'], 'tooth_code' => ['nullable', 'string', 'max:8'], 'surfaces' => ['nullable', 'array'], 'surfaces.*' => ['string', 'max:2'], 'anesthesia' => ['nullable', 'string', 'max:32'], 'notes' => ['nullable', 'string', 'max:2000'], 'plan_item_id' => ['nullable', 'integer'], 'bill' => ['nullable', 'boolean'], ]); try { $procedures->complete( $this->organization($request), $visit, [ ...$validated, 'bill' => $request->boolean('bill', true), ], $this->ownerRef($request), $this->actorRef($request), ); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'treat']) ->with('success', 'Procedure recorded and billed.'); } public function saveNotes( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalVisitNoteService $notes, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ 'chief_complaint' => ['nullable', 'string', 'max:5000'], 'soft_tissue' => ['nullable', 'string', 'max:5000'], 'occlusion' => ['nullable', 'string', 'max:5000'], 'notes' => ['nullable', 'string', 'max:5000'], ]); $notes->upsert( $this->organization($request), $visit, $validated, $this->ownerRef($request), $this->actorRef($request), ); return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'notes']) ->with('success', 'Visit notes saved.'); } public function uploadImage( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalImagingService $imaging, DentalProcedureService $procedures, ): RedirectResponse { $this->authorizeAbility($request, 'consultations.manage'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $validated = $request->validate([ 'attachment' => ['required', 'file', 'max:10240', 'mimes:pdf,jpeg,jpg,png,webp'], 'modality' => ['required', 'string', 'in:pa,bw,opg,photo'], 'tooth_codes' => ['nullable', 'array'], 'tooth_codes.*' => ['string', 'max:8'], 'caption' => ['nullable', 'string', 'max:255'], 'add_fee' => ['nullable', 'boolean'], ]); try { $imaging->upload( $this->organization($request), $visit, $validated['attachment'], $validated, $this->ownerRef($request), $this->actorRef($request), ); if ($request->boolean('add_fee')) { $feeCode = match ($validated['modality']) { 'pa' => 'den.xray_pa', 'bw' => 'den.xray_bw', 'opg' => 'den.xray_opg', default => 'den.photo', }; $procedures->complete( $this->organization($request), $visit, [ 'procedure_code' => $feeCode, 'tooth_code' => ($validated['tooth_codes'][0] ?? null), 'bill' => true, 'notes' => 'Imaging fee', ], $this->ownerRef($request), $this->actorRef($request), ); } } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return redirect() ->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'imaging']) ->with('success', 'Image uploaded.'); } public function reports( Request $request, SpecialtyModuleService $modules, SpecialtyShellService $shell, DentalAnalyticsService $analytics, ): View { $this->assertDentistryAccess($request, $modules); $organization = $this->organization($request); $member = $this->member($request); $branchScope = app(\App\Services\Care\OrganizationResolver::class)->branchScope($member); return view('care.specialty.dentistry.reports', [ 'moduleKey' => 'dentistry', 'definition' => $modules->definition('dentistry'), 'report' => $analytics->report($organization, $this->ownerRef($request), $branchScope), 'modalities' => DentalCatalog::modalities(), 'currency' => config('care.billing.currency', 'GHS'), 'shellNav' => $shell->navItems('dentistry'), ]); } public function printChart( Request $request, Visit $visit, SpecialtyModuleService $modules, DentalChartService $charts, DentalTreatmentPlanService $plans, ): StreamedResponse|View { $this->authorizeAbility($request, 'consultations.view'); $this->assertDentistryAccess($request, $modules); $this->assertVisit($request, $visit); $organization = $this->organization($request); $owner = $this->ownerRef($request); $visit->loadMissing('patient'); $chart = $charts->chartForPatient($organization, $visit->patient, $owner); $plan = $plans->activePlanForPatient($organization, $visit->patient, $owner); return view('care.specialty.dentistry.print', [ 'visit' => $visit, 'patient' => $visit->patient, 'chart' => $chart, 'teethMap' => $charts->teethMap($chart), 'plan' => $plan, 'rows' => DentalCatalog::odontogramRows(), 'conditions' => DentalCatalog::conditions(), ]); } }