Self Serve > Lane > Status */ $this->get('/modules/self-serve/lane/status', function () { global $response; self::requirePermission('modules_selfserve_lane_status_view'); $selfserve = new selfserve(); $lane_id = self::isParametersSet(['lane_id']) ? (int)self::getParameter('lane_id') : 1; $lane = $selfserve->lane($lane_id); $response->success([ 'id' => $lane->id, 'status' => $lane->getLaneStatus()->name, 'mode' => $lane->getLaneMode()->name, 'state' => $lane->getLaneState()->name, 'wash_start_time' => $lane->getWashStartTime(), 'elapsed_wash_time' => $lane->getElapsedWashTime(), 'license_plate' => $lane->getLicensePlate(), 'customer_number' => $lane->getCustomerNumber(), ]); }, [ 'modules_selfserve_lane_status_view' => 'View self-serve lane status', ] ); /** 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); $rows = (new selfserve_wash_sessions_o())->getFieldsWhere([ 'lane_id' => $lane_id, 'completed_at' => null, 'deleted_at' => null, ], ['id']); if ($rows === []) { $response->success([ 'lane_id' => $lane_id, 'in_progress' => false, 'session' => null, 'customer' => null, 'vehicle' => null, ]); return; } usort($rows, static fn(array $a, array $b): int => (int)$b['id'] <=> (int)$a['id']); $session = (new selfserve_wash_sessions_o())->select((int)$rows[0]['id']); if (!$session->exists()) { $response->success([ 'lane_id' => $lane_id, 'in_progress' => false, 'session' => null, 'customer' => null, 'vehicle' => null, ]); return; } $customer = null; $customer_number = $session->customer_number->value() === null ? null : (int)$session->customer_number->value(); if ($customer_number !== null && $customer_number > 0) { $customer_obj = (new users_o())->getUserByCustomerNumber($customer_number); if ($customer_obj->exists()) { $customer = [ 'id' => (int)$customer_obj->id, 'customer_number' => $customer_number, 'display_name' => $customer_obj->display_name->value() === null ? null : (string)$customer_obj->display_name->value(), 'email' => $customer_obj->email->value() === null ? null : (string)$customer_obj->email->value(), 'phone_country_code' => $customer_obj->phone_country_code->value() === null ? null : (int)$customer_obj->phone_country_code->value(), 'phone' => $customer_obj->phone->value() === null ? null : (string)$customer_obj->phone->value(), ]; } else { $customer = [ 'id' => null, 'customer_number' => $customer_number, 'display_name' => null, 'email' => null, 'phone_country_code' => null, 'phone' => null, ]; } } $vehicle = null; $vehicle_obj = null; $vehicle_id = $session->vehicle_id->value() === null ? null : (int)$session->vehicle_id->value(); if ($vehicle_id !== null && $vehicle_id > 0) { $tmp_vehicle = (new customer_vehicles_o())->select($vehicle_id); if ($tmp_vehicle->exists()) { $vehicle_obj = $tmp_vehicle; } } if ($vehicle_obj === null) { $session_reg = trim((string)$session->reg->value()); if ($session_reg !== '') { $tmp_vehicle = (new customer_vehicles_o())->selectByPlate($session_reg); if ($tmp_vehicle->exists()) { $vehicle_obj = $tmp_vehicle; } } } if ($vehicle_obj !== null) { $vehicle = [ 'id' => (int)$vehicle_obj->id, 'customer_id' => (int)$vehicle_obj->customer_id->value(), 'type' => (int)$vehicle_obj->type->value(), 'reg' => (string)$vehicle_obj->reg->value(), 'reference' => $vehicle_obj->reference->value() === null ? null : (string)$vehicle_obj->reference->value(), ]; } $response->success([ 'lane_id' => $lane_id, 'in_progress' => true, 'session' => [ 'id' => (int)$session->id, 'status' => (string)$session->status->value(), 'reg' => (string)$session->reg->value(), 'customer_number' => $customer_number, 'vehicle_id' => $vehicle_id, 'vehicle_type_id' => $session->vehicle_type_id->value() === null ? null : (int)$session->vehicle_type_id->value(), 'machine_type_id' => $session->machine_type_id->value() === null ? null : (int)$session->machine_type_id->value(), 'machine_start_triggered' => (bool)$session->machine_start_triggered->value(), 'machine_start_triggered_at' => $session->machine_start_triggered_at->value() === null ? null : (string)$session->machine_start_triggered_at->value(), 'created_at' => (string)$session->created_at->value(), 'updated_at' => $session->updated_at->value() === null ? null : (string)$session->updated_at->value(), ], 'customer' => $customer, 'vehicle' => $vehicle, ]); }, [ 'modules_selfserve_lane_wash_in_progress_view' => 'View customer and vehicle details for an in-progress self-serve wash on a lane', ] ); /** Modules > Self Serve > Lane > Command */ $this->post('/modules/self-serve/lane/command', function () { global $response; self::requirePermission('modules_selfserve_lane_command_execute'); $selfserve = new selfserve(); // Get the request user $user = (new authentication())->get_user(); $param_lane_id = 'lane_id'; $param_command = 'command'; // Validate parameters self::requireParameters([$param_lane_id, $param_command]); $lane_id = (int)$this->getParameter($param_lane_id); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $commandParam = (string)$this->getParameter($param_command); self::requireType($commandParam, self::type_string()); // Get the lane and command $lane = $selfserve->lane($lane_id); // If the user has the bypass permission, set the lane to bypass customer number validation if (self::hasPermission('modules_selfserve_lane_command_bypass_customer_number_validation')) { $lane->setBypassCustomerNumberValidation(true); } $command = selfserve_lane_command::tryFrom($commandParam); if ($command === null) { $response->error("Invalid command: " . $commandParam); } // Require permissions for specific commands switch ($command) { case selfserve_lane_command::START: self::requirePermission('modules_selfserve_lane_command_execute_start'); break; case selfserve_lane_command::STOP: self::requirePermission('modules_selfserve_lane_command_execute_stop'); break; case selfserve_lane_command::RESERVE: self::requirePermission('modules_selfserve_lane_command_execute_reserve'); break; case selfserve_lane_command::RELEASE: self::requirePermission('modules_selfserve_lane_command_execute_release'); break; case selfserve_lane_command::RESET: self::requirePermission('modules_selfserve_lane_command_execute_reset'); break; } // Execute the command try { $args = new \modules\selfserve\classes\selfserve_lane_command_arguments(); $args->setParameters([ ...$this->getParametersAsArray(), // Pass all parameters 'customer_number' => (int)$user->customer_number->value(), // Get customer number from request user ]); $lane->execute($command, $args); $response->success([ 'id' => $lane->id, 'status' => $lane->getLaneStatus()->name, 'mode' => $lane->getLaneMode()->name, 'state' => $lane->getLaneState()->name, 'wash_start_time' => $lane->getWashStartTime(), 'elapsed_wash_time' => $lane->getElapsedWashTime(), 'license_plate' => $lane->getLicensePlate(), 'customer_number' => $lane->getCustomerNumber(), ]); } catch (\Exception $e) { $response->error("Failed to execute command: " . $e->getMessage()); } }, [ 'modules_selfserve_lane_command_execute' => 'Execute self-serve lane command. This is required together with specific command permissions below.', 'modules_selfserve_lane_command_execute_start' => 'Execute self-serve lane START command', 'modules_selfserve_lane_command_execute_stop' => 'Execute self-serve lane STOP command', 'modules_selfserve_lane_command_execute_reserve' => 'Execute self-serve lane RESERVE command', 'modules_selfserve_lane_command_execute_release' => 'Execute self-serve lane RELEASE command (Release the lane reservation and reset its state)', 'modules_selfserve_lane_command_execute_reset' => 'Execute self-serve lane RESET command', 'modules_selfserve_lane_command_bypass_customer_number_validation' => 'Bypass customer number validation when executing commands', ] ); /** Modules > Self Serve > Lane > Allowed services (derived from shown tasks) */ $this->post('/modules/self-serve/lane/services/allowed', function () { global $response; self::requirePermission('modules_selfserve_lane_services_set_allowed'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); // Parse task_ids from request (optional, may be empty to clear) $task_ids_param = $this->isParametersSet(['task_ids']) ? $this->getParameter('task_ids') : []; if (is_string($task_ids_param)) { $decoded = json_decode($task_ids_param, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $task_ids_param = $decoded; } else { $task_ids_param = array_filter(array_map(fn($s) => (int)trim($s), explode(',', $task_ids_param)), fn($v) => $v > 0); } } if (!is_array($task_ids_param)) { $response->error('Invalid format for task_ids. Expected array, JSON array, or comma-separated string of IDs.', 400); } $task_ids = array_values(array_unique(array_map(fn($v) => (int)$v, $task_ids_param))); // Build allowed services from provided tasks $lane = $selfserve->lane($lane_id); $allowed_services = []; foreach ($task_ids as $tid) { if ($tid <= 0) continue; $t = new \objects\department_selfserve_tasks_o(); $t->select($tid); if (!$t->exists()) continue; // ignore unknown ids // Validate the task belongs to the same lane if ((int)$t->lane->value() !== $lane_id) continue; // Merge services (if any) $services = (array)$t->services->value(); foreach ($services as $srv) { $name = strtoupper((string)$srv); if (!in_array($name, $allowed_services, true)) { $allowed_services[] = $name; } } } // Persist on lane cache (overwrites previous allowed services) $lane->setLaneCache($lane_id, $lane::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES, $allowed_services); $response->success(['lane_id' => $lane_id, 'allowed_services' => $allowed_services]); }, [ 'modules_selfserve_lane_services_set_allowed' => 'Set allowed services for a lane based on currently shown tasks (post-Q&A)' ]); /** Modules > Self Serve > Lane > Gate > Open (ENTRANCE/EXIT) */ $this->post('/modules/self-serve/lane/gate/open', function () { global $response; self::requirePermission('modules_selfserve_lane_gate_open'); $selfserve = new selfserve(); self::requireParameters(['lane_id', 'gate']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $gate_name = strtoupper(trim((string)$this->getParameter('gate'))); $gate = match ($gate_name) { 'ENTRANCE' => selfserve_lane_port::ENTRANCE, 'EXIT' => selfserve_lane_port::EXIT, default => null, }; if ($gate === null) { $response->error('Invalid gate value. Expected ENTRANCE or EXIT.', 400); } $lane = $selfserve->lane($lane_id); try { $lane->open($gate); $response->success([ 'lane_id' => $lane_id, 'gate' => $gate->name, 'opened' => true, 'state' => $lane->getLaneState()->name, ]); } catch (\Exception $e) { $response->error('Failed to open lane gate: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_gate_open' => 'Open ENTRANCE or EXIT gate for a self-serve lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE_PROGRAM_PICKER status */ $this->get('/modules/self-serve/lane/relay/machine_program_picker/status', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_program_picker_status_view'); $selfserve = new selfserve(); self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $lane = $selfserve->lane($lane_id); try { $status = $lane->getMachineProgramPickerRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE_PROGRAM_PICKER', 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to get MACHINE_PROGRAM_PICKER relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_program_picker_status_view' => 'Get MACHINE_PROGRAM_PICKER relay status for a lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE_PROGRAM_PICKER set on/off */ $this->post('/modules/self-serve/lane/relay/machine_program_picker/set', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_program_picker_status_set'); $selfserve = new selfserve(); self::requireParameters(['lane_id', 'on']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $raw_on = $this->getParameter('on'); $on = is_bool($raw_on) ? $raw_on : filter_var($raw_on, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($on === null) { $response->error('Invalid on value. Expected boolean true/false.', 400); } $lane = $selfserve->lane($lane_id); try { $lane->setMachineProgramPickerRelayStatus((bool)$on); $status = $lane->getMachineProgramPickerRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE_PROGRAM_PICKER', 'requested_on' => (bool)$on, 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to set MACHINE_PROGRAM_PICKER relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_program_picker_status_set' => 'Set MACHINE_PROGRAM_PICKER relay status (on/off) for a lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE_CLEANER status */ $this->get('/modules/self-serve/lane/relay/machine_cleaner/status', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_cleaner_status_view'); $selfserve = new selfserve(); self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $lane = $selfserve->lane($lane_id); try { $status = $lane->getMachineCleanerRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE_CLEANER', 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to get MACHINE_CLEANER relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_cleaner_status_view' => 'Get MACHINE_CLEANER relay status for a lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE_CLEANER set on/off */ $this->post('/modules/self-serve/lane/relay/machine_cleaner/set', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_cleaner_status_set'); $selfserve = new selfserve(); self::requireParameters(['lane_id', 'on']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $raw_on = $this->getParameter('on'); $on = is_bool($raw_on) ? $raw_on : filter_var($raw_on, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($on === null) { $response->error('Invalid on value. Expected boolean true/false.', 400); } $lane = $selfserve->lane($lane_id); try { $lane->setMachineCleanerRelayStatus((bool)$on); $status = $lane->getMachineCleanerRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE_CLEANER', 'requested_on' => (bool)$on, 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to set MACHINE_CLEANER relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_cleaner_status_set' => 'Set MACHINE_CLEANER relay status (on/off) for a lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE status */ $this->get('/modules/self-serve/lane/relay/machine/status', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_status_view'); $selfserve = new selfserve(); self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $lane = $selfserve->lane($lane_id); try { $status = $lane->getMachineRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE', 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to get MACHINE relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_status_view' => 'Get MACHINE relay status for a lane' ]); /** Modules > Self Serve > Lane > Relay > MACHINE set on/off */ $this->post('/modules/self-serve/lane/relay/machine/set', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_machine_status_set'); $selfserve = new selfserve(); self::requireParameters(['lane_id', 'on']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $raw_on = $this->getParameter('on'); $on = is_bool($raw_on) ? $raw_on : filter_var($raw_on, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($on === null) { $response->error('Invalid on value. Expected boolean true/false.', 400); } $lane = $selfserve->lane($lane_id); try { $lane->setMachineRelayStatus((bool)$on); // Keep lane cache state aligned with the latest explicit relay action try { $lane->setLaneState((bool)$on ? \modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_ON : \modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_OFF); } catch (\Throwable $ignored) {} $status = $lane->getMachineRelayStatus(); $response->success([ 'lane_id' => $lane_id, 'relay' => 'MACHINE', 'requested_on' => (bool)$on, 'relay_id' => (string)$status['relay_id'], 'online' => (bool)$status['online'], 'on' => (bool)$status['on'], ]); } catch (\Exception $e) { $response->error('Failed to set MACHINE relay status: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_machine_status_set' => 'Set MACHINE relay status (on/off) for a lane' ]); /** Modules > Self Serve > Lane > Relay > Enable MACHINE_PROGRAM_PICKER (manual) */ $this->post('/modules/self-serve/lane/relay/machine_program_picker/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_enable_machine_program_picker'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $lane = $selfserve->lane($lane_id); try { $lane->turnOnRelay(selfserve_lane_relay::MACHINE_PROGRAM_PICKER); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_PROGRAM_PICKER', 'enabled' => true, 'duration' => $duration]); } catch (\Exception $e) { $response->error('Failed to enable MACHINE_PROGRAM_PICKER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_enable_machine_program_picker' => 'Manually enable MACHINE_PROGRAM_PICKER relay for a lane' ]); /** Modules > Self Serve > Lane > Relay > Enable MACHINE_CLEANER (manual) */ $this->post('/modules/self-serve/lane/relay/machine_cleaner/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_enable_machine_cleaner'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $lane = $selfserve->lane($lane_id); try { $lane->turnOnRelay(selfserve_lane_relay::MACHINE_CLEANER); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_CLEANER', 'enabled' => true, 'duration' => $duration]); } catch (\Exception $e) { $response->error('Failed to enable MACHINE_CLEANER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_relay_enable_machine_cleaner' => 'Manually enable MACHINE_CLEANER relay for a lane' ]); /** Modules > Self Serve > Lane > Relay > Enable MACHINE (manual, gated by allowed services) */ $this->post('/modules/self-serve/lane/relay/machine/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_relay_enable_machine'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $lane = $selfserve->lane($lane_id); try { $lane->turnOnRelay(selfserve_lane_relay::MACHINE, $duration); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE', 'enabled' => true, 'duration' => $duration]); } catch (\Exception $e) { $msg = $e->getMessage(); // If gating prevented activation, respond with 403 if (str_contains(strtoupper($msg), 'NOT ALLOWED')) { $response->error($msg, 403); } $response->error('Failed to enable MACHINE relay: ' . $msg, 400); } }, [ 'modules_selfserve_lane_relay_enable_machine' => 'Manually enable MACHINE relay for a lane (requires allowed task to be present)' ]); /** Modules > Self Serve > Lane > Force > MACHINE_PROGRAM_PICKER enable */ $this->post('/modules/self-serve/lane/force/machine_program_picker/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_program_picker_enable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $lane = $selfserve->lane($lane_id); try { $lane->forceTurnOnRelay(selfserve_lane_relay::MACHINE_PROGRAM_PICKER, $duration); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_PROGRAM_PICKER', 'enabled' => true, 'duration' => $duration]); } catch (\Exception $e) { $response->error('Failed to force enable MACHINE_PROGRAM_PICKER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_program_picker_enable' => 'Force enable MACHINE_PROGRAM_PICKER relay for a lane' ]); /** Modules > Self Serve > Lane > Force > MACHINE_PROGRAM_PICKER disable */ $this->post('/modules/self-serve/lane/force/machine_program_picker/disable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_program_picker_disable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $lane = $selfserve->lane($lane_id); try { $lane->forceTurnOffRelay(selfserve_lane_relay::MACHINE_PROGRAM_PICKER); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_PROGRAM_PICKER', 'disabled' => true]); } catch (\Exception $e) { $response->error('Failed to force disable MACHINE_PROGRAM_PICKER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_program_picker_disable' => 'Force disable MACHINE_PROGRAM_PICKER relay for a lane' ]); /** Modules > Self Serve > Lane > Force > MACHINE_CLEANER enable */ $this->post('/modules/self-serve/lane/force/machine_cleaner/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_cleaner_enable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $lane = $selfserve->lane($lane_id); try { $lane->forceTurnOnRelay(selfserve_lane_relay::MACHINE_CLEANER, $duration); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_CLEANER', 'enabled' => true, 'duration' => $duration]); } catch (\Exception $e) { $response->error('Failed to force enable MACHINE_CLEANER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_cleaner_enable' => 'Force enable MACHINE_CLEANER relay for a lane' ]); /** Modules > Self Serve > Lane > Force > MACHINE_CLEANER disable */ $this->post('/modules/self-serve/lane/force/machine_cleaner/disable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_cleaner_disable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $lane = $selfserve->lane($lane_id); try { $lane->forceTurnOffRelay(selfserve_lane_relay::MACHINE_CLEANER); $response->success(['lane_id' => $lane_id, 'relay' => 'MACHINE_CLEANER', 'disabled' => true]); } catch (\Exception $e) { $response->error('Failed to force disable MACHINE_CLEANER relay: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_cleaner_disable' => 'Force disable MACHINE_CLEANER relay for a lane' ]); /** Modules > Self Serve > Lane > Force > MACHINE enable (simulate started wash) */ $this->post('/modules/self-serve/lane/force/machine/enable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_enable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $duration = null; if ($this->isParametersSet(['duration'])) { $duration = (int)$this->getParameter('duration'); self::requireMinValue($duration, 1); } $license_plate = $this->isParametersSet(['license_plate']) ? (string)$this->getParameter('license_plate') : null; $lane = $selfserve->lane($lane_id); try { // If lane looks idle/available, mark as occupied and in wash, set timers if ($lane->getLaneStatus()->equals(\modules\selfserve\helpers\selfserve_lane_status::AVAILABLE)) { $lane->setLaneStatus(\modules\selfserve\helpers\selfserve_lane_status::OCCUPIED); } if (!$lane->getLaneState()->equals(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH)) { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH); } if ($license_plate !== null && $license_plate !== '') { $lane->setLicensePlate($license_plate); } if ((int)$lane->getWashStartTime() <= 0) { $lane->setWashStartTime(time()); } // Force enable the machine relay (bypass gating) $lane->forceTurnOnMachineRelay($duration); // Reflect relay state explicitly try { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_ON); } catch (\Throwable $ignored) {} $response->success([ 'lane_id' => $lane_id, 'forced' => true, 'machine' => 'ENABLED', 'duration' => $duration, 'status' => $lane->getLaneStatus()->name, 'state' => $lane->getLaneState()->name, 'wash_start_time' => $lane->getWashStartTime(), ]); } catch (\Exception $e) { $response->error('Failed to force-enable MACHINE: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_enable' => 'Force enable MACHINE relay and mark lane as in-wash (superusers only)' ]); /** Modules > Self Serve > Lane > Force > MACHINE disable (simulate started wash without machine) */ $this->post('/modules/self-serve/lane/force/machine/disable', function () { global $response; self::requirePermission('modules_selfserve_lane_force_machine_disable'); $selfserve = new selfserve(); // Validate parameters self::requireParameters(['lane_id']); $lane_id = (int)$this->getParameter('lane_id'); self::requireType($lane_id, self::type_int()); self::requireMinValue($lane_id, 1); $license_plate = $this->isParametersSet(['license_plate']) ? (string)$this->getParameter('license_plate') : null; $lane = $selfserve->lane($lane_id); try { // Ensure lane reflects an ongoing wash, but with machine off if ($lane->getLaneStatus()->equals(\modules\selfserve\helpers\selfserve_lane_status::AVAILABLE)) { $lane->setLaneStatus(\modules\selfserve\helpers\selfserve_lane_status::OCCUPIED); } if (!$lane->getLaneState()->equals(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH)) { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH); } if ($license_plate !== null && $license_plate !== '') { $lane->setLicensePlate($license_plate); } if ((int)$lane->getWashStartTime() <= 0) { $lane->setWashStartTime(time()); } // Turn off the machine relay (do not swallow errors) $lane->forceTurnOffMachineRelay(); // Reflect relay state explicitly try { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_OFF); } catch (\Throwable $ignored) {} $response->success([ 'lane_id' => $lane_id, 'forced' => true, 'machine' => 'DISABLED', 'status' => $lane->getLaneStatus()->name, 'state' => $lane->getLaneState()->name, 'wash_start_time' => $lane->getWashStartTime(), ]); } catch (\Exception $e) { $response->error('Failed to force-disable MACHINE: ' . $e->getMessage(), 400); } }, [ 'modules_selfserve_lane_force_machine_disable' => 'Force disable MACHINE relay but keep lane as in-wash (superusers only)' ]); } }