Add reusable routes for department self-serve machine types, n8n workflows, and executions, with corresponding unit tests and service integration.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace modules\selfserve\classes;
|
||||
|
||||
require_once WD . '/modules/selfserve/interfaces/selfserve_condition_evaluator_i.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_condition_rule_object_type.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_condition_rule_type.php';
|
||||
|
||||
use modules\selfserve\helpers\selfserve_condition_rule_object_type;
|
||||
use modules\selfserve\helpers\selfserve_condition_rule_type;
|
||||
use modules\selfserve\interfaces\selfserve_condition_evaluator_i;
|
||||
|
||||
class selfserve_condition_evaluator implements selfserve_condition_evaluator_i
|
||||
{
|
||||
public function evaluate(array $conditions, array $rules, array $answers): array
|
||||
{
|
||||
$rulesByConditionId = [];
|
||||
foreach ($rules as $rule) {
|
||||
$conditionId = (int)($rule['condition_id'] ?? 0);
|
||||
if ($conditionId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$rulesByConditionId[$conditionId][] = $rule;
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$resolving = [];
|
||||
$resolver = function (int $conditionId) use (&$resolver, &$results, &$resolving, $rulesByConditionId, $answers): bool {
|
||||
if (array_key_exists($conditionId, $results)) {
|
||||
return $results[$conditionId];
|
||||
}
|
||||
if (isset($resolving[$conditionId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resolving[$conditionId] = true;
|
||||
$conditionRules = $rulesByConditionId[$conditionId] ?? [];
|
||||
if ($conditionRules === []) {
|
||||
unset($resolving[$conditionId]);
|
||||
$results[$conditionId] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$orRules = [];
|
||||
$andRules = [];
|
||||
foreach ($conditionRules as $rule) {
|
||||
$ruleType = selfserve_condition_rule_type::tryFrom((string)($rule['type'] ?? ''));
|
||||
if ($ruleType === selfserve_condition_rule_type::IS_TRUE_OR_ANY_TRUE) {
|
||||
$orRules[] = $rule;
|
||||
continue;
|
||||
}
|
||||
$andRules[] = $rule;
|
||||
}
|
||||
|
||||
$andSatisfied = true;
|
||||
foreach ($andRules as $rule) {
|
||||
if (!$this->isRuleSatisfied($rule, $answers, $resolver)) {
|
||||
$andSatisfied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$orSatisfied = true;
|
||||
if ($orRules !== []) {
|
||||
$orSatisfied = false;
|
||||
foreach ($orRules as $rule) {
|
||||
if ($this->isRuleSatisfied($rule, $answers, $resolver)) {
|
||||
$orSatisfied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($resolving[$conditionId]);
|
||||
$results[$conditionId] = $andSatisfied && $orSatisfied;
|
||||
return $results[$conditionId];
|
||||
};
|
||||
|
||||
foreach ($conditions as $condition) {
|
||||
$conditionId = (int)($condition['id'] ?? 0);
|
||||
if ($conditionId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$resolver($conditionId);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function taskGateSatisfied(?int $gateId, array $conditionResults, array $answers): bool
|
||||
{
|
||||
if ($gateId === null || $gateId <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (array_key_exists($gateId, $conditionResults)) {
|
||||
return $conditionResults[$gateId] === true;
|
||||
}
|
||||
return ($answers[$gateId] ?? null) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $rule
|
||||
* @param array<int,bool|null> $answers
|
||||
* @param callable(int):bool $conditionResolver
|
||||
* @return bool
|
||||
*/
|
||||
private function isRuleSatisfied(array $rule, array $answers, callable $conditionResolver): bool
|
||||
{
|
||||
$ruleType = selfserve_condition_rule_type::tryFrom((string)($rule['type'] ?? ''));
|
||||
if ($ruleType === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objectType = selfserve_condition_rule_object_type::tryFrom((string)($rule['object_type'] ?? ''));
|
||||
if ($objectType === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objectId = (int)($rule['object_id'] ?? 0);
|
||||
$value = match ($objectType) {
|
||||
selfserve_condition_rule_object_type::QUESTION => ($answers[$objectId] ?? null),
|
||||
selfserve_condition_rule_object_type::CONDITION => $conditionResolver($objectId),
|
||||
};
|
||||
|
||||
return match ($ruleType) {
|
||||
selfserve_condition_rule_type::IS_TRUE,
|
||||
selfserve_condition_rule_type::IS_TRUE_OR_ANY_TRUE => $value === true,
|
||||
selfserve_condition_rule_type::IS_FALSE => $value === false,
|
||||
selfserve_condition_rule_type::IS_SET => $value !== null,
|
||||
selfserve_condition_rule_type::IS_TRUE_OR_NOT_SET => $value === true || $value === null,
|
||||
selfserve_condition_rule_type::IS_FALSE_OR_NOT_SET => $value === false || $value === null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
<?php
|
||||
|
||||
namespace modules\selfserve\classes;
|
||||
|
||||
require_once WD . '/classes/selfserve.php';
|
||||
require_once WD . '/classes/selfserve_schema_bootstrap.php';
|
||||
require_once WD . '/modules/selfserve/classes/selfserve_condition_evaluator.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_relay.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_services.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_state.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_status.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_wash_event_type.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_wash_session_status.php';
|
||||
require_once WD . '/modules/selfserve/interfaces/selfserve_condition_evaluator_i.php';
|
||||
require_once WD . '/modules/selfserve/interfaces/selfserve_wash_flow_i.php';
|
||||
require_once WD . '/objects/customer_vehicles_o.php';
|
||||
require_once WD . '/objects/department_lanes_o.php';
|
||||
require_once WD . '/objects/department_selfserve_condition_rules_o.php';
|
||||
require_once WD . '/objects/department_selfserve_conditions_o.php';
|
||||
require_once WD . '/objects/department_selfserve_questions_o.php';
|
||||
require_once WD . '/objects/department_selfserve_tasks_o.php';
|
||||
require_once WD . '/objects/department_selfserve_vehicle_conditions_o.php';
|
||||
require_once WD . '/objects/selfserve_machine_types_o.php';
|
||||
require_once WD . '/objects/selfserve_wash_session_answers_o.php';
|
||||
require_once WD . '/objects/selfserve_wash_session_events_o.php';
|
||||
require_once WD . '/objects/selfserve_wash_session_tasks_o.php';
|
||||
require_once WD . '/objects/selfserve_wash_sessions_o.php';
|
||||
|
||||
use classes\selfserve;
|
||||
use classes\selfserve_schema_bootstrap;
|
||||
use modules\selfserve\helpers\selfserve_lane_relay;
|
||||
use modules\selfserve\helpers\selfserve_lane_services;
|
||||
use modules\selfserve\helpers\selfserve_lane_state;
|
||||
use modules\selfserve\helpers\selfserve_lane_status;
|
||||
use modules\selfserve\helpers\selfserve_wash_event_type;
|
||||
use modules\selfserve\helpers\selfserve_wash_session_status;
|
||||
use modules\selfserve\interfaces\selfserve_condition_evaluator_i;
|
||||
use modules\selfserve\interfaces\selfserve_wash_flow_i;
|
||||
use objects\customer_vehicles_o;
|
||||
use objects\department_lanes_o;
|
||||
use objects\department_selfserve_condition_rules_o;
|
||||
use objects\department_selfserve_conditions_o;
|
||||
use objects\department_selfserve_questions_o;
|
||||
use objects\department_selfserve_tasks_o;
|
||||
use objects\department_selfserve_vehicle_conditions_o;
|
||||
use objects\selfserve_machine_types_o;
|
||||
use objects\selfserve_wash_session_answers_o;
|
||||
use objects\selfserve_wash_session_events_o;
|
||||
use objects\selfserve_wash_session_tasks_o;
|
||||
use objects\selfserve_wash_sessions_o;
|
||||
|
||||
class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
{
|
||||
public function __construct(
|
||||
protected ?selfserve_condition_evaluator_i $conditionEvaluator = null,
|
||||
) {
|
||||
selfserve_schema_bootstrap::ensureTables();
|
||||
$this->conditionEvaluator ??= new selfserve_condition_evaluator();
|
||||
}
|
||||
|
||||
public function previewVehicleEligibility(int $laneId, string $reg, ?int $customerNumber = null): array
|
||||
{
|
||||
$snapshot = $this->buildEligibilitySnapshot($laneId, $reg, $customerNumber);
|
||||
$session = $this->findLatestOpenSession($laneId, $snapshot['reg'], $snapshot['customer_number']);
|
||||
|
||||
return $this->formatSnapshotResponse($snapshot, $session->exists() ? $session->asArray() : null);
|
||||
}
|
||||
|
||||
public function synchronizeSession(int $laneId, string $reg, ?int $customerNumber = null, bool $activateMachine = true): array
|
||||
{
|
||||
$snapshot = $this->buildEligibilitySnapshot($laneId, $reg, $customerNumber);
|
||||
$session = $this->findLatestOpenSession($laneId, $snapshot['reg'], $snapshot['customer_number']);
|
||||
|
||||
if (!$session->exists()) {
|
||||
$session = (new selfserve_wash_sessions_o())->add(
|
||||
$laneId,
|
||||
(int)$snapshot['lane']['department'],
|
||||
$snapshot['machine_type']['id'] ?? null,
|
||||
$snapshot['customer_number'],
|
||||
$snapshot['reg'],
|
||||
$snapshot['vehicle']['id'] ?? null,
|
||||
$snapshot['vehicle']['type'] ?? null,
|
||||
$this->deriveBaseStatus($snapshot),
|
||||
(bool)$snapshot['allowed'],
|
||||
$this->buildSessionMetadata($snapshot),
|
||||
);
|
||||
} else {
|
||||
$session->allowed->set((bool)$snapshot['allowed']);
|
||||
$session->metadata_json->set($this->buildSessionMetadata($snapshot));
|
||||
$session->updateStatus($this->deriveCurrentStatus($snapshot, $session));
|
||||
}
|
||||
|
||||
$this->syncSessionAnswers((int)$session->id, $snapshot['questions']);
|
||||
$this->syncSessionTasks((int)$session->id, $snapshot['tasks']);
|
||||
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::SESSION_SYNCED, [
|
||||
'allowed' => (bool)$snapshot['allowed'],
|
||||
'all_visible_questions_answered' => (bool)$snapshot['all_visible_questions_answered'],
|
||||
'allowed_services' => $snapshot['allowed_services'],
|
||||
'task_ids' => array_map(static fn(array $task): int => (int)$task['id'], $snapshot['tasks']),
|
||||
]);
|
||||
|
||||
if ($activateMachine && (bool)$snapshot['allowed']) {
|
||||
$this->enableMachineRelayIfAllowed($snapshot, $session);
|
||||
}
|
||||
|
||||
return $this->getSessionSummary((int)$session->id);
|
||||
}
|
||||
|
||||
public function recordMachineStartWebhook(int $laneId, ?string $reg = null, array $payload = []): array
|
||||
{
|
||||
$normalizedReg = $reg === null ? null : selfserve::standardize_registration($reg);
|
||||
$session = $normalizedReg !== null
|
||||
? $this->findLatestOpenSession($laneId, $normalizedReg)
|
||||
: $this->findLatestOpenSessionByLane($laneId);
|
||||
|
||||
if (!$session->exists()) {
|
||||
if ($normalizedReg === null) {
|
||||
throw new \RuntimeException('No active self-serve wash session found for the lane.');
|
||||
}
|
||||
$summary = $this->synchronizeSession($laneId, $normalizedReg, null, false);
|
||||
$session = (new selfserve_wash_sessions_o())->select((int)$summary['session']['id']);
|
||||
}
|
||||
|
||||
$lane = (new selfserve())->lane($laneId);
|
||||
$effectiveReg = $normalizedReg ?? (string)$session->reg->value();
|
||||
$customerNumber = $session->customer_number->value() === null ? null : (int)$session->customer_number->value();
|
||||
|
||||
if ($lane->getLaneStatus()->equals(selfserve_lane_status::AVAILABLE)) {
|
||||
$lane->setLaneStatus(selfserve_lane_status::OCCUPIED);
|
||||
}
|
||||
if (!$lane->getLaneState()->equals(selfserve_lane_state::IN_WASH)) {
|
||||
$lane->setLaneState(selfserve_lane_state::IN_WASH);
|
||||
}
|
||||
if ($effectiveReg !== '') {
|
||||
$lane->setLicensePlate($effectiveReg);
|
||||
}
|
||||
if ($customerNumber !== null && $customerNumber > 0) {
|
||||
$lane->setCustomerNumber($customerNumber);
|
||||
}
|
||||
if ((int)$lane->getWashStartTime() <= 0) {
|
||||
$lane->setWashStartTime(time());
|
||||
}
|
||||
|
||||
$session->markMachineStartTriggered();
|
||||
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::MACHINE_START_TRIGGERED, $payload + [
|
||||
'lane_id' => $laneId,
|
||||
'reg' => $effectiveReg,
|
||||
'customer_number' => $customerNumber,
|
||||
]);
|
||||
|
||||
return $this->getSessionSummary((int)$session->id);
|
||||
}
|
||||
|
||||
public function getSessionSummary(int $sessionId): array
|
||||
{
|
||||
$session = (new selfserve_wash_sessions_o())->select($sessionId);
|
||||
if (!$session->exists()) {
|
||||
throw new \RuntimeException('Self-serve wash session not found.');
|
||||
}
|
||||
|
||||
$lane = (new department_lanes_o())->select((int)$session->lane_id->value());
|
||||
$machineType = null;
|
||||
if ($session->machine_type_id->value() !== null) {
|
||||
$machineTypeObject = (new selfserve_machine_types_o())->select((int)$session->machine_type_id->value());
|
||||
if ($machineTypeObject->exists()) {
|
||||
$machineType = $machineTypeObject->asArray();
|
||||
}
|
||||
}
|
||||
|
||||
$answers = array_map(function (array $row): array {
|
||||
return [
|
||||
'question_id' => (int)$row['question_id'],
|
||||
'question' => (string)$row['question_text'],
|
||||
'answer' => (bool)$row['answer_value'],
|
||||
'answered_at' => (string)$row['answered_at'],
|
||||
];
|
||||
}, (new selfserve_wash_session_answers_o())->listBySession($sessionId));
|
||||
|
||||
$tasks = array_map(function (array $row): array {
|
||||
return [
|
||||
'task_id' => $row['task_id'] === null ? null : (int)$row['task_id'],
|
||||
'task' => (string)$row['task_text'],
|
||||
'description' => $row['description'] === null ? null : (string)$row['description'],
|
||||
'services' => $this->normalizeJsonArray($row['services'] ?? null),
|
||||
'buttons' => $this->normalizeJsonArray($row['buttons'] ?? null),
|
||||
];
|
||||
}, (new selfserve_wash_session_tasks_o())->listBySession($sessionId));
|
||||
|
||||
$events = array_map(function (array $row): array {
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'type' => (string)$row['event_type'],
|
||||
'payload' => $this->normalizeJsonValue($row['payload_json'] ?? null),
|
||||
'created_at' => (string)$row['created_at'],
|
||||
];
|
||||
}, (new selfserve_wash_session_events_o())->listBySession($sessionId));
|
||||
|
||||
return [
|
||||
'session' => $session->asArray(),
|
||||
'lane' => $lane->exists() ? $lane->asArray() : null,
|
||||
'machine_type' => $machineType,
|
||||
'questions' => $answers,
|
||||
'tasks' => $tasks,
|
||||
'events' => $events,
|
||||
];
|
||||
}
|
||||
|
||||
public function getLatestSessionSummary(int $laneId, string $reg): array
|
||||
{
|
||||
$session = (new selfserve_wash_sessions_o())->selectLatestByLaneAndReg($laneId, selfserve::standardize_registration($reg));
|
||||
if (!$session->exists()) {
|
||||
throw new \RuntimeException('No self-serve wash session found for the lane and vehicle.');
|
||||
}
|
||||
|
||||
return $this->getSessionSummary((int)$session->id);
|
||||
}
|
||||
|
||||
public function completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null): ?array
|
||||
{
|
||||
$session = $reg !== null
|
||||
? $this->findLatestOpenSession($laneId, selfserve::standardize_registration($reg), $customerNumber)
|
||||
: $this->findLatestOpenSessionByLane($laneId, $customerNumber);
|
||||
|
||||
if (!$session->exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session->markCompleted($orderId);
|
||||
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::SESSION_COMPLETED, [
|
||||
'lane_id' => $laneId,
|
||||
'reg' => $reg === null ? (string)$session->reg->value() : selfserve::standardize_registration($reg),
|
||||
'customer_number' => $customerNumber ?? ($session->customer_number->value() === null ? null : (int)$session->customer_number->value()),
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
|
||||
return $this->getSessionSummary((int)$session->id);
|
||||
}
|
||||
|
||||
protected function buildEligibilitySnapshot(int $laneId, string $reg, ?int $customerNumber = null): array
|
||||
{
|
||||
$normalizedReg = selfserve::standardize_registration($reg);
|
||||
$lane = (new department_lanes_o())->select($laneId);
|
||||
if (!$lane->exists()) {
|
||||
throw new \RuntimeException('Department lane not found.');
|
||||
}
|
||||
|
||||
$departmentId = (int)$lane->department->value();
|
||||
$machineTypeId = $lane->machine_type_id->value() === null ? null : (int)$lane->machine_type_id->value();
|
||||
$vehicle = $this->findVehicleByRegistration($normalizedReg);
|
||||
$vehicleData = $vehicle?->asArray();
|
||||
$vehicleTypeId = $vehicle !== null ? (int)$vehicle->type->value() : null;
|
||||
$resolvedCustomerNumber = $customerNumber ?? ($vehicle !== null ? (int)$vehicle->customer_id->value() : null);
|
||||
|
||||
$questions = $this->loadQuestions($departmentId, $laneId, $vehicleTypeId);
|
||||
$conditions = $this->loadConditions($departmentId, $laneId, $vehicleTypeId, $machineTypeId);
|
||||
$rules = $this->loadConditionRules($conditions);
|
||||
$answers = (new department_selfserve_vehicle_conditions_o())->getAnswerMapForVehicle($departmentId, $laneId, $normalizedReg);
|
||||
$conditionResults = $this->conditionEvaluator->evaluate($conditions, $rules, $answers);
|
||||
|
||||
$visibleQuestions = [];
|
||||
foreach ($questions as $question) {
|
||||
$gateId = $this->nullableInt($question['condition_id'] ?? null);
|
||||
if ($gateId !== null && (($conditionResults[$gateId] ?? false) !== true)) {
|
||||
continue;
|
||||
}
|
||||
$questionId = (int)$question['id'];
|
||||
$visibleQuestions[] = [
|
||||
'id' => $questionId,
|
||||
'question' => (string)$question['question'],
|
||||
'description' => (string)($question['description'] ?? ''),
|
||||
'condition_id' => $gateId,
|
||||
'order_priority' => (int)($question['order_priority'] ?? 0),
|
||||
'answer' => array_key_exists($questionId, $answers) ? (bool)$answers[$questionId] : null,
|
||||
];
|
||||
}
|
||||
|
||||
usort($visibleQuestions, static fn(array $a, array $b): int => $a['order_priority'] <=> $b['order_priority']);
|
||||
|
||||
$tasks = $this->loadTasks($departmentId, $laneId, $vehicleTypeId, $machineTypeId);
|
||||
$activeTasks = [];
|
||||
foreach ($tasks as $task) {
|
||||
$gateId = $this->nullableInt($task['condition_id'] ?? null);
|
||||
if (!$this->conditionEvaluator->taskGateSatisfied($gateId, $conditionResults, $answers)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$activeTasks[] = [
|
||||
'id' => (int)$task['id'],
|
||||
'task' => (string)$task['task'],
|
||||
'description' => (string)($task['description'] ?? ''),
|
||||
'condition_id' => $gateId,
|
||||
'order_priority' => (int)($task['order_priority'] ?? 0),
|
||||
'services' => $this->normalizeServiceNames($this->normalizeJsonArray($task['services'] ?? null)),
|
||||
'buttons' => $this->normalizeIntArray($this->normalizeJsonArray($task['buttons'] ?? null)),
|
||||
];
|
||||
}
|
||||
usort($activeTasks, static fn(array $a, array $b): int => $a['order_priority'] <=> $b['order_priority']);
|
||||
|
||||
$allowedServices = [];
|
||||
foreach ($activeTasks as $task) {
|
||||
foreach ($task['services'] as $service) {
|
||||
if (!in_array($service, $allowedServices, true)) {
|
||||
$allowedServices[] = $service;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$machineAvailable = !empty($lane->relay_machine_id->value());
|
||||
$allVisibleQuestionsAnswered = true;
|
||||
foreach ($visibleQuestions as $question) {
|
||||
if ($question['answer'] === null) {
|
||||
$allVisibleQuestionsAnswered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$machineAllowed = $allVisibleQuestionsAnswered
|
||||
&& $machineAvailable
|
||||
&& in_array(selfserve_lane_services::MACHINE->name, $allowedServices, true);
|
||||
|
||||
$machineType = null;
|
||||
if ($machineTypeId !== null) {
|
||||
$machineTypeObject = (new selfserve_machine_types_o())->select($machineTypeId);
|
||||
if ($machineTypeObject->exists()) {
|
||||
$machineType = $machineTypeObject->asArray();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'lane' => $lane->asArray(),
|
||||
'machine_type' => $machineType,
|
||||
'vehicle' => $vehicleData,
|
||||
'reg' => $normalizedReg,
|
||||
'customer_number' => $resolvedCustomerNumber,
|
||||
'vehicle_type_id' => $vehicleTypeId,
|
||||
'answers' => $answers,
|
||||
'questions' => $visibleQuestions,
|
||||
'conditions' => $conditionResults,
|
||||
'tasks' => $activeTasks,
|
||||
'allowed_services' => $allowedServices,
|
||||
'machine_available' => $machineAvailable,
|
||||
'all_visible_questions_answered' => $allVisibleQuestionsAnswered,
|
||||
'allowed' => $machineAllowed,
|
||||
];
|
||||
}
|
||||
|
||||
protected function enableMachineRelayIfAllowed(array $snapshot, selfserve_wash_sessions_o $session): void
|
||||
{
|
||||
if ((bool)$session->machine_relay_enabled->value() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$laneId = (int)$snapshot['lane']['id'];
|
||||
$lane = (new selfserve())->lane($laneId);
|
||||
$lane->setLaneCache($laneId, $lane::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES, $snapshot['allowed_services']);
|
||||
$lane->turnOnRelay(selfserve_lane_relay::MACHINE);
|
||||
|
||||
$session->markRelayEnabled();
|
||||
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::MACHINE_RELAY_ENABLED, [
|
||||
'lane_id' => $laneId,
|
||||
'reg' => $snapshot['reg'],
|
||||
'allowed_services' => $snapshot['allowed_services'],
|
||||
'task_ids' => array_map(static fn(array $task): int => (int)$task['id'], $snapshot['tasks']),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function deriveBaseStatus(array $snapshot): selfserve_wash_session_status
|
||||
{
|
||||
if (!$snapshot['all_visible_questions_answered']) {
|
||||
return selfserve_wash_session_status::PENDING_QUESTIONS;
|
||||
}
|
||||
if ($snapshot['allowed']) {
|
||||
return selfserve_wash_session_status::READY_FOR_MACHINE_START;
|
||||
}
|
||||
return selfserve_wash_session_status::MACHINE_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
protected function deriveCurrentStatus(array $snapshot, selfserve_wash_sessions_o $session): selfserve_wash_session_status
|
||||
{
|
||||
if ($session->completed_at->value() !== null) {
|
||||
return selfserve_wash_session_status::COMPLETED;
|
||||
}
|
||||
if ((bool)$session->machine_start_triggered->value() === true) {
|
||||
return selfserve_wash_session_status::MACHINE_STARTED;
|
||||
}
|
||||
if ((bool)$session->machine_relay_enabled->value() === true) {
|
||||
return selfserve_wash_session_status::MACHINE_RELAY_ENABLED;
|
||||
}
|
||||
return $this->deriveBaseStatus($snapshot);
|
||||
}
|
||||
|
||||
protected function formatSnapshotResponse(array $snapshot, ?array $session = null): array
|
||||
{
|
||||
return [
|
||||
'lane' => $snapshot['lane'],
|
||||
'machine_type' => $snapshot['machine_type'],
|
||||
'vehicle' => $snapshot['vehicle'],
|
||||
'reg' => $snapshot['reg'],
|
||||
'customer_number' => $snapshot['customer_number'],
|
||||
'questions' => $snapshot['questions'],
|
||||
'tasks' => $snapshot['tasks'],
|
||||
'allowed_services' => $snapshot['allowed_services'],
|
||||
'machine_available' => $snapshot['machine_available'],
|
||||
'all_visible_questions_answered' => $snapshot['all_visible_questions_answered'],
|
||||
'allowed' => $snapshot['allowed'],
|
||||
'session' => $session,
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadQuestions(int $departmentId, int $laneId, ?int $vehicleTypeId): array
|
||||
{
|
||||
$questionsObject = new department_selfserve_questions_o();
|
||||
$sharedQuestions = $questionsObject->getSharedQuestions();
|
||||
if ($sharedQuestions !== []) {
|
||||
return $sharedQuestions;
|
||||
}
|
||||
if ($vehicleTypeId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $questionsObject->getLegacyQuestionsForLaneProduct($departmentId, $laneId, $vehicleTypeId);
|
||||
}
|
||||
|
||||
protected function loadConditions(int $departmentId, int $laneId, ?int $vehicleTypeId, ?int $machineTypeId): array
|
||||
{
|
||||
$conditionsObject = new department_selfserve_conditions_o();
|
||||
if ($machineTypeId !== null) {
|
||||
$machineTypeConditions = $conditionsObject->getConditionsForMachineType($machineTypeId);
|
||||
if ($machineTypeConditions !== []) {
|
||||
return $machineTypeConditions;
|
||||
}
|
||||
}
|
||||
if ($vehicleTypeId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $conditionsObject->getLegacyConditionsForLaneProduct($departmentId, $laneId, $vehicleTypeId);
|
||||
}
|
||||
|
||||
protected function loadTasks(int $departmentId, int $laneId, ?int $vehicleTypeId, ?int $machineTypeId): array
|
||||
{
|
||||
$tasksObject = new department_selfserve_tasks_o();
|
||||
if ($machineTypeId !== null) {
|
||||
$machineTypeTasks = $tasksObject->getTasksForMachineType($machineTypeId);
|
||||
if ($machineTypeTasks !== []) {
|
||||
return $machineTypeTasks;
|
||||
}
|
||||
}
|
||||
if ($vehicleTypeId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $tasksObject->getLegacyTasksForLaneProduct($departmentId, $laneId, $vehicleTypeId);
|
||||
}
|
||||
|
||||
protected function loadConditionRules(array $conditions): array
|
||||
{
|
||||
if ($conditions === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$conditionIds = array_map(static fn(array $condition): int => (int)$condition['id'], $conditions);
|
||||
|
||||
return (new department_selfserve_condition_rules_o())->getFieldsWhereIn([
|
||||
'condition_id' => $conditionIds,
|
||||
'deleted_at' => null,
|
||||
], ['id', 'condition_id', 'type', 'object_type', 'object_id', 'name', 'description']);
|
||||
}
|
||||
|
||||
protected function syncSessionAnswers(int $sessionId, array $questions): void
|
||||
{
|
||||
$answersObject = new selfserve_wash_session_answers_o();
|
||||
$answeredQuestionIds = [];
|
||||
foreach ($questions as $question) {
|
||||
if ($question['answer'] === null) {
|
||||
continue;
|
||||
}
|
||||
$answeredQuestionIds[] = (int)$question['id'];
|
||||
$answersObject->upsert(
|
||||
$sessionId,
|
||||
(int)$question['id'],
|
||||
(string)$question['question'],
|
||||
(bool)$question['answer'],
|
||||
);
|
||||
}
|
||||
$answersObject->deleteMissingForSession($sessionId, $answeredQuestionIds);
|
||||
}
|
||||
|
||||
protected function syncSessionTasks(int $sessionId, array $tasks): void
|
||||
{
|
||||
$tasksObject = new selfserve_wash_session_tasks_o();
|
||||
$tasksObject->deleteBySession($sessionId);
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$tasksObject->addSnapshot(
|
||||
$sessionId,
|
||||
(int)$task['id'],
|
||||
(string)$task['task'],
|
||||
(string)$task['description'],
|
||||
$task['services'],
|
||||
$task['buttons'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function logSessionEvent(int $sessionId, selfserve_wash_event_type $eventType, ?array $payload = null): void
|
||||
{
|
||||
(new selfserve_wash_session_events_o())->add($sessionId, $eventType, $payload);
|
||||
}
|
||||
|
||||
protected function buildSessionMetadata(array $snapshot): array
|
||||
{
|
||||
return [
|
||||
'allowed_services' => $snapshot['allowed_services'],
|
||||
'machine_available' => (bool)$snapshot['machine_available'],
|
||||
'all_visible_questions_answered' => (bool)$snapshot['all_visible_questions_answered'],
|
||||
'visible_question_ids' => array_map(static fn(array $question): int => (int)$question['id'], $snapshot['questions']),
|
||||
'task_ids' => array_map(static fn(array $task): int => (int)$task['id'], $snapshot['tasks']),
|
||||
];
|
||||
}
|
||||
|
||||
protected function findVehicleByRegistration(string $reg): ?customer_vehicles_o
|
||||
{
|
||||
$vehicle = (new customer_vehicles_o())->selectByPlate($reg);
|
||||
return $vehicle->exists() ? $vehicle : null;
|
||||
}
|
||||
|
||||
protected function findLatestOpenSession(int $laneId, string $reg, ?int $customerNumber = null): selfserve_wash_sessions_o
|
||||
{
|
||||
$session = new selfserve_wash_sessions_o();
|
||||
$session->selectLatestOpenByLaneAndReg($laneId, $reg, $customerNumber);
|
||||
return $session;
|
||||
}
|
||||
|
||||
protected function findLatestOpenSessionByLane(int $laneId, ?int $customerNumber = null): selfserve_wash_sessions_o
|
||||
{
|
||||
$rows = (new selfserve_wash_sessions_o())->getFieldsWhere(
|
||||
[
|
||||
'lane_id' => $laneId,
|
||||
'completed_at' => null,
|
||||
'deleted_at' => null,
|
||||
...($customerNumber !== null ? ['customer_number' => $customerNumber] : []),
|
||||
],
|
||||
['id']
|
||||
);
|
||||
if ($rows === []) {
|
||||
return new selfserve_wash_sessions_o();
|
||||
}
|
||||
|
||||
usort($rows, static fn(array $a, array $b): int => (int)$b['id'] <=> (int)$a['id']);
|
||||
return (new selfserve_wash_sessions_o())->select((int)$rows[0]['id']);
|
||||
}
|
||||
|
||||
protected function nullableInt(mixed $value): ?int
|
||||
{
|
||||
if ($value === null || $value === '' || $value === 0 || $value === '0') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
protected function normalizeJsonValue(mixed $value): mixed
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function normalizeJsonArray(mixed $value): array
|
||||
{
|
||||
$decoded = $this->normalizeJsonValue($value);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
protected function normalizeServiceNames(array $services): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($services as $service) {
|
||||
$name = strtoupper(trim((string)$service));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($name, $normalized, true)) {
|
||||
$normalized[] = $name;
|
||||
}
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
protected function normalizeIntArray(array $values): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($values as $value) {
|
||||
$intValue = (int)$value;
|
||||
if (!in_array($intValue, $normalized, true)) {
|
||||
$normalized[] = $intValue;
|
||||
}
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user