-- This schema supports the phase-1 hybrid proof of concept. -- The database stores centrally managed configuration only: templates, -- lookup values, image policy, export settings, and lightweight app flags. -- Completed reports are intentionally excluded from the server in this phase; -- they remain browser-local artifacts exported by the client. CREATE DATABASE IF NOT EXISTS check_list CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE check_list; -- Template identity stays separate from version rows so a single checklist can -- publish multiple definitions over time while preserving a stable code. CREATE TABLE IF NOT EXISTS templates ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, code VARCHAR(100) NOT NULL, name VARCHAR(200) NOT NULL, description TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_templates_code (code) ); -- Each template version stores a full JSON definition snapshot. That keeps the -- frontend contract simple because the client can render a form from one payload -- without performing additional joins or reconstruction logic. CREATE TABLE IF NOT EXISTS template_versions ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, template_id BIGINT UNSIGNED NOT NULL, version_number INT NOT NULL, status ENUM('draft', 'active', 'retired') NOT NULL DEFAULT 'draft', definition_json JSON NOT NULL, published_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_template_version (template_id, version_number), KEY idx_template_versions_template_status (template_id, status), CONSTRAINT fk_template_versions_template FOREIGN KEY (template_id) REFERENCES templates (id) ON DELETE CASCADE ); -- Lookup sets support dropdown-style fields in a normalized way. The template -- JSON references the set by code, while individual values remain editable as -- data rather than hardcoded frontend options. CREATE TABLE IF NOT EXISTS lookup_sets ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, code VARCHAR(100) NOT NULL, name VARCHAR(200) NOT NULL, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_lookup_sets_code (code) ); CREATE TABLE IF NOT EXISTS lookup_values ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, lookup_set_id BIGINT UNSIGNED NOT NULL, value VARCHAR(100) NOT NULL, label VARCHAR(200) NOT NULL, sort_order INT NOT NULL DEFAULT 0, is_default TINYINT(1) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY uq_lookup_value (lookup_set_id, value), KEY idx_lookup_values_lookup_set (lookup_set_id), CONSTRAINT fk_lookup_values_lookup_set FOREIGN KEY (lookup_set_id) REFERENCES lookup_sets (id) ON DELETE CASCADE ); -- Image rules are server-managed so administrators can tighten or relax client -- behavior, such as size limits or attachment counts, without changing browser -- code. The client reads the active row and enforces it locally. CREATE TABLE IF NOT EXISTS image_rules ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, code VARCHAR(100) NOT NULL, name VARCHAR(200) NOT NULL, allowed_mime_types_json JSON NOT NULL, max_file_size_bytes INT UNSIGNED NOT NULL, max_width_px INT UNSIGNED NOT NULL, max_height_px INT UNSIGNED NOT NULL, jpeg_quality INT UNSIGNED NOT NULL, oversize_behavior ENUM('auto_optimize', 'warn_then_optimize', 'block') NOT NULL DEFAULT 'auto_optimize', max_attachments_per_field INT UNSIGNED NOT NULL DEFAULT 5, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_image_rules_code (code) ); -- Export profiles represent the eventual shape of the generated ZIP/XLSX output. -- The current PoC only reads this information, but separating it now makes later -- export customization easier. CREATE TABLE IF NOT EXISTS export_profiles ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, code VARCHAR(100) NOT NULL, name VARCHAR(200) NOT NULL, zip_image_dir VARCHAR(100) NOT NULL DEFAULT 'images', excel_sheet_name VARCHAR(100) NOT NULL DEFAULT 'Checklist', include_template_version TINYINT(1) NOT NULL DEFAULT 1, include_export_timestamp TINYINT(1) NOT NULL DEFAULT 1, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_export_profiles_code (code) ); -- Submitted reports are stored server-side for centralized review and archival. -- The browser creates reports locally first; this table receives them when the -- operator explicitly submits. The report_uuid links back to the browser-local -- report ID so resubmissions are idempotent via ON DUPLICATE KEY UPDATE. CREATE TABLE IF NOT EXISTS reports ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, report_uuid CHAR(36) NOT NULL, report_number VARCHAR(100) NOT NULL, template_code VARCHAR(100) NOT NULL, template_version INT NOT NULL, status ENUM('draft', 'final', 'in_progress', 'ready_for_export', 'exported', 'archived') NOT NULL DEFAULT 'draft', answers_json JSON NOT NULL, submitted_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_reports_uuid (report_uuid), KEY idx_reports_template (template_code, template_version), KEY idx_reports_status (status) ); -- The audit log captures every administrative mutation so the team can trace -- when configuration changed and what the previous value was. Each row stores -- the entity type, the entity identifier, the action, and JSON snapshots of -- the old and new values. CREATE TABLE IF NOT EXISTS audit_log ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, entity_type VARCHAR(100) NOT NULL, entity_code VARCHAR(200) NOT NULL, action VARCHAR(50) NOT NULL, old_value_json JSON NULL, new_value_json JSON NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_audit_entity (entity_type, entity_code) ); -- Report images are stored as binary BLOBs in the database alongside metadata. -- This keeps image storage self-contained without filesystem dependencies. CREATE TABLE IF NOT EXISTS report_images ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, report_uuid CHAR(36) NOT NULL, record_id VARCHAR(100) NOT NULL, image_index SMALLINT UNSIGNED NOT NULL DEFAULT 0, file_name VARCHAR(500) NOT NULL, file_size INT UNSIGNED NOT NULL DEFAULT 0, mime_type VARCHAR(100) NOT NULL DEFAULT 'image/jpeg', width_px INT UNSIGNED NULL, height_px INT UNSIGNED NULL, exif_json JSON NULL, image_data LONGBLOB NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_report_images_report (report_uuid), KEY idx_report_images_record (report_uuid, record_id), CONSTRAINT fk_report_images_report FOREIGN KEY (report_uuid) REFERENCES reports (report_uuid) ON DELETE CASCADE ); -- ═══════════════════════════════════════════════════════════════════════════════ -- Admin entity tables — store all admin-managed data relationally so it -- persists across Docker restarts and browser sessions. -- ═══════════════════════════════════════════════════════════════════════════════ -- Categories for template settings (e.g. "Electrical", "Mechanical") CREATE TABLE IF NOT EXISTS admin_categories ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_categories_value (value) ); -- Sub-categories are children of categories CREATE TABLE IF NOT EXISTS admin_sub_categories ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, category_id BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_sub_categories_category (category_id), CONSTRAINT fk_sub_categories_category FOREIGN KEY (category_id) REFERENCES admin_categories (id) ON DELETE CASCADE ); -- Severity levels CREATE TABLE IF NOT EXISTS admin_severities ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_severities_value (value) ); -- Status options CREATE TABLE IF NOT EXISTS admin_statuses ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, require_handled_by TINYINT(1) NOT NULL DEFAULT 0, require_comment TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_statuses_value (value) ); -- Handled By options CREATE TABLE IF NOT EXISTS admin_handled_by ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_handled_by_value (value) ); -- Projects for task settings CREATE TABLE IF NOT EXISTS admin_projects ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_projects_value (value) ); -- Processes are children of projects CREATE TABLE IF NOT EXISTS admin_processes ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, value VARCHAR(200) NOT NULL, project_id BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_processes_project (project_id), CONSTRAINT fk_processes_project FOREIGN KEY (project_id) REFERENCES admin_projects (id) ON DELETE CASCADE ); -- Users managed by the admin console CREATE TABLE IF NOT EXISTS admin_users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL DEFAULT '', name VARCHAR(200) NOT NULL, family_name VARCHAR(200) NOT NULL, company VARCHAR(200) NOT NULL DEFAULT '', role VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_users_email (email) ); -- Sites managed by the admin console CREATE TABLE IF NOT EXISTS admin_sites ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, site_code VARCHAR(100) NOT NULL, host VARCHAR(50) NOT NULL DEFAULT '', obe_site_code VARCHAR(100) NOT NULL DEFAULT '', pxs_site_code VARCHAR(100) NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_sites_code (site_code) ); -- Check list records (inspection items) CREATE TABLE IF NOT EXISTS admin_cl_records ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, sort_order INT NOT NULL DEFAULT 0, category VARCHAR(200) NOT NULL DEFAULT '', sub_category VARCHAR(200) NOT NULL DEFAULT '', severity VARCHAR(200) NOT NULL DEFAULT '', image_required TINYINT(1) NOT NULL DEFAULT 0, description_en TEXT NOT NULL, description_fr TEXT NOT NULL, description_nl TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_cl_records_sort (sort_order) ); -- Check list templates CREATE TABLE IF NOT EXISTS admin_cl_templates ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(200) NOT NULL, scope VARCHAR(50) NOT NULL DEFAULT '', version VARCHAR(50) NOT NULL DEFAULT '', valid_from DATE NULL, valid_till DATE NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ); -- Join table: which records belong to which template CREATE TABLE IF NOT EXISTS admin_cl_template_records ( template_id BIGINT UNSIGNED NOT NULL, record_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (template_id, record_id), CONSTRAINT fk_tpl_rec_template FOREIGN KEY (template_id) REFERENCES admin_cl_templates (id) ON DELETE CASCADE, CONSTRAINT fk_tpl_rec_record FOREIGN KEY (record_id) REFERENCES admin_cl_records (id) ON DELETE CASCADE ); -- Task assignments (user + site + template + project/process) CREATE TABLE IF NOT EXISTS admin_tasks ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, site_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, template_id BIGINT UNSIGNED NOT NULL, project VARCHAR(200) NOT NULL DEFAULT '', process VARCHAR(200) NOT NULL DEFAULT '', status VARCHAR(50) NOT NULL DEFAULT 'pending', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_tasks_user (user_id), KEY idx_tasks_site (site_id), CONSTRAINT fk_tasks_site FOREIGN KEY (site_id) REFERENCES admin_sites (id) ON DELETE CASCADE, CONSTRAINT fk_tasks_user FOREIGN KEY (user_id) REFERENCES admin_users (id) ON DELETE CASCADE, CONSTRAINT fk_tasks_template FOREIGN KEY (template_id) REFERENCES admin_cl_templates (id) ON DELETE CASCADE ); -- Admin credentials for application administrator login CREATE TABLE IF NOT EXISTS admin_credentials ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(100) NOT NULL, password VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_admin_credentials_username (username) );