补充实施计划,修正设计文档中与临时表中转不一致的改动清单
- 新增 docs/superpowers/plans/2026-10-15-material-sources-db-migration.md: 拆成两个任务的实施计划(表结构改造 + 基础货源派生;32 条多供应商 补入),每个任务自带隔离库验证步骤,各自结束时数据库都自洽可用 - 修正设计文档 7.1:原改动清单按「逐行删值」列出,与 D10 的临时表 中转方案矛盾。走临时表后 199 行 VALUES 一个字都不用改,改动项由 12 条收敛为 9 条,并说明这正是选中转而非逐个删值的原因
This commit is contained in:
parent
f8b1a479ff
commit
f18414b3ab
|
|
@ -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` 仍是单文件、可重复执行、空库一次跑通。
|
||||||
|
- 后端与前端**尚未适配**,此时启动应用会因查询旧列而失败——这是预期状态,应用层改造另行计划。
|
||||||
|
|
@ -244,13 +244,12 @@ CREATE TABLE material_suppliers (
|
||||||
| 3 | `CREATE TABLE organization_partners`(`:27`) | 删 `uk_organization_partners_org_id`(对应 D9) |
|
| 3 | `CREATE TABLE organization_partners`(`:27`) | 删 `uk_organization_partners_org_id`(对应 D9) |
|
||||||
| 4 | 新建 `material_manufacturers` | 见 6.1 |
|
| 4 | 新建 `material_manufacturers` | 见 6.1 |
|
||||||
| 5 | 新建 `material_suppliers` | 见 6.2,替换原 `:34` 的建表语句 |
|
| 5 | 新建 `material_suppliers` | 见 6.2,替换原 `:34` 的建表语句 |
|
||||||
| 6 | 北京物料 INSERT 列清单(`:663`) | 删 `supplier_id, manufacturer_id` 两个列名 |
|
| 6 | 两处物料 `INSERT`(`:663`、`:739`) | 目标表由 `materials` 改为临时表;**列清单与 199 行 VALUES 原样保留** |
|
||||||
| 7 | 北京 16 行 VALUES | 每行删对应的 2 个值 |
|
| 7 | 两处 `ON DUPLICATE KEY UPDATE`(`:731-735`、`:1495`) | 整段删除——临时表是空的,不需要幂等 |
|
||||||
| 8 | 北京 `ON DUPLICATE KEY UPDATE`(`:731-735`) | 删 `supplier_id = new.supplier_id, manufacturer_id = new.manufacturer_id` |
|
| 8 | 新增分流语句 | 临时表 → `materials` / `material_manufacturers` / `material_suppliers`,见 7.2 |
|
||||||
| 9 | 武汉物料 INSERT 列清单(`:739-741`) | 同 6 |
|
| 9 | 删除临时表 | 分流完成后清掉 |
|
||||||
| 10 | 武汉 183 行 VALUES | 同 7 |
|
|
||||||
| 11 | 武汉 `ON DUPLICATE KEY UPDATE`(`:1495`) | 同 8 |
|
**注意第 6、7 项:临时表中转的最大好处是 199 行 VALUES 一个字都不用改。** 原本要逐行删掉 398 个值(199 行 × 2 列),既繁琐又容易漏;改走临时表后,那 199 行原样搬进临时表,删列的影响只体现在分流语句的列清单上。这也正是 D10 选择中转而非"逐个删值"的原因。
|
||||||
| 12 | 新增货源数据 | 161 行 `material_suppliers` + 10 行 `material_manufacturers` |
|
|
||||||
|
|
||||||
### 7.2 161 条货源数据的生成方式:临时表中转(D10)
|
### 7.2 161 条货源数据的生成方式:临时表中转(D10)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user