Refactor self-serve wash flow to decouple visible question logic, improve condition evaluation, and handle nullable fields in the OpenAPI spec.

This commit is contained in:
Jeppe Bundgaard
2026-03-18 13:05:41 +01:00
parent f04f79e3da
commit 7c55beae42
2 changed files with 153 additions and 12 deletions
+2
View File
@@ -11816,9 +11816,11 @@ components:
type: string
answer:
type: boolean
nullable: true
answered_at:
type: string
format: date-time
nullable: true
SelfserveWashTaskSnapshot:
type: object
@@ -167,14 +167,8 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
}
}
$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));
$answerRows = (new selfserve_wash_session_answers_o())->listBySession($sessionId);
$answers = $this->buildSessionQuestions($session, $answerRows);
$tasks = array_map(function (array $row): array {
return [
@@ -255,15 +249,17 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
$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);
$visibilityConditionResults = $this->conditionEvaluator->evaluate($conditions, $rules, $answers);
$visibleQuestions = [];
$visibleQuestionIds = [];
foreach ($questions as $question) {
$gateId = $this->nullableInt($question['condition_id'] ?? null);
if ($gateId !== null && (($conditionResults[$gateId] ?? false) !== true)) {
if ($gateId !== null && (($visibilityConditionResults[$gateId] ?? false) !== true)) {
continue;
}
$questionId = (int)$question['id'];
$visibleQuestionIds[] = $questionId;
$visibleQuestions[] = [
'id' => $questionId,
'question' => (string)$question['question'],
@@ -275,12 +271,14 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
}
usort($visibleQuestions, static fn(array $a, array $b): int => $a['order_priority'] <=> $b['order_priority']);
$visibleAnswers = $this->filterAnswersToVisibleQuestions($answers, $visibleQuestionIds);
$serviceConditionResults = $this->conditionEvaluator->evaluate($conditions, $rules, $visibleAnswers);
$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)) {
if (!$this->conditionEvaluator->taskGateSatisfied($gateId, $serviceConditionResults, $visibleAnswers)) {
continue;
}
@@ -335,7 +333,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
'vehicle_type_id' => $vehicleTypeId,
'answers' => $answers,
'questions' => $visibleQuestions,
'conditions' => $conditionResults,
'conditions' => $serviceConditionResults,
'tasks' => $activeTasks,
'allowed_services' => $allowedServices,
'machine_available' => $machineAvailable,
@@ -515,10 +513,133 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
'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']),
'visible_questions' => array_map(static fn(array $question): array => [
'id' => (int)$question['id'],
'question' => (string)$question['question'],
'order_priority' => (int)($question['order_priority'] ?? 0),
], $snapshot['questions']),
'task_ids' => array_map(static fn(array $task): int => (int)$task['id'], $snapshot['tasks']),
];
}
/**
* @param array<int,array<string,mixed>> $answerRows
* @return array<int,array<string,mixed>>
*/
protected function buildSessionQuestions(selfserve_wash_sessions_o $session, array $answerRows): array
{
$answersByQuestionId = [];
foreach ($answerRows as $row) {
$answersByQuestionId[(int)$row['question_id']] = $row;
}
$metadata = $session->metadata_json->value();
$metadata = is_array($metadata) ? $metadata : [];
$visibleQuestions = $this->resolveVisibleQuestionsFromMetadata(
$metadata,
(int)$session->department_id->value(),
(int)$session->lane_id->value(),
$session->vehicle_type_id->value() === null ? null : (int)$session->vehicle_type_id->value(),
);
if ($visibleQuestions === []) {
return array_map(static 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'],
];
}, $answerRows);
}
$questions = [];
foreach ($visibleQuestions as $visibleQuestion) {
$questionId = (int)$visibleQuestion['id'];
if ($questionId <= 0) {
continue;
}
$answerRow = $answersByQuestionId[$questionId] ?? null;
$questions[] = [
'question_id' => $questionId,
'question' => $answerRow === null ? (string)$visibleQuestion['question'] : (string)$answerRow['question_text'],
'answer' => $answerRow === null ? null : (bool)$answerRow['answer_value'],
'answered_at' => $answerRow === null ? null : (string)$answerRow['answered_at'],
];
unset($answersByQuestionId[$questionId]);
}
foreach ($answerRows as $row) {
$questionId = (int)$row['question_id'];
if (!array_key_exists($questionId, $answersByQuestionId)) {
continue;
}
$questions[] = [
'question_id' => $questionId,
'question' => (string)$row['question_text'],
'answer' => (bool)$row['answer_value'],
'answered_at' => (string)$row['answered_at'],
];
unset($answersByQuestionId[$questionId]);
}
return $questions;
}
/**
* @param array<string,mixed> $metadata
* @return array<int,array{id:int,question:string,order_priority:int}>
*/
protected function resolveVisibleQuestionsFromMetadata(array $metadata, int $departmentId, int $laneId, ?int $vehicleTypeId): array
{
$visibleQuestions = [];
if (isset($metadata['visible_questions']) && is_array($metadata['visible_questions'])) {
foreach ($metadata['visible_questions'] as $visibleQuestion) {
if (!is_array($visibleQuestion)) {
continue;
}
$questionId = (int)($visibleQuestion['id'] ?? 0);
if ($questionId <= 0) {
continue;
}
$visibleQuestions[] = [
'id' => $questionId,
'question' => (string)($visibleQuestion['question'] ?? ''),
'order_priority' => (int)($visibleQuestion['order_priority'] ?? 0),
];
}
return $visibleQuestions;
}
if (!isset($metadata['visible_question_ids']) || !is_array($metadata['visible_question_ids'])) {
return [];
}
$questionById = [];
foreach ($this->loadQuestions($departmentId, $laneId, $vehicleTypeId) as $question) {
$questionById[(int)$question['id']] = [
'question' => (string)($question['question'] ?? ''),
'order_priority' => (int)($question['order_priority'] ?? 0),
];
}
foreach ($metadata['visible_question_ids'] as $visibleQuestionId) {
$questionId = (int)$visibleQuestionId;
if ($questionId <= 0) {
continue;
}
$visibleQuestions[] = [
'id' => $questionId,
'question' => (string)($questionById[$questionId]['question'] ?? ''),
'order_priority' => (int)($questionById[$questionId]['order_priority'] ?? 0),
];
}
return $visibleQuestions;
}
protected function findVehicleByRegistration(string $reg): ?customer_vehicles_o
{
$vehicle = (new customer_vehicles_o())->selectByPlate($reg);
@@ -560,6 +681,24 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
return (int)$value;
}
/**
* @param array<int,bool|null> $answers
* @param array<int,int> $visibleQuestionIds
* @return array<int,bool|null>
*/
protected function filterAnswersToVisibleQuestions(array $answers, array $visibleQuestionIds): array
{
$filtered = [];
foreach ($visibleQuestionIds as $questionId) {
$questionId = (int)$questionId;
if ($questionId <= 0 || !array_key_exists($questionId, $answers)) {
continue;
}
$filtered[$questionId] = $answers[$questionId];
}
return $filtered;
}
protected function normalizeJsonValue(mixed $value): mixed
{
if ($value === null || $value === '') {