Files
api/services/nginx/app/modules/selfserve/classes/selfserve_studio_actions.php
T
Jeppe Bundgaard 5875371d13 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.
2026-04-29 16:03:03 +02:00

218 lines
7.8 KiB
PHP

<?php
namespace modules\selfserve\classes;
class selfserve_studio_actions
{
public const EVENT_WASH_START_COMMAND = 'wash_start_command';
public const EVENT_WASH_STOP_COMMAND = 'wash_stop_command';
public const EVENT_MACHINE_START_TRIGGERED = 'machine_start_triggered';
public const MODE_MANUAL = 'manual';
public const MODE_MACHINE = 'machine';
public const MODE_BOTH = 'both';
public const OP_OPEN_PROPERTY_ENTRANCE_GATE = 'open_property_entrance_gate';
public const OP_OPEN_PROPERTY_EXIT_GATE = 'open_property_exit_gate';
public const OP_OPEN_LANE_ENTRANCE_PORT = 'open_lane_entrance_port';
public const OP_OPEN_LANE_EXIT_PORT = 'open_lane_exit_port';
public const OP_SET_CLEANER_RELAY = 'set_cleaner_relay';
public const OP_SET_MACHINE_RELAY = 'set_machine_relay';
public const OP_SET_PROGRAM_PICKER_RELAY = 'set_program_picker_relay';
public const FAILURE_CONTINUE = 'continue';
public const FAILURE_BLOCK = 'block';
/**
* @return array<int,string>
*/
public static function events(): array
{
return [
self::EVENT_WASH_START_COMMAND,
self::EVENT_WASH_STOP_COMMAND,
self::EVENT_MACHINE_START_TRIGGERED,
];
}
/**
* @return array<int,string>
*/
public static function washModes(): array
{
return [
self::MODE_MANUAL,
self::MODE_MACHINE,
self::MODE_BOTH,
];
}
/**
* @return array<int,string>
*/
public static function operations(): array
{
return [
self::OP_OPEN_PROPERTY_ENTRANCE_GATE,
self::OP_OPEN_PROPERTY_EXIT_GATE,
self::OP_OPEN_LANE_ENTRANCE_PORT,
self::OP_OPEN_LANE_EXIT_PORT,
self::OP_SET_CLEANER_RELAY,
self::OP_SET_MACHINE_RELAY,
self::OP_SET_PROGRAM_PICKER_RELAY,
];
}
/**
* @return array<int,string>
*/
public static function failurePolicies(): array
{
return [
self::FAILURE_CONTINUE,
self::FAILURE_BLOCK,
];
}
public static function isRelayOperation(string $operation): bool
{
return in_array($operation, [
self::OP_SET_CLEANER_RELAY,
self::OP_SET_MACHINE_RELAY,
self::OP_SET_PROGRAM_PICKER_RELAY,
], true);
}
public static function isOpenOperation(string $operation): bool
{
return in_array($operation, [
self::OP_OPEN_PROPERTY_ENTRANCE_GATE,
self::OP_OPEN_PROPERTY_EXIT_GATE,
self::OP_OPEN_LANE_ENTRANCE_PORT,
self::OP_OPEN_LANE_EXIT_PORT,
], true);
}
public static function relayRoleForOperation(string $operation): string
{
return match ($operation) {
self::OP_OPEN_PROPERTY_ENTRANCE_GATE => 'PROPERTY_ENTRANCE',
self::OP_OPEN_PROPERTY_EXIT_GATE => 'PROPERTY_EXIT',
self::OP_OPEN_LANE_ENTRANCE_PORT => 'ENTRY',
self::OP_OPEN_LANE_EXIT_PORT => 'EXIT',
self::OP_SET_CLEANER_RELAY => 'CLEANER',
self::OP_SET_MACHINE_RELAY => 'MACHINE',
self::OP_SET_PROGRAM_PICKER_RELAY => 'PROGRAM_PICKER',
default => 'ACTION',
};
}
public static function eventLabel(string $event): string
{
return match ($event) {
self::EVENT_WASH_START_COMMAND => 'When wash starts',
self::EVENT_WASH_STOP_COMMAND => 'When wash stops',
self::EVENT_MACHINE_START_TRIGGERED => 'When the machine start button is triggered',
default => 'On action event',
};
}
public static function operationLabel(string $operation, ?bool $relayState = null): string
{
$state = $relayState === null ? '' : ($relayState ? 'ON ' : 'OFF ');
return match ($operation) {
self::OP_OPEN_PROPERTY_ENTRANCE_GATE => 'Open property entrance gate',
self::OP_OPEN_PROPERTY_EXIT_GATE => 'Open property exit gate',
self::OP_OPEN_LANE_ENTRANCE_PORT => 'Open lane entrance port',
self::OP_OPEN_LANE_EXIT_PORT => 'Open lane exit port',
self::OP_SET_CLEANER_RELAY => 'Turn ' . $state . 'CLEANER',
self::OP_SET_MACHINE_RELAY => 'Turn ' . $state . 'MACHINE',
self::OP_SET_PROGRAM_PICKER_RELAY => 'Turn ' . $state . 'PROGRAM PICKER',
default => 'Action',
};
}
public static function runtimeStageForEvent(string $event): string
{
return match ($event) {
self::EVENT_WASH_START_COMMAND => 'start',
self::EVENT_WASH_STOP_COMMAND => 'stop',
self::EVENT_MACHINE_START_TRIGGERED => 'machine_start',
default => 'action',
};
}
/**
* @param array<string,mixed> $action
* @return array<string,mixed>
*/
public static function normalize(array $action): array
{
$event = strtolower(trim((string)($action['event'] ?? self::EVENT_WASH_START_COMMAND)));
if (!in_array($event, self::events(), true)) {
$event = self::EVENT_WASH_START_COMMAND;
}
$operation = strtolower(trim((string)($action['operation'] ?? self::OP_OPEN_LANE_ENTRANCE_PORT)));
if (!in_array($operation, self::operations(), true)) {
$operation = self::OP_OPEN_LANE_ENTRANCE_PORT;
}
$washMode = strtolower(trim((string)($action['wash_mode'] ?? self::MODE_BOTH)));
if (!in_array($washMode, self::washModes(), true)) {
$washMode = self::MODE_BOTH;
}
$options = is_array($action['options'] ?? null) ? (array)$action['options'] : [];
$failurePolicy = strtolower(trim((string)($options['failure_policy'] ?? self::FAILURE_CONTINUE)));
if (!in_array($failurePolicy, self::failurePolicies(), true)) {
$failurePolicy = self::FAILURE_CONTINUE;
}
$relayState = array_key_exists('relay_state', $action)
? filter_var($action['relay_state'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
: null;
if (self::isRelayOperation($operation) && $relayState === null) {
$relayState = true;
}
$label = trim((string)($action['name'] ?? $action['label'] ?? ''));
if ($label === '') {
$label = self::operationLabel($operation, $relayState);
}
return [
'id' => (int)($action['id'] ?? 0),
'department' => (int)($action['department'] ?? 0),
'lane' => (int)($action['lane'] ?? 0),
'product' => (int)($action['product'] ?? 0),
'machine_type_id' => self::nullableInt($action['machine_type_id'] ?? null),
'condition_id' => self::nullableInt($action['condition_id'] ?? null),
'name' => $label,
'description' => (string)($action['description'] ?? ''),
'event' => $event,
'wash_mode' => $washMode,
'operation' => $operation,
'relay_state' => $relayState,
'enabled' => filter_var($action['enabled'] ?? true, FILTER_VALIDATE_BOOLEAN),
'order_priority' => (int)($action['order_priority'] ?? 0),
'options' => [
'delay_ms' => max(0, (int)($options['delay_ms'] ?? 0)),
'toggle_after_seconds' => self::nullableInt($options['toggle_after_seconds'] ?? null),
'retry_count' => max(0, min(3, (int)($options['retry_count'] ?? 0))),
'failure_policy' => $failurePolicy,
'record_event' => filter_var($options['record_event'] ?? true, FILTER_VALIDATE_BOOLEAN),
],
];
}
private static function nullableInt(mixed $value): ?int
{
if ($value === null || $value === '' || $value === 'null') {
return null;
}
$intValue = (int)$value;
return $intValue <= 0 ? null : $intValue;
}
}