2026-07-12-crm-product-category-data-migration-design.md 7.1 KB

CRM 产品分类数据移植设计

日期:2026-07-12
范围:仅 crm_product_category 表(P1 第一步)
权威业务设计:docs/superpowers/specs/2026-05-30-crm-migration-design.md §4.1、§7.6 第 4 步
数据操作手册:docs/data-migration/phase-1-crm/step-01-product-category.md


1. 背景与目标

山峡 1.0 CRM 产品分类在 sx 库中为 bigint 主键 + 若依树字段;门户 2.0 已具备产品分类 前后端能力(web-admin/src/views/crm/product-category/、backend/app/crud/crm/product.py),但目标表 当前无业务数据。

目标:

  1. 将 sx crm_product_category 迁入门户 crm_product_category
  2. 生成 crm_product_category_id_mapping.csv,供 产品(step-02) 及报价/合同行项目 category_id 使用
  3. 脚本可 --dry-run、可重复执行、全过程写入 phase-1-crm/logs/

2. 源数据概况(sx.sql 快照)

项 值
有效行数(INSERT) 5
树深度 仅顶级(parent_id = 0)
分类名称 有机原料、无机原料、水处理溶剂、其他、未分类
create_by 均为 1(CRM 若依管理员)
tenant_id 000000

后续若生产库存在子分类,脚本须按 父先于子 拓扑排序插入(本快照无子节点)。

3.1 主键策略

遵循全局策略:05-id-preservation-policy.md(2026-07-12 确认)。

与地磅 migrate_dibang_data.py 一致:源 bigint 主键原样写入目标 VARCHAR。

源 id 目标 id
1975766404158746625 "1975766404158746625"

好处:

  • crm_product.category_id、报价/合同行 category_id 可直接 str(源值) 写入,无需查 category mapping
  • crm_product_category_id_mapping.csv 仍为恒等映射,便于审计与二次移植对账

注意:用户相关外键仍经 user_mapping;业务实体 id 均原样保留。


3. 表结构差异

源字段 (sx) 目标字段 (shanxia2.0) 转换规则
id bigint id varchar(64) 原样保留:str(source_id)(如 1975766404158746625);mapping 为恒等映射
parent_id bigint parent_id varchar(64) 0→'0';否则 str(parent_id)(与源同值)
category_name category_name 原样
— category_code NULL(源无此列)
ancestors — 丢弃;门户用 parent_id + level 组树
sort order_num 原样
status varchar status char(1) 保持若依语义:0=正常、1=停用(见 §4)
— level parent_id=0 → 1;否则父 level+1
tenant_id tenant_no 固定 'shanxia2.0'
create_dept bigint create_dept_id 默认 ''(或后续 dept_mapping)
create_by bigint create_by varchar(64) 经 user_mapping_result.csv 的 crm_user_id
create_time create_time 原样
update_by update_by 同 create_by 映射
update_time update_time 原样
del_flag del_flag 原样(0/1)
remark remark 原样
version — 丢弃

4. 状态字段语义(重要)

产品分类页使用字典 sys_normal_disable(与若依一致):

  • 0 = 正常(启用)
  • 1 = 停用

前端默认 formData.status = '0'(product-category/index.vue)。

源数据 5 条均为 status='0' → 目标 仍为 '0',不要按 Model 注释「1启用 0停用」做反转。


5. 用户 ID 映射

依赖 P0 产物:docs/data-migration/mappings/user_mapping_result.csv

源 create_by 处理
1(若依 admin) 映射到门户 sys_user.user_no='admin' 的 id
其他 CRM sys_user.user_id 查 crm_user_id 列 → new_user_id
无匹配 create_by/update_by 置 NULL,日志 WARNING

源 5 条分类的 create_by=1、update_by=1 → 预期写入门户 admin UUID。


6. 依赖与阻塞

依赖 状态 说明
P0 用户映射 ✅ 已有 user_mapping_result.csv create_by 转换
P0 字典 ✅ 无需导入 sys_normal_disable 已对齐
P0 部门 可选 create_dept_id 可暂空
目标表 DDL ✅ shanxia2.0.sql 已存在 与 CrmProductCategory ORM 一致

不依赖产品、客户、附件。


7. 脚本设计 migrate_crm_product_category.py

7.1 接口

cd backend && source venv/bin/activate
ENV_FILE=.env.production python scripts/migrate_crm_product_category.py --dry-run \
  --source-url mysql+pymysql://user:***@host:3306/sx \
  --user-mapping ../docs/data-migration/mappings/user_mapping_result.csv

ENV_FILE=.env.production python scripts/migrate_crm_product_category.py \
  --mapping-out ../docs/data-migration/mappings/crm_product_category_id_mapping.csv

环境变量:

  • DATABASE_URL / ENV_FILE:目标库
  • SX_SOURCE_DATABASE_URL:默认源库(可覆盖 --source-url)

7.2 算法

  1. 读源表 WHERE del_flag = 0(可选 --include-deleted)
  2. 按 parent_id 拓扑排序(Kahn 或递归:父节点先于子节点)
  3. 对每行:
    • new_id = str(source_id)(恒等,非 generate_uuid())
    • 若目标已存在同 id → SKIP(默认)或 --update 覆盖非主键字段
    • 否则 INSERT
  4. 写 mapping CSV:source_id 与 new_id 相同
  5. 打印 stats:inserted / skipped / warnings

7.3 幂等策略

模式 行为
默认 mapping 存在则 skip
--update 按 source_id 更新名称、排序、状态、parent、level
--force 测试库:软删目标租户下已有分类后重灌(禁止生产默认开启)

8. 验收

8.1 数据

-- 源
SELECT COUNT(*) FROM crm_product_category WHERE del_flag = 0;  -- 预期 5

-- 目标
SELECT COUNT(*) FROM crm_product_category WHERE tenant_no='shanxia2.0' AND del_flag=0;

SELECT id, parent_id, category_name, order_num, status, level
FROM crm_product_category WHERE tenant_no='shanxia2.0' ORDER BY order_num;

8.2 界面

  • 路径:/#/crm/product-category(或菜单「产品分类」)
  • 账号:方金强 / 销售主管权限账号
  • 预期:5 个顶级节点,名称与源一致,状态显示「正常」

8.3 下游

  • mappings/crm_product_category_id_mapping.csv 行数 = 源有效行数
  • step-02 产品迁移脚本可读取该文件转换 crm_product.category_id

9. 风险

风险 对策
生产库分类数 > 快照 5 条 脚本按全量 SELECT,不仅依赖 sx.sql
同名分类重复灌入 幂等靠 source_id mapping;二次移植先加载 mapping
status 误反转 固定走 sys_normal_disable 语义,单测 5 条
create_by=1 无映射 显式 fallback 到 admin
子分类 parent 未先插入 拓扑排序 + 孤儿检测报错

10. 不在本步范围

  • crm_product 及产品附件
  • category_code 自动生成(源无数据,保持 NULL)
  • 部门 create_dept_id 精细映射(可后续增强)