features->enabled($organization, CareFeatures::WORKFLOW_ENGINE); } public function canRelease( Organization $organization, ?Visit $visit, string $queueContext, ): bool { if (! $this->enabled($organization) || $visit === null) { return true; } $advance = $this->engine->currentAdvance($visit) ? $this->engine->refreshGate($visit) : $this->engine->start($visit); $stage = $advance?->stage; // An enabled org without a materialized workflow keeps legacy behavior. if ($stage === null) { return true; } // Pay-before-service holds belong on the cashier / billing line, not the // clinical service queue for the current stage. if ($queueContext === CareQueueContexts::BILLING && $this->isAwaitingFinancialGate($organization, $visit, $stage)) { return true; } if (! $this->contextsMatch($stage->queue_context, $stage->type, $queueContext)) { return false; } if (! $this->features->enabled($organization, CareFeatures::FINANCIAL_GATES)) { return true; } return $this->engine->isQueueReleasable($visit); } /** * Whether the visit is blocked on a before-service financial gate. */ public function isAwaitingFinancialGate( Organization $organization, Visit $visit, ?WorkflowStage $stage = null, ): bool { if (! $this->features->enabled($organization, CareFeatures::FINANCIAL_GATES)) { return false; } $stage ??= $this->engine->currentStage($visit); if ($stage === null || ! $stage->gatesBeforeService()) { return false; } return ! $this->engine->isQueueReleasable($visit); } protected function contextsMatch(?string $stageContext, string $stageType, string $queueContext): bool { if ($stageContext === $queueContext) { return true; } if ($stageType === 'payment' && $queueContext === CareQueueContexts::BILLING) { return true; } // Specialty appointments still fulfill a consultation workflow stage. return $stageType === 'consultation' && CareQueueContexts::isSpecialty($queueContext) && $stageContext === CareQueueContexts::CONSULTATION; } }