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>
787 lines
29 KiB
PHP
787 lines
29 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Care;
|
|
|
|
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\DentalImage;
|
|
use App\Models\DentalLabCase;
|
|
use App\Models\DentalPlanItem;
|
|
use App\Models\DentalRecall;
|
|
use App\Models\Visit;
|
|
use App\Services\Care\Dentistry\DentalAnalyticsService;
|
|
use App\Services\Care\Dentistry\DentalCatalog;
|
|
use App\Services\Care\Dentistry\DentalChartService;
|
|
use App\Services\Care\Dentistry\DentalImagingService;
|
|
use App\Services\Care\Dentistry\DentalLabCaseService;
|
|
use App\Services\Care\Dentistry\DentalPerioService;
|
|
use App\Services\Care\Dentistry\DentalProcedureService;
|
|
use App\Services\Care\Dentistry\DentalRecallService;
|
|
use App\Services\Care\Dentistry\DentalTreatmentPlanService;
|
|
use App\Services\Care\Dentistry\DentalVisitNoteService;
|
|
use App\Services\Care\SpecialtyModuleService;
|
|
use App\Services\Care\SpecialtyShellService;
|
|
use App\Services\Care\SpecialtyVisitStageService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class DentistryWorkspaceController extends Controller
|
|
{
|
|
use ScopesToAccount;
|
|
|
|
protected function assertDentistryAccess(Request $request, SpecialtyModuleService $modules): void
|
|
{
|
|
$organization = $this->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);
|
|
|
|
$from = $request->query('from');
|
|
$to = $request->query('to');
|
|
|
|
return view('care.specialty.dentistry.reports', [
|
|
'moduleKey' => 'dentistry',
|
|
'definition' => $modules->definition('dentistry'),
|
|
'report' => $analytics->report($organization, $this->ownerRef($request), $branchScope, $from, $to),
|
|
'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(),
|
|
]);
|
|
}
|
|
|
|
public function setDentition(
|
|
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([
|
|
'dentition' => ['required', 'string', 'in:adult,primary,mixed'],
|
|
]);
|
|
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
$visit->loadMissing('patient');
|
|
$chart = $charts->chartForPatient($organization, $visit->patient, $owner);
|
|
|
|
try {
|
|
$charts->setDentition(
|
|
$chart,
|
|
$validated['dentition'],
|
|
$owner,
|
|
$this->actorRef($request),
|
|
);
|
|
} catch (\InvalidArgumentException $e) {
|
|
return back()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()
|
|
->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'odontogram'])
|
|
->with('success', 'Dentition updated.');
|
|
}
|
|
|
|
public function updatePlanItem(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalPlanItem $item,
|
|
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->activePlanForPatient($organization, $visit->patient, $owner);
|
|
abort_unless($plan && $item->treatment_plan_id === $plan->id, 404);
|
|
|
|
try {
|
|
$plans->updateItem($item, $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', 'Plan item updated.');
|
|
}
|
|
|
|
public function cancelPlanItem(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalPlanItem $item,
|
|
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);
|
|
abort_unless($plan && $item->treatment_plan_id === $plan->id, 404);
|
|
|
|
try {
|
|
$plans->cancelItem($item, $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', 'Plan item cancelled.');
|
|
}
|
|
|
|
public function rejectPlan(
|
|
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([
|
|
'rejection_reason' => ['nullable', 'string', 'max:2000'],
|
|
]);
|
|
|
|
$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.');
|
|
}
|
|
|
|
try {
|
|
$plans->rejectPlan(
|
|
$plan,
|
|
$owner,
|
|
$validated['rejection_reason'] ?? null,
|
|
$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', 'Treatment plan rejected.');
|
|
}
|
|
|
|
public function cancelPlan(
|
|
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.');
|
|
}
|
|
|
|
try {
|
|
$plans->cancelPlan($plan, $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', 'Treatment plan cancelled.');
|
|
}
|
|
|
|
public function voidImage(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalImage $image,
|
|
SpecialtyModuleService $modules,
|
|
DentalImagingService $imaging,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$organization = $this->organization($request);
|
|
$visit->loadMissing('patient');
|
|
abort_unless(
|
|
$image->organization_id === $organization->id
|
|
&& ($image->visit_id === $visit->id || $image->patient_id === $visit->patient_id),
|
|
404,
|
|
);
|
|
|
|
try {
|
|
$imaging->void($image, $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 removed.');
|
|
}
|
|
|
|
public function setStage(
|
|
Request $request,
|
|
Visit $visit,
|
|
SpecialtyModuleService $modules,
|
|
SpecialtyVisitStageService $stages,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$validated = $request->validate([
|
|
'stage' => ['required', 'string', 'max:32'],
|
|
]);
|
|
|
|
try {
|
|
$stages->setStage(
|
|
$this->organization($request),
|
|
$visit,
|
|
'dentistry',
|
|
$validated['stage'],
|
|
$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' => 'overview'])
|
|
->with('success', 'Visit stage updated.');
|
|
}
|
|
|
|
public function savePerio(
|
|
Request $request,
|
|
Visit $visit,
|
|
SpecialtyModuleService $modules,
|
|
DentalPerioService $perio,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$validated = $request->validate([
|
|
'sites' => ['nullable', 'array'],
|
|
'sites.*.tooth_code' => ['required', 'string', 'max:8'],
|
|
'sites.*.surface' => ['required', 'string', 'max:8'],
|
|
'sites.*.probing_mm' => ['nullable', 'integer', 'min:0', 'max:20'],
|
|
'sites.*.bleeding' => ['nullable', 'boolean'],
|
|
'sites.*.plaque' => ['nullable', 'boolean'],
|
|
'sites.*.recession_mm' => ['nullable', 'integer', 'min:0', 'max:20'],
|
|
'notes' => ['nullable', 'string', 'max:5000'],
|
|
]);
|
|
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
$visit->loadMissing('patient');
|
|
|
|
try {
|
|
$perio->saveExam(
|
|
$organization,
|
|
$visit->patient,
|
|
$owner,
|
|
$validated['sites'] ?? [],
|
|
$visit,
|
|
$validated['notes'] ?? null,
|
|
$this->actorRef($request),
|
|
);
|
|
} catch (\InvalidArgumentException $e) {
|
|
return back()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()
|
|
->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'perio'])
|
|
->with('success', 'Perio exam saved.');
|
|
}
|
|
|
|
public function createLabCase(
|
|
Request $request,
|
|
Visit $visit,
|
|
SpecialtyModuleService $modules,
|
|
DentalLabCaseService $labCases,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$validated = $request->validate([
|
|
'case_type' => ['required', 'string', 'in:crown,bridge,denture,veneer,ortho,other'],
|
|
'lab_name' => ['nullable', 'string', 'max:255'],
|
|
'tooth_codes' => ['nullable', 'array'],
|
|
'tooth_codes.*' => ['string', 'max:8'],
|
|
'due_at' => ['nullable', 'date'],
|
|
'notes' => ['nullable', 'string', 'max:2000'],
|
|
'status' => ['nullable', 'string', 'in:draft,ordered,sent,received,fitted,cancelled'],
|
|
]);
|
|
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
$visit->loadMissing('patient');
|
|
|
|
try {
|
|
$labCases->create(
|
|
$organization,
|
|
$visit->patient,
|
|
$owner,
|
|
$validated,
|
|
$visit,
|
|
$this->actorRef($request),
|
|
);
|
|
} catch (\InvalidArgumentException $e) {
|
|
return back()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()
|
|
->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'lab'])
|
|
->with('success', 'Lab case created.');
|
|
}
|
|
|
|
public function transitionLabCase(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalLabCase $labCase,
|
|
SpecialtyModuleService $modules,
|
|
DentalLabCaseService $labCases,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$validated = $request->validate([
|
|
'status' => ['required', 'string', 'in:draft,ordered,sent,received,fitted,cancelled'],
|
|
]);
|
|
|
|
$organization = $this->organization($request);
|
|
$visit->loadMissing('patient');
|
|
abort_unless(
|
|
$labCase->organization_id === $organization->id
|
|
&& $labCase->patient_id === $visit->patient_id,
|
|
404,
|
|
);
|
|
|
|
try {
|
|
$labCases->transition(
|
|
$labCase,
|
|
$validated['status'],
|
|
$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' => 'lab'])
|
|
->with('success', 'Lab case updated.');
|
|
}
|
|
|
|
public function scheduleRecall(
|
|
Request $request,
|
|
Visit $visit,
|
|
SpecialtyModuleService $modules,
|
|
DentalRecallService $recalls,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$validated = $request->validate([
|
|
'due_on' => ['required', 'date'],
|
|
'reason' => ['nullable', 'string', 'max:255'],
|
|
'notes' => ['nullable', 'string', 'max:2000'],
|
|
]);
|
|
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
$visit->loadMissing('patient');
|
|
|
|
try {
|
|
$recalls->schedule(
|
|
$organization,
|
|
$visit->patient,
|
|
$owner,
|
|
$validated,
|
|
$this->actorRef($request),
|
|
);
|
|
} catch (\InvalidArgumentException $e) {
|
|
return back()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()
|
|
->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'recalls'])
|
|
->with('success', 'Recall scheduled.');
|
|
}
|
|
|
|
public function completeRecall(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalRecall $recall,
|
|
SpecialtyModuleService $modules,
|
|
DentalRecallService $recalls,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$organization = $this->organization($request);
|
|
$visit->loadMissing('patient');
|
|
abort_unless(
|
|
$recall->organization_id === $organization->id
|
|
&& $recall->patient_id === $visit->patient_id,
|
|
404,
|
|
);
|
|
|
|
try {
|
|
$recalls->complete(
|
|
$recall,
|
|
$this->ownerRef($request),
|
|
$visit,
|
|
$this->actorRef($request),
|
|
);
|
|
} catch (\InvalidArgumentException $e) {
|
|
return back()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()
|
|
->route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'recalls'])
|
|
->with('success', 'Recall completed.');
|
|
}
|
|
|
|
public function cancelRecall(
|
|
Request $request,
|
|
Visit $visit,
|
|
DentalRecall $recall,
|
|
SpecialtyModuleService $modules,
|
|
DentalRecallService $recalls,
|
|
): RedirectResponse {
|
|
$this->authorizeAbility($request, 'consultations.manage');
|
|
$this->assertDentistryAccess($request, $modules);
|
|
$this->assertVisit($request, $visit);
|
|
|
|
$organization = $this->organization($request);
|
|
$visit->loadMissing('patient');
|
|
abort_unless(
|
|
$recall->organization_id === $organization->id
|
|
&& $recall->patient_id === $visit->patient_id,
|
|
404,
|
|
);
|
|
|
|
try {
|
|
$recalls->cancel($recall, $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' => 'recalls'])
|
|
->with('success', 'Recall cancelled.');
|
|
}
|
|
}
|