Deploy Ladill Care / deploy (push) Successful in 37s
Mirror Emergency: stage workflow, issue/transfusion records, analytics, print, and action-menu stage flow without a parallel EHR. Co-authored-by: Cursor <cursoragent@cursor.com>
95 lines
3.1 KiB
PHP
95 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\BloodBank;
|
|
|
|
/**
|
|
* Map blood request / issue payloads onto Blood Bank visit stages.
|
|
*/
|
|
class BloodBankWorkflowService
|
|
{
|
|
public const STAGE_REQUEST = 'request';
|
|
|
|
public const STAGE_CROSSMATCH = 'crossmatch';
|
|
|
|
public const STAGE_ISSUE = 'issue';
|
|
|
|
public const STAGE_TRANSFUSION = 'transfusion';
|
|
|
|
public const STAGE_COMPLETED = 'completed';
|
|
|
|
/**
|
|
* Suggested next visit stage after a blood request payload is saved.
|
|
*
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function stageFromRequest(array $payload): string
|
|
{
|
|
$crossmatch = strtolower((string) ($payload['crossmatch_status'] ?? ''));
|
|
$issued = (int) ($payload['issued_units'] ?? 0);
|
|
$urgency = strtolower((string) ($payload['urgency'] ?? ''));
|
|
|
|
if ($issued > 0 || $crossmatch === 'issued') {
|
|
return self::STAGE_ISSUE;
|
|
}
|
|
|
|
if (in_array($crossmatch, ['compatible', 'incompatible', 'in progress'], true)) {
|
|
return self::STAGE_CROSSMATCH;
|
|
}
|
|
|
|
if (str_contains($urgency, 'emergency') || str_contains($urgency, 'massive')) {
|
|
return self::STAGE_CROSSMATCH;
|
|
}
|
|
|
|
return self::STAGE_REQUEST;
|
|
}
|
|
|
|
/**
|
|
* Whether a transfusion payload should close the visit episode.
|
|
*
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function shouldCompleteVisit(array $payload): bool
|
|
{
|
|
$outcome = strtolower((string) ($payload['outcome'] ?? ''));
|
|
|
|
return $outcome !== '' && (
|
|
str_contains($outcome, 'completed')
|
|
|| str_contains($outcome, 'stopped')
|
|
|| str_contains($outcome, 'complete')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Map product label to catalog service code for billing.
|
|
*/
|
|
public function serviceCodeForProduct(?string $product): ?string
|
|
{
|
|
$normalized = strtolower(trim((string) $product));
|
|
|
|
return match (true) {
|
|
str_contains($normalized, 'whole') => 'bb.whole_blood',
|
|
str_contains($normalized, 'packed') || str_contains($normalized, 'rbc') => 'bb.packed_rbc',
|
|
str_contains($normalized, 'platelet') => 'bb.platelets',
|
|
str_contains($normalized, 'ffp') || str_contains($normalized, 'plasma') => 'bb.ffp',
|
|
str_contains($normalized, 'cryo') => 'bb.cryo',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Default stage progression for action buttons.
|
|
*
|
|
* @return array<string, array{next: string, label: string}>
|
|
*/
|
|
public function stageFlow(): array
|
|
{
|
|
return [
|
|
self::STAGE_REQUEST => ['next' => self::STAGE_CROSSMATCH, 'label' => 'Start cross-match'],
|
|
self::STAGE_CROSSMATCH => ['next' => self::STAGE_ISSUE, 'label' => 'Ready to issue'],
|
|
self::STAGE_ISSUE => ['next' => self::STAGE_TRANSFUSION, 'label' => 'Start transfusion'],
|
|
self::STAGE_TRANSFUSION => ['next' => self::STAGE_COMPLETED, 'label' => 'Complete visit'],
|
|
self::STAGE_COMPLETED => ['next' => self::STAGE_COMPLETED, 'label' => 'Completed'],
|
|
];
|
|
}
|
|
}
|