From f18414b3aba2d097f5db04d2e58ca8b47473f612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E7=9D=BF=20=E5=BC=A0?= Date: Thu, 15 Oct 2026 15:40:19 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=AE=9E=E6=96=BD=E8=AE=A1?= =?UTF-8?q?=E5=88=92=EF=BC=8C=E4=BF=AE=E6=AD=A3=E8=AE=BE=E8=AE=A1=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=AD=E4=B8=8E=E4=B8=B4=E6=97=B6=E8=A1=A8=E4=B8=AD?= =?UTF-8?q?=E8=BD=AC=E4=B8=8D=E4=B8=80=E8=87=B4=E7=9A=84=E6=94=B9=E5=8A=A8?= =?UTF-8?q?=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 docs/superpowers/plans/2026-10-15-material-sources-db-migration.md: 拆成两个任务的实施计划(表结构改造 + 基础货源派生;32 条多供应商 补入),每个任务自带隔离库验证步骤,各自结束时数据库都自洽可用 - 修正设计文档 7.1:原改动清单按「逐行删值」列出,与 D10 的临时表 中转方案矛盾。走临时表后 199 行 VALUES 一个字都不用改,改动项由 12 条收敛为 9 条,并说明这正是选中转而非逐个删值的原因 --- ...026-10-15-material-sources-db-migration.md | 428 ++++++++++++++++++ ...5-material-supplier-manufacturer-design.md | 13 +- 2 files changed, 434 insertions(+), 7 deletions(-) create mode 100644 docs/superpowers/plans/2026-10-15-material-sources-db-migration.md diff --git a/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md b/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md new file mode 100644 index 00000000..b1c2d27e --- /dev/null +++ b/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md @@ -0,0 +1,428 @@ +# 物料多供应商与多制造商:数据库改造实施计划 + +> **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`。 +- **所有外键均为单列**,不引入任何复合外键(spec 的 D7)。公司隔离与物料一致性由应用层校验。 +- `status` 取值固定为 `active` / `obsolete`(D8),不用数字。 +- 货源表表名沿用 `material_suppliers`,不改名(D5)。 +- `init.sql` 必须保持**可重复执行**(重跑不报错)。 +- **验证一律使用隔离库**,绝不碰真实库。本机 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 materials` 之后插入: + +```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(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 id 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(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 +); +``` + +- [ ] **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: 确认脚本可重复执行** + +```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 -u root -p1234 +mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) FROM material_suppliers;" +``` + +预期:不报错,且第二次执行后仍是 `129`(不翻倍)。 + +- [ ] **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: 确认脚本仍可重复执行** + +```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 -u root -p1234 +mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) FROM material_suppliers;" +``` + +预期:不报错,仍为 `161`。 + +- [ ] **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` 仍是单文件、可重复执行、空库一次跑通。 +- 后端与前端**尚未适配**,此时启动应用会因查询旧列而失败——这是预期状态,应用层改造另行计划。 diff --git a/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md b/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md index 5b18fdcb..eb0b77a2 100644 --- a/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md +++ b/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md @@ -244,13 +244,12 @@ CREATE TABLE material_suppliers ( | 3 | `CREATE TABLE organization_partners`(`:27`) | 删 `uk_organization_partners_org_id`(对应 D9) | | 4 | 新建 `material_manufacturers` | 见 6.1 | | 5 | 新建 `material_suppliers` | 见 6.2,替换原 `:34` 的建表语句 | -| 6 | 北京物料 INSERT 列清单(`:663`) | 删 `supplier_id, manufacturer_id` 两个列名 | -| 7 | 北京 16 行 VALUES | 每行删对应的 2 个值 | -| 8 | 北京 `ON DUPLICATE KEY UPDATE`(`:731-735`) | 删 `supplier_id = new.supplier_id, manufacturer_id = new.manufacturer_id` | -| 9 | 武汉物料 INSERT 列清单(`:739-741`) | 同 6 | -| 10 | 武汉 183 行 VALUES | 同 7 | -| 11 | 武汉 `ON DUPLICATE KEY UPDATE`(`:1495`) | 同 8 | -| 12 | 新增货源数据 | 161 行 `material_suppliers` + 10 行 `material_manufacturers` | +| 6 | 两处物料 `INSERT`(`:663`、`:739`) | 目标表由 `materials` 改为临时表;**列清单与 199 行 VALUES 原样保留** | +| 7 | 两处 `ON DUPLICATE KEY UPDATE`(`:731-735`、`:1495`) | 整段删除——临时表是空的,不需要幂等 | +| 8 | 新增分流语句 | 临时表 → `materials` / `material_manufacturers` / `material_suppliers`,见 7.2 | +| 9 | 删除临时表 | 分流完成后清掉 | + +**注意第 6、7 项:临时表中转的最大好处是 199 行 VALUES 一个字都不用改。** 原本要逐行删掉 398 个值(199 行 × 2 列),既繁琐又容易漏;改走临时表后,那 199 行原样搬进临时表,删列的影响只体现在分流语句的列清单上。这也正是 D10 选择中转而非"逐个删值"的原因。 ### 7.2 161 条货源数据的生成方式:临时表中转(D10)