feat: complete jobcard client management foundation

This commit is contained in:
Marco0300
2026-09-01 20:43:24 +02:00
parent 168c15c6ae
commit de2bf277c4
34 changed files with 2072 additions and 74 deletions
+68 -2
View File
@@ -72,6 +72,37 @@ CREATE TABLE IF NOT EXISTS jobcard_sequences (
next_sequence INT UNSIGNED NOT NULL
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS technical_information (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id BIGINT UNSIGNED NOT NULL,
category VARCHAR(80) NOT NULL,
data_json JSON NOT NULL,
updated_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
UNIQUE KEY technical_client_category (client_id, category)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS credentials (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id BIGINT UNSIGNED NOT NULL,
category VARCHAR(80) NOT NULL,
label VARCHAR(120) NOT NULL,
username VARCHAR(190) NULL,
secret_ciphertext TEXT NOT NULL,
notes TEXT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by BIGINT UNSIGNED NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX credentials_client_idx (client_id),
INDEX credentials_category_idx (category)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS sla_agreements (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id BIGINT UNSIGNED NOT NULL,
@@ -153,6 +184,35 @@ CREATE TABLE IF NOT EXISTS time_entries (
INDEX time_date_idx (work_date)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS attachments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
jobcard_id BIGINT UNSIGNED NOT NULL,
original_name VARCHAR(255) NOT NULL,
stored_name VARCHAR(255) NOT NULL UNIQUE,
mime_type VARCHAR(120) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL,
client_visible BOOLEAN NOT NULL DEFAULT FALSE,
uploaded_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE,
FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX attachments_jobcard_idx (jobcard_id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS notifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
type VARCHAR(80) NOT NULL,
title VARCHAR(190) NOT NULL,
body TEXT NULL,
deduplication_key VARCHAR(190) NULL,
read_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY notification_dedup_idx (user_id, deduplication_key),
INDEX notification_unread_idx (user_id, read_at, created_at)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS audit_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NULL,
@@ -181,14 +241,20 @@ INSERT IGNORE INTO permissions (name, description) VALUES
('jobcards.assign', 'Assign technicians to jobcards'),
('jobcards.internal_notes', 'View and edit internal jobcard notes'),
('time_entries.record', 'Record technician time entries'),
('technical.view', 'View client technical information'),
('technical.manage', 'Manage client technical information'),
('credentials.view', 'View protected credentials'),
('credentials.manage', 'Manage protected credentials'),
('attachments.view', 'View jobcard attachments'),
('attachments.manage', 'Manage jobcard attachments'),
('notifications.view', 'View notifications'),
('sla.view', 'View client SLA agreements and usage'),
('sla.manage', 'Configure client SLA agreements'),
('reports.view', 'View reports'),
('reports.export', 'Export reports'),
('users.manage', 'Manage users'),
('roles.manage', 'Manage roles and permissions'),
('audit.view', 'View audit events'),
('credentials.view', 'View protected credentials');
('audit.view', 'View audit events');
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.name = 'Administrator';
+111
View File
@@ -0,0 +1,111 @@
-- JOBcard additive upgrade for installations created before the current schema.
-- Take a database backup first. Run with the target database selected:
-- mysql --default-character-set=utf8mb4 -u USER -p DATABASE < database/upgrade.sql
-- Resolve duplicate SLA rows before adding the unique client constraint.
CREATE TABLE IF NOT EXISTS technical_information (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id BIGINT UNSIGNED NOT NULL,
category VARCHAR(80) NOT NULL,
data_json JSON NOT NULL,
updated_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
UNIQUE KEY technical_client_category (client_id, category)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS credentials (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id BIGINT UNSIGNED NOT NULL,
category VARCHAR(80) NOT NULL,
label VARCHAR(120) NOT NULL,
username VARCHAR(190) NULL,
secret_ciphertext TEXT NOT NULL,
notes TEXT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by BIGINT UNSIGNED NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX credentials_client_idx (client_id),
INDEX credentials_category_idx (category)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS jobcard_sequences (
sequence_year SMALLINT UNSIGNED PRIMARY KEY,
next_sequence INT UNSIGNED NOT NULL
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS jobcard_status_history (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
jobcard_id BIGINT UNSIGNED NOT NULL,
from_status VARCHAR(60) NOT NULL,
to_status VARCHAR(60) NOT NULL,
changed_by BIGINT UNSIGNED NULL,
changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE,
FOREIGN KEY (changed_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX status_history_jobcard_idx (jobcard_id, changed_at)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS attachments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
jobcard_id BIGINT UNSIGNED NOT NULL,
original_name VARCHAR(255) NOT NULL,
stored_name VARCHAR(255) NOT NULL UNIQUE,
mime_type VARCHAR(120) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL,
client_visible BOOLEAN NOT NULL DEFAULT FALSE,
uploaded_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE,
FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX attachments_jobcard_idx (jobcard_id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS notifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
type VARCHAR(80) NOT NULL,
title VARCHAR(190) NOT NULL,
body TEXT NULL,
deduplication_key VARCHAR(190) NULL,
read_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY notification_dedup_idx (user_id, deduplication_key),
INDEX notification_unread_idx (user_id, read_at, created_at)
) ENGINE=InnoDB;
INSERT IGNORE INTO permissions (name, description) VALUES
('technical.view', 'View client technical information'),
('technical.manage', 'Manage client technical information'),
('credentials.view', 'View protected credentials'),
('credentials.manage', 'Manage protected credentials'),
('attachments.view', 'View jobcard attachments'),
('attachments.manage', 'Manage jobcard attachments'),
('notifications.view', 'View notifications');
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.name = 'Administrator';
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM roles r JOIN permissions p ON p.name IN ('technical.view') WHERE r.name = 'Technician';
-- Enforce one SLA agreement per client after duplicate remediation.
SET @sla_unique_exists := (
SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'sla_agreements' AND index_name = 'sla_client_unique'
);
SET @sla_duplicate_count := (
SELECT COUNT(*) FROM (SELECT client_id FROM sla_agreements GROUP BY client_id HAVING COUNT(*) > 1) duplicates_check
);
SET @sla_add_unique_sql := IF(@sla_duplicate_count = 0 AND @sla_unique_exists = 0,
'ALTER TABLE sla_agreements ADD UNIQUE KEY sla_client_unique (client_id)',
IF(@sla_unique_exists > 0, 'SELECT 1', 'SELECT 1 FROM jobcard_upgrade_duplicate_sla_rows'));
PREPARE sla_add_unique_stmt FROM @sla_add_unique_sql;
EXECUTE sla_add_unique_stmt;
DEALLOCATE PREPARE sla_add_unique_stmt;