隔离(db_migrations/phase15_warehouse_location_company.sql) - sys_warehouse_location 新增 company_name,NULL = 全局共享 - 存量 3371 行按 full_path 顶段首字母回填:Y*→IRIS(500),L*/C*→LICA(2871) - 读范围 = 本公司 + 全局;写范围 = 仅本公司 删除/改名/批量生成都加归属校验 —— 只隔离读接口等于没隔离, 否则 LICA 一条 DELETE 就能删掉 IRIS 整棵库位树 - /tree 与 /children 补上缺失的 @jwt_required() - 公司名走白名单校验,非法值 400(拼错会造出谁都看不见的库位,且无从排查) - 超管可改归属,级联整棵子树(归属撕裂会让父子可见性错乱) 改名补偿 - 级联重写子孙 full_path(原实现只改自己一行,子孙仍挂旧前缀, 而库存快照存的正是 full_path,改一次名整棵子树就和台账对不上) - 同事务改写在库台账 stock_buy/semi/product/adjustment 的快照, 以及未结束盘点会话的 scope_config.locations - 刻意不含 trans_outbound / trans_borrow 等历史单据(改历史 = 篡改操作记录), 也不含 trans_repair.customer_location(那是客户地址不是库位) 批量生成 - 起点支持三种:勾选的一批库位 / 某子树下所有末级 / 单个父级 - 已存在同名跳过 —— full_path 上有唯一约束 uniq_full_path, 不跳过会整单 500 并整体回滚,连本该新建的也一并丢失 - 数量上限改按实际创建数(各层之和 × 起点数),不再是各层相乘 (旧口径低估,3000 的上限因此能被悄悄突破) - 前端加生成结构预览:逐层数量 + 完整路径示例,边填边看 前端清理 - 删除 utils/warehouseCompany.ts 前缀白名单,WarehouseSelector 与三个 入库页改为纯渲染后端数据(隔离已下沉到后端) - 盘点页 loadLocationTree 必须带 company_name:跨域角色在后端返回 None, 不带就会拉全量、盘点范围脱离所选公司 - 盘点页切公司时清库位树缓存(原来会继续沿用上一个公司的树) - 修复批量删除只取叶子导致中间层节点残留成孤儿
149 lines
7.2 KiB
PL/PgSQL
149 lines
7.2 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 库位归属公司化: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;
|