Files
api/services/nginx/app/classes/selfserve_schema_bootstrap.php
T
Jeppe Bundgaard 8bf957b273 Add support for selfserve_enabled lanes and synchronize behavior across tasks, sessions, and projections
- Introduced `selfserve_enabled` property for `department_lanes` with schema update, object properties, and associated methods/tests.
- Enhanced `selfserve_wash_flow` and session logic to respect lane self-serve settings, including block handling and task filtering.
- Updated API routes and Studio Graph projections to include `selfserve_enabled` in payloads and progress callbacks.
- Added unit tests for session statuses, lane configuration, and blocking behavior due to disabled self-serve settings.
2026-04-29 17:28:42 +02:00

221 lines
9.8 KiB
PHP

<?php
namespace classes;
/**
* Ensures additive self-serve tables and columns exist.
*
* This project has no centralized migration runner, so the bootstrap must be
* idempotent and safe to call from runtime flows.
*/
class selfserve_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
$queries = [
"CREATE TABLE IF NOT EXISTS selfserve_config_versions (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
version_number INT NOT NULL,
config_json JSON NOT NULL,
validation_result_json JSON NULL,
source_version_id INT NULL,
created_by INT NULL,
published_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_selfserve_config_versions_department_status (department_id, status),
INDEX idx_selfserve_config_versions_department_version (department_id, version_number)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS selfserve_machine_types (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_selfserve_machine_types_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS selfserve_wash_sessions (
id INT AUTO_INCREMENT PRIMARY KEY,
lane_id INT NOT NULL,
department_id INT NOT NULL,
machine_type_id INT NULL,
customer_number INT NULL,
vehicle_id INT NULL,
vehicle_type_id INT NULL,
reg VARCHAR(255) NOT NULL,
status VARCHAR(64) NOT NULL DEFAULT 'PENDING_QUESTIONS',
allowed TINYINT(1) NOT NULL DEFAULT 0,
machine_relay_enabled TINYINT(1) NOT NULL DEFAULT 0,
machine_relay_enabled_at DATETIME NULL,
machine_start_triggered TINYINT(1) NOT NULL DEFAULT 0,
machine_start_triggered_at DATETIME NULL,
wash_started_at DATETIME NULL,
order_id INT NULL,
completed_at DATETIME NULL,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_selfserve_wash_sessions_lane_reg (lane_id, reg),
INDEX idx_selfserve_wash_sessions_status (status),
INDEX idx_selfserve_wash_sessions_customer (customer_number),
INDEX idx_selfserve_wash_sessions_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS selfserve_wash_session_answers (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
question_id INT NOT NULL,
question_text VARCHAR(255) NOT NULL,
answer_value TINYINT(1) NOT NULL,
answered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_selfserve_wash_session_answer (session_id, question_id),
INDEX idx_selfserve_wash_session_answers_session (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS selfserve_wash_session_tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
task_id INT NULL,
task_text VARCHAR(255) NOT NULL,
description VARCHAR(255) NULL,
services JSON NULL,
buttons JSON NULL,
dynamic_image_id INT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_selfserve_wash_session_tasks_session (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS selfserve_wash_session_events (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_selfserve_wash_session_events_session (session_id),
INDEX idx_selfserve_wash_session_events_type (event_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS department_selfserve_studio_layouts (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
user_id INT NULL,
layout_json JSON NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_department_selfserve_studio_layouts_department_user (department_id, user_id),
INDEX idx_department_selfserve_studio_layouts_department_updated (department_id, updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS department_selfserve_studio_virtual_hardware (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
config_json JSON NOT NULL,
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_selfserve_vhw_department (department_id),
INDEX idx_selfserve_vhw_dept_updated (department_id, updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($queries as $sql) {
$db->query($sql);
}
self::ensureColumn(
'department_lanes',
'machine_type_id',
'ALTER TABLE department_lanes ADD COLUMN machine_type_id INT NULL AFTER dynamic_image_id'
);
self::ensureColumn(
'department_lanes',
'selfserve_enabled',
'ALTER TABLE department_lanes ADD COLUMN selfserve_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER machine_type_id'
);
self::ensureColumn(
'department_selfserve_conditions',
'machine_type_id',
'ALTER TABLE department_selfserve_conditions ADD COLUMN machine_type_id INT NULL AFTER product'
);
self::ensureColumn(
'department_selfserve_tasks',
'machine_type_id',
'ALTER TABLE department_selfserve_tasks ADD COLUMN machine_type_id INT NULL AFTER product'
);
self::ensureColumn(
'department_selfserve_tasks',
'gate_type',
"ALTER TABLE department_selfserve_tasks ADD COLUMN gate_type VARCHAR(16) NULL DEFAULT 'ALWAYS' AFTER condition_id"
);
self::ensureColumn(
'department_selfserve_tasks',
'gate_ref_id',
'ALTER TABLE department_selfserve_tasks ADD COLUMN gate_ref_id INT NULL AFTER gate_type'
);
self::ensureColumn(
'selfserve_wash_session_tasks',
'dynamic_images_vehicle_type',
'ALTER TABLE selfserve_wash_session_tasks ADD COLUMN dynamic_images_vehicle_type INT NULL AFTER buttons'
);
self::ensureColumn(
'selfserve_wash_sessions',
'wash_started_at',
'ALTER TABLE selfserve_wash_sessions ADD COLUMN wash_started_at DATETIME NULL AFTER machine_start_triggered_at'
);
self::$initialized = true;
}
public static function tableHasColumn(string $table, string $column): bool
{
global $db;
$table = $db->escape_string($table);
$column = $db->escape_string($column);
$database = $db->escape_string($db->getDatabase());
$sql = "SELECT COUNT(*) AS c
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$database'
AND TABLE_NAME = '$table'
AND COLUMN_NAME = '$column'";
$result = $db->query($sql);
if (!$result) {
return false;
}
$row = $result->fetch_assoc();
return ((int)($row['c'] ?? 0)) > 0;
}
public static function ensureColumn(string $table, string $column, string $alterSql): void
{
global $db;
if (self::tableHasColumn($table, $column)) {
return;
}
$db->query($alterSql);
}
}