Add attachment payload handling and tests for self-serve tasks
- Introduced `selfserve_task_attachment_payloads` class for managing task attachments, including formatting and download URL generation. - Added unit and API tests to validate attachment handling in self-serve tasks and customer-scoped workflows. - Enhanced wash start simulation and studio graph projections to integrate task attachment data.
This commit is contained in:
@@ -9,6 +9,7 @@ require_once WD . '/modules/selfserve/classes/selfserve_lane_command_arguments.p
|
||||
require_once WD . '/modules/selfserve/classes/selfserve_config_versioning.php';
|
||||
require_once WD . '/modules/selfserve/classes/selfserve_studio_action_runner.php';
|
||||
require_once WD . '/modules/selfserve/classes/selfserve_studio_actions.php';
|
||||
require_once WD . '/modules/selfserve/classes/selfserve_task_attachment_payloads.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_command.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_relay.php';
|
||||
require_once WD . '/modules/selfserve/helpers/selfserve_lane_services.php';
|
||||
@@ -186,17 +187,41 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
'reg' => $effectiveReg,
|
||||
'customer_number' => $customerNumber,
|
||||
]);
|
||||
$actionContext = [
|
||||
'lane_id' => $laneId,
|
||||
'reg' => $effectiveReg,
|
||||
'customer_number' => $customerNumber,
|
||||
'session_id' => (int)$session->id,
|
||||
'source_payload' => $payload,
|
||||
];
|
||||
try {
|
||||
if ($effectiveReg !== '') {
|
||||
$actionSnapshot = $this->buildEligibilitySnapshot(
|
||||
$laneId,
|
||||
$effectiveReg,
|
||||
$customerNumber,
|
||||
$session->vehicle_type_id->value() === null ? null : (int)$session->vehicle_type_id->value(),
|
||||
['config_source' => 'published']
|
||||
);
|
||||
if (is_array($actionSnapshot['evaluation_trace']['condition_results'] ?? null)) {
|
||||
$actionContext['condition_results'] = (array)$actionSnapshot['evaluation_trace']['condition_results'];
|
||||
}
|
||||
if (is_array($actionSnapshot['evaluation_trace']['visibility_condition_results'] ?? null)) {
|
||||
$actionContext['visibility_condition_results'] = (array)$actionSnapshot['evaluation_trace']['visibility_condition_results'];
|
||||
}
|
||||
$actionContext['allowed_services'] = (array)($actionSnapshot['allowed_services'] ?? []);
|
||||
$actionContext['vehicle_type_id'] = $actionSnapshot['vehicle_type_id'] ?? null;
|
||||
$actionContext['product'] = $actionSnapshot['vehicle_type_id'] ?? null;
|
||||
$actionContext['machine_type_id'] = $actionSnapshot['machine_type']['id'] ?? null;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Action execution should stay best-effort even when preview context cannot be rebuilt.
|
||||
}
|
||||
(new selfserve_studio_action_runner())->executeForLaneEvent(
|
||||
$lane,
|
||||
selfserve_studio_actions::EVENT_MACHINE_START_TRIGGERED,
|
||||
selfserve_studio_actions::MODE_MACHINE,
|
||||
[
|
||||
'lane_id' => $laneId,
|
||||
'reg' => $effectiveReg,
|
||||
'customer_number' => $customerNumber,
|
||||
'session_id' => (int)$session->id,
|
||||
'source_payload' => $payload,
|
||||
]
|
||||
$actionContext
|
||||
);
|
||||
$this->enableCleanerRelayForStartedWash($lane);
|
||||
|
||||
@@ -310,6 +335,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
'dynamic_images_vehicle_type' => $row['dynamic_images_vehicle_type'] === null ? null : (int)$row['dynamic_images_vehicle_type']
|
||||
];
|
||||
}, (new selfserve_wash_session_tasks_o())->listBySession($sessionId));
|
||||
$tasks = (new selfserve_task_attachment_payloads())->attachToTasks($tasks);
|
||||
|
||||
$events = array_map(function (array $row): array {
|
||||
return [
|
||||
@@ -491,27 +517,17 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
$serviceExpressionTrace = [];
|
||||
}
|
||||
|
||||
$tasks = $this->loadTasks($departmentId, $laneId, $vehicleTypeId, $machineTypeId, $publishedConfigPayload);
|
||||
$tasks = (new selfserve_task_attachment_payloads())->attachToTasks(
|
||||
$this->loadTasks($departmentId, $laneId, $vehicleTypeId, $machineTypeId, $publishedConfigPayload)
|
||||
);
|
||||
$activeTasks = [];
|
||||
$taskGateTrace = [];
|
||||
$conditionIds = array_map(static fn(array $condition): int => (int)($condition['id'] ?? 0), $conditions);
|
||||
foreach ($tasks as $task) {
|
||||
$gateId = $this->nullableInt($task['condition_id'] ?? null);
|
||||
$typedGateType = selfserve_task_gate_type::tryFrom((string)($task['gate_type'] ?? ''));
|
||||
$typedGateRefId = $this->nullableInt($task['gate_ref_id'] ?? null);
|
||||
|
||||
if ($typedGateType === null) {
|
||||
if ($gateId === null) {
|
||||
$typedGateType = selfserve_task_gate_type::ALWAYS;
|
||||
$typedGateRefId = null;
|
||||
} elseif (in_array($gateId, $conditionIds, true)) {
|
||||
$typedGateType = selfserve_task_gate_type::CONDITION;
|
||||
$typedGateRefId = $gateId;
|
||||
} else {
|
||||
$typedGateType = selfserve_task_gate_type::QUESTION;
|
||||
$typedGateRefId = $gateId;
|
||||
}
|
||||
}
|
||||
$resolvedGate = $this->resolveTaskGate($task, $conditionIds);
|
||||
$typedGateType = $resolvedGate['gate_type'];
|
||||
$typedGateRefId = $resolvedGate['gate_ref_id'];
|
||||
|
||||
$gateSatisfied = $this->conditionEvaluator->taskGateSatisfiedTyped(
|
||||
$typedGateType->value,
|
||||
@@ -543,6 +559,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
'services' => $this->normalizeServiceNames($this->normalizeJsonArray($task['services'] ?? null)),
|
||||
'buttons' => $this->normalizeButtonList($task['buttons'] ?? null),
|
||||
'dynamic_images_vehicle_type' => ($task['dynamic_images_vehicle_type'] ?? null) === null ? null : (int)$task['dynamic_images_vehicle_type'],
|
||||
'attachments' => $task['attachments'] ?? [],
|
||||
];
|
||||
}
|
||||
usort($activeTasks, static fn(array $a, array $b): int => $a['order_priority'] <=> $b['order_priority']);
|
||||
@@ -991,6 +1008,16 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
protected function buildDebugTasks(array $snapshot, array $tasks, array $lookups, array $gatewayWorkspace): array
|
||||
{
|
||||
$activeIds = array_flip(array_map(static fn(array $task): int => (int)($task['id'] ?? 0), (array)($snapshot['tasks'] ?? [])));
|
||||
$activeAttachments = [];
|
||||
foreach ((array)($snapshot['tasks'] ?? []) as $task) {
|
||||
if (!is_array($task)) {
|
||||
continue;
|
||||
}
|
||||
$taskId = (int)($task['id'] ?? $task['task_id'] ?? 0);
|
||||
if ($taskId > 0 && is_array($task['attachments'] ?? null)) {
|
||||
$activeAttachments[$taskId] = array_values($task['attachments']);
|
||||
}
|
||||
}
|
||||
$gateTrace = [];
|
||||
foreach ((array)($snapshot['evaluation_trace']['task_gates'] ?? []) as $trace) {
|
||||
if (is_array($trace)) {
|
||||
@@ -1016,10 +1043,12 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
$active = isset($activeIds[$taskId]);
|
||||
$gateType = (string)($trace['gate_type'] ?? $task['gate_type'] ?? selfserve_task_gate_type::ALWAYS->value);
|
||||
$gateRefId = $this->nullableInt($trace['gate_ref_id'] ?? $task['gate_ref_id'] ?? $task['condition_id'] ?? null);
|
||||
$taskAttachments = is_array($task['attachments'] ?? null) ? array_values($task['attachments']) : ($activeAttachments[$taskId] ?? []);
|
||||
$items[] = [
|
||||
'id' => $taskId,
|
||||
'node_id' => 'task:' . $taskId,
|
||||
'label' => (string)($task['task'] ?? $this->debugLabel($lookups, 'tasks', $taskId, 'Task ' . $taskId)),
|
||||
'description' => (string)($task['description'] ?? ''),
|
||||
'active' => $active,
|
||||
'state' => $active ? 'active' : 'blocked',
|
||||
'gate_type' => $gateType,
|
||||
@@ -1029,6 +1058,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
'services' => $services,
|
||||
'buttons' => $this->normalizeButtonList($task['buttons'] ?? null),
|
||||
'dynamic_images_vehicle_type' => ($task['dynamic_images_vehicle_type'] ?? null) === null ? null : (int)$task['dynamic_images_vehicle_type'],
|
||||
'attachments' => $taskAttachments,
|
||||
'relay_bindings' => $bindings,
|
||||
'order_priority' => (int)($task['order_priority'] ?? 0),
|
||||
'reason' => $active ? 'Task gate passed.' : 'Task gate did not pass.',
|
||||
@@ -2468,6 +2498,57 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
|
||||
return $tasksObject->getLegacyTasksForLaneProduct($departmentId, $laneId, $vehicleTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $task
|
||||
* @param array<int,int> $conditionIds
|
||||
* @return array{gate_type:selfserve_task_gate_type,gate_ref_id:int|null}
|
||||
*/
|
||||
protected function resolveTaskGate(array $task, array $conditionIds): array
|
||||
{
|
||||
$gateType = selfserve_task_gate_type::tryFrom(strtoupper(trim((string)($task['gate_type'] ?? ''))));
|
||||
$gateRefId = $this->nullableInt($task['gate_ref_id'] ?? null);
|
||||
$legacyGateId = $this->nullableInt($task['condition_id'] ?? null);
|
||||
|
||||
if (
|
||||
$gateType === selfserve_task_gate_type::CONDITION
|
||||
|| $gateType === selfserve_task_gate_type::QUESTION
|
||||
) {
|
||||
return [
|
||||
'gate_type' => $gateType,
|
||||
'gate_ref_id' => $gateRefId ?? $legacyGateId,
|
||||
];
|
||||
}
|
||||
|
||||
$shouldInferLegacyGate = $gateType === null
|
||||
|| (
|
||||
$gateType === selfserve_task_gate_type::ALWAYS
|
||||
&& $gateRefId === null
|
||||
&& $legacyGateId !== null
|
||||
);
|
||||
|
||||
if ($shouldInferLegacyGate) {
|
||||
$fallbackGateId = $gateRefId ?? $legacyGateId;
|
||||
if ($fallbackGateId === null) {
|
||||
return [
|
||||
'gate_type' => selfserve_task_gate_type::ALWAYS,
|
||||
'gate_ref_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'gate_type' => in_array($fallbackGateId, $conditionIds, true)
|
||||
? selfserve_task_gate_type::CONDITION
|
||||
: selfserve_task_gate_type::QUESTION,
|
||||
'gate_ref_id' => $fallbackGateId,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'gate_type' => selfserve_task_gate_type::ALWAYS,
|
||||
'gate_ref_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed>|null $publishedConfig
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user