CREATE TABLE IF NOT EXISTS qipai_cleaning_templates ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, tenant_id BIGINT UNSIGNED NOT NULL, scope_key VARCHAR(64) NOT NULL, scope_type VARCHAR(16) NOT NULL, store_id BIGINT UNSIGNED NULL, room_id BIGINT UNSIGNED NULL, name VARCHAR(128) NOT NULL, requirement VARCHAR(512) NOT NULL DEFAULT '', photo_required TINYINT(1) NOT NULL DEFAULT 1, min_photo_count TINYINT UNSIGNED NOT NULL DEFAULT 1, max_photo_count TINYINT UNSIGNED NOT NULL DEFAULT 9, exempt_policy VARCHAR(32) NOT NULL DEFAULT 'ANY_ACTIVE', version INT UNSIGNED NOT NULL DEFAULT 1, status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', created_by BIGINT UNSIGNED NOT NULL, updated_by BIGINT UNSIGNED NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), CONSTRAINT fk_qipai_cleaning_templates_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id), CONSTRAINT fk_qipai_cleaning_templates_store FOREIGN KEY (store_id) REFERENCES qipai_stores(id), CONSTRAINT fk_qipai_cleaning_templates_room FOREIGN KEY (room_id) REFERENCES qipai_rooms(id), CONSTRAINT fk_qipai_cleaning_templates_creator FOREIGN KEY (created_by) REFERENCES qipai_users(id), CONSTRAINT fk_qipai_cleaning_templates_updater FOREIGN KEY (updated_by) REFERENCES qipai_users(id), CONSTRAINT chk_qipai_cleaning_template_scope CHECK (scope_type IN ('TENANT', 'STORE', 'ROOM')), CONSTRAINT chk_qipai_cleaning_template_photos CHECK ( min_photo_count <= max_photo_count AND max_photo_count <= 9 AND ((photo_required = 1 AND min_photo_count >= 1) OR (photo_required = 0 AND min_photo_count = 0)) ), CONSTRAINT chk_qipai_cleaning_template_exempt CHECK ( exempt_policy IN ('DISABLED', 'BEFORE_START', 'ANY_ACTIVE') ), CONSTRAINT chk_qipai_cleaning_template_status CHECK (status IN ('ACTIVE', 'DISABLED')), UNIQUE KEY uq_qipai_cleaning_template_scope (tenant_id, scope_key), KEY idx_qipai_cleaning_template_resolve (tenant_id, room_id, store_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ALTER TABLE qipai_cleaning_tasks ADD COLUMN cleaning_template_id BIGINT UNSIGNED NULL AFTER requirement, ADD COLUMN cleaning_template_version INT UNSIGNED NOT NULL DEFAULT 0 AFTER cleaning_template_id, ADD COLUMN photo_required TINYINT(1) NOT NULL DEFAULT 1 AFTER cleaning_template_version, ADD COLUMN min_photo_count TINYINT UNSIGNED NOT NULL DEFAULT 1 AFTER photo_required, ADD COLUMN max_photo_count TINYINT UNSIGNED NOT NULL DEFAULT 9 AFTER min_photo_count, ADD COLUMN exempt_policy VARCHAR(32) NOT NULL DEFAULT 'ANY_ACTIVE' AFTER max_photo_count, ADD COLUMN rework_count INT UNSIGNED NOT NULL DEFAULT 0 AFTER reject_reason, ADD CONSTRAINT fk_qipai_cleaning_tasks_template FOREIGN KEY (cleaning_template_id) REFERENCES qipai_cleaning_templates(id), ADD CONSTRAINT chk_qipai_cleaning_task_photo_rule CHECK ( min_photo_count <= max_photo_count AND max_photo_count <= 9 AND ((photo_required = 1 AND min_photo_count >= 1) OR (photo_required = 0 AND min_photo_count = 0)) ), ADD CONSTRAINT chk_qipai_cleaning_task_exempt_policy CHECK ( exempt_policy IN ('DISABLED', 'BEFORE_START', 'ANY_ACTIVE') ); CREATE TABLE IF NOT EXISTS qipai_cleaning_task_photos ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, tenant_id BIGINT UNSIGNED NOT NULL, task_id BIGINT UNSIGNED NOT NULL, store_id BIGINT UNSIGNED NOT NULL, uploaded_by BIGINT UNSIGNED NOT NULL, storage_path VARCHAR(512) NOT NULL, public_url VARCHAR(700) NOT NULL, mime_type VARCHAR(64) NOT NULL, byte_size INT UNSIGNED NOT NULL, width INT UNSIGNED NOT NULL, height INT UNSIGNED NOT NULL, checksum_sha256 CHAR(64) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'PENDING', attached_revision INT UNSIGNED NULL, retention_until DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), deleted_at DATETIME(3) NULL, CONSTRAINT fk_qipai_cleaning_photos_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id), CONSTRAINT fk_qipai_cleaning_photos_task FOREIGN KEY (task_id) REFERENCES qipai_cleaning_tasks(id), CONSTRAINT fk_qipai_cleaning_photos_store FOREIGN KEY (store_id) REFERENCES qipai_stores(id), CONSTRAINT fk_qipai_cleaning_photos_uploader FOREIGN KEY (uploaded_by) REFERENCES qipai_users(id), CONSTRAINT chk_qipai_cleaning_photo_status CHECK (status IN ('PENDING', 'ATTACHED', 'ORPHANED')), UNIQUE KEY uq_qipai_cleaning_photo_storage (tenant_id, storage_path), UNIQUE KEY uq_qipai_cleaning_photo_url (tenant_id, public_url), KEY idx_qipai_cleaning_photo_task (tenant_id, task_id, status, created_at), KEY idx_qipai_cleaning_photo_retention (status, retention_until) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS qipai_cleaning_task_submissions ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, tenant_id BIGINT UNSIGNED NOT NULL, task_id BIGINT UNSIGNED NOT NULL, revision INT UNSIGNED NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED', photo_urls_json JSON NOT NULL, note VARCHAR(512) NOT NULL DEFAULT '', reject_reason VARCHAR(512) NOT NULL DEFAULT '', submitted_by BIGINT UNSIGNED NOT NULL, reviewed_by BIGINT UNSIGNED NULL, submitted_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), reviewed_at DATETIME(3) NULL, CONSTRAINT fk_qipai_cleaning_submissions_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id), CONSTRAINT fk_qipai_cleaning_submissions_task FOREIGN KEY (task_id) REFERENCES qipai_cleaning_tasks(id), CONSTRAINT fk_qipai_cleaning_submissions_submitter FOREIGN KEY (submitted_by) REFERENCES qipai_users(id), CONSTRAINT fk_qipai_cleaning_submissions_reviewer FOREIGN KEY (reviewed_by) REFERENCES qipai_users(id), CONSTRAINT chk_qipai_cleaning_submission_status CHECK (status IN ('SUBMITTED', 'ACCEPTED', 'REJECTED')), UNIQUE KEY uq_qipai_cleaning_submission_revision (tenant_id, task_id, revision), KEY idx_qipai_cleaning_submission_review (tenant_id, task_id, status, submitted_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT IGNORE INTO qipai_schema_migrations (version, name) VALUES ('2026081005', 'm09b_cleaning_rules');