-- ============================================================================= -- 库位归属公司化:sys_warehouse_location 增加 company_name -- -- 背景 -- 库位字典表全表共享,IRIS 与 LICA 都能看到对方的库位。此前的「隔离」是 -- 前端硬编码前缀白名单(inventory-web/src/utils/warehouseCompany.ts: -- IRIS→['Y'],LICA→['C','L']),改前端即可绕过,且新增公司必须改代码。 -- 更有甚者,/tree 与 /children 两个读接口连 @jwt_required() 都没有。 -- -- 本次把「归属」从命名约定升级为数据字段,隔离下沉到后端。 -- -- 口径(依据 2026-09-29 全表摸底,实测 3371 行 / 33 个顶级前缀) -- 顶级前缀首字母唯一决定归属,无第三种取值、无跨司同名前缀: -- Y1~Y8 → IRIS 500 行 -- L1~L14 / C10,C11 / CC1~CC9 → LICA 2871 行 -- 故按「full_path 顶段首字母」回填,而非裸 LIKE 'Y%': -- split_part 取顶段是精确匹配,不受 LIKE 通配符(_ / %)影响 —— -- 库位名由用户自由输入,含下划线时 LIKE 会误命中。 -- -- ★ L14(仅 2 个节点,同族其余均为 217)与 CC1~CC9(各 4 个节点)结构异常, -- 疑似历史遗留/半截生成。按前缀归入 LICA,但**建议业务侧复核后**再定, -- 若需改归属,单行 UPDATE 即可(见文件末「手工修正」段)。 -- -- 安全性 -- 纯追加式 DDL,无 NOT NULL、无 DEFAULT,既有行取值 NULL。 -- NULL 是**有意义**的取值 —— 表示全局共享(两司均可见),不是「未归类」。 -- 本迁移把全部存量行都收敛到 IRIS/LICA,迁移后应为 0 行 NULL。 -- -- 幂等 -- ADD COLUMN IF NOT EXISTS + CREATE TABLE IF NOT EXISTS + -- UPDATE 带 company_name IS NULL 条件,可重复执行; -- 重复执行不会覆盖已归类(含人工修正过)的值。 -- -- 执行(dev 库;生产为 *_prod 容器与 pgdata_prod,须由部署流程另行执行) -- docker exec -i inventory_db psql -U test -d inventory_system \ -- -v ON_ERROR_STOP=1 < 本文件 -- -- ★ 上线顺序:必须先跑本文件,再发布带 company_name 列的代码。 -- 反过来会让 /api/v1/warehouse/* 全部 500(模型引用了不存在的列)。 -- ============================================================================= BEGIN; -- --------------------------------------------------------------------------- -- 0) 备份原表(3371 行,代价可忽略)。回滚时从这里捞。 -- IF NOT EXISTS 保证重复执行时**不会**用迁移后的数据覆盖掉原始备份。 -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS sys_warehouse_location_bak_20260929 AS SELECT * FROM sys_warehouse_location; -- --------------------------------------------------------------------------- -- 1) 归属列 -- --------------------------------------------------------------------------- ALTER TABLE sys_warehouse_location ADD COLUMN IF NOT EXISTS company_name varchar(50); COMMENT ON COLUMN sys_warehouse_location.company_name IS '公司归属:IRIS/LICA;NULL = 全局共享(两司均可见)。由 warehouse.py 依据 JWT claim company_name 过滤'; CREATE INDEX IF NOT EXISTS ix_wh_loc_company ON sys_warehouse_location (company_name); -- --------------------------------------------------------------------------- -- 2) 存量回填:按 full_path 顶段首字母 -- WHERE company_name IS NULL 保证幂等,且不覆盖已归类/人工修正过的值。 -- full_path 为 NULL 的行 split_part 返回 NULL,不命中任何分支 → 保持 NULL -- (即全局共享)。摸底确认当前无此类行。 -- --------------------------------------------------------------------------- UPDATE sys_warehouse_location SET company_name = 'IRIS' WHERE company_name IS NULL AND upper(left(split_part(full_path, '/', 1), 1)) = 'Y'; UPDATE sys_warehouse_location SET company_name = 'LICA' WHERE company_name IS NULL AND upper(left(split_part(full_path, '/', 1), 1)) IN ('L', 'C'); COMMIT; -- ============================================================================= -- 执行后核对 -- ============================================================================= SELECT '=== 1) 归属分布(期望 IRIS=500 / LICA=2871 / NULL=0)===' AS "核对项"; SELECT COALESCE(company_name, '(NULL=全局)') AS 归属, count(*) AS 节点数 FROM sys_warehouse_location GROUP BY company_name ORDER BY 节点数 DESC; SELECT '=== 2) ★ 仍未归类的行(应为 0;非 0 需人工判定)===' AS "核对项"; SELECT id, parent_id, name, full_path, level FROM sys_warehouse_location WHERE company_name IS NULL ORDER BY id; SELECT '=== 3) 顶级节点归属明细(应 33 行,Y*→IRIS,其余→LICA)===' AS "核对项"; SELECT COALESCE(company_name, '(NULL)') AS 归属, split_part(full_path, '/', 1) AS 顶级库位, count(*) AS 含子孙节点数 FROM sys_warehouse_location WHERE parent_id IS NULL GROUP BY company_name, split_part(full_path, '/', 1) ORDER BY 归属, 顶级库位; SELECT '=== 4) ★ 跨公司父子节点(应为 0;非 0 说明回填把某棵树切开了)===' AS "核对项"; SELECT c.id AS 子节点id, c.full_path AS 子路径, c.company_name AS 子公司, p.id AS 父节点id, p.full_path AS 父路径, p.company_name AS 父公司 FROM sys_warehouse_location c JOIN sys_warehouse_location p ON c.parent_id = p.id WHERE c.company_name IS DISTINCT FROM p.company_name ORDER BY c.id; SELECT '=== 5) 疑似重复的 (company_name, parent_id, name)(>=2 表示可考虑加唯一约束)===' AS "核对项"; SELECT company_name, parent_id, name, count(*) AS 重复数 FROM sys_warehouse_location GROUP BY company_name, parent_id, name HAVING count(*) >= 2 ORDER BY 重复数 DESC, name; -- ============================================================================= -- 手工修正(按需单独执行,本迁移不自动跑) -- ============================================================================= -- 例:L14 结构异常,若业务确认它其实不属于 LICA: -- UPDATE sys_warehouse_location SET company_name = 'IRIS' -- WHERE full_path = 'L14' OR full_path LIKE 'L14/%'; -- -- 例:某个库位要改成全局共享: -- UPDATE sys_warehouse_location SET company_name = NULL WHERE id = ?; -- ============================================================================= -- 回滚段(仅撤销本迁移) -- ============================================================================= -- BEGIN; -- -- 方式一:从备份表整体还原(会丢弃迁移后新增的库位) -- -- TRUNCATE sys_warehouse_location; -- -- INSERT INTO sys_warehouse_location SELECT * FROM sys_warehouse_location_bak_20260929; -- -- -- -- 方式二:只去列(应用层须同步回滚,否则 /warehouse/* 报列不存在) -- DROP INDEX IF EXISTS ix_wh_loc_company; -- ALTER TABLE sys_warehouse_location DROP COLUMN IF EXISTS company_name; -- COMMIT; -- -- 备份表确认无误后可另行清理: -- DROP TABLE sys_warehouse_location_bak_20260929;