engine->currentAdvance($visit); $stage = $advance?->stage; if ($stage === null) { return; } // Hold at the cashier booth while a before-service gate is unpaid. if ($advance->isBlocked() && $advance->blocked_reason === 'awaiting_payment') { $this->enqueueCashier($organization, $visit, $stage); return; } if ($stage->queue_context === null) { if ($stage->type === 'payment') { $this->enqueueCashier($organization, $visit, $stage); } return; } $context = $stage->queue_context; if ($context === CareQueueContexts::CONSULTATION || CareQueueContexts::isSpecialty($context)) { $appointment = Appointment::query() ->where('visit_id', $visit->id) ->with(['patient', 'organization', 'practitioner', 'visit']) ->latest('id') ->first(); if ($appointment) { if ($appointment->status === Appointment::STATUS_COMPLETED) { $nextPosition = ((int) Appointment::owned($appointment->owner_ref) ->where('branch_id', $appointment->branch_id) ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN]) ->max('queue_position')) + 1; $appointment->forceFill([ 'status' => Appointment::STATUS_WAITING, 'waiting_at' => now(), 'started_at' => null, 'completed_at' => null, 'queue_position' => $nextPosition, ])->save(); } if (filled($appointment->queue_ticket_uuid) && in_array($appointment->queue_ticket_status, ['completed', 'cancelled'], true)) { $appointment->forceFill([ 'queue_ticket_uuid' => null, 'queue_ticket_number' => null, 'queue_ticket_status' => null, 'queue_service_point_uuid' => null, 'queue_destination' => null, 'queue_staff_display_name' => null, 'queue_routing_status' => null, ])->save(); } $this->queue->issueForAppointment($organization, $appointment); } return; } if ($context === CareQueueContexts::LABORATORY || $context === CareQueueContexts::IMAGING) { InvestigationRequest::query() ->where('visit_id', $visit->id) ->with(['patient', 'visit']) ->get() ->each(fn (InvestigationRequest $request) => $this->queue->issueForInvestigation($organization, $request)); return; } if ($context === CareQueueContexts::PHARMACY) { Prescription::query() ->where('visit_id', $visit->id) ->with(['patient', 'visit']) ->get() ->each(fn (Prescription $prescription) => $this->queue->issueForPrescription($organization, $prescription)); return; } if ($context === CareQueueContexts::BILLING) { $this->enqueueCashier($organization, $visit, $stage); } } /** * Ensure a visit blocked on payment has an open bill and an active cashier * booth ticket. No-ops when Care Queue Engine is disabled. */ public function enqueueCashier( Organization $organization, Visit $visit, ?WorkflowStage $stage = null, ): ?Bill { if (! $this->queue->isEnabled($organization)) { return null; } $stage ??= $this->engine->currentStage($visit); if ($stage === null) { return null; } $obligation = FinancialObligation::query() ->where('visit_id', $visit->id) ->where('workflow_stage_id', $stage->id) ->whereNull('deleted_at') ->first(); if ($obligation === null || $obligation->isCleared() || (int) $obligation->amount_minor <= 0) { return null; } try { $bill = app(BillService::class)->billForObligation( $obligation, (string) $visit->owner_ref, (string) $visit->owner_ref, ); return $this->queue->issueForBill($organization, $bill->fresh(['patient', 'visit'])); } catch (\Throwable $e) { Log::warning('care.cashier_queue_enqueue_failed', [ 'visit_id' => $visit->id, 'stage_code' => $stage->code, 'message' => $e->getMessage(), ]); return null; } } /** * Complete (or retire) the cashier booth ticket after a financial gate clears. * Non-payment clearances void unpaid obligation-only placeholder bills. */ public function completeCashier( Organization $organization, FinancialObligation $obligation, string $clearanceMethod, ): void { if (! $this->queue->isEnabled($organization)) { return; } $obligation->loadMissing('bill'); $bill = $obligation->bill; if ($bill === null) { return; } if (filled($bill->queue_ticket_uuid) && ! in_array($bill->queue_ticket_status, ['completed', 'cancelled'], true)) { $this->queue->complete($organization, $bill); $bill = $bill->fresh(); } if (in_array($clearanceMethod, ['payment', 'bank_receipt'], true)) { return; } $bill = $bill->fresh(['lineItems']) ?? $bill; if ((int) $bill->amount_paid_minor > 0 || in_array($bill->status, [Bill::STATUS_PAID, Bill::STATUS_VOID], true)) { return; } $hasForeignLines = $bill->lineItems ->contains(function ($item) use ($obligation) { return $item->source_type !== FinancialObligation::class || (int) $item->source_id !== (int) $obligation->id; }); if ($hasForeignLines) { return; } try { app(BillService::class)->void($bill, (string) $obligation->owner_ref, $obligation->cleared_by); } catch (\Throwable $e) { Log::warning('care.cashier_placeholder_bill_void_failed', [ 'bill_id' => $bill->id, 'obligation_id' => $obligation->id, 'message' => $e->getMessage(), ]); } } }