Files
ladill-care/app/Services/Care/BloodBank/BloodBankWorkflowService.php
T
isaaccladandCursor f688a48c37
Deploy Ladill Care / deploy (push) Successful in 37s
Add full Blood Bank specialty suite on shared shell.
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>
2026-07-18 21:36:57 +00:00

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'],
];
}
}