-- Versioned Smart Package workspace. The JSON preserves Lego block instances, -- ordering, geometry formulas and manual overrides; flattened project rows remain -- the authority used by Final Review, PDF and exports. ALTER TABLE project_materials ADD COLUMN IF NOT EXISTS material_id INT NULL AFTER project_id, ADD COLUMN IF NOT EXISTS package_instance_id VARCHAR(100) NULL AFTER project_id, ADD COLUMN IF NOT EXISTS source_package_id INT NULL AFTER package_instance_id, ADD COLUMN IF NOT EXISTS price_source VARCHAR(120) NULL AFTER material_source, ADD COLUMN IF NOT EXISTS price_source_updated_at DATETIME NULL AFTER price_source, MODIFY COLUMN material_source ENUM('manual','database','bygma_api','package') DEFAULT 'manual'; CREATE INDEX IF NOT EXISTS idx_project_materials_material_id ON project_materials (material_id); CREATE INDEX IF NOT EXISTS idx_project_materials_package_instance ON project_materials (project_id, package_instance_id); ALTER TABLE project_rentals ADD COLUMN IF NOT EXISTS package_instance_id VARCHAR(100) NULL AFTER project_id, ADD COLUMN IF NOT EXISTS source_package_id INT NULL AFTER package_instance_id; CREATE INDEX IF NOT EXISTS idx_project_rentals_package_instance ON project_rentals (project_id, package_instance_id); CREATE TABLE IF NOT EXISTS project_smart_package_workspaces ( id INT AUTO_INCREMENT PRIMARY KEY, project_id INT NOT NULL, version INT NOT NULL DEFAULT 0, workspace_json JSON NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_project_workspace (project_id), CONSTRAINT fk_smart_package_workspace_project FOREIGN KEY (project_id) REFERENCES customer_projects(id) ON DELETE CASCADE ); -- project_labor is an upsert target and must contain exactly one row per project. -- Retain the newest legacy row before adding the idempotent unique constraint. DELETE stale FROM project_labor stale JOIN project_labor newest ON newest.project_id = stale.project_id AND newest.id > stale.id; SELECT project_id, COUNT(*) AS remaining_rows FROM project_labor GROUP BY project_id HAVING COUNT(*) > 1; CREATE UNIQUE INDEX IF NOT EXISTS unique_project_labor ON project_labor (project_id);