Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jeppe Bundgaard
2026-06-02 10:29:25 +02:00
8 changed files with 553 additions and 11 deletions
@@ -475,18 +475,34 @@ class moduleSelfServeRoute
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);
}
// Get the lane and command
$lane = $selfserve->lane($lane_id);
// Preserve not-found behavior before evaluating elevated/customer alternatives.
if (empty($lane->department_lane) || empty($lane->department_lane->department)) {
$response->error('Lane department not found', 404);
}
$customer_number = $this->resolveEffectiveCustomerNumber();
$customer_number = $customer_number === null ? 0 : (int)$customer_number;
[
$allow_customer_self_serve,
$requires_active_wash,
$allow_department_active_wash
] = $this->customerSelfServeCommandAccessRequirements($command);
$this->requireSelfServeLaneDepartmentOrCustomerAccess(
$lane,
$customer_number,
$allow_customer_self_serve,
$requires_active_wash,
$allow_department_active_wash
);
// 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);
}
// Require permissions for specific commands
switch ($command) {
case selfserve_lane_command::START:
@@ -503,7 +519,8 @@ class moduleSelfServeRoute
$customer_number,
'modules_selfserve_lane_command_execute_stop',
true,
true
true,
false
);
break;
case selfserve_lane_command::RESERVE:
@@ -1557,6 +1574,51 @@ class moduleSelfServeRoute
$lane->setShellyTransportOverride($transport);
}
/**
* @return array{0:bool,1:bool,2:bool} [customer self-serve allowed, active wash required, department active wash fallback allowed]
*/
private function customerSelfServeCommandAccessRequirements(selfserve_lane_command $command): array
{
return match ($command) {
selfserve_lane_command::START => [true, false, false],
selfserve_lane_command::STOP => [true, true, false],
selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE,
selfserve_lane_command::OPEN_PROPERTY_EXIT_GATE => [true, true, true],
selfserve_lane_command::RESERVE,
selfserve_lane_command::RELEASE,
selfserve_lane_command::RESET => [false, false, false],
};
}
private function requireSelfServeLaneDepartmentOrCustomerAccess(
selfserve_lane $lane,
int $customer_number,
bool $allow_customer_self_serve,
bool $requires_active_wash = false,
bool $allow_department_active_wash = false
): void {
$department_id = $this->departmentIdForLane($lane);
if ($department_id > 0 && $this->hasDepartmentAccess((string)$department_id)) {
return;
}
if ($allow_customer_self_serve) {
$customer_allowed = $requires_active_wash
? $this->canCustomerUseActiveOperationalSelfServeLane($lane, $customer_number, $allow_department_active_wash)
: $this->canCustomerUseSelfServeLane($lane, $customer_number);
if ($customer_allowed) {
return;
}
}
$missing_permissions = $department_id > 0 ? ['department_access_' . $department_id] : [];
if ($allow_customer_self_serve) {
$missing_permissions[] = self::CUSTOMER_SELFSERVE_PERMISSION;
}
$this->emitForbidden($missing_permissions);
}
/**
* @param array<int,string> $permissions
*/
@@ -1604,7 +1666,8 @@ class moduleSelfServeRoute
int $customer_number,
string $command_permission,
bool $allow_customer_self_serve,
bool $requires_active_wash = false
bool $requires_active_wash = false,
bool $allow_department_active_wash = false
): void {
$elevated_permissions = [
'modules_selfserve_lane_command_execute',
@@ -1616,7 +1679,7 @@ class moduleSelfServeRoute
if ($allow_customer_self_serve && $this->isSelfServeModuleEnabled()) {
$customer_allowed = $requires_active_wash
? $this->canCustomerUseActiveSelfServeLane($lane, $customer_number)
? $this->canCustomerUseActiveOperationalSelfServeLane($lane, $customer_number, $allow_department_active_wash)
: $this->canCustomerUseSelfServeLane($lane, $customer_number);
if ($customer_allowed) {
@@ -1697,7 +1760,61 @@ class moduleSelfServeRoute
protected function canCustomerUsePropertyGateForLane(selfserve_lane $lane, int $customer_number): bool
{
return $this->canCustomerUseActiveSelfServeLane($lane, $customer_number);
return $this->canCustomerUseActiveOperationalSelfServeLane($lane, $customer_number, true);
}
protected function canCustomerUseActiveOperationalSelfServeLane(
selfserve_lane $lane,
int $customer_number,
bool $allow_department_active_wash = false
): bool {
return $this->isLaneSelfServeOperationallyEnabled($lane)
&& (
$allow_department_active_wash
? $this->canCustomerUseActiveSelfServeLane($lane, $customer_number)
: $this->canCustomerUseActiveSelfServeLaneSession($lane, $customer_number)
);
}
protected function canCustomerUseActiveSelfServeLaneSession(selfserve_lane $lane, int $customer_number): bool
{
if ($customer_number <= 0 || !$this->hasPermission(self::CUSTOMER_SELFSERVE_PERMISSION)) {
return false;
}
try {
if ((int)$lane->getCustomerNumber() === $customer_number) {
return true;
}
} catch (\Throwable) {
// Fall back to the persisted lane session lookup below.
}
$active_statuses = array_map(
static fn(selfserve_wash_session_status $status): string => $status->value,
[
selfserve_wash_session_status::MACHINE_RELAY_ENABLED,
selfserve_wash_session_status::READY_FOR_MACHINE_START,
selfserve_wash_session_status::MACHINE_STARTED,
selfserve_wash_session_status::PENDING_QUESTIONS,
selfserve_wash_session_status::MACHINE_NOT_ALLOWED,
]
);
$sessions = (new selfserve_wash_sessions_o())->getFieldsWhere([
'lane_id' => (int)$lane->id,
'customer_number' => $customer_number,
'completed_at' => null,
'deleted_at' => null,
], ['id', 'status']);
foreach ($sessions as $session) {
if (in_array((string)($session['status'] ?? ''), $active_statuses, true)) {
return true;
}
}
return false;
}
protected function isLaneSelfServeOperationallyEnabled(selfserve_lane $lane): bool