Reserve floor queues for clinical staff and deepen specialty forms.
Deploy Ladill Care / deploy (push) Successful in 41s
Deploy Ladill Care / deploy (push) Successful in 41s
Hospital admins no longer see specialty/queue floor nav; Call next is a full-width button; specialty encounters open module-specific clinical forms instead of the general consultation page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user