diff --git a/deploy/sql/README.md b/deploy/sql/README.md index 05f4ae572..1045b098b 100644 --- a/deploy/sql/README.md +++ b/deploy/sql/README.md @@ -67,7 +67,7 @@ | 区域 / schema | 迁移 | 目的 | |---|---|---| | CN / `baiyue_iam` | `migrations/cn/baiyue_iam/V1__align_schema_baseline.sql`、`V2__add_pda_release_permissions.sql`、`V3__align_domestic_pda_mojia_sailing_permissions.sql` | 收敛客户编号、昵称和资质原文件名定义;补齐 PDA 发布、国内作业仓绑定、墨家 SKU 同步、仓管 PDA 和船期日历权限 | -| CN / `baiyue_firstleg` | `migrations/cn/baiyue_firstleg/V1__align_schema_baseline.sql`、`V2__align_measurement_and_fee_schema.sql`、`V3__add_customs_receipt_declaration_schema.sql`、`V4__add_sailing_calendar_schema.sql` | 收敛一件代发托盘码非空定义;补齐墨家测量和人工报价字段、报关行回执/申报事实、船期日历事实表;V2 对存量收件记录仅做阻断检查,不自动回填 | +| CN / `baiyue_firstleg` | `migrations/cn/baiyue_firstleg/V1__align_schema_baseline.sql`、`V2__align_measurement_and_fee_schema.sql`、`V3__add_customs_receipt_declaration_schema.sql`、`V4__add_sailing_calendar_schema.sql` | 收敛一件代发托盘码非空定义;补齐墨家测量和人工报价字段、报关行回执/申报事实、船期日历事实表;V2 保留未上报重量为 NULL,放宽已有非空重量列,不回填或删除收件记录 | | CN / `baiyue_file` | `migrations/cn/baiyue_file/V1__align_schema_baseline.sql`、`V2__widen_business_type.sql` | 补齐 PDA 发布表和固定渠道,并支持正式长业务类型 | | OVERSEAS / `baiyue_lastleg` | `migrations/overseas/baiyue_lastleg/V1__align_lastleg_production_schema.sql`、`V2__align_schema_baseline.sql`、`V3__relax_valid_nullable_fields.sql`、`V4__cargo_discard_return_inventory.sql`、`V5__allow_multiple_relabel_applications.sql` | 补齐尾程字段并收敛字段定义;放宽退货出库和预入仓批量 SKU 换标合法空值;货物废弃支持退货库存箱;允许同一尾程订单多个换标申请 | | OVERSEAS / `baiyue_lastlag_warehouse` | `migrations/overseas/baiyue_lastlag_warehouse/V1__add_shared_overflow_location.sql`、`V2__retire_location_zone_and_rebuild_query_index.sql` | 补齐共享溢出库位并移除退役 `zone_code`;V2 含 `DROP COLUMN`,生产执行必须单独授权并确认无外部依赖 | @@ -96,6 +96,8 @@ node deploy/sql/scripts/db-migrations.mjs run --manifest release-manifest.json - `new` 使用独占创建避免并发覆盖;成功历史或已冻结制品不能由普通命令改写。`plan` 只打包选中服务及评审确认的 schema,不会因为同一提交存在其他区域或服务 SQL 就自动扩大范围。`run` 的真实 Flyway 执行仍未接入测试数据库和 Jenkins;生产增量由专用验收脚本在当前轮明确授权后执行,不把统一初始化文件用于生产。 +墨家原始收件表允许未收到的 `weight_kg` 为空,不把未知重量补成 0。数据库可空仅用于如实保存收件数据,不代表 SKU 同步已支持无重量;当前上传及 SKU 编辑接口仍要求正数重量,向领星推送也需要完整测量值。 + ## 本地验证 离线合同不连接数据库: diff --git a/deploy/sql/migrations/cn/baiyue_firstleg/V2__align_measurement_and_fee_schema.sql b/deploy/sql/migrations/cn/baiyue_firstleg/V2__align_measurement_and_fee_schema.sql index b207b75e1..80b27fb3f 100644 --- a/deploy/sql/migrations/cn/baiyue_firstleg/V2__align_measurement_and_fee_schema.sql +++ b/deploy/sql/migrations/cn/baiyue_firstleg/V2__align_measurement_and_fee_schema.sql @@ -1,15 +1,14 @@ -- Region: CN -- Schema: baiyue_firstleg -- Purpose: 对齐墨家测量收件箱字段并允许人工报价费用快照缺少船司价格。 --- Preconditions: 连接必须指向 baiyue_firstleg;若墨家收件箱已有记录,必须先完成经批准的重量回填方案;本文件不执行 UPDATE。 +-- Preconditions: 连接必须指向 baiyue_firstleg;墨家收件表已存在;未收到的设备重量保留 NULL,不回填为 0。 -- Postconditions: 墨家收件箱字段、入库费用船司价格字段与当前 Firstleg 基线一致。 SET NAMES utf8mb4; SET @guard = IF( - EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fl_mojia_measurement_receipt') - AND EXISTS (SELECT 1 FROM fl_mojia_measurement_receipt LIMIT 1), - 'SELECT 1 FROM migration_blocked_firstleg_mojia_receipt_requires_weight_review', + NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fl_mojia_measurement_receipt'), + 'SELECT 1 FROM migration_blocked_firstleg_mojia_receipt_table_missing', 'SELECT 1' ); PREPARE stmt FROM @guard; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -21,7 +20,10 @@ SET @ddl = IF(NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_s 'ALTER TABLE fl_mojia_measurement_receipt ADD COLUMN measurement_kind VARCHAR(16) NOT NULL DEFAULT ''BOX_LABEL'' COMMENT ''测量分流:BOX_LABEL箱唛;SKU一件代发SKU'' AFTER inbound_code', 'SELECT 1'); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @ddl = IF(NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_mojia_measurement_receipt' AND column_name = 'weight_kg'), - 'ALTER TABLE fl_mojia_measurement_receipt ADD COLUMN weight_kg DECIMAL(18,3) NOT NULL COMMENT ''设备实测重量kg'' AFTER height_cm', 'SELECT 1'); + 'ALTER TABLE fl_mojia_measurement_receipt ADD COLUMN weight_kg DECIMAL(18,3) NULL COMMENT ''设备实测重量kg;未收到时为空,不以0代替'' AFTER height_cm', 'SELECT 1'); +PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +SET @ddl = IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_mojia_measurement_receipt' AND column_name = 'weight_kg' AND is_nullable = 'NO'), + 'ALTER TABLE fl_mojia_measurement_receipt MODIFY COLUMN weight_kg DECIMAL(18,3) NULL COMMENT ''设备实测重量kg;未收到时为空,不以0代替''', 'SELECT 1'); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @ddl = IF(NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_mojia_measurement_receipt' AND column_name = 'sync_status'), 'ALTER TABLE fl_mojia_measurement_receipt ADD COLUMN sync_status VARCHAR(24) NULL COMMENT ''SKU同步状态:PENDING/SYNCING/SUCCESS/FAILED'' AFTER status', 'SELECT 1'); diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 4dcf76941..c7ffeb09f 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -3436,7 +3436,7 @@ CREATE TABLE fl_mojia_measurement_receipt ( length_cm decimal(12,3) NOT NULL COMMENT '设备实测长度cm', width_cm decimal(12,3) NOT NULL COMMENT '设备实测宽度cm', height_cm decimal(12,3) NOT NULL COMMENT '设备实测高度cm', - weight_kg decimal(18,3) NOT NULL COMMENT '设备实测重量kg', + weight_kg decimal(18,3) DEFAULT NULL COMMENT '设备实测重量kg;未收到时为空,不以0代替', machine_no varchar(64) NOT NULL COMMENT '设备编号', measured_at datetime(3) NOT NULL COMMENT '设备测量时间或服务接收时间', image_file_id bigint unsigned NOT NULL COMMENT 'File服务中的墨家联调图片ID', diff --git a/firstleg/src/main/resources/db/phase2-inbound-schema.sql b/firstleg/src/main/resources/db/phase2-inbound-schema.sql index 2afc054aa..56fb62383 100644 --- a/firstleg/src/main/resources/db/phase2-inbound-schema.sql +++ b/firstleg/src/main/resources/db/phase2-inbound-schema.sql @@ -335,7 +335,7 @@ CREATE TABLE IF NOT EXISTS fl_mojia_measurement_receipt ( length_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测长度cm', width_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测宽度cm', height_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测高度cm', - weight_kg DECIMAL(18,3) NOT NULL COMMENT '设备实测重量kg', + weight_kg DECIMAL(18,3) NULL COMMENT '设备实测重量kg;未收到时为空,不以0代替', machine_no VARCHAR(64) NOT NULL COMMENT '设备编号', measured_at DATETIME(3) NOT NULL COMMENT '设备测量时间或服务接收时间', image_file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务中的墨家联调图片ID', diff --git a/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs b/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs index 9f67e26ef..8de5b7f41 100644 --- a/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs +++ b/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs @@ -89,7 +89,7 @@ test('近期头程与海外结构完整进入各自统一初始化入口', () => const receipt = table(cn, 'baiyue_firstleg', 'fl_mojia_measurement_receipt') for (const field of ['measurement_kind', 'weight_kg', 'sync_status', 'sync_failure_code', 'sync_failure_reason', 'lingxing_sku_id', 'revision']) assert.match(receipt, new RegExp(`\\b${field}\\b`, 'i')) - assert.match(receipt, /weight_kg decimal\(18,3\) NOT NULL/i) + assert.match(receipt, /weight_kg decimal\(18,3\) DEFAULT NULL/i) const relabel = table(overseas, 'baiyue_lastleg', 'll_relabel_application') assert.match(relabel, /KEY idx_ll_relabel_application_order \(lastleg_order_id\)/i) diff --git a/scripts/database-schema-alignment-migrations-contract.test.mjs b/scripts/database-schema-alignment-migrations-contract.test.mjs index 09b02d27c..5af5926f3 100644 --- a/scripts/database-schema-alignment-migrations-contract.test.mjs +++ b/scripts/database-schema-alignment-migrations-contract.test.mjs @@ -87,6 +87,19 @@ test('schema alignment migrations cover the verified drift', async () => { assert.match(source.lastlegRelabel, /idx_ll_relabel_application_order/i) }) +test('墨家迁移保留未知重量,不因已有收件记录阻断', async () => { + const { firstlegMeasurement: source } = await sources() + assert.match(source, /ADD COLUMN weight_kg DECIMAL\(18,3\) NULL/i) + assert.match(source, /MODIFY COLUMN weight_kg DECIMAL\(18,3\) NULL/i) + assert.doesNotMatch(source, /weight_kg DECIMAL\(18,3\) NOT NULL/i) + assert.doesNotMatch(source, /FROM\s+fl_mojia_measurement_receipt\b/i) + assert.match(source, /migration_blocked_firstleg_mojia_receipt_table_missing/) + const module = await readFile(path.join(repositoryRoot, 'firstleg/src/main/resources/db/phase2-inbound-schema.sql'), 'utf8') + const receipt = module.match(/CREATE TABLE IF NOT EXISTS fl_mojia_measurement_receipt\s*\(([\s\S]*?)\n\) ENGINE=/i)?.[1] + assert.ok(receipt, '必须保留墨家收件表定义') + assert.match(receipt, /weight_kg DECIMAL\(18,3\) NULL/i) +}) + test('constraint tightening is guarded before any alter table', async () => { for (const [name, source] of Object.entries(await sources())) { const firstAlter = source.search(/\bALTER TABLE\b/i)