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:
Jeppe Bundgaard
2026-04-29 16:03:03 +02:00
parent 93cac644a1
commit 5875371d13
22 changed files with 3058 additions and 65 deletions
@@ -63,11 +63,11 @@ class moduleSelfServeRoute
/** Modules > Self Serve > Lane > Wash > In-progress details */
$this->get('/modules/self-serve/lane/wash/in-progress', function () {
global $response;
self::requirePermission('modules_selfserve_lane_wash_in_progress_view');
self::requireParameters(['lane_id']);
$lane_id = (int)$this->getParameter('lane_id');
self::requireType($lane_id, self::type_int());
self::requireMinValue($lane_id, 1);
$customer_scope = $this->requireInProgressWashDetailsAccess();
$build_customer = static function (?int $customer_number): ?array {
if ($customer_number === null || $customer_number <= 0) {
@@ -170,13 +170,13 @@ class moduleSelfServeRoute
|| $lane_state->equals(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH);
if (!$in_progress) {
$response->success([
$response->success($this->scopeInProgressWashResponseForCustomer([
'lane_id' => $lane_id,
'in_progress' => false,
'session' => null,
'customer' => null,
'vehicle' => null,
]);
], $customer_scope));
return;
}
@@ -191,7 +191,7 @@ class moduleSelfServeRoute
$customer = $build_customer($runtime_customer_number);
$vehicle = $build_vehicle(null, $runtime_reg);
$response->success([
$response->success($this->scopeInProgressWashResponseForCustomer([
'lane_id' => $lane_id,
'in_progress' => true,
'session' => [
@@ -213,7 +213,7 @@ class moduleSelfServeRoute
],
'customer' => $customer,
'vehicle' => $vehicle,
]);
], $customer_scope));
return;
}
@@ -245,7 +245,7 @@ class moduleSelfServeRoute
];
$in_progress = in_array($status, $in_progress_statusses);
$response->success([
$response->success($this->scopeInProgressWashResponseForCustomer([
'lane_id' => $lane_id,
'status' => (string)$session->status->value(),
'in_progress' => $in_progress,
@@ -269,10 +269,11 @@ class moduleSelfServeRoute
],
'customer' => $customer,
'vehicle' => $vehicle,
]);
], $customer_scope));
},
[
'modules_selfserve_lane_wash_in_progress_view' => 'View customer and vehicle details for an in-progress self-serve wash on a lane',
'list_own_department_selfserve_vehicle_conditions' => 'View in-progress self-serve wash details for the authenticated customer',
]
);
@@ -1142,6 +1143,83 @@ class moduleSelfServeRoute
]);
}
private function requireInProgressWashDetailsAccess(): ?int
{
global $response;
$user = (new authentication())->get_user();
if (!$user) {
$response->error('Authentication failed. Invalid or missing token.', 401);
}
if (self::hasPermission('modules_selfserve_lane_wash_in_progress_view')) {
return null;
}
if (self::hasPermission('list_own_department_selfserve_vehicle_conditions')) {
$customer_number = $this->resolveEffectiveCustomerNumber();
if ($customer_number !== null && $customer_number > 0) {
return (int)$customer_number;
}
}
$this->emitForbidden([
'modules_selfserve_lane_wash_in_progress_view',
'list_own_department_selfserve_vehicle_conditions',
]);
return null;
}
/**
* Customers poll all visible lanes to restore their own active wash. Keep that
* poll successful without exposing another customer's session details.
*
* @param array<string,mixed> $payload
* @return array<string,mixed>
*/
protected function scopeInProgressWashResponseForCustomer(array $payload, ?int $customer_number): array
{
if ($customer_number === null || $customer_number <= 0 || ($payload['in_progress'] ?? false) !== true) {
return $payload;
}
$session_customer_number = $this->extractInProgressWashCustomerNumber($payload);
if ($session_customer_number === $customer_number) {
return $payload;
}
return [
'lane_id' => (int)($payload['lane_id'] ?? 0),
'in_progress' => true,
'session' => null,
'customer' => null,
'vehicle' => null,
];
}
/**
* @param array<string,mixed> $payload
*/
private function extractInProgressWashCustomerNumber(array $payload): ?int
{
$candidates = [
$payload['session']['customer_number'] ?? null,
$payload['customer']['customer_number'] ?? null,
];
foreach ($candidates as $candidate) {
if ($candidate === null || $candidate === '') {
continue;
}
$customer_number = (int)$candidate;
if ($customer_number > 0) {
return $customer_number;
}
}
return null;
}
/**
* @param array<string,mixed> $status
* @param array<string,mixed> $extra