plans->canUseQueueIntegration($organization) && (bool) data_get($organization->settings, 'queue_integration_enabled') && $this->queue->configured(); } /** * Resolve which queue context an appointment should use (specialty if applicable). */ public function contextForAppointment(Organization $organization, Appointment $appointment): string { $departmentId = $appointment->department_id; if (! $departmentId) { return CareQueueContexts::CONSULTATION; } foreach ($this->specialties->enabledKeys($organization) as $key) { $deptIds = data_get($organization->settings, "specialty_module_provisioning.{$key}.department_ids", []); if (is_array($deptIds) && in_array($departmentId, array_map('intval', $deptIds), true)) { return $key; } } return CareQueueContexts::CONSULTATION; } public function issueForAppointment(Organization $organization, Appointment $appointment): ?Appointment { if (! $this->isEnabled($organization) || filled($appointment->queue_ticket_uuid)) { return $appointment; } $context = $this->contextForAppointment($organization, $appointment); $ticket = $this->issue( $organization, $context, (int) $appointment->branch_id, $appointment->patient, [ 'care_entity' => 'appointment', 'care_entity_uuid' => $appointment->uuid, 'care_entity_id' => $appointment->id, ], $appointment->type === Appointment::TYPE_WALK_IN ? 'walk_in' : 'appointment', 'appointment', ); if (! $ticket) { return $appointment; } $this->applyTicket($appointment, $ticket); return $appointment->fresh(); } public function issueForPrescription(Organization $organization, Prescription $prescription): ?Prescription { if (! $this->isEnabled($organization) || filled($prescription->queue_ticket_uuid)) { return $prescription; } $prescription->loadMissing(['patient', 'visit']); $branchId = (int) ($prescription->visit?->branch_id ?? 0); if ($branchId < 1) { return $prescription; } $ticket = $this->issue( $organization, CareQueueContexts::PHARMACY, $branchId, $prescription->patient, [ 'care_entity' => 'prescription', 'care_entity_uuid' => $prescription->uuid, 'care_entity_id' => $prescription->id, ], ); if (! $ticket) { return $prescription; } $this->applyTicket($prescription, $ticket); return $prescription->fresh(); } public function issueForInvestigation(Organization $organization, InvestigationRequest $request): ?InvestigationRequest { if (! $this->isEnabled($organization) || filled($request->queue_ticket_uuid)) { return $request; } $request->loadMissing('patient'); $ticket = $this->issue( $organization, CareQueueContexts::LABORATORY, (int) $request->branch_id, $request->patient, [ 'care_entity' => 'investigation_request', 'care_entity_uuid' => $request->uuid, 'care_entity_id' => $request->id, ], ); if (! $ticket) { return $request; } $this->applyTicket($request, $ticket); return $request->fresh(); } public function issueForBill(Organization $organization, Bill $bill): ?Bill { if (! $this->isEnabled($organization) || filled($bill->queue_ticket_uuid)) { return $bill; } if (in_array($bill->status, [Bill::STATUS_PAID, Bill::STATUS_VOID], true)) { return $bill; } $bill->loadMissing('patient'); $ticket = $this->issue( $organization, CareQueueContexts::BILLING, (int) $bill->branch_id, $bill->patient, [ 'care_entity' => 'bill', 'care_entity_uuid' => $bill->uuid, 'care_entity_id' => $bill->id, ], ); if (! $ticket) { return $bill; } $this->applyTicket($bill, $ticket); return $bill->fresh(); } /** * Call the next waiting ticket for a Care page context + branch. * * @return array{ticket: ?array, entity: ?Model, resources: ?array} */ public function callNext(Organization $organization, string $context, int $branchId): array { $resources = $this->provisioner->ensure($organization, $context, $branchId); if (! $resources || empty($resources['queue_uuid']) || empty($resources['counter_uuid'])) { return ['ticket' => null, 'entity' => null, 'resources' => $resources]; } $owner = (string) $organization->owner_ref; try { $ticket = $this->queue->callNext( $owner, (string) $resources['queue_uuid'], (string) $resources['counter_uuid'], ); } catch (RequestException $e) { Log::warning('care.queue_call_next_failed', [ 'context' => $context, 'branch_id' => $branchId, 'message' => $e->getMessage(), ]); return ['ticket' => null, 'entity' => null, 'resources' => $resources]; } if (! $ticket) { return ['ticket' => null, 'entity' => null, 'resources' => $resources]; } $entity = $this->findEntityByTicket($organization, $context, (string) ($ticket['uuid'] ?? '')); if ($entity) { $this->applyTicket($entity, $ticket); } return ['ticket' => $ticket, 'entity' => $entity?->fresh(), 'resources' => $resources]; } /** * Mark ticket as serving (Queue "start"). * * @return array|null */ public function serve(Organization $organization, Model $entity): ?array { return $this->ticketAction($organization, $entity, 'start'); } /** * Complete the Queue ticket linked to a Care entity. * * @return array|null */ public function complete(Organization $organization, Model $entity): ?array { return $this->ticketAction($organization, $entity, 'complete'); } /** * Recall the Queue ticket linked to a Care entity. * * @return array|null */ public function recall(Organization $organization, Model $entity): ?array { return $this->ticketAction($organization, $entity, 'recall'); } /** * @return array{enabled: bool, context: string, queue_uuid: ?string, counter_uuid: ?string} */ public function listMeta(Organization $organization, string $context, int $branchId): array { if (! $this->isEnabled($organization)) { return [ 'enabled' => false, 'context' => $context, 'queue_uuid' => null, 'counter_uuid' => null, ]; } $resources = $this->provisioner->ensure($organization->fresh(), $context, $branchId); return [ 'enabled' => true, 'context' => $context, 'queue_uuid' => $resources['queue_uuid'] ?? null, 'counter_uuid' => $resources['counter_uuid'] ?? null, ]; } /** * @param array $metadata * @return array|null */ protected function issue( Organization $organization, string $context, int $branchId, ?Patient $patient, array $metadata = [], string $priority = 'walk_in', string $source = 'api', ): ?array { $resources = $this->provisioner->ensure($organization, $context, $branchId); if (! $resources || empty($resources['queue_uuid'])) { Log::info('care.queue_issue_skipped_no_queue', [ 'context' => $context, 'branch_id' => $branchId, ]); return null; } $owner = (string) $organization->owner_ref; try { return $this->queue->issueTicket($owner, [ 'service_queue_id' => $resources['queue_uuid'], 'customer_name' => $patient?->fullName(), 'customer_phone' => $patient?->phone, 'priority' => $priority, 'source' => $source, 'metadata' => $metadata, ]); } catch (\Throwable $e) { Log::warning('care.queue_issue_failed', [ 'context' => $context, 'branch_id' => $branchId, 'message' => $e->getMessage(), ]); return null; } } /** * @param array $ticket */ protected function applyTicket(Model $entity, array $ticket): void { $entity->forceFill([ 'queue_ticket_uuid' => $ticket['uuid'] ?? $entity->getAttribute('queue_ticket_uuid'), 'queue_ticket_number' => $ticket['ticket_number'] ?? $entity->getAttribute('queue_ticket_number'), 'queue_ticket_status' => $ticket['status'] ?? $entity->getAttribute('queue_ticket_status'), ])->save(); } /** * @return array|null */ protected function ticketAction(Organization $organization, Model $entity, string $action): ?array { $uuid = (string) $entity->getAttribute('queue_ticket_uuid'); if ($uuid === '' || ! $this->isEnabled($organization)) { return null; } try { $ticket = $this->queue->ticketAction((string) $organization->owner_ref, $uuid, [ 'action' => $action, ]); $this->applyTicket($entity, $ticket); return $ticket; } catch (\Throwable $e) { Log::warning('care.queue_ticket_action_failed', [ 'action' => $action, 'ticket_uuid' => $uuid, 'message' => $e->getMessage(), ]); return null; } } protected function findEntityByTicket(Organization $organization, string $context, string $ticketUuid): ?Model { if ($ticketUuid === '') { return null; } $owner = (string) $organization->owner_ref; return match (true) { $context === CareQueueContexts::PHARMACY => Prescription::owned($owner) ->where('organization_id', $organization->id) ->where('queue_ticket_uuid', $ticketUuid) ->first(), $context === CareQueueContexts::LABORATORY => InvestigationRequest::owned($owner) ->where('organization_id', $organization->id) ->where('queue_ticket_uuid', $ticketUuid) ->first(), $context === CareQueueContexts::BILLING => Bill::owned($owner) ->where('organization_id', $organization->id) ->where('queue_ticket_uuid', $ticketUuid) ->first(), default => Appointment::owned($owner) ->where('organization_id', $organization->id) ->where('queue_ticket_uuid', $ticketUuid) ->first(), }; } }