Activate Care workflows across check-in and financial clearance.
Deploy Ladill Care / deploy (push) Failing after 1m9s

Enforce service-queue gates, cashier settlements, and clinical stage progression so patient journeys cannot bypass configured payment or authorization rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-17 21:13:06 +00:00
co-authored by Cursor
parent 86bfce1e17
commit 3ee59a0956
38 changed files with 1953 additions and 107 deletions
+44 -9
View File
@@ -8,7 +8,9 @@ use App\Models\InvestigationRequest;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Practitioner;
use App\Models\Prescription;
use App\Services\Care\Workflow\WorkflowQueueGate;
use App\Services\Queue\QueueClient;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Client\RequestException;
@@ -25,6 +27,7 @@ class CareQueueBridge
protected CareQueueProvisioner $provisioner,
protected PlanService $plans,
protected SpecialtyModuleService $specialties,
protected WorkflowQueueGate $workflowGate,
) {}
public function isEnabled(Organization $organization): bool
@@ -57,7 +60,12 @@ class CareQueueBridge
return $appointment;
}
$appointment->loadMissing('visit');
$context = $this->contextForAppointment($organization, $appointment);
if (! $this->workflowGate->canRelease($organization, $appointment->visit, $context)) {
return $appointment;
}
$point = $this->resolveConsultationPoint($organization, $appointment);
if (CareQueueContexts::usesAssignedRouting($context) && ! $point) {
@@ -104,6 +112,10 @@ class CareQueueBridge
}
$prescription->loadMissing(['patient', 'visit']);
if (! $this->workflowGate->canRelease($organization, $prescription->visit, CareQueueContexts::PHARMACY)) {
return $prescription;
}
$branchId = (int) ($prescription->visit?->branch_id ?? 0);
if ($branchId < 1) {
return $prescription;
@@ -139,10 +151,15 @@ class CareQueueBridge
return $request;
}
$request->loadMissing('patient');
$request->loadMissing(['patient', 'visit', 'investigationType']);
$context = $this->contextForInvestigation($request);
if (! $this->workflowGate->canRelease($organization, $request->visit, $context)) {
return $request;
}
$ticket = $this->issue(
$organization,
CareQueueContexts::LABORATORY,
$context,
(int) $request->branch_id,
$request->patient,
[
@@ -164,6 +181,15 @@ class CareQueueBridge
return $request->fresh();
}
public function contextForInvestigation(InvestigationRequest $request): string
{
$request->loadMissing('investigationType');
return in_array($request->investigationType?->category, ['xray', 'ultrasound', 'ct', 'mri'], true)
? CareQueueContexts::IMAGING
: CareQueueContexts::LABORATORY;
}
public function issueForBill(Organization $organization, Bill $bill): ?Bill
{
if (! $this->isEnabled($organization) || filled($bill->queue_ticket_uuid)) {
@@ -174,7 +200,11 @@ class CareQueueBridge
return $bill;
}
$bill->loadMissing('patient');
$bill->loadMissing(['patient', 'visit']);
if (! $this->workflowGate->canRelease($organization, $bill->visit, CareQueueContexts::BILLING)) {
return $bill;
}
$ticket = $this->issue(
$organization,
CareQueueContexts::BILLING,
@@ -209,7 +239,8 @@ class CareQueueBridge
}
// Lab/imaging completion → return-to-doctor consultation ticket when practitioner known.
if ($fromContext === CareQueueContexts::LABORATORY && $entity instanceof InvestigationRequest) {
if (in_array($fromContext, [CareQueueContexts::LABORATORY, CareQueueContexts::IMAGING], true)
&& $entity instanceof InvestigationRequest) {
$entity->loadMissing(['patient', 'practitioner', 'visit']);
$appointment = Appointment::owned($organization->owner_ref)
->where('organization_id', $organization->id)
@@ -243,7 +274,7 @@ class CareQueueBridge
return match (true) {
$context === CareQueueContexts::PHARMACY => $this->syncMissingPrescriptions($organization, $branchId, $limit),
$context === CareQueueContexts::LABORATORY => $this->syncMissingInvestigations($organization, $branchId, $limit),
in_array($context, [CareQueueContexts::LABORATORY, CareQueueContexts::IMAGING], true) => $this->syncMissingInvestigations($organization, $context, $branchId, $limit),
$context === CareQueueContexts::BILLING => $this->syncMissingBills($organization, $branchId, $limit),
default => $this->syncMissingAppointments($organization, $context, $branchId, $limit),
};
@@ -418,7 +449,7 @@ class CareQueueBridge
);
}
if ($member && $member->role === 'doctor') {
$prac = \App\Models\Practitioner::owned($organization->owner_ref)
$prac = Practitioner::owned($organization->owner_ref)
->where('organization_id', $organization->id)
->where('branch_id', $branchId)
->where(function ($q) use ($member) {
@@ -569,7 +600,7 @@ class CareQueueBridge
->where('organization_id', $organization->id)
->where('queue_ticket_uuid', $ticketUuid)
->first(),
$context === CareQueueContexts::LABORATORY => InvestigationRequest::owned($owner)
in_array($context, [CareQueueContexts::LABORATORY, CareQueueContexts::IMAGING], true) => InvestigationRequest::owned($owner)
->where('organization_id', $organization->id)
->where('queue_ticket_uuid', $ticketUuid)
->first(),
@@ -643,7 +674,7 @@ class CareQueueBridge
return $issued;
}
protected function syncMissingInvestigations(Organization $organization, int $branchId, int $limit): int
protected function syncMissingInvestigations(Organization $organization, string $context, int $branchId, int $limit): int
{
$owner = (string) $organization->owner_ref;
$requests = InvestigationRequest::owned($owner)
@@ -657,13 +688,17 @@ class CareQueueBridge
->where(function ($q) {
$q->whereNull('queue_ticket_uuid')->orWhere('queue_ticket_uuid', '');
})
->with('patient')
->with(['patient', 'visit', 'investigationType'])
->orderBy('id')
->limit($limit)
->get();
$issued = 0;
foreach ($requests as $request) {
if ($this->contextForInvestigation($request) !== $context) {
continue;
}
$updated = $this->issueForInvestigation($organization, $request);
if ($updated && filled($updated->queue_ticket_uuid)) {
$issued++;