-- 价格套餐版本中间表 + 版本附加费 -- 关系: -- prc_price_set -- -> prc_price_set_version (步骤2: 版本号自动生成, 生效时间) -- -> prc_price_set_detail (步骤3: 产品价格, 通过 psv_id) -- -> prc_price_set_version_surcharge (psd_id 关联明细) -- 可重复执行 SET NAMES utf8mb4; SET @db := DATABASE(); CREATE TABLE IF NOT EXISTS prc_price_set_version ( psv_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '套餐版本id', price_set_id BIGINT(20) NOT NULL COMMENT '价格套餐id', version_no VARCHAR(20) NOT NULL COMMENT '版本号 V1/V2,自动+1', version_seq INT(11) NOT NULL COMMENT '版本序号,用于自动+1', effective_time DATETIME DEFAULT NULL COMMENT 'version start time (null until activate)', expire_time DATETIME DEFAULT NULL COMMENT '失效时间(空=长期;下一版本启动时写入该启动时间)', status CHAR(1) NOT NULL DEFAULT '0' COMMENT '0草稿 1生效 2失效', created_by VARCHAR(64) DEFAULT '' COMMENT '创建人', create_time DATETIME DEFAULT NULL COMMENT '创建时间', updated_by VARCHAR(64) DEFAULT '' COMMENT '更新人', update_time DATETIME DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (psv_id), UNIQUE KEY uk_psv_set_no (price_set_id, version_no), UNIQUE KEY uk_psv_set_seq (price_set_id, version_seq), INDEX idx_psv_set_eff (price_set_id, effective_time), INDEX idx_psv_status (status), CONSTRAINT fk_psv_set FOREIGN KEY (price_set_id) REFERENCES prc_price_set (price_set_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='价格套餐版本'; CREATE TABLE IF NOT EXISTS prc_price_set_version_surcharge ( psvs_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '版本附加费id', psv_id BIGINT(20) NOT NULL COMMENT '套餐版本id', psd_id BIGINT(20) NOT NULL COMMENT '套餐明细id', sc_id BIGINT(20) DEFAULT NULL COMMENT '来源附加费配置id', product_code VARCHAR(20) DEFAULT NULL COMMENT '产品代码(与明细一致)', sc_name VARCHAR(100) NOT NULL COMMENT '附加费名称快照', fee_type VARCHAR(30) NOT NULL DEFAULT 'SURCHARGE' COMMENT '费用类型(字典bms_fee_type)', charge_type VARCHAR(20) NOT NULL COMMENT '计费方式 FIXED固定金额/WEIGHT按重量/RATE按比例', charge_value DECIMAL(18,6) NOT NULL COMMENT '计费数值快照', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种', round_mode VARCHAR(20) NOT NULL DEFAULT 'HALF_UP' COMMENT '取值方式(字典bms_round_mode)', country_code CHAR(2) DEFAULT NULL COMMENT '适用国家ISO2(空=全部)', sc_note VARCHAR(500) DEFAULT NULL COMMENT '备注', created_by VARCHAR(64) DEFAULT '' COMMENT '创建人', create_time DATETIME DEFAULT NULL COMMENT '创建时间', updated_by VARCHAR(64) DEFAULT '' COMMENT '更新人', update_time DATETIME DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (psvs_id), INDEX idx_psvs_psv (psv_id), INDEX idx_psvs_psd (psd_id), INDEX idx_psvs_product (psv_id, product_code), INDEX idx_psvs_sc (sc_id), CONSTRAINT fk_psvs_psv FOREIGN KEY (psv_id) REFERENCES prc_price_set_version (psv_id) ON DELETE CASCADE, CONSTRAINT fk_psvs_psd FOREIGN KEY (psd_id) REFERENCES prc_price_set_detail (psd_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='价格套餐版本附加费快照'; -- expire_time: previous version expire = next version effective_time (start) SET @has := ( SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_version' AND COLUMN_NAME = 'expire_time' ); SET @sql := IF(@has = 0, 'ALTER TABLE prc_price_set_version ADD COLUMN expire_time DATETIME DEFAULT NULL COMMENT ''失效时间(空=长期;下一版本启动时写入该启动时间)'' AFTER effective_time', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- backfill: expire_time = next version start time (by version_seq) UPDATE prc_price_set_version cur INNER JOIN prc_price_set_version nxt ON nxt.price_set_id = cur.price_set_id AND nxt.version_seq = cur.version_seq + 1 SET cur.expire_time = nxt.effective_time; -- prc_price_set_detail.psv_id SET @has := ( SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND COLUMN_NAME = 'psv_id' ); SET @sql := IF(@has = 0, 'ALTER TABLE prc_price_set_detail ADD COLUMN psv_id BIGINT(20) DEFAULT NULL COMMENT ''套餐版本id'' AFTER price_set_id', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @has := ( SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND INDEX_NAME = 'idx_psd_psv' ); SET @sql := IF(@has = 0, 'ALTER TABLE prc_price_set_detail ADD INDEX idx_psd_psv (psv_id)', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @has := ( SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND CONSTRAINT_NAME = 'fk_psd_psv' ); SET @sql := IF(@has = 0, 'ALTER TABLE prc_price_set_detail ADD CONSTRAINT fk_psd_psv FOREIGN KEY (psv_id) REFERENCES prc_price_set_version (psv_id) ON DELETE CASCADE', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 已建表补关联: 附加费.psd_id -> 套餐明细 DELETE FROM prc_price_set_version_surcharge WHERE psd_id IS NULL OR NOT EXISTS ( SELECT 1 FROM prc_price_set_detail d WHERE d.psd_id = prc_price_set_version_surcharge.psd_id ); SET @sql := CONCAT( 'ALTER TABLE prc_price_set_version_surcharge MODIFY COLUMN psd_id BIGINT(20) NOT NULL COMMENT ''套餐明细id''' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @has := ( SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_version_surcharge' AND CONSTRAINT_NAME = 'fk_psvs_psd' ); SET @sql := IF(@has = 0, 'ALTER TABLE prc_price_set_version_surcharge ADD CONSTRAINT fk_psvs_psd FOREIGN KEY (psd_id) REFERENCES prc_price_set_detail (psd_id) ON DELETE CASCADE', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 回填: 每个已有套餐生成一条 V1 INSERT INTO prc_price_set_version ( price_set_id, version_no, version_seq, effective_time, status, created_by, create_time ) SELECT s.price_set_id, 'V1', 1, COALESCE(CAST(s.effective_date AS DATETIME), s.create_time, NOW()), CASE WHEN s.status = 'S' THEN '2' WHEN s.expire_date IS NOT NULL AND s.expire_date <= CURDATE() THEN '2' WHEN s.status = 'E' AND (s.effective_date IS NULL OR s.effective_date <= CURDATE()) THEN '1' ELSE '0' END, IFNULL(s.created_by, ''), NOW() FROM prc_price_set s WHERE NOT EXISTS ( SELECT 1 FROM prc_price_set_version v WHERE v.price_set_id = s.price_set_id ); -- 将已有明细绑定到同一套餐的 V1 UPDATE prc_price_set_detail d INNER JOIN prc_price_set_version v ON v.price_set_id = d.price_set_id AND v.version_seq = 1 SET d.psv_id = v.psv_id WHERE d.psv_id IS NULL;