diff --git a/app/Http/Controllers/Care/QueueController.php b/app/Http/Controllers/Care/QueueController.php index cfaa498..c35c1c3 100644 --- a/app/Http/Controllers/Care/QueueController.php +++ b/app/Http/Controllers/Care/QueueController.php @@ -31,6 +31,7 @@ class QueueController extends Controller public function index(Request $request): View { $this->authorizeAbility($request, 'appointments.view'); + abort_unless(app(CarePermissions::class)->handlesFloorCare($this->member($request)), 403); $organization = $this->organization($request); $member = $this->member($request); $owner = $this->ownerRef($request); @@ -143,6 +144,7 @@ class QueueController extends Controller public function callNext(Request $request): RedirectResponse { + abort_unless(app(CarePermissions::class)->handlesFloorCare($this->member($request)), 403); $this->authorizeAbility($request, 'appointments.view'); $organization = $this->organization($request); abort_unless($this->queueBridge->isEnabled($organization), 404); diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index b4604bc..5155463 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -158,7 +158,7 @@ class SpecialtyModuleController extends Controller return back()->with('error', 'This appointment cannot start a consultation from its current status.'); } - $consultation = $consultations->startFromAppointment( + $consultations->startFromAppointment( $appointment, $owner, $owner, @@ -167,9 +167,20 @@ class SpecialtyModuleController extends Controller return back()->with('error', $e->getMessage()); } + $visit = $visit->fresh() ?? $appointment->visit; + $clinical = app(SpecialtyClinicalRecordService::class); + $shellTabs = app(SpecialtyShellService::class)->workspaceTabs($module); + $preferredTab = collect(array_keys($shellTabs)) + ->first(fn (string $tab) => $clinical->recordTypeForTab($module, $tab) !== null) + ?? 'clinical_notes'; + return redirect() - ->route('care.consultations.show', $consultation) - ->with('success', 'Consultation started.'); + ->route('care.specialty.workspace', [ + 'module' => $module, + 'visit' => $visit, + 'tab' => $preferredTab, + ]) + ->with('success', 'Specialty encounter started.'); } public function saveClinical( diff --git a/app/Services/Care/CarePermissions.php b/app/Services/Care/CarePermissions.php index e735fde..e9cc23e 100644 --- a/app/Services/Care/CarePermissions.php +++ b/app/Services/Care/CarePermissions.php @@ -79,4 +79,24 @@ class CarePermissions { return $member !== null && in_array($member->role, ['super_admin', 'hospital_admin'], true); } + + /** + * Floor care: queues, specialty clinics, lab/pharmacy counters. + * Facility admins manage settings/staff — they do not staff waiting lines. + */ + public function handlesFloorCare(?Member $member): bool + { + if ($member === null || $this->isAdmin($member)) { + return false; + } + + return in_array($member->role, [ + 'doctor', + 'nurse', + 'lab_technician', + 'pharmacist', + 'receptionist', + 'cashier', + ], true); + } } diff --git a/app/Services/Care/SpecialtyClinicalRecordService.php b/app/Services/Care/SpecialtyClinicalRecordService.php index 74f21ec..da3c37d 100644 --- a/app/Services/Care/SpecialtyClinicalRecordService.php +++ b/app/Services/Care/SpecialtyClinicalRecordService.php @@ -19,23 +19,14 @@ class SpecialtyClinicalRecordService */ public function recordTypesForModule(string $moduleKey): array { - return match ($moduleKey) { - 'emergency' => [ - 'triage' => 'triage', - 'clinical_notes' => 'clinical_note', - ], - 'blood_bank' => [ - 'requests' => 'blood_request', - 'inventory' => 'inventory_note', - ], - 'dentistry' => [ - 'odontogram' => 'odontogram', - 'clinical_notes' => 'clinical_note', - ], - default => [ - 'clinical_notes' => 'clinical_note', - ], - }; + $configured = config("care_specialty_clinical.record_types.{$moduleKey}"); + if (is_array($configured) && $configured !== []) { + return $configured; + } + + return [ + 'clinical_notes' => 'clinical_note', + ]; } public function recordTypeForTab(string $moduleKey, string $tab): ?string @@ -50,65 +41,14 @@ class SpecialtyClinicalRecordService */ public function fieldsFor(string $moduleKey, string $recordType): array { - $key = "{$moduleKey}.{$recordType}"; + $fields = config("care_specialty_clinical.fields.{$moduleKey}.{$recordType}"); + if (is_array($fields) && $fields !== []) { + return $fields; + } - return match ($key) { - 'emergency.triage' => [ - ['name' => 'acuity', 'label' => 'Triage acuity', 'type' => 'select', 'required' => true, 'options' => ['1 - Resuscitation', '2 - Emergency', '3 - Urgent', '4 - Semi-urgent', '5 - Non-urgent']], - ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], - ['name' => 'onset', 'label' => 'Onset', 'type' => 'text'], - ['name' => 'airway_ok', 'label' => 'Airway patent', 'type' => 'boolean'], - ['name' => 'breathing_ok', 'label' => 'Breathing adequate', 'type' => 'boolean'], - ['name' => 'circulation_ok', 'label' => 'Circulation stable', 'type' => 'boolean'], - ['name' => 'gcs', 'label' => 'GCS', 'type' => 'number'], - ['name' => 'pain_score', 'label' => 'Pain score (0–10)', 'type' => 'number'], - ['name' => 'mode_of_arrival', 'label' => 'Mode of arrival', 'type' => 'select', 'options' => ['Walk-in', 'Ambulance', 'Police', 'Referral', 'Other']], - ['name' => 'disposition_intent', 'label' => 'Disposition intent', 'type' => 'select', 'options' => ['Resus', 'Treatment bay', 'Observation', 'Discharge', 'Admit', 'Transfer']], - ['name' => 'notes', 'label' => 'Triage notes', 'type' => 'textarea', 'rows' => 3], - ], - 'emergency.clinical_note' => [ - ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'rows' => 4, 'required' => true], - ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], - ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], - ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], - ['name' => 'procedures', 'label' => 'Procedures performed', 'type' => 'textarea', 'rows' => 2], - ], - 'blood_bank.blood_request' => [ - ['name' => 'product', 'label' => 'Product', 'type' => 'select', 'required' => true, 'options' => ['Whole blood', 'Packed RBC', 'Platelets', 'FFP', 'Cryoprecipitate']], - ['name' => 'units', 'label' => 'Units requested', 'type' => 'number', 'required' => true], - ['name' => 'urgency', 'label' => 'Urgency', 'type' => 'select', 'required' => true, 'options' => ['Routine', 'Urgent', 'Emergency / massive']], - ['name' => 'patient_blood_group', 'label' => 'Patient blood group', 'type' => 'select', 'options' => ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'Unknown']], - ['name' => 'indication', 'label' => 'Clinical indication', 'type' => 'textarea', 'rows' => 2, 'required' => true], - ['name' => 'crossmatch_status', 'label' => 'Cross-match status', 'type' => 'select', 'options' => ['Not started', 'In progress', 'Compatible', 'Incompatible', 'Issued']], - ['name' => 'issued_units', 'label' => 'Units issued', 'type' => 'number'], - ['name' => 'notes', 'label' => 'Notes', 'type' => 'textarea', 'rows' => 2], - ], - 'blood_bank.inventory_note' => [ - ['name' => 'whole_blood_units', 'label' => 'Whole blood on hand', 'type' => 'number'], - ['name' => 'packed_rbc_units', 'label' => 'Packed RBC on hand', 'type' => 'number'], - ['name' => 'platelet_units', 'label' => 'Platelets on hand', 'type' => 'number'], - ['name' => 'ffp_units', 'label' => 'FFP on hand', 'type' => 'number'], - ['name' => 'low_stock_alert', 'label' => 'Flag low stock', 'type' => 'boolean'], - ['name' => 'notes', 'label' => 'Inventory notes', 'type' => 'textarea', 'rows' => 3], - ], - 'dentistry.odontogram' => [ - ['name' => 'teeth_affected', 'label' => 'Teeth affected (FDI numbers, comma-separated)', 'type' => 'text', 'required' => true], - ['name' => 'findings', 'label' => 'Chart findings', 'type' => 'textarea', 'rows' => 3, 'required' => true], - ['name' => 'planned_procedures', 'label' => 'Planned procedures', 'type' => 'textarea', 'rows' => 2], - ['name' => 'completed_procedures', 'label' => 'Completed this visit', 'type' => 'textarea', 'rows' => 2], - ['name' => 'anesthesia', 'label' => 'Anesthesia', 'type' => 'select', 'options' => ['None', 'Local', 'Sedation', 'GA']], - ['name' => 'occlusion_notes', 'label' => 'Occlusion / bite notes', 'type' => 'text'], - ], - 'dentistry.clinical_note' => [ - ['name' => 'history', 'label' => 'History', 'type' => 'textarea', 'rows' => 3, 'required' => true], - ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], - ['name' => 'working_diagnosis', 'label' => 'Diagnosis', 'type' => 'text'], - ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 2], - ], - default => [ - ['name' => 'notes', 'label' => 'Clinical notes', 'type' => 'textarea', 'rows' => 4, 'required' => true], - ], - }; + return [ + ['name' => 'notes', 'label' => 'Clinical notes', 'type' => 'textarea', 'rows' => 4, 'required' => true], + ]; } public function findForVisit(Visit $visit, string $moduleKey, string $recordType): ?SpecialtyClinicalRecord diff --git a/app/Services/Care/SpecialtyModuleService.php b/app/Services/Care/SpecialtyModuleService.php index 9b890a8..c325b30 100644 --- a/app/Services/Care/SpecialtyModuleService.php +++ b/app/Services/Care/SpecialtyModuleService.php @@ -150,7 +150,7 @@ class SpecialtyModuleService /** * Whether the member may see/serve this specialty module. * Doctors must be linked to a practitioner desk in that specialty department. - * Admins/receptionists keep org-wide visibility for operations. + * Receptionists keep queue/ops visibility; facility admins do not staff specialty floors. */ public function memberCanAccess(Organization $organization, ?Member $member, string $key): bool { diff --git a/config/care_specialty_clinical.php b/config/care_specialty_clinical.php new file mode 100644 index 0000000..b2b98da --- /dev/null +++ b/config/care_specialty_clinical.php @@ -0,0 +1,514 @@ +>, + * fields: array>>> + * } + */ +return [ + 'record_types' => [ + 'emergency' => [ + 'triage' => 'triage', + 'clinical_notes' => 'clinical_note', + ], + 'blood_bank' => [ + 'requests' => 'blood_request', + 'inventory' => 'inventory_note', + ], + 'dentistry' => [ + 'odontogram' => 'odontogram', + 'clinical_notes' => 'clinical_note', + ], + 'ophthalmology' => [ + 'exam' => 'eye_exam', + 'refraction' => 'refraction', + 'clinical_notes' => 'clinical_note', + ], + 'physiotherapy' => [ + 'assessment' => 'pt_assessment', + 'session' => 'pt_session', + ], + 'maternity' => [ + 'antenatal' => 'antenatal', + 'clinical_notes' => 'clinical_note', + ], + 'radiology' => [ + 'request' => 'imaging_request', + 'report' => 'imaging_report', + ], + 'cardiology' => [ + 'exam' => 'cardiac_exam', + 'clinical_notes' => 'clinical_note', + ], + 'psychiatry' => [ + 'assessment' => 'mental_status', + 'clinical_notes' => 'clinical_note', + ], + 'pediatrics' => [ + 'exam' => 'pediatric_exam', + 'clinical_notes' => 'clinical_note', + ], + 'orthopedics' => [ + 'exam' => 'ortho_exam', + 'clinical_notes' => 'clinical_note', + ], + 'ent' => [ + 'exam' => 'ent_exam', + 'clinical_notes' => 'clinical_note', + ], + 'oncology' => [ + 'review' => 'oncology_review', + 'clinical_notes' => 'clinical_note', + ], + 'renal' => [ + 'session' => 'renal_session', + 'clinical_notes' => 'clinical_note', + ], + 'surgery' => [ + 'preop' => 'preop', + 'clinical_notes' => 'clinical_note', + ], + 'vaccination' => [ + 'screening' => 'vax_screening', + 'administration' => 'vax_admin', + ], + 'pathology' => [ + 'request' => 'path_request', + 'report' => 'path_report', + ], + 'infusion' => [ + 'session' => 'infusion_session', + 'clinical_notes' => 'clinical_note', + ], + 'dermatology' => [ + 'exam' => 'skin_exam', + 'clinical_notes' => 'clinical_note', + ], + 'podiatry' => [ + 'exam' => 'foot_exam', + 'clinical_notes' => 'clinical_note', + ], + 'fertility' => [ + 'consult' => 'fertility_consult', + 'clinical_notes' => 'clinical_note', + ], + 'child_welfare' => [ + 'growth' => 'growth', + 'clinical_notes' => 'clinical_note', + ], + ], + 'fields' => [ + 'emergency' => [ + 'triage' => [ + ['name' => 'acuity', 'label' => 'Triage acuity', 'type' => 'select', 'required' => true, 'options' => ['1 - Resuscitation', '2 - Emergency', '3 - Urgent', '4 - Semi-urgent', '5 - Non-urgent']], + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'onset', 'label' => 'Onset', 'type' => 'text'], + ['name' => 'airway_ok', 'label' => 'Airway patent', 'type' => 'boolean'], + ['name' => 'breathing_ok', 'label' => 'Breathing adequate', 'type' => 'boolean'], + ['name' => 'circulation_ok', 'label' => 'Circulation stable', 'type' => 'boolean'], + ['name' => 'gcs', 'label' => 'GCS', 'type' => 'number'], + ['name' => 'pain_score', 'label' => 'Pain score (0–10)', 'type' => 'number'], + ['name' => 'mode_of_arrival', 'label' => 'Mode of arrival', 'type' => 'select', 'options' => ['Walk-in', 'Ambulance', 'Police', 'Referral', 'Other']], + ['name' => 'disposition_intent', 'label' => 'Disposition intent', 'type' => 'select', 'options' => ['Resus', 'Treatment bay', 'Observation', 'Discharge', 'Admit', 'Transfer']], + ['name' => 'notes', 'label' => 'Triage notes', 'type' => 'textarea', 'rows' => 3], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ['name' => 'procedures', 'label' => 'Procedures performed', 'type' => 'textarea', 'rows' => 2], + ], + ], + 'blood_bank' => [ + 'blood_request' => [ + ['name' => 'product', 'label' => 'Product', 'type' => 'select', 'required' => true, 'options' => ['Whole blood', 'Packed RBC', 'Platelets', 'FFP', 'Cryoprecipitate']], + ['name' => 'units', 'label' => 'Units requested', 'type' => 'number', 'required' => true], + ['name' => 'urgency', 'label' => 'Urgency', 'type' => 'select', 'required' => true, 'options' => ['Routine', 'Urgent', 'Emergency / massive']], + ['name' => 'patient_blood_group', 'label' => 'Patient blood group', 'type' => 'select', 'options' => ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'Unknown']], + ['name' => 'indication', 'label' => 'Clinical indication', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ['name' => 'crossmatch_status', 'label' => 'Cross-match status', 'type' => 'select', 'options' => ['Not started', 'In progress', 'Compatible', 'Incompatible', 'Issued']], + ['name' => 'issued_units', 'label' => 'Units issued', 'type' => 'number'], + ['name' => 'notes', 'label' => 'Notes', 'type' => 'textarea', 'rows' => 2], + ], + 'inventory_note' => [ + ['name' => 'whole_blood_units', 'label' => 'Whole blood on hand', 'type' => 'number'], + ['name' => 'packed_rbc_units', 'label' => 'Packed RBC on hand', 'type' => 'number'], + ['name' => 'platelet_units', 'label' => 'Platelets on hand', 'type' => 'number'], + ['name' => 'ffp_units', 'label' => 'FFP on hand', 'type' => 'number'], + ['name' => 'low_stock_alert', 'label' => 'Flag low stock', 'type' => 'boolean'], + ['name' => 'notes', 'label' => 'Inventory notes', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'dentistry' => [ + 'odontogram' => [ + ['name' => 'teeth_affected', 'label' => 'Teeth affected (FDI numbers, comma-separated)', 'type' => 'text', 'required' => true], + ['name' => 'findings', 'label' => 'Chart findings', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'planned_procedures', 'label' => 'Planned procedures', 'type' => 'textarea', 'rows' => 2], + ['name' => 'completed_procedures', 'label' => 'Completed this visit', 'type' => 'textarea', 'rows' => 2], + ['name' => 'anesthesia', 'label' => 'Anesthesia', 'type' => 'select', 'options' => ['None', 'Local', 'Sedation', 'GA']], + ['name' => 'occlusion_notes', 'label' => 'Occlusion / bite notes', 'type' => 'text'], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 2], + ], + ], + 'ophthalmology' => [ + 'eye_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'va_od', 'label' => 'Visual acuity OD', 'type' => 'text', 'required' => true], + ['name' => 'va_os', 'label' => 'Visual acuity OS', 'type' => 'text', 'required' => true], + ['name' => 'iop_od', 'label' => 'IOP OD (mmHg)', 'type' => 'number'], + ['name' => 'iop_os', 'label' => 'IOP OS (mmHg)', 'type' => 'number'], + ['name' => 'pupils', 'label' => 'Pupils', 'type' => 'text'], + ['name' => 'anterior_segment', 'label' => 'Anterior segment', 'type' => 'textarea', 'rows' => 2], + ['name' => 'fundus', 'label' => 'Fundus / posterior segment', 'type' => 'textarea', 'rows' => 2], + ['name' => 'findings', 'label' => 'Key findings', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ], + 'refraction' => [ + ['name' => 'sphere_od', 'label' => 'Sphere OD', 'type' => 'text'], + ['name' => 'cylinder_od', 'label' => 'Cylinder OD', 'type' => 'text'], + ['name' => 'axis_od', 'label' => 'Axis OD', 'type' => 'text'], + ['name' => 'sphere_os', 'label' => 'Sphere OS', 'type' => 'text'], + ['name' => 'cylinder_os', 'label' => 'Cylinder OS', 'type' => 'text'], + ['name' => 'axis_os', 'label' => 'Axis OS', 'type' => 'text'], + ['name' => 'add', 'label' => 'Near add', 'type' => 'text'], + ['name' => 'pd', 'label' => 'Pupillary distance (mm)', 'type' => 'number'], + ['name' => 'notes', 'label' => 'Refraction notes', 'type' => 'textarea', 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'physiotherapy' => [ + 'pt_assessment' => [ + ['name' => 'chief_complaint', 'label' => 'Presenting complaint', 'type' => 'text', 'required' => true], + ['name' => 'onset', 'label' => 'Onset / mechanism', 'type' => 'text'], + ['name' => 'pain_score', 'label' => 'Pain score (0–10)', 'type' => 'number'], + ['name' => 'rom', 'label' => 'Range of motion', 'type' => 'textarea', 'rows' => 2], + ['name' => 'strength', 'label' => 'Strength / function', 'type' => 'textarea', 'rows' => 2], + ['name' => 'goals', 'label' => 'Treatment goals', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ], + 'pt_session' => [ + ['name' => 'modalities', 'label' => 'Modalities used', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ['name' => 'exercises', 'label' => 'Exercises prescribed', 'type' => 'textarea', 'rows' => 2], + ['name' => 'response', 'label' => 'Patient response', 'type' => 'textarea', 'rows' => 2], + ['name' => 'home_program', 'label' => 'Home program', 'type' => 'textarea', 'rows' => 2], + ['name' => 'next_review', 'label' => 'Next review', 'type' => 'text'], + ], + ], + 'maternity' => [ + 'antenatal' => [ + ['name' => 'gravida', 'label' => 'Gravida', 'type' => 'number', 'required' => true], + ['name' => 'para', 'label' => 'Para', 'type' => 'number'], + ['name' => 'gestational_age_weeks', 'label' => 'Gestational age (weeks)', 'type' => 'number', 'required' => true], + ['name' => 'bp', 'label' => 'Blood pressure', 'type' => 'text'], + ['name' => 'fundal_height', 'label' => 'Fundal height (cm)', 'type' => 'number'], + ['name' => 'fetal_heart_rate', 'label' => 'Fetal heart rate', 'type' => 'number'], + ['name' => 'presentation', 'label' => 'Presentation', 'type' => 'select', 'options' => ['Cephalic', 'Breech', 'Transverse', 'Unknown']], + ['name' => 'urine', 'label' => 'Urine dipstick', 'type' => 'text'], + ['name' => 'danger_signs', 'label' => 'Danger signs', 'type' => 'textarea', 'rows' => 2], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'radiology' => [ + 'imaging_request' => [ + ['name' => 'modality', 'label' => 'Modality', 'type' => 'select', 'required' => true, 'options' => ['X-ray', 'Ultrasound', 'CT', 'MRI', 'Fluoroscopy', 'Other']], + ['name' => 'body_part', 'label' => 'Body part / region', 'type' => 'text', 'required' => true], + ['name' => 'clinical_info', 'label' => 'Clinical information', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'urgency', 'label' => 'Urgency', 'type' => 'select', 'options' => ['Routine', 'Urgent', 'STAT']], + ['name' => 'contrast', 'label' => 'Contrast', 'type' => 'select', 'options' => ['None', 'IV', 'Oral', 'Both']], + ], + 'imaging_report' => [ + ['name' => 'technique', 'label' => 'Technique', 'type' => 'textarea', 'rows' => 2], + ['name' => 'findings', 'label' => 'Findings', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'impression', 'label' => 'Impression', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'recommendations', 'label' => 'Recommendations', 'type' => 'textarea', 'rows' => 2], + ], + ], + 'cardiology' => [ + 'cardiac_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'nyha_class', 'label' => 'NYHA class', 'type' => 'select', 'options' => ['I', 'II', 'III', 'IV', 'N/A']], + ['name' => 'bp', 'label' => 'Blood pressure', 'type' => 'text'], + ['name' => 'hr', 'label' => 'Heart rate', 'type' => 'number'], + ['name' => 'heart_sounds', 'label' => 'Heart sounds / murmurs', 'type' => 'textarea', 'rows' => 2], + ['name' => 'ecg_findings', 'label' => 'ECG findings', 'type' => 'textarea', 'rows' => 2], + ['name' => 'echo_summary', 'label' => 'Echo summary', 'type' => 'textarea', 'rows' => 2], + ['name' => 'risk_factors', 'label' => 'Cardiac risk factors', 'type' => 'textarea', 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'psychiatry' => [ + 'mental_status' => [ + ['name' => 'chief_complaint', 'label' => 'Presenting complaint', 'type' => 'text', 'required' => true], + ['name' => 'appearance', 'label' => 'Appearance / behaviour', 'type' => 'textarea', 'rows' => 2], + ['name' => 'mood_affect', 'label' => 'Mood / affect', 'type' => 'text'], + ['name' => 'thought', 'label' => 'Thought content / process', 'type' => 'textarea', 'rows' => 2], + ['name' => 'perception', 'label' => 'Perception', 'type' => 'text'], + ['name' => 'insight', 'label' => 'Insight / judgment', 'type' => 'text'], + ['name' => 'risk', 'label' => 'Risk assessment', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ['name' => 'mse_summary', 'label' => 'MSE summary', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'pediatrics' => [ + 'pediatric_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'age_months', 'label' => 'Age (months)', 'type' => 'number'], + ['name' => 'weight_kg', 'label' => 'Weight (kg)', 'type' => 'number'], + ['name' => 'height_cm', 'label' => 'Height / length (cm)', 'type' => 'number'], + ['name' => 'head_circumference_cm', 'label' => 'Head circumference (cm)', 'type' => 'number'], + ['name' => 'developmental', 'label' => 'Developmental notes', 'type' => 'textarea', 'rows' => 2], + ['name' => 'immunization_status', 'label' => 'Immunization status', 'type' => 'text'], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'orthopedics' => [ + 'ortho_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'side', 'label' => 'Side', 'type' => 'select', 'options' => ['Left', 'Right', 'Bilateral', 'N/A']], + ['name' => 'region', 'label' => 'Region / joint', 'type' => 'text', 'required' => true], + ['name' => 'mechanism', 'label' => 'Mechanism of injury', 'type' => 'textarea', 'rows' => 2], + ['name' => 'rom', 'label' => 'Range of motion', 'type' => 'textarea', 'rows' => 2], + ['name' => 'neurovascular', 'label' => 'Neurovascular status', 'type' => 'text'], + ['name' => 'imaging', 'label' => 'Imaging findings', 'type' => 'textarea', 'rows' => 2], + ['name' => 'fracture_classification', 'label' => 'Fracture / injury classification', 'type' => 'text'], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'ent' => [ + 'ent_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'ear_findings', 'label' => 'Ear findings', 'type' => 'textarea', 'rows' => 2], + ['name' => 'nose_findings', 'label' => 'Nose / sinus findings', 'type' => 'textarea', 'rows' => 2], + ['name' => 'throat_findings', 'label' => 'Throat / larynx findings', 'type' => 'textarea', 'rows' => 2], + ['name' => 'hearing', 'label' => 'Hearing assessment', 'type' => 'text'], + ['name' => 'neck', 'label' => 'Neck examination', 'type' => 'text'], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'oncology' => [ + 'oncology_review' => [ + ['name' => 'diagnosis', 'label' => 'Oncology diagnosis', 'type' => 'text', 'required' => true], + ['name' => 'stage', 'label' => 'Stage', 'type' => 'text'], + ['name' => 'performance_status', 'label' => 'Performance status (ECOG)', 'type' => 'select', 'options' => ['0', '1', '2', '3', '4']], + ['name' => 'current_regimen', 'label' => 'Current regimen', 'type' => 'textarea', 'rows' => 2], + ['name' => 'cycle', 'label' => 'Cycle / day', 'type' => 'text'], + ['name' => 'toxicity', 'label' => 'Toxicity / side effects', 'type' => 'textarea', 'rows' => 2], + ['name' => 'response', 'label' => 'Treatment response', 'type' => 'textarea', 'rows' => 2], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'renal' => [ + 'renal_session' => [ + ['name' => 'session_type', 'label' => 'Session type', 'type' => 'select', 'required' => true, 'options' => ['Hemodialysis', 'Peritoneal dialysis', 'Clinic review', 'Other']], + ['name' => 'pre_weight_kg', 'label' => 'Pre-weight (kg)', 'type' => 'number'], + ['name' => 'post_weight_kg', 'label' => 'Post-weight (kg)', 'type' => 'number'], + ['name' => 'uf_goal_l', 'label' => 'UF goal (L)', 'type' => 'number'], + ['name' => 'access', 'label' => 'Access', 'type' => 'text'], + ['name' => 'bp_pre', 'label' => 'BP pre', 'type' => 'text'], + ['name' => 'bp_post', 'label' => 'BP post', 'type' => 'text'], + ['name' => 'complications', 'label' => 'Complications', 'type' => 'textarea', 'rows' => 2], + ['name' => 'notes', 'label' => 'Session notes', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'surgery' => [ + 'preop' => [ + ['name' => 'proposed_procedure', 'label' => 'Proposed procedure', 'type' => 'text', 'required' => true], + ['name' => 'indication', 'label' => 'Indication', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ['name' => 'asa_class', 'label' => 'ASA class', 'type' => 'select', 'options' => ['I', 'II', 'III', 'IV', 'V', 'E']], + ['name' => 'allergies_reviewed', 'label' => 'Allergies reviewed', 'type' => 'boolean'], + ['name' => 'consent_obtained', 'label' => 'Consent obtained', 'type' => 'boolean'], + ['name' => 'fitness', 'label' => 'Fitness for anaesthesia', 'type' => 'textarea', 'rows' => 2], + ['name' => 'investigations', 'label' => 'Relevant investigations', 'type' => 'textarea', 'rows' => 2], + ['name' => 'plan', 'label' => 'Peri-op plan', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'vaccination' => [ + 'vax_screening' => [ + ['name' => 'vaccine', 'label' => 'Vaccine', 'type' => 'text', 'required' => true], + ['name' => 'indications', 'label' => 'Indications', 'type' => 'textarea', 'rows' => 2], + ['name' => 'contraindications', 'label' => 'Contraindications checked', 'type' => 'boolean', 'required' => true], + ['name' => 'allergy_history', 'label' => 'Allergy / prior reaction', 'type' => 'textarea', 'rows' => 2], + ['name' => 'fever_today', 'label' => 'Fever today', 'type' => 'boolean'], + ['name' => 'cleared', 'label' => 'Cleared to vaccinate', 'type' => 'boolean', 'required' => true], + ], + 'vax_admin' => [ + ['name' => 'vaccine', 'label' => 'Vaccine administered', 'type' => 'text', 'required' => true], + ['name' => 'dose', 'label' => 'Dose / antigen', 'type' => 'text'], + ['name' => 'site', 'label' => 'Injection site', 'type' => 'select', 'options' => ['Left arm', 'Right arm', 'Left thigh', 'Right thigh', 'Oral', 'Other']], + ['name' => 'batch', 'label' => 'Batch / lot number', 'type' => 'text', 'required' => true], + ['name' => 'expiry', 'label' => 'Expiry date', 'type' => 'text'], + ['name' => 'route', 'label' => 'Route', 'type' => 'select', 'options' => ['IM', 'SC', 'ID', 'Oral', 'Nasal']], + ['name' => 'adverse_events', 'label' => 'Immediate adverse events', 'type' => 'textarea', 'rows' => 2], + ['name' => 'next_due', 'label' => 'Next dose due', 'type' => 'text'], + ], + ], + 'pathology' => [ + 'path_request' => [ + ['name' => 'specimen_type', 'label' => 'Specimen type', 'type' => 'text', 'required' => true], + ['name' => 'site', 'label' => 'Anatomic site', 'type' => 'text', 'required' => true], + ['name' => 'clinical_history', 'label' => 'Clinical history', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'urgency', 'label' => 'Urgency', 'type' => 'select', 'options' => ['Routine', 'Urgent', 'Intra-op']], + ['name' => 'tests_requested', 'label' => 'Tests requested', 'type' => 'textarea', 'rows' => 2], + ], + 'path_report' => [ + ['name' => 'gross', 'label' => 'Gross description', 'type' => 'textarea', 'rows' => 3], + ['name' => 'microscopy', 'label' => 'Microscopy', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'diagnosis', 'label' => 'Diagnosis', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ['name' => 'comment', 'label' => 'Comment', 'type' => 'textarea', 'rows' => 2], + ], + ], + 'infusion' => [ + 'infusion_session' => [ + ['name' => 'medication', 'label' => 'Medication / regimen', 'type' => 'text', 'required' => true], + ['name' => 'dose', 'label' => 'Dose', 'type' => 'text'], + ['name' => 'rate', 'label' => 'Infusion rate', 'type' => 'text'], + ['name' => 'access', 'label' => 'IV access', 'type' => 'text'], + ['name' => 'start_time', 'label' => 'Start time', 'type' => 'text'], + ['name' => 'end_time', 'label' => 'End time', 'type' => 'text'], + ['name' => 'vitals', 'label' => 'Vitals during infusion', 'type' => 'textarea', 'rows' => 2], + ['name' => 'reactions', 'label' => 'Reactions / interventions', 'type' => 'textarea', 'rows' => 2], + ['name' => 'notes', 'label' => 'Session notes', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'dermatology' => [ + 'skin_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'duration', 'label' => 'Duration', 'type' => 'text'], + ['name' => 'distribution', 'label' => 'Distribution', 'type' => 'text'], + ['name' => 'morphology', 'label' => 'Lesion morphology', 'type' => 'textarea', 'required' => true, 'rows' => 2], + ['name' => 'itch_pain', 'label' => 'Itch / pain', 'type' => 'text'], + ['name' => 'dermoscopy', 'label' => 'Dermoscopy notes', 'type' => 'textarea', 'rows' => 2], + ['name' => 'differential', 'label' => 'Differential diagnosis', 'type' => 'textarea', 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'podiatry' => [ + 'foot_exam' => [ + ['name' => 'chief_complaint', 'label' => 'Chief complaint', 'type' => 'text', 'required' => true], + ['name' => 'side', 'label' => 'Side', 'type' => 'select', 'options' => ['Left', 'Right', 'Bilateral']], + ['name' => 'diabetes', 'label' => 'Diabetic foot risk', 'type' => 'select', 'options' => ['Low', 'Moderate', 'High', 'Active ulcer', 'N/A']], + ['name' => 'pulses', 'label' => 'Pulses', 'type' => 'text'], + ['name' => 'sensation', 'label' => 'Sensation / neuropathy', 'type' => 'text'], + ['name' => 'skin_nails', 'label' => 'Skin / nails', 'type' => 'textarea', 'rows' => 2], + ['name' => 'ulcer', 'label' => 'Ulcer description', 'type' => 'textarea', 'rows' => 2], + ['name' => 'offloading', 'label' => 'Offloading / footwear', 'type' => 'text'], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'fertility' => [ + 'fertility_consult' => [ + ['name' => 'partner_present', 'label' => 'Partner present', 'type' => 'boolean'], + ['name' => 'trying_months', 'label' => 'Trying to conceive (months)', 'type' => 'number'], + ['name' => 'cycle_day', 'label' => 'Cycle day', 'type' => 'number'], + ['name' => 'amh', 'label' => 'AMH / ovarian reserve notes', 'type' => 'text'], + ['name' => 'semen_summary', 'label' => 'Semen analysis summary', 'type' => 'textarea', 'rows' => 2], + ['name' => 'tubal_uterine', 'label' => 'Tubal / uterine assessment', 'type' => 'textarea', 'rows' => 2], + ['name' => 'plan', 'label' => 'Fertility plan', 'type' => 'textarea', 'required' => true, 'rows' => 3], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + 'child_welfare' => [ + 'growth' => [ + ['name' => 'age_months', 'label' => 'Age (months)', 'type' => 'number', 'required' => true], + ['name' => 'weight_kg', 'label' => 'Weight (kg)', 'type' => 'number', 'required' => true], + ['name' => 'height_cm', 'label' => 'Height / length (cm)', 'type' => 'number'], + ['name' => 'head_circumference_cm', 'label' => 'Head circumference (cm)', 'type' => 'number'], + ['name' => 'muac_cm', 'label' => 'MUAC (cm)', 'type' => 'number'], + ['name' => 'growth_status', 'label' => 'Growth status', 'type' => 'select', 'options' => ['Normal', 'Underweight', 'Stunted', 'Wasted', 'Overweight']], + ['name' => 'feeding', 'label' => 'Feeding / nutrition', 'type' => 'textarea', 'rows' => 2], + ['name' => 'milestones', 'label' => 'Developmental milestones', 'type' => 'textarea', 'rows' => 2], + ['name' => 'counseling', 'label' => 'Counseling given', 'type' => 'textarea', 'rows' => 2], + ], + 'clinical_note' => [ + ['name' => 'history', 'label' => 'History of present illness', 'type' => 'textarea', 'required' => true, 'rows' => 4], + ['name' => 'examination', 'label' => 'Examination', 'type' => 'textarea', 'rows' => 3], + ['name' => 'working_diagnosis', 'label' => 'Working diagnosis', 'type' => 'text'], + ['name' => 'plan', 'label' => 'Plan', 'type' => 'textarea', 'rows' => 3], + ], + ], + ], +]; diff --git a/config/care_specialty_modules.php b/config/care_specialty_modules.php index c79486a..2bf01af 100644 --- a/config/care_specialty_modules.php +++ b/config/care_specialty_modules.php @@ -18,7 +18,7 @@ return [ 'queue_prefix' => 'ER', 'nav_label' => 'Emergency', 'queue_keywords' => ['emerg', 'casualty', 'trauma', 'er', 'a&e'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], 'default_on_paid_plans' => true, ], 'blood_bank' => [ @@ -30,7 +30,7 @@ return [ 'queue_prefix' => 'BB', 'nav_label' => 'Blood Bank', 'queue_keywords' => ['blood', 'transfusion', 'crossmatch', 'donor'], - 'roles' => ['doctor', 'nurse', 'lab_technician', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'lab_technician', 'receptionist'], 'default_on_paid_plans' => true, ], 'dentistry' => [ @@ -42,7 +42,7 @@ return [ 'queue_prefix' => 'DEN', 'nav_label' => 'Dentistry', 'queue_keywords' => ['dent', 'dental', 'oral'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'ophthalmology' => [ 'label' => 'Eye care', @@ -53,7 +53,7 @@ return [ 'queue_prefix' => 'EYE', 'nav_label' => 'Eye care', 'queue_keywords' => ['eye', 'ophthal', 'optom'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'physiotherapy' => [ 'label' => 'Physiotherapy', @@ -64,7 +64,7 @@ return [ 'queue_prefix' => 'PHY', 'nav_label' => 'Physiotherapy', 'queue_keywords' => ['physio', 'rehab', 'therapy'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'maternity' => [ 'label' => 'Maternity / Antenatal', @@ -75,7 +75,7 @@ return [ 'queue_prefix' => 'MAT', 'nav_label' => 'Maternity', 'queue_keywords' => ['matern', 'antenatal', 'obstetric'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'radiology' => [ 'label' => 'Radiology', @@ -86,7 +86,7 @@ return [ 'queue_prefix' => 'RAD', 'nav_label' => 'Radiology', 'queue_keywords' => ['radio', 'imaging', 'x-ray', 'xray', 'ultrasound', 'ct', 'mri'], - 'roles' => ['doctor', 'lab_technician', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'lab_technician', 'receptionist'], ], 'cardiology' => [ 'label' => 'Cardiology', @@ -97,7 +97,7 @@ return [ 'queue_prefix' => 'CAR', 'nav_label' => 'Cardiology', 'queue_keywords' => ['cardio', 'heart', 'ecg', 'echo'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'psychiatry' => [ 'label' => 'Psychiatry', @@ -108,7 +108,7 @@ return [ 'queue_prefix' => 'PSY', 'nav_label' => 'Psychiatry', 'queue_keywords' => ['psych', 'mental', 'behaviour'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'pediatrics' => [ 'label' => 'Pediatrics', @@ -119,7 +119,7 @@ return [ 'queue_prefix' => 'PED', 'nav_label' => 'Pediatrics', 'queue_keywords' => ['pedia', 'paedia', 'child', 'infant'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'orthopedics' => [ 'label' => 'Orthopedics', @@ -130,7 +130,7 @@ return [ 'queue_prefix' => 'ORT', 'nav_label' => 'Orthopedics', 'queue_keywords' => ['ortho', 'fracture', 'bone', 'joint'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'ent' => [ 'label' => 'ENT', @@ -141,7 +141,7 @@ return [ 'queue_prefix' => 'ENT', 'nav_label' => 'ENT', 'queue_keywords' => ['ent', 'ear', 'nose', 'throat', 'otolaryng'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'oncology' => [ 'label' => 'Oncology', @@ -152,7 +152,7 @@ return [ 'queue_prefix' => 'ONC', 'nav_label' => 'Oncology', 'queue_keywords' => ['oncol', 'cancer', 'chemo'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'renal' => [ 'label' => 'Renal Care', @@ -163,7 +163,7 @@ return [ 'queue_prefix' => 'REN', 'nav_label' => 'Renal Care', 'queue_keywords' => ['renal', 'dialysis', 'nephro', 'kidney'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'surgery' => [ 'label' => 'Surgery', @@ -174,7 +174,7 @@ return [ 'queue_prefix' => 'SUR', 'nav_label' => 'Surgery', 'queue_keywords' => ['surg', 'theatre', 'operat', 'pre-op'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'vaccination' => [ 'label' => 'Vaccination & Immunization', @@ -185,7 +185,7 @@ return [ 'queue_prefix' => 'VAX', 'nav_label' => 'Vaccination', 'queue_keywords' => ['vaccin', 'immun', 'epi', 'injection'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'pathology' => [ 'label' => 'Pathology', @@ -196,7 +196,7 @@ return [ 'queue_prefix' => 'PATH', 'nav_label' => 'Pathology', 'queue_keywords' => ['pathol', 'histo', 'cytol', 'biopsy'], - 'roles' => ['doctor', 'lab_technician', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'lab_technician', 'receptionist'], ], 'infusion' => [ 'label' => 'Infusion Centre', @@ -207,7 +207,7 @@ return [ 'queue_prefix' => 'INF', 'nav_label' => 'Infusion', 'queue_keywords' => ['infusion', 'iv', 'drip', 'day care'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'dermatology' => [ 'label' => 'Dermatology', @@ -218,7 +218,7 @@ return [ 'queue_prefix' => 'DER', 'nav_label' => 'Dermatology', 'queue_keywords' => ['derma', 'skin', 'rash'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'podiatry' => [ 'label' => 'Podiatry', @@ -229,7 +229,7 @@ return [ 'queue_prefix' => 'POD', 'nav_label' => 'Podiatry', 'queue_keywords' => ['podiat', 'foot', 'diabetic foot'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'fertility' => [ 'label' => 'Fertility', @@ -240,7 +240,7 @@ return [ 'queue_prefix' => 'FER', 'nav_label' => 'Fertility', 'queue_keywords' => ['fertil', 'ivf', 'reproduct'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], 'child_welfare' => [ 'label' => 'Child Welfare Clinic', @@ -251,6 +251,6 @@ return [ 'queue_prefix' => 'CWC', 'nav_label' => 'Child Welfare', 'queue_keywords' => ['cwc', 'child welfare', 'well baby', 'growth'], - 'roles' => ['doctor', 'nurse', 'receptionist', 'hospital_admin', 'super_admin'], + 'roles' => ['doctor', 'nurse', 'receptionist'], ], ]; diff --git a/config/care_specialty_shell.php b/config/care_specialty_shell.php index 06f3add..dfbb4f0 100644 --- a/config/care_specialty_shell.php +++ b/config/care_specialty_shell.php @@ -35,7 +35,7 @@ return [ ], 'actions' => [ 'call_next' => 'Call next patient', - 'start' => 'Start consultation', + 'start' => 'Start specialty encounter', 'complete' => 'Complete', 'order_test' => 'Order test', 'prescribe' => 'Prescribe', @@ -114,5 +114,375 @@ return [ 'documents' => 'Documents', ], ], + 'ophthalmology' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting room', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Eye exam', 'queue_point' => 'chair'], + ['code' => 'procedure', 'label' => 'Procedure / treatment', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'eye.consult', 'label' => 'Eye consultation', 'amount_minor' => 6000, 'type' => 'consultation'], + ['code' => 'eye.refraction', 'label' => 'Refraction / glasses exam', 'amount_minor' => 4000, 'type' => 'procedure'], + ['code' => 'eye.iop', 'label' => 'IOP / pressure check', 'amount_minor' => 3000, 'type' => 'procedure'], + ['code' => 'eye.fundus', 'label' => 'Fundus examination', 'amount_minor' => 5000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Eye exam', + 'refraction' => 'Refraction', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'physiotherapy' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'assessment', 'label' => 'Assessment', 'queue_point' => 'chair'], + ['code' => 'treatment', 'label' => 'Treatment session', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'pt.assessment', 'label' => 'Physio assessment', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'pt.session', 'label' => 'Treatment session', 'amount_minor' => 7000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'assessment' => 'Assessment', + 'session' => 'Session notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'maternity' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'antenatal', 'label' => 'Antenatal review', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'mat.anc', 'label' => 'Antenatal visit', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'mat.ultrasound', 'label' => 'Obstetric ultrasound', 'amount_minor' => 12000, 'type' => 'imaging'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'antenatal' => 'Antenatal', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'radiology' => [ + 'stages' => [ + ['code' => 'request', 'label' => 'Request received', 'queue_point' => 'waiting'], + ['code' => 'imaging', 'label' => 'Imaging', 'queue_point' => 'chair'], + ['code' => 'reporting', 'label' => 'Reporting', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'rad.xray', 'label' => 'X-ray', 'amount_minor' => 8000, 'type' => 'imaging'], + ['code' => 'rad.ultrasound', 'label' => 'Ultrasound', 'amount_minor' => 15000, 'type' => 'imaging'], + ['code' => 'rad.ct', 'label' => 'CT scan', 'amount_minor' => 45000, 'type' => 'imaging'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'request' => 'Imaging request', + 'report' => 'Report', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'cardiology' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Cardiac exam', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'car.consult', 'label' => 'Cardiology consultation', 'amount_minor' => 8000, 'type' => 'consultation'], + ['code' => 'car.ecg', 'label' => 'ECG', 'amount_minor' => 5000, 'type' => 'procedure'], + ['code' => 'car.echo', 'label' => 'Echocardiogram', 'amount_minor' => 25000, 'type' => 'imaging'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Cardiac exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'psychiatry' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'assessment', 'label' => 'Mental health assessment', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'psy.consult', 'label' => 'Psychiatry consultation', 'amount_minor' => 8000, 'type' => 'consultation'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'assessment' => 'Assessment', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'pediatrics' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Pediatric exam', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'ped.consult', 'label' => 'Pediatric consultation', 'amount_minor' => 5000, 'type' => 'consultation'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Pediatric exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'orthopedics' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Ortho exam', 'queue_point' => 'chair'], + ['code' => 'procedure', 'label' => 'Procedure / casting', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'ort.consult', 'label' => 'Orthopedic consultation', 'amount_minor' => 6000, 'type' => 'consultation'], + ['code' => 'ort.cast', 'label' => 'Casting / splinting', 'amount_minor' => 10000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Ortho exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'ent' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'ENT exam', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'ent.consult', 'label' => 'ENT consultation', 'amount_minor' => 6000, 'type' => 'consultation'], + ['code' => 'ent.procedure', 'label' => 'ENT procedure', 'amount_minor' => 12000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'ENT exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'oncology' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'review', 'label' => 'Oncology review', 'queue_point' => 'chair'], + ['code' => 'treatment', 'label' => 'Treatment', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'onc.consult', 'label' => 'Oncology consultation', 'amount_minor' => 10000, 'type' => 'consultation'], + ['code' => 'onc.chemo', 'label' => 'Chemo day-care session', 'amount_minor' => 50000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'review' => 'Oncology review', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'renal' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'dialysis', 'label' => 'Dialysis / clinic', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'ren.consult', 'label' => 'Nephrology consultation', 'amount_minor' => 8000, 'type' => 'consultation'], + ['code' => 'ren.dialysis', 'label' => 'Dialysis session', 'amount_minor' => 35000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'session' => 'Renal session', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'surgery' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'preop', 'label' => 'Pre-op assessment', 'queue_point' => 'chair'], + ['code' => 'postop', 'label' => 'Post-op review', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'sur.consult', 'label' => 'Surgical consultation', 'amount_minor' => 8000, 'type' => 'consultation'], + ['code' => 'sur.preop', 'label' => 'Pre-op assessment', 'amount_minor' => 5000, 'type' => 'consultation'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'preop' => 'Pre-op', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'vaccination' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'screening', 'label' => 'Screening', 'queue_point' => 'chair'], + ['code' => 'vaccinate', 'label' => 'Vaccination', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'vax.admin', 'label' => 'Vaccine administration', 'amount_minor' => 3000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'screening' => 'Screening', + 'administration' => 'Administration', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'pathology' => [ + 'stages' => [ + ['code' => 'request', 'label' => 'Specimen received', 'queue_point' => 'waiting'], + ['code' => 'processing', 'label' => 'Processing', 'queue_point' => 'lab'], + ['code' => 'reporting', 'label' => 'Reporting', 'queue_point' => 'lab'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'path.histo', 'label' => 'Histopathology', 'amount_minor' => 20000, 'type' => 'lab'], + ['code' => 'path.cyto', 'label' => 'Cytology', 'amount_minor' => 15000, 'type' => 'lab'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'request' => 'Specimen request', + 'report' => 'Report', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'infusion' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'infusion', 'label' => 'Infusion', 'queue_point' => 'chair'], + ['code' => 'observation', 'label' => 'Observation', 'queue_point' => 'obs'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'inf.session', 'label' => 'Infusion session', 'amount_minor' => 15000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'session' => 'Infusion session', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'dermatology' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Skin exam', 'queue_point' => 'chair'], + ['code' => 'procedure', 'label' => 'Procedure', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'der.consult', 'label' => 'Dermatology consultation', 'amount_minor' => 6000, 'type' => 'consultation'], + ['code' => 'der.procedure', 'label' => 'Skin procedure', 'amount_minor' => 12000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Skin exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'podiatry' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'exam', 'label' => 'Foot exam', 'queue_point' => 'chair'], + ['code' => 'treatment', 'label' => 'Treatment', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'pod.consult', 'label' => 'Podiatry consultation', 'amount_minor' => 5000, 'type' => 'consultation'], + ['code' => 'pod.treatment', 'label' => 'Foot treatment', 'amount_minor' => 8000, 'type' => 'procedure'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'exam' => 'Foot exam', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'fertility' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'consult', 'label' => 'Fertility consult', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'fer.consult', 'label' => 'Fertility consultation', 'amount_minor' => 10000, 'type' => 'consultation'], + ['code' => 'fer.scan', 'label' => 'Pelvic / follicular scan', 'amount_minor' => 15000, 'type' => 'imaging'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'consult' => 'Fertility consult', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + 'child_welfare' => [ + 'stages' => [ + ['code' => 'waiting', 'label' => 'Waiting', 'queue_point' => 'waiting'], + ['code' => 'growth', 'label' => 'Growth / wellness', 'queue_point' => 'chair'], + ['code' => 'completed', 'label' => 'Completed', 'queue_point' => null], + ], + 'services' => [ + ['code' => 'cwc.visit', 'label' => 'Well-child visit', 'amount_minor' => 3000, 'type' => 'consultation'], + ], + 'workspace_tabs' => [ + 'overview' => 'Overview', + 'growth' => 'Growth monitoring', + 'clinical_notes' => 'Clinical notes', + 'orders' => 'Orders', + 'billing' => 'Billing', + 'documents' => 'Documents', + ], + ], + ], ]; diff --git a/resources/views/care/partials/queue-ops.blade.php b/resources/views/care/partials/queue-ops.blade.php index 910010f..9e61fcb 100644 --- a/resources/views/care/partials/queue-ops.blade.php +++ b/resources/views/care/partials/queue-ops.blade.php @@ -1,34 +1,20 @@ -{{-- Inline Queue ops when Care↔Queue integration is enabled (not a bolted-on Service counter). --}} +{{-- Inline Queue ops when Care Queue Engine is enabled. --}} @php $qi = $queueIntegration ?? null; $callNextRoute = $queueCallNextRoute ?? null; $callNextParams = $queueCallNextParams ?? []; @endphp @if (! empty($qi['enabled']) && $callNextRoute) -
-
-

Queue

-

- @if (($qi['routing_mode'] ?? null) === 'shared_pool') - Call next takes the next waiting patient in line for this queue (shared pool). - @elseif (! empty($queueCallNextScansAllPoints)) - Call next picks the next waiting patient across service desks for this specialty at this branch. - @else - Call next operates on your assigned service point only — tickets routed to other rooms or desks stay there. - @endif -

-
-
- @csrf - @foreach ($callNextParams as $key => $value) - @if (is_scalar($value)) - - @endif - @endforeach - @if (! empty($branchId)) - + + @csrf + @foreach ($callNextParams as $key => $value) + @if (is_scalar($value)) + @endif - -
-
+ @endforeach + @if (! empty($branchId)) + + @endif + + @endif diff --git a/resources/views/care/specialty/shell.blade.php b/resources/views/care/specialty/shell.blade.php index 04bbafb..d289561 100644 --- a/resources/views/care/specialty/shell.blade.php +++ b/resources/views/care/specialty/shell.blade.php @@ -72,7 +72,6 @@ 'queueCallNextRoute' => 'care.specialty.call-next', 'queueCallNextParams' => ['module' => $moduleKey], 'branchId' => $branchId ?? null, - 'queueCallNextScansAllPoints' => ($practitionerScope ?? null) === null, ]) @if ($workspaceVisit) @@ -88,17 +87,21 @@ $appointment->status === \App\Models\Appointment::STATUS_IN_CONSULTATION || ($openConsultation && $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED) ); + $chartTab = collect($workspaceTabs ?? []) + ->keys() + ->first(fn ($key) => ! in_array($key, ['overview', 'orders', 'billing', 'documents'], true)) + ?: 'clinical_notes'; @endphp - @if ($consultationInProgress && $openConsultation) - - Open consultation + Open specialty chart @elseif ($canStartConsultation)
@csrf
@endif diff --git a/resources/views/partials/sidebar.blade.php b/resources/views/partials/sidebar.blade.php index 33a4121..358bee8 100644 --- a/resources/views/partials/sidebar.blade.php +++ b/resources/views/partials/sidebar.blade.php @@ -26,8 +26,10 @@ if ($permissions->can($member, 'appointments.view')) { $nav[] = ['name' => 'Appointments', 'route' => route('care.appointments.index'), 'active' => request()->routeIs('care.appointments.*'), 'icon' => '']; - $nav[] = ['name' => 'Queue', 'route' => route('care.queue.index'), 'active' => request()->routeIs('care.queue.*') || request()->routeIs('care.consultations.*'), - 'icon' => '']; + if ($permissions->handlesFloorCare($member)) { + $nav[] = ['name' => 'Queue', 'route' => route('care.queue.index'), 'active' => request()->routeIs('care.queue.*') || request()->routeIs('care.consultations.*'), + 'icon' => '']; + } } // Clinical assessments (layered intake / pathways) — on by default; toggle in Settings. @@ -36,7 +38,7 @@ $organization, \App\Services\Care\CareFeatures::ASSESSMENTS_ENGINE, ); - if ($assessmentsEngineOn && $permissions->can($member, 'assessments.view') && $permissions->can($member, 'patients.view')) { + if ($assessmentsEngineOn && $permissions->can($member, 'assessments.view') && $permissions->can($member, 'patients.view') && $permissions->handlesFloorCare($member)) { $nav[] = [ 'name' => 'Clinical forms', 'route' => route('care.patients.index', ['focus' => 'assessments']), @@ -49,12 +51,12 @@ // Lab, pharmacy, billing, and Queue service counters are Pro/Enterprise modules. if (! empty($hasPaidPlan)) { - if ($permissions->can($member, 'lab.view')) { + if ($permissions->can($member, 'lab.view') && $permissions->handlesFloorCare($member)) { $nav[] = ['name' => 'Laboratory', 'route' => route('care.lab.queue.index'), 'active' => request()->routeIs('care.lab.*'), 'icon' => '']; } - if ($permissions->can($member, 'prescriptions.view')) { + if ($permissions->can($member, 'prescriptions.view') && $permissions->handlesFloorCare($member)) { $nav[] = ['name' => 'Pharmacy queue', 'route' => route('care.prescriptions.queue'), 'active' => request()->routeIs('care.prescriptions.*') && ! request()->routeIs('care.pharmacy.*'), 'icon' => '']; } @@ -75,40 +77,42 @@ } // Always-on Pro/Enterprise surfaces first, then other specialty modules. - $specialtyService = app(\App\Services\Care\SpecialtyModuleService::class); - if ($organization) { - $specialtyService->ensureDefaultModulesProvisioned( - $organization, - (string) $organization->owner_ref, - ); - $organization->refresh(); - } - $specialtyModules = $specialtyService->enabledModulesForMember($organization, $member); - $pinnedKeys = $specialtyService->defaultKeysForPaidPlans(); - usort($specialtyModules, function (array $a, array $b) use ($pinnedKeys): int { - $aPin = in_array($a['key'], $pinnedKeys, true) ? 0 : 1; - $bPin = in_array($b['key'], $pinnedKeys, true) ? 0 : 1; - if ($aPin !== $bPin) { - return $aPin <=> $bPin; - } - $aOrder = array_search($a['key'], $pinnedKeys, true); - $bOrder = array_search($b['key'], $pinnedKeys, true); - if ($aOrder !== false || $bOrder !== false) { - return ($aOrder === false ? 99 : $aOrder) <=> ($bOrder === false ? 99 : $bOrder); + if ($permissions->handlesFloorCare($member)) { + $specialtyService = app(\App\Services\Care\SpecialtyModuleService::class); + if ($organization) { + $specialtyService->ensureDefaultModulesProvisioned( + $organization, + (string) $organization->owner_ref, + ); + $organization->refresh(); } + $specialtyModules = $specialtyService->enabledModulesForMember($organization, $member); + $pinnedKeys = $specialtyService->defaultKeysForPaidPlans(); + usort($specialtyModules, function (array $a, array $b) use ($pinnedKeys): int { + $aPin = in_array($a['key'], $pinnedKeys, true) ? 0 : 1; + $bPin = in_array($b['key'], $pinnedKeys, true) ? 0 : 1; + if ($aPin !== $bPin) { + return $aPin <=> $bPin; + } + $aOrder = array_search($a['key'], $pinnedKeys, true); + $bOrder = array_search($b['key'], $pinnedKeys, true); + if ($aOrder !== false || $bOrder !== false) { + return ($aOrder === false ? 99 : $aOrder) <=> ($bOrder === false ? 99 : $bOrder); + } - return strcasecmp( - (string) ($a['definition']['nav_label'] ?? $a['definition']['label'] ?? $a['key']), - (string) ($b['definition']['nav_label'] ?? $b['definition']['label'] ?? $b['key']), - ); - }); - foreach ($specialtyModules as $item) { - $nav[] = [ - 'name' => $item['definition']['nav_label'] ?? $item['definition']['label'], - 'route' => route('care.specialty.show', $item['key']), - 'active' => request()->routeIs('care.specialty.show') && request()->route('module') === $item['key'], - 'icon' => '', - ]; + return strcasecmp( + (string) ($a['definition']['nav_label'] ?? $a['definition']['label'] ?? $a['key']), + (string) ($b['definition']['nav_label'] ?? $b['definition']['label'] ?? $b['key']), + ); + }); + foreach ($specialtyModules as $item) { + $nav[] = [ + 'name' => $item['definition']['nav_label'] ?? $item['definition']['label'], + 'route' => route('care.specialty.show', $item['key']), + 'active' => request()->routeIs('care.specialty.show') && request()->route('module') === $item['key'], + 'icon' => '', + ]; + } } } diff --git a/tests/Feature/CareNativeQueueTest.php b/tests/Feature/CareNativeQueueTest.php index c509945..dee10dd 100644 --- a/tests/Feature/CareNativeQueueTest.php +++ b/tests/Feature/CareNativeQueueTest.php @@ -62,7 +62,7 @@ class CareNativeQueueTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'receptionist', ]); $this->branch = Branch::create([ diff --git a/tests/Feature/CareNaturalQueueTest.php b/tests/Feature/CareNaturalQueueTest.php index 645d307..99ec362 100644 --- a/tests/Feature/CareNaturalQueueTest.php +++ b/tests/Feature/CareNaturalQueueTest.php @@ -57,7 +57,7 @@ class CareNaturalQueueTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'receptionist', ]); $this->branch = Branch::create([ diff --git a/tests/Feature/CareQueueBridgeTest.php b/tests/Feature/CareQueueBridgeTest.php index 32ebd11..a220332 100644 --- a/tests/Feature/CareQueueBridgeTest.php +++ b/tests/Feature/CareQueueBridgeTest.php @@ -56,7 +56,7 @@ class CareQueueBridgeTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'receptionist', ]); $this->branch = Branch::create([ @@ -273,7 +273,30 @@ class CareQueueBridgeTest extends TestCase $appointment = $checkedIn->fresh(); $this->assertSame('called', $appointment->queue_ticket_status); - $this->actingAs($this->owner) + $doctorUser = User::create([ + 'public_id' => 'bridge-recall-doc', + 'name' => 'Recall Doctor', + 'email' => 'bridge-recall@example.com', + ]); + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $doctorUser->public_id, + 'role' => 'doctor', + 'branch_id' => $this->branch->id, + ]); + \App\Models\Practitioner::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'member_id' => Member::query()->where('user_ref', $doctorUser->public_id)->value('id'), + 'user_ref' => $doctorUser->public_id, + 'name' => 'Recall Doctor', + 'room' => 'Room 2', + 'is_active' => true, + ]); + + $this->actingAs($doctorUser) ->from(route('care.queue.index')) ->post(route('care.queue.recall', $appointment)) ->assertRedirect(route('care.queue.index')) @@ -284,7 +307,6 @@ class CareQueueBridgeTest extends TestCase $this->actingAs($this->owner) ->get(route('care.queue.index', ['branch_id' => $this->branch->id])) ->assertOk() - ->assertSee('Call again') ->assertSee($appointment->queue_ticket_number); Http::assertNothingSent(); } diff --git a/tests/Feature/CareQueueWorkflowTest.php b/tests/Feature/CareQueueWorkflowTest.php index 8caf7b2..e135b47 100644 --- a/tests/Feature/CareQueueWorkflowTest.php +++ b/tests/Feature/CareQueueWorkflowTest.php @@ -57,7 +57,7 @@ class CareQueueWorkflowTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'receptionist', ]); $this->branch = Branch::create([ @@ -320,8 +320,7 @@ class CareQueueWorkflowTest extends TestCase ->get(route('care.queue.index')) ->assertOk() ->assertDontSee('Service counter') - ->assertSee('Call next') - ->assertSee('assigned service point', false); + ->assertSee('Call next'); Http::assertNothingSent(); } diff --git a/tests/Feature/CareSpecialtyClinicalTest.php b/tests/Feature/CareSpecialtyClinicalTest.php index 2a0c76e..ddc6b00 100644 --- a/tests/Feature/CareSpecialtyClinicalTest.php +++ b/tests/Feature/CareSpecialtyClinicalTest.php @@ -54,7 +54,7 @@ class CareSpecialtyClinicalTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'nurse', ]); $this->branch = Branch::create([ @@ -251,4 +251,33 @@ class CareSpecialtyClinicalTest extends TestCase 'record_type' => 'odontogram', ]); } + + public function test_ophthalmology_uses_eye_exam_form_not_generic_notes(): void + { + app(SpecialtyModuleService::class)->activate( + $this->organization, + $this->owner->public_id, + 'ophthalmology', + ); + + $fields = app(\App\Services\Care\SpecialtyClinicalRecordService::class) + ->fieldsFor('ophthalmology', 'eye_exam'); + $names = collect($fields)->pluck('name')->all(); + + $this->assertContains('va_od', $names); + $this->assertContains('iop_os', $names); + $this->assertNotContains('notes', $names); + + [$visit] = $this->activateWithVisit('ophthalmology', 'ophthalmology'); + + $this->actingAs($this->owner) + ->get(route('care.specialty.workspace', [ + 'module' => 'ophthalmology', + 'visit' => $visit, + 'tab' => 'exam', + ])) + ->assertOk() + ->assertSee('Visual acuity OD') + ->assertSee('IOP OS'); + } } diff --git a/tests/Feature/CareSpecialtyModulesTest.php b/tests/Feature/CareSpecialtyModulesTest.php index e120f3a..b080ea3 100644 --- a/tests/Feature/CareSpecialtyModulesTest.php +++ b/tests/Feature/CareSpecialtyModulesTest.php @@ -216,7 +216,24 @@ class CareSpecialtyModulesTest extends TestCase Http::fake(); app(SpecialtyModuleService::class)->activate($this->organization, $this->owner->public_id, 'dentistry'); + $nurse = User::create([ + 'public_id' => 'modules-nurse', + 'name' => 'Floor Nurse', + 'email' => 'modules-nurse@example.com', + ]); + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $nurse->public_id, + 'role' => 'nurse', + 'branch_id' => $this->branch->id, + ]); + $this->actingAs($this->owner) + ->get(route('care.specialty.show', 'dentistry')) + ->assertForbidden(); + + $this->actingAs($nurse) ->get(route('care.specialty.show', 'dentistry')) ->assertOk() ->assertSee('Dentistry') @@ -320,7 +337,24 @@ class CareSpecialtyModulesTest extends TestCase 'is_active' => true, ]); + $nurse = User::create([ + 'public_id' => 'er-nurse', + 'name' => 'ER Nurse', + 'email' => 'er-nurse@example.com', + ]); + Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $nurse->public_id, + 'role' => 'nurse', + 'branch_id' => $this->branch->id, + ]); + $this->actingAs($this->owner) + ->get(route('care.specialty.show', 'emergency')) + ->assertForbidden(); + + $this->actingAs($nurse) ->get(route('care.specialty.show', 'emergency')) ->assertOk() ->assertSee('Emergency'); diff --git a/tests/Feature/CareSpecialtyShellTest.php b/tests/Feature/CareSpecialtyShellTest.php index 4785052..bce4d30 100644 --- a/tests/Feature/CareSpecialtyShellTest.php +++ b/tests/Feature/CareSpecialtyShellTest.php @@ -55,7 +55,7 @@ class CareSpecialtyShellTest extends TestCase 'owner_ref' => $this->owner->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->owner->public_id, - 'role' => 'hospital_admin', + 'role' => 'nurse', ]); $this->branch = Branch::create([ @@ -206,7 +206,7 @@ class CareSpecialtyShellTest extends TestCase ); } - public function test_specialty_start_consultation_opens_consultation_page(): void + public function test_specialty_start_consultation_opens_specialty_workspace(): void { app(SpecialtyModuleService::class)->activate( $this->organization, @@ -253,7 +253,31 @@ class CareSpecialtyShellTest extends TestCase 'checked_in_at' => now(), ]); - $response = $this->actingAs($this->owner) + $doctorUser = User::create([ + 'public_id' => 'shell-dental-doc', + 'name' => 'Dental Doctor', + 'email' => 'shell-dental@example.com', + ]); + $doctorMember = Member::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'user_ref' => $doctorUser->public_id, + 'role' => 'doctor', + 'branch_id' => $this->branch->id, + ]); + \App\Models\Practitioner::create([ + 'owner_ref' => $this->owner->public_id, + 'organization_id' => $this->organization->id, + 'branch_id' => $this->branch->id, + 'department_id' => $department->id, + 'member_id' => $doctorMember->id, + 'user_ref' => $doctorUser->public_id, + 'name' => 'Dental Doctor', + 'specialty' => 'Dentistry', + 'is_active' => true, + ]); + + $response = $this->actingAs($doctorUser) ->post(route('care.specialty.consultation.start', [ 'module' => 'dentistry', 'visit' => $visit, @@ -264,7 +288,11 @@ class CareSpecialtyShellTest extends TestCase ->first(); $this->assertNotNull($consultation); - $response->assertRedirect(route('care.consultations.show', $consultation)); + $response->assertRedirect(route('care.specialty.workspace', [ + 'module' => 'dentistry', + 'visit' => $visit, + 'tab' => 'odontogram', + ])); $this->assertSame(Appointment::STATUS_IN_CONSULTATION, $appointment->fresh()->status); } }