Deploy Ladill Care / deploy (push) Successful in 35s
Home columns follow New call → Dispatched → On scene → Transport → Handover; walk-ins land as New call and Call next is disabled for this module. Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
7.0 KiB
PHP
186 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Ambulance;
|
|
|
|
use App\Models\Organization;
|
|
use App\Models\Visit;
|
|
use App\Services\Care\SpecialtyShellService;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* Stage-grouped EMS dispatch board (replaces Call next queue home for ambulance).
|
|
*/
|
|
class AmbulanceDispatchBoardService
|
|
{
|
|
public function __construct(
|
|
protected SpecialtyShellService $shell,
|
|
) {}
|
|
|
|
/**
|
|
* @param list<int>|null $practitionerScope
|
|
* @return array{
|
|
* columns: list<array{
|
|
* key: string,
|
|
* label: string,
|
|
* count: int,
|
|
* items: Collection<int, Visit>,
|
|
* empty: string,
|
|
* accent: string,
|
|
* dot: string,
|
|
* next_stage: ?string,
|
|
* next_label: ?string
|
|
* }>,
|
|
* active_count: int,
|
|
* completed_today: int
|
|
* }
|
|
*/
|
|
public function board(
|
|
Organization $organization,
|
|
string $ownerRef,
|
|
?int $branchScope = null,
|
|
?array $practitionerScope = null,
|
|
): array {
|
|
$flow = app(AmbulanceWorkflowService::class)->stageFlow();
|
|
$open = $this->shell->openVisits($organization, 'ambulance', $ownerRef, $branchScope, $practitionerScope, 80);
|
|
$completed = $this->completedToday($organization, $ownerRef, $branchScope, $practitionerScope);
|
|
|
|
$byStage = [
|
|
AmbulanceWorkflowService::STAGE_CHECK_IN => collect(),
|
|
AmbulanceWorkflowService::STAGE_DISPATCH => collect(),
|
|
AmbulanceWorkflowService::STAGE_ON_SCENE => collect(),
|
|
AmbulanceWorkflowService::STAGE_TRANSPORT => collect(),
|
|
AmbulanceWorkflowService::STAGE_HANDOVER => collect(),
|
|
AmbulanceWorkflowService::STAGE_COMPLETED => $completed,
|
|
];
|
|
|
|
foreach ($open as $visit) {
|
|
$stage = $visit->specialty_stage;
|
|
if ($stage === null || $stage === '' || $stage === AmbulanceWorkflowService::STAGE_CHECK_IN) {
|
|
$byStage[AmbulanceWorkflowService::STAGE_CHECK_IN]->push($visit);
|
|
} elseif (isset($byStage[$stage]) && $stage !== AmbulanceWorkflowService::STAGE_COMPLETED) {
|
|
$byStage[$stage]->push($visit);
|
|
} else {
|
|
// Unknown / unexpected stage — keep visible under New call.
|
|
$byStage[AmbulanceWorkflowService::STAGE_CHECK_IN]->push($visit);
|
|
}
|
|
}
|
|
|
|
$meta = [
|
|
AmbulanceWorkflowService::STAGE_CHECK_IN => [
|
|
'label' => 'New call',
|
|
'empty' => 'No new calls.',
|
|
'accent' => 'text-amber-700',
|
|
'dot' => 'bg-amber-500',
|
|
],
|
|
AmbulanceWorkflowService::STAGE_DISPATCH => [
|
|
'label' => 'Dispatched',
|
|
'empty' => 'Nothing dispatched.',
|
|
'accent' => 'text-indigo-700',
|
|
'dot' => 'bg-indigo-500',
|
|
],
|
|
AmbulanceWorkflowService::STAGE_ON_SCENE => [
|
|
'label' => 'On scene',
|
|
'empty' => 'No crews on scene.',
|
|
'accent' => 'text-sky-700',
|
|
'dot' => 'bg-sky-500',
|
|
],
|
|
AmbulanceWorkflowService::STAGE_TRANSPORT => [
|
|
'label' => 'Transport',
|
|
'empty' => 'No patients in transport.',
|
|
'accent' => 'text-violet-700',
|
|
'dot' => 'bg-violet-500',
|
|
],
|
|
AmbulanceWorkflowService::STAGE_HANDOVER => [
|
|
'label' => 'Handover',
|
|
'empty' => 'No handovers in progress.',
|
|
'accent' => 'text-emerald-700',
|
|
'dot' => 'bg-emerald-500',
|
|
],
|
|
AmbulanceWorkflowService::STAGE_COMPLETED => [
|
|
'label' => 'Completed today',
|
|
'empty' => 'None completed today.',
|
|
'accent' => 'text-slate-500',
|
|
'dot' => 'bg-slate-300',
|
|
],
|
|
];
|
|
|
|
$columns = [];
|
|
foreach ($meta as $key => $column) {
|
|
$items = $byStage[$key] ?? collect();
|
|
$next = $flow[$key]['next'] ?? null;
|
|
$nextLabel = $flow[$key]['label'] ?? null;
|
|
if ($key === AmbulanceWorkflowService::STAGE_COMPLETED) {
|
|
$next = null;
|
|
$nextLabel = null;
|
|
}
|
|
|
|
$columns[] = [
|
|
'key' => $key,
|
|
'label' => $column['label'],
|
|
'count' => $items->count(),
|
|
'items' => $items,
|
|
'empty' => $column['empty'],
|
|
'accent' => $column['accent'],
|
|
'dot' => $column['dot'],
|
|
'next_stage' => $next !== $key ? $next : null,
|
|
'next_label' => $next !== $key ? $nextLabel : null,
|
|
];
|
|
}
|
|
|
|
$activeCount = collect($columns)
|
|
->reject(fn (array $col) => $col['key'] === AmbulanceWorkflowService::STAGE_COMPLETED)
|
|
->sum('count');
|
|
|
|
return [
|
|
'columns' => $columns,
|
|
'active_count' => $activeCount,
|
|
'completed_today' => $completed->count(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<int>|null $practitionerScope
|
|
* @return Collection<int, Visit>
|
|
*/
|
|
protected function completedToday(
|
|
Organization $organization,
|
|
string $ownerRef,
|
|
?int $branchScope = null,
|
|
?array $practitionerScope = null,
|
|
int $limit = 40,
|
|
): Collection {
|
|
$departmentIds = $this->shell->departmentIds($organization, 'ambulance', $ownerRef, $branchScope);
|
|
|
|
return Visit::owned($ownerRef)
|
|
->where('organization_id', $organization->id)
|
|
->where(function ($q) {
|
|
$q->where('specialty_stage', AmbulanceWorkflowService::STAGE_COMPLETED)
|
|
->orWhere(function ($qq) {
|
|
$qq->where('status', Visit::STATUS_COMPLETED)
|
|
->whereDate('completed_at', today());
|
|
});
|
|
})
|
|
->where(function ($q) {
|
|
$q->whereDate('completed_at', today())
|
|
->orWhere(function ($qq) {
|
|
$qq->whereNull('completed_at')
|
|
->whereDate('updated_at', today());
|
|
});
|
|
})
|
|
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
|
|
->whereHas('appointment', function ($q) use ($departmentIds, $practitionerScope) {
|
|
$q->when($departmentIds !== [], fn ($qq) => $qq->whereIn('department_id', $departmentIds))
|
|
->when($departmentIds === [], fn ($qq) => $qq->whereRaw('1 = 0'))
|
|
->when(
|
|
$practitionerScope !== null,
|
|
fn ($qq) => $qq->whereIn('practitioner_id', $practitionerScope ?: [0]),
|
|
);
|
|
})
|
|
->with(['patient', 'appointment.practitioner', 'appointment.department'])
|
|
->orderByDesc('completed_at')
|
|
->orderByDesc('id')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
}
|