From ce8ba88b16b79e5abba30ebfe07485f8e3856c9d Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Mon, 29 Jun 2026 15:15:29 +0200 Subject: [PATCH] Add machine wash configuration and controls to self-serve module --- .../selfserve/classes/selfserve_wash_flow.php | 41 +++++++++++++++++-- .../selfserve_machine_wash_enabled_c.php | 29 +++++++++++++ .../app/modules/selfserve/selfserve_c.php | 9 ++++ .../nginx/app/routes/moduleSelfServeRoute.php | 17 +++++++- .../Selfserve/SelfserveRouteWiringTest.php | 27 +++++++++++- 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 services/nginx/app/modules/selfserve/config/selfserve_machine_wash_enabled_c.php diff --git a/services/nginx/app/modules/selfserve/classes/selfserve_wash_flow.php b/services/nginx/app/modules/selfserve/classes/selfserve_wash_flow.php index 1228babf..17b8de0a 100644 --- a/services/nginx/app/modules/selfserve/classes/selfserve_wash_flow.php +++ b/services/nginx/app/modules/selfserve/classes/selfserve_wash_flow.php @@ -360,9 +360,13 @@ class selfserve_wash_flow implements selfserve_wash_flow_i $allowedServices = $this->normalizeServiceNames( is_array($metadata['allowed_services'] ?? null) ? (array)$metadata['allowed_services'] : [] ); - $machineAvailable = array_key_exists('machine_available', $metadata) + $machineWashEnabled = $this->isMachineWashEnabled(); + if (!$machineWashEnabled) { + $allowedServices = $this->withoutMachineService($allowedServices); + } + $machineAvailable = $machineWashEnabled && (array_key_exists('machine_available', $metadata) ? (bool)$metadata['machine_available'] - : ($lane->exists() && !empty($lane->relay_machine_id->value())); + : ($lane->exists() && !empty($lane->relay_machine_id->value()))); $allVisibleQuestionsAnswered = array_key_exists('all_visible_questions_answered', $metadata) ? (bool)$metadata['all_visible_questions_answered'] : true; @@ -408,6 +412,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i 'events' => $events, 'allowed_services' => $allowedServices, 'machine_available' => $machineAvailable, + 'machine_wash_enabled' => $machineWashEnabled, 'all_visible_questions_answered' => $allVisibleQuestionsAnswered, 'allowed' => (bool)$session->allowed->value(), 'config_version_id' => $metadata['config_version_id'] ?? null, @@ -687,7 +692,12 @@ class selfserve_wash_flow implements selfserve_wash_flow_i } } - $machineAvailable = !empty($lane->relay_machine_id->value()); + $machineWashEnabled = $this->isMachineWashEnabled(); + if (!$machineWashEnabled) { + $allowedServices = $this->withoutMachineService($allowedServices); + } + + $machineAvailable = $machineWashEnabled && !empty($lane->relay_machine_id->value()); $allVisibleQuestionsAnswered = true; foreach ($visibleQuestions as $question) { if ($question['answer'] === null) { @@ -726,8 +736,10 @@ class selfserve_wash_flow implements selfserve_wash_flow_i 'tasks' => $visibleTasks, 'allowed_services' => $allowedServices, 'machine_available' => $machineAvailable, + 'machine_wash_enabled' => $machineWashEnabled, 'all_visible_questions_answered' => $allVisibleQuestionsAnswered, 'allowed' => $machineAllowed, + 'blocked_reason' => !$machineWashEnabled ? 'Machine wash is disabled globally.' : null, 'config_version_id' => $publishedConfigVersionId === null ? null : (int)$publishedConfigVersionId, 'config_source' => $configSource, 'evaluation_trace' => [ @@ -826,6 +838,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i 'tasks' => $snapshot['tasks'], 'allowed_services' => $snapshot['allowed_services'], 'machine_available' => $snapshot['machine_available'], + 'machine_wash_enabled' => $snapshot['machine_wash_enabled'] ?? true, 'all_visible_questions_answered' => $snapshot['all_visible_questions_answered'], 'allowed' => $snapshot['allowed'], 'blocked_reason' => $snapshot['blocked_reason'] ?? null, @@ -3310,6 +3323,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i return [ 'allowed_services' => $snapshot['allowed_services'], 'machine_available' => (bool)$snapshot['machine_available'], + 'machine_wash_enabled' => (bool)($snapshot['machine_wash_enabled'] ?? true), 'all_visible_questions_answered' => (bool)$snapshot['all_visible_questions_answered'], 'config_version_id' => $snapshot['config_version_id'] ?? null, 'evaluation_trace' => $snapshot['evaluation_trace'] ?? null, @@ -3608,6 +3622,27 @@ class selfserve_wash_flow implements selfserve_wash_flow_i )); } + protected function isMachineWashEnabled(): bool + { + try { + return (new selfserve())->config->machine_wash_enabled->isTrue(); + } catch (\Throwable) { + return true; + } + } + + /** + * @param array $services + * @return array + */ + protected function withoutMachineService(array $services): array + { + return array_values(array_filter( + $this->normalizeServiceNames($services), + static fn(string $service): bool => $service !== selfserve_lane_services::MACHINE->name + )); + } + protected function taskUsesMachineControls(array $task): bool { if (in_array(selfserve_lane_services::MACHINE->name, $this->normalizeServiceNames($this->normalizeJsonArray($task['services'] ?? null)), true)) { diff --git a/services/nginx/app/modules/selfserve/config/selfserve_machine_wash_enabled_c.php b/services/nginx/app/modules/selfserve/config/selfserve_machine_wash_enabled_c.php new file mode 100644 index 00000000..b29c92ee --- /dev/null +++ b/services/nginx/app/modules/selfserve/config/selfserve_machine_wash_enabled_c.php @@ -0,0 +1,29 @@ +setupConfig('selfserve'); $this->allowUpdate([ selfserve_enabled_c::class, + selfserve_machine_wash_enabled_c::class, selfserve_minute_product_c::class, selfserve_machine_wash_minutes_included_c::class, selfserve_dynamic_image_size_c::class ]); $this->enabled = new selfserve_enabled_c(); + $this->machine_wash_enabled = new selfserve_machine_wash_enabled_c(); $this->minute_product = new selfserve_minute_product_c(); $this->machine_wash_minutes_included = new selfserve_machine_wash_minutes_included_c(); $this->dynamic_image_size = new selfserve_dynamic_image_size_c(); diff --git a/services/nginx/app/routes/moduleSelfServeRoute.php b/services/nginx/app/routes/moduleSelfServeRoute.php index e6ebb8e4..9f690b93 100644 --- a/services/nginx/app/routes/moduleSelfServeRoute.php +++ b/services/nginx/app/routes/moduleSelfServeRoute.php @@ -716,6 +716,12 @@ class moduleSelfServeRoute // Merge services (if any) $merge_services((array)$t->services->value()); } + if (!$this->isMachineWashEnabled()) { + $allowed_services = array_values(array_filter( + $allowed_services, + static fn(string $service): bool => $service !== selfserve_lane_services::MACHINE->name + )); + } // Persist on lane cache (overwrites previous allowed services) try { $relay_sync = $lane->setAllowedServicesFromVisibleTasks($allowed_services); @@ -1807,12 +1813,21 @@ class moduleSelfServeRoute protected function isSelfServeModuleEnabled(): bool { try { - return (bool)(new selfserve())->config->enabled->getVariableValue(); + return (new selfserve())->config->enabled->isTrue(); } catch (\Throwable) { return false; } } + protected function isMachineWashEnabled(): bool + { + try { + return (new selfserve())->config->machine_wash_enabled->isTrue(); + } catch (\Throwable) { + return true; + } + } + protected function canCustomerUseSelfServeLane(selfserve_lane $lane, int $customer_number): bool { return $customer_number > 0 diff --git a/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php b/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php index 534a9011..f2407a6d 100644 --- a/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php @@ -167,12 +167,19 @@ it('wires the all-in-one self-serve studio replacement endpoints', function (): expect($studioGraph)->toContain('resolved'); }); -it('wires machine wash included minutes into self-serve module config', function (): void { +it('wires machine wash controls into self-serve module config', function (): void { $selfserveConfig = file_get_contents(app_path('modules/selfserve/selfserve_c.php')); + $machineWashEnabledConfig = file_get_contents(app_path('modules/selfserve/config/selfserve_machine_wash_enabled_c.php')); expect($selfserveConfig)->not->toBeFalse(); expect($selfserveConfig)->toContain('selfserve_machine_wash_minutes_included_c'); expect($selfserveConfig)->toContain('machine_wash_minutes_included'); + expect($selfserveConfig)->toContain('selfserve_machine_wash_enabled_c'); + expect($selfserveConfig)->toContain('machine_wash_enabled'); + expect($machineWashEnabledConfig)->not->toBeFalse() + ->and($machineWashEnabledConfig)->toContain("'machine_wash_enabled'") + ->and($machineWashEnabledConfig)->toContain("'bool'") + ->and($machineWashEnabledConfig)->toContain('true'); }); it('keeps legacy self-serve CRUD routes syncing canonical drafts', function (): void { @@ -409,6 +416,24 @@ it('filters machine button tasks out of self-serve snapshots when MACHINE is not ->and($washFlow)->toContain("\$task['dynamic_images_vehicle_type'] ?? null"); }); +it('applies the global machine wash toggle before exposing machine tasks or services', function (): void { + $washFlow = file_get_contents(app_path('modules/selfserve/classes/selfserve_wash_flow.php')); + $moduleSelfServeRoute = file_get_contents(app_path('routes/moduleSelfServeRoute.php')); + + expect($washFlow)->not->toBeFalse() + ->and($washFlow)->toContain('$machineWashEnabled = $this->isMachineWashEnabled();') + ->and($washFlow)->toContain('$allowedServices = $this->withoutMachineService($allowedServices);') + ->and($washFlow)->toContain('$machineAvailable = $machineWashEnabled && !empty($lane->relay_machine_id->value());') + ->and($washFlow)->toContain("'machine_wash_enabled' => \$machineWashEnabled") + ->and($washFlow)->toContain('Machine wash is disabled globally.') + ->and($washFlow)->toContain('->config->machine_wash_enabled->isTrue()'); + + expect($moduleSelfServeRoute)->not->toBeFalse() + ->and($moduleSelfServeRoute)->toContain('if (!$this->isMachineWashEnabled())') + ->and($moduleSelfServeRoute)->toContain('selfserve_lane_services::MACHINE->name') + ->and($moduleSelfServeRoute)->toContain('->config->machine_wash_enabled->isTrue()'); +}); + it('keeps self-serve force stop distinct from normal STOP relay and gate behavior', function (): void { $washFlow = file_get_contents(app_path('modules/selfserve/classes/selfserve_wash_flow.php')); $sessionObject = file_get_contents(app_path('objects/selfserve_wash_sessions_o.php'));