# 物料多供应商与多制造商:数据库改造实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 把 `materials` 上的单值供应商/制造商改造为「物料 × 供应商 × 制造商」的三元货源模型,并在 `init.sql` 中完成 161 条货源数据的分流回填。 **Architecture:** 只改 `scripts/init.sql` 一个文件。新增 `material_manufacturers` 表,改造现有空表 `material_suppliers`,`materials` 删掉三个字段。为避免逐行修改 199 行数据字面量,用一张临时表中转:199 行原样进临时表,再分流到三张目标表。 **Tech Stack:** MySQL 8.0、单文件初始化脚本 `scripts/init.sql`(由 MySQL 官方镜像的 `/docker-entrypoint-initdb.d/` 机制执行,整个文件在同一连接内,临时表可用)。 **Spec:** `docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md` ## Global Constraints - MySQL 8.0,`CHECK` 约束依赖 8.0.16+。 - 字符集:库默认 `utf8mb4` / `utf8mb4_unicode_ci`(`CREATE DATABASE ... COLLATE utf8mb4_unicode_ci`),但**每张持久表(含本次新增的两张)实际落在 `utf8mb4_0900_ai_ci`**——建表语句只写了 `DEFAULT CHARSET=utf8mb4`、没有 `COLLATE`,MySQL 会采用该字符集的默认排序规则而非库默认值。这是全库既有现象,本次不处理,统一排序规则另开任务。 - **所有外键均为单列**,不引入任何复合外键(spec 的 D7)。公司隔离与物料一致性由应用层校验。 - `status` 取值固定为 `active` / `obsolete`(D8),不用数字。 - 货源表表名沿用 `material_suppliers`,不改名(D5)。 - `init.sql` **本来就是一次性脚本,不保证可重复执行**:普通建表没有 `IF NOT EXISTS`,`schema_migrations` 的 INSERT 也没有幂等处理,第二次执行会在第 8 行以 `Table 'users' already exists` 中止。本任务只要求**新增的分流语句自身幂等**,不要求整脚本幂等,**也不要顺手去改既有的建表语句**——那是另一个话题。 - **验证一律使用隔离库**,绝不碰真实库。本机 MySQL 在 3306,账号 `root` / 密码 `1234`。 - **`init.sql` 不能靠「指定库名」来隔离**:它第 5-6 行硬编码了 `CREATE DATABASE IF NOT EXISTS wmms;` 与 `USE wmms;`,脚本会自己切回 `wmms`,把表建到真实库上去。必须先用 `sed` 把这两行的库名替换掉,再喂给 mysql: ```bash mysql -u root -p1234 -e "DROP DATABASE IF EXISTS wms_verify;" sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" \ -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql -u root -p1234 ``` 本计划下面每个验证步骤都已按这个写法给出。另外注意:mysql 在批处理模式下遇到 SQL 错误会**中止**,所以脚本跑挂时不用担心半途污染——但也意味着报错后必须从头重跑。 - 仓库里 `scripts/upgrades/` 目录不存在,没有增量升级路径——改完只能删库用 `init.sql` 重建。 ## 本计划的文件改动总览 | 文件 | 职责 | | --- | --- | | `scripts/init.sql` | 唯一被改的文件。表结构定义 + 数据分流 + 货源回填全在这里 | 以下行号均以**改造前**的 `scripts/init.sql` 为准。 --- ## Task 1: 表结构改造 + 基础货源派生(129 条) **交付物:** `init.sql` 在空库上完整跑通,`materials` 199 条、`material_manufacturers` 10 条、`material_suppliers` 129 条。 **Files:** - Modify: `scripts/init.sql`(`CREATE TABLE materials` 约 `:19`;`ALTER TABLE materials` `:30-33`;`CREATE TABLE organization_partners` 约 `:27`;`CREATE TABLE material_suppliers` `:34`;两处物料 `INSERT` 约 `:661` 与 `:738`) **Interfaces:** - Produces: 表 `material_manufacturers(organization_id, material_id, manufacturer_id, model, status, ...)`;表 `material_suppliers(organization_id, material_id, supplier_id, material_manufacturer_id, price, currency, minimum_order_quantity, lead_time_days, is_preferred, status, ...)`。Task 2 依赖这两张表的列名与唯一键。 - [ ] **Step 1: 建立隔离验证库** 改造前先跑通一次基线,确认验证流程可用: ```bash mysql -u root -p1234 -e "DROP DATABASE IF EXISTS wms_verify;" sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql -u root -p1234 mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) AS materials FROM materials;" ``` 预期:输出 `199`。若这一步就失败,先解决环境问题,不要往下走。 - [ ] **Step 2: 改 `CREATE TABLE materials`,删掉三个字段** 在 `scripts/init.sql:19` 那一行的建表语句中,删除三段列定义: ``` manufacturer_model VARCHAR(200), supplier_id BIGINT NULL COMMENT 'Default supplier organization_partner ID', manufacturer_id BIGINT NULL COMMENT 'Manufacturer organization_partner ID', ``` 删完该行应为(其余列保持原样,此处只显示关键区段): ```sql CREATE TABLE materials (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_code VARCHAR(50) NOT NULL, name VARCHAR(200) NOT NULL, material_image VARCHAR(500), spec VARCHAR(200), source VARCHAR(20) NULL COMMENT '物料来源:外购或外协', remark VARCHAR(500) NULL COMMENT '物料备注,最多500字', material_type_id BIGINT, category_id BIGINT NOT NULL, base_unit_id BIGINT, unit VARCHAR(20), status VARCHAR(20) NOT NULL DEFAULT 'active', version INT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_materials_org_code(organization_id,material_code), KEY idx_materials_category(category_id), FOREIGN KEY(organization_id) REFERENCES organizations(id) ON DELETE RESTRICT, FOREIGN KEY(material_type_id) REFERENCES material_types(id) ON DELETE RESTRICT, FOREIGN KEY(category_id) REFERENCES material_categories(id) ON DELETE RESTRICT, FOREIGN KEY(base_unit_id) REFERENCES units(id) ON DELETE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - [ ] **Step 3: 删除 `ALTER TABLE materials` 整段** 删除 `scripts/init.sql:29-33` 整段(4 行 `ALTER TABLE materials ADD ...`:两个索引、两个复合外键)。这段是为旧的两列加的,列已删,整段作废。 - [ ] **Step 4: 删除 `uk_organization_partners_org_id`** 在 `CREATE TABLE organization_partners`(约 `:27`)中删掉: ``` UNIQUE KEY uk_organization_partners_org_id(organization_id,id), ``` 注意只删这一个唯一键,另外两个(`uk_organization_partners_org_partner`、`uk_organization_partners_org_code`)保留。 - [ ] **Step 5: 新建 `material_manufacturers`** 插在 `CREATE TABLE organization_partners` **之后**、`CREATE TABLE material_suppliers` **之前**(**首行注释不可省**,它标明这是一张关系表): > **不能紧接 `CREATE TABLE materials`**:本表含 `FOREIGN KEY(manufacturer_id) REFERENCES organization_partners(id)`,而 `organization_partners` 创建在更后面;MySQL 不允许前向外键,实测 `ERROR 1824: Failed to open the referenced table 'organization_partners'`。该脚本里也没有 `SET FOREIGN_KEY_CHECKS=0`,所以只能靠调整建表顺序解决。 ```sql -- 物料与制造商关系表:某物料有哪些可用制造商,以及各自的厂家型号 CREATE TABLE material_manufacturers (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_id BIGINT NOT NULL, manufacturer_id BIGINT NOT NULL COMMENT 'organization_partners ID', model VARCHAR(200) NULL COMMENT '厂家型号', status VARCHAR(20) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_material_manufacturers_dim(organization_id,material_id,manufacturer_id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(material_id) REFERENCES materials(id), FOREIGN KEY(manufacturer_id) REFERENCES organization_partners(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - [ ] **Step 6: 用改造后的定义替换 `CREATE TABLE material_suppliers`** 把 `scripts/init.sql:34` 整行替换为(**首行注释不可省**,它标明这是一张关系表): ```sql -- 物料与供应商关系表:每条记录一个货源(某供应商供某物料、供的是哪个制造商条目)及其采购条件 CREATE TABLE material_suppliers (id BIGINT AUTO_INCREMENT PRIMARY KEY, organization_id BIGINT NOT NULL, material_id BIGINT NOT NULL, supplier_id BIGINT NULL COMMENT 'organization_partners ID', material_manufacturer_id BIGINT NULL COMMENT 'material_manufacturers ID', price DECIMAL(20,6) NULL, currency CHAR(3) NULL, minimum_order_quantity DECIMAL(20,6) NOT NULL DEFAULT 0, lead_time_days INT NOT NULL DEFAULT 0, is_preferred BOOLEAN NOT NULL DEFAULT FALSE, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, supplier_key BIGINT GENERATED ALWAYS AS (COALESCE(supplier_id, 0)) VIRTUAL, manufacturer_entry_key BIGINT GENERATED ALWAYS AS (COALESCE(material_manufacturer_id, 0)) VIRTUAL, preferred_row BIGINT GENERATED ALWAYS AS (CASE WHEN is_preferred AND status = 'active' THEN 1 ELSE NULL END) VIRTUAL, UNIQUE KEY uk_material_suppliers_dim(organization_id,material_id,supplier_key,manufacturer_entry_key), UNIQUE KEY uk_material_suppliers_preferred(organization_id,material_id,preferred_row), CONSTRAINT chk_material_suppliers_dimension CHECK (supplier_id IS NOT NULL OR material_manufacturer_id IS NOT NULL), KEY idx_material_suppliers_supplier(organization_id,supplier_key), KEY idx_material_suppliers_manufacturer(organization_id,manufacturer_entry_key), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(material_id) REFERENCES materials(id), FOREIGN KEY(supplier_id) REFERENCES organization_partners(id), FOREIGN KEY(material_manufacturer_id) REFERENCES material_manufacturers(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - [ ] **Step 7: 在物料 INSERT 之前建临时表** 在 `-- ---------- 物料 ----------` 注释块(约 `:654`)之后、第一条物料 `INSERT`(约 `:661`)之前插入: ```sql -- ---------- 物料:经临时表中转,分流到 materials 与货源表 ---------- -- 源字面量含 supplier_id / manufacturer_id 两列,而 materials 已删掉它们, -- 因此原样进临时表,再分别流向 materials 与货源表。见设计文档 7.2。 DROP TEMPORARY TABLE IF EXISTS tmp_materials; CREATE TEMPORARY TABLE tmp_materials ( organization_id BIGINT, material_code VARCHAR(50), name VARCHAR(200), spec VARCHAR(200), source VARCHAR(20), remark VARCHAR(500), supplier_id BIGINT, manufacturer_id BIGINT, category_id BIGINT, base_unit_id BIGINT, unit VARCHAR(20), status VARCHAR(20), version INT, created_at DATETIME, updated_at DATETIME ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - [ ] **Step 8: 把两处物料 INSERT 的目标表改成临时表** 北京段(约 `:661`):把 `INSERT INTO materials` 改为 `INSERT INTO tmp_materials`,**列清单与 16 行 VALUES 一字不动**。 武汉段(约 `:738`):同样把 `INSERT INTO materials` 改为 `INSERT INTO tmp_materials`,183 行 VALUES 一字不动。 - [ ] **Step 9: 删掉两处 `AS new` / `ON DUPLICATE KEY UPDATE`** 两处 INSERT 的结尾(约 `:730-735` 与 `:1494-1499`)目前形如: ```sql 'active', 1, '2026-10-10 07:54:57') AS new ON DUPLICATE KEY UPDATE name = new.name, spec = new.spec, source = new.source, remark = new.remark, supplier_id = new.supplier_id, manufacturer_id = new.manufacturer_id, category_id = new.category_id, base_unit_id = new.base_unit_id, unit = new.unit, status = new.status, updated_at = new.updated_at; ``` 改为让 VALUES 直接以分号结束: ```sql 'active', 1, '2026-10-10 07:54:57'); ``` 临时表是空的,不需要幂等处理;幂等由下一步的分流语句承担。 - [ ] **Step 10: 加分流语句(三张表)并删除临时表** 紧接武汉段 INSERT 结束之后插入: ```sql -- 分流 1:物料本体 INSERT INTO materials (organization_id, material_code, name, spec, source, remark, category_id, base_unit_id, unit, status, version, created_at, updated_at) SELECT t.organization_id, t.material_code, t.name, t.spec, t.source, t.remark, t.category_id, t.base_unit_id, t.unit, t.status, t.version, t.created_at, t.updated_at FROM tmp_materials t ON DUPLICATE KEY UPDATE name = t.name, spec = t.spec, source = t.source, remark = t.remark, category_id = t.category_id, base_unit_id = t.base_unit_id, unit = t.unit, status = t.status, updated_at = t.updated_at; -- 分流 2:制造商条目(有制造商的物料) INSERT INTO material_manufacturers (organization_id, material_id, manufacturer_id, status) SELECT t.organization_id, m.id, t.manufacturer_id, 'active' FROM tmp_materials t JOIN materials m ON m.organization_id = t.organization_id AND m.material_code = t.material_code WHERE t.manufacturer_id IS NOT NULL ON DUPLICATE KEY UPDATE status = material_manufacturers.status; -- 分流 3:货源(有供应商或有制造商的物料) INSERT INTO material_suppliers (organization_id, material_id, supplier_id, material_manufacturer_id, is_preferred, status) SELECT t.organization_id, m.id, t.supplier_id, mm.id, TRUE, 'active' FROM tmp_materials t JOIN materials m ON m.organization_id = t.organization_id AND m.material_code = t.material_code LEFT JOIN material_manufacturers mm ON mm.organization_id = t.organization_id AND mm.material_id = m.id AND mm.manufacturer_id = t.manufacturer_id WHERE t.supplier_id IS NOT NULL OR t.manufacturer_id IS NOT NULL ON DUPLICATE KEY UPDATE is_preferred = material_suppliers.is_preferred; DROP TEMPORARY TABLE tmp_materials; ``` 三条顺序不可调换:分流 2 依赖分流 1 已写入 `materials`(要 JOIN 出 `material_id`),分流 3 依赖分流 2 已写入 `material_manufacturers`(要 JOIN 出 `material_manufacturer_id`)。 - [ ] **Step 11: 隔离库重建并核对数量** ```bash mysql -u root -p1234 -e "DROP DATABASE IF EXISTS wms_verify;" sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql -u root -p1234 mysql -u root -p1234 wms_verify -e " SELECT (SELECT COUNT(*) FROM materials) AS materials, (SELECT COUNT(*) FROM material_manufacturers) AS manufacturers, (SELECT COUNT(*) FROM material_suppliers) AS sources;" ``` 预期输出: ``` materials manufacturers sources 199 10 129 ``` - [ ] **Step 12: 抽查货源的配对是否正确** ```bash mysql -u root -p1234 wms_verify -e " SELECT m.material_code, m.name, sup.partner_code AS supplier, manu.partner_code AS manufacturer, ms.is_preferred FROM material_suppliers ms JOIN materials m ON m.id = ms.material_id LEFT JOIN organization_partners sup ON sup.id = ms.supplier_id LEFT JOIN material_manufacturers mm ON mm.id = ms.material_manufacturer_id LEFT JOIN organization_partners manu ON manu.id = mm.manufacturer_id WHERE m.material_code IN ('B010201','A020201','B010202');" ``` 预期:`B010201` 与 `A020201` 各一行,供应商与制造商均为 `BJ-P001` / `BJ-P003`(原厂直销,两者相同);`B010202` 一行,供应商为空、制造商为 `BJ-P002`。 - [ ] **Step 13: 确认分流语句自身幂等** **不要**在同一个库上重跑整个脚本——`init.sql` 整体不幂等(见 Global Constraints),那样只会得到 `ERROR 1050 at line 8: Table 'users' already exists` 然后中止,证明不了任何事。 分流语句的幂等性由「唯一键 + `ON DUPLICATE KEY UPDATE`」的写法保证。若要实证,单独建一个 scratch 库、把 `tmp_materials` 建表 + 几行样例数据 + 三条分流语句抽成一个独立脚本,连跑两轮: ```bash mysql -u root -p1234 -e "DROP DATABASE IF EXISTS wms_idem; CREATE DATABASE wms_idem CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -u root -p1234 wms_idem < /tmp/idem_probe.sql # 第一轮 mysql -u root -p1234 wms_idem < /tmp/idem_probe.sql # 第二轮 mysql -u root -p1234 wms_idem -e "SELECT (SELECT COUNT(*) FROM materials) AS m, (SELECT COUNT(*) FROM material_manufacturers) AS mm, (SELECT COUNT(*) FROM material_suppliers) AS ms;" ``` 预期:两轮之后计数完全相同(不翻倍)。验证完 `DROP DATABASE wms_idem;`。 - [ ] **Step 14: 提交** ```bash git add scripts/init.sql git commit -F /tmp/msg.txt # 中文提交信息写入文件,避免 shell 编码问题 ``` --- ## Task 2: 补入 32 条被截断的多供应商(161 条) **交付物:** 上述 32 条物料各多出一行货源,`material_suppliers` 从 129 条增至 **161 条**。 **Files:** - Modify: `scripts/init.sql`(在 Task 1 的分流 3 之后追加一段) - Create: `scripts/verify-multi-supplier.sql`(可选,用于核对的查询) **Interfaces:** - Consumes: Task 1 建立的三张表与分流语句;源库 `original/material-apply-wuhan/material.db`。 - Produces: 32 条额外的 `material_suppliers` 行。 **背景:** 源库用 7 种分隔符(`\r\n`、`\n`、`\r`、`/`、`、`、`;`、`;`)在 `supplier` 字段写多家供应商,迁移时只保留了第一家。实测 32 条,每条恰好 2 家,全在武汉。被丢弃的第二家其伙伴记录**已经建好**(`migrate_to_mysql.py:323-326` 对每个名字都建了伙伴),所以只需补货源行,不需要新建伙伴。 - [ ] **Step 1: 确认源库的多供应商条数** 源库在仓库外:`C:\1111\12.project\PyCharm\sqlite-to-mysql-migration\original\material-apply-wuhan\material.db`。用**只读**方式打开,按七种分隔符计数(用 `char()` 码点写中文分隔符,避免 shell 传中文被写坏): ```bash DB="file:C:/1111/12.project/PyCharm/sqlite-to-mysql-migration/original/material-apply-wuhan/material.db?mode=ro" sqlite3 "$DB" "SELECT COUNT(*) FROM material WHERE supplier LIKE '%/%' OR supplier LIKE '%'||char(10)||'%' OR supplier LIKE '%'||char(13)||'%' OR supplier LIKE '%'||char(12289)||'%' OR supplier LIKE '%'||char(65307)||'%' OR supplier LIKE '%;%';" ``` 预期:`32`。若不等,停下来核对源库是不是换了版本,不要继续。 - [ ] **Step 2: 用脚本直接生成全部 32 条补入 SQL** 拆分规则与 `migrate_to_mysql.py:98` 的 `PARTNER_SEPARATORS` 完全一致,需 strip 与去重(见该文件 `split_partners`,`:134-149`)。**脚本写在临时目录,不要放进仓库。** 32 条物料总共只涉及 8 家供应商,其伙伴编码已从 `init.sql:562-595` 的 `tmp_partner_wh` 查出,写死在脚本里: ```python """从源库生成 32 条多供应商的补入 SQL(第 2 家起)。只读源库。""" import sqlite3 import sys PARTNER_SEPARATORS = ("\r\n", "\n", "\r", "/", "、", ";", ";") # 供应商名 → 组织伙伴编码(取自 init.sql:562-595 的 tmp_partner_wh) NAME_TO_CODE = { "东莞市纳百智造技术有限公司": "WH-P009", "佛山戊实五金有限公司": "WH-P017", "北京恒瑞博远科技有限公司": "WH-P016", "北京致远合创电子科技有限公司": "WH-P007", "昆山聚格电子材料有限公司": "WH-P029", "欧时电子元件(上海)有限公司": "WH-P008", "武汉市普创模型技术有限公司": "WH-P010", "深圳市美星新材料科技有限公司": "WH-P028", } def split_partners(text): if not text: return [] parts = [text] for sep in PARTNER_SEPARATORS: nxt = [] for part in parts: nxt.extend(part.split(sep)) parts = nxt out = [] for part in parts: name = part.strip() if name and name not in out: out.append(name) return out DB = "file:C:/1111/12.project/PyCharm/sqlite-to-mysql-migration/original/material-apply-wuhan/material.db?mode=ro" con = sqlite3.connect(DB, uri=True) STMT = """INSERT INTO material_suppliers (organization_id, material_id, supplier_id, material_manufacturer_id, is_preferred, status) SELECT @org_wh, m.id, {var}, NULL, FALSE, 'active' FROM materials m WHERE m.organization_id = @org_wh AND m.material_code = '{code}' ON DUPLICATE KEY UPDATE is_preferred = material_suppliers.is_preferred;""" count = 0 for code, supplier in con.execute( "SELECT material_code, supplier FROM material ORDER BY material_code" ): names = split_partners(supplier) if len(names) <= 1: continue for name in names[1:]: # 第 1 家已由 Task 1 写入 tag = NAME_TO_CODE.get(name) if tag is None: raise SystemExit(f"未映射的供应商名:{name}") var = "@p_" + tag.lower().replace("-p", "_") # WH-P008 → @p_wh_008 print(STMT.format(var=var, code=code)) count += 1 print(f"-- 共生成 {count} 条", file=sys.stderr) ``` 运行(输出重定向到文件,避免 Windows 控制台的中文乱码): ```bash PYTHONIOENCODING=utf-8 python -I gen_multi_supplier.py > multi_supplier.sql ``` 预期 stderr 打印 `-- 共生成 32 条`。 - [ ] **Step 3: 把生成的 SQL 追加进 `init.sql`** 把 `multi_supplier.sql` 里的 32 条语句,连同一行说明注释,追加到 Task 1 的「分流 3」之后: ```sql -- ---------- 多供应商补入:源里用分隔符写了多家,迁移时只留了第一家 ---------- -- 见设计文档 7.4。32 条,每条 2 家,故每条补 1 行,全在武汉。 ``` `is_preferred = FALSE`(第一家已在 Task 1 标为 `TRUE`),`material_manufacturer_id` 为 `NULL`(这 32 条的制造商字段本来就是空的)。 **不需要新增伙伴变量。** `@p_wh_001` ~ `@p_wh_034` 已在 `init.sql:619-652` 全部定义,本次用到的 `@p_wh_007`、`@p_wh_008`、`@p_wh_010`、`@p_wh_017`、`@p_wh_029` 都在其中。 - [ ] **Step 4: 隔离库重建并核对总数** ```bash mysql -u root -p1234 -e "DROP DATABASE IF EXISTS wms_verify;" sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql -u root -p1234 mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) AS sources FROM material_suppliers;" ``` 预期:`161`。 - [ ] **Step 5: 抽查多供应商物料** ```bash mysql -u root -p1234 wms_verify -e " SELECT m.material_code, op.partner_code, ms.is_preferred FROM material_suppliers ms JOIN materials m ON m.id = ms.material_id JOIN organization_partners op ON op.id = ms.supplier_id WHERE m.material_code = 'B010019' ORDER BY ms.is_preferred DESC;" ``` 预期:两行,`WH-P0xx`(原保留的第一家)`is_preferred=1`,`WH-P008`(欧时电子)`is_preferred=0`。 - [ ] **Step 6: 全量核对 32 条都有 2 行** ```bash mysql -u root -p1234 wms_verify -e " SELECT COUNT(*) AS materials_with_two FROM ( SELECT material_id FROM material_suppliers GROUP BY material_id HAVING COUNT(*) = 2 ) x;" ``` 预期:`32`(这 32 条各 2 行;其余物料若有供应商则各 1 行)。 - [ ] **Step 7: 确认新增语句自身幂等** 同 Task 1 Step 13:`init.sql` **整体不可重复执行**(见 Global Constraints),所以**不要**在原库重跑整个脚本——第二遍会在第 8 行以 `Table 'users' already exists` 中止,证明不了任何事。 若要实证新增的 32 条语句幂等,用 `mysql --force` 跑完整个脚本(忽略建表段那批既有报错),确认新增语句本身一条都没报错、且计数稳定: ```bash sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql --force -u root -p1234 mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) FROM material_suppliers;" ``` 预期:计数仍为 `161`,且输出中**没有**任何来自新增 32 条语句的错误。 - [ ] **Step 8: 提交** ```bash git add scripts/init.sql git commit -F /tmp/msg.txt ``` --- ## 完成后的状态 - `materials`:199 条,已无 `supplier_id` / `manufacturer_id` / `manufacturer_model`。 - `material_manufacturers`:10 条。 - `material_suppliers`:161 条(129 基础 + 32 补入)。 - `init.sql` 仍是单文件,可从空库一次重建;**整脚本不可重复执行**(见 Global Constraints),只有本次新增的分流/补入语句各自幂等。 - 后端与前端**尚未适配**,此时启动应用会因查询旧列而失败——这是预期状态,应用层改造另行计划。 - **货源功能的完成前提:** 应用层必须实现 D7 所需的四条完整性校验(数据库已移除复合外键,无法代劳);清单见设计文档第 8 节第 5 条。在其全部落地前,货源功能不算完成。