feat: 组织隔离(IRIS 单实例)与出料功能基础
本轮之前累积的未提交工作,一并固化:
- 组织隔离:同一份代码部署给不同部门只需改 config 的 ORG_DEPARTMENT 与
MATERIAL_CATEGORY_PREFIX。过滤点在登录/人员列表/物料/MOM 出库单四处,
全部服务端钉死,客户端传什么都放不大。
★ 物料必须用**前缀** LIKE,不能反推成 ILIKE '%IRIS%':MOM 里 LICA 的物料是
`LICA/<中文>`,而本部门分类树里另有 `IRIS/成品/LICA/…`(本就属于本部门),
前缀匹配天然区分得开。
- MOM 出库单只读查询(直连 MOM 库):不走 MOM 现成的 /outbound 接口 ——
那个要 JWT + permission_required,且对非特权账号按 consumer_name 做行级
隔离,服务账号只能拿到自己名下的单。分页必须两段式(先按单号 GROUP BY
分页,再 IN 捞明细),对宽表直接分页会得到明细行数而不是单据数。
- 出料功能:产品 ↔ 出库单存档(product_outbounds)与任务 ↔ 出库明细
(task_outbound_materials),供「这台设备对应 MOM 哪张单」的展示。
⚠️ 快照一律由后端拿 ID 去 MOM 现查,不接受前端传入,否则前端可伪造单据。
This commit is contained in:
73
AGENTS.md
73
AGENTS.md
@ -12,6 +12,79 @@
|
||||
`WHERE username LIKE '%/<账号>'` 匹配,`display_name` 由 `/` 拆解得到。
|
||||
MOM 连接配置在 `app/core/mom_database.py`(同步 psycopg2 引擎)。
|
||||
|
||||
## 组织隔离(2026-09 新增)
|
||||
|
||||
本实例只服务 IRIS 部门,开关是 `app/core/config.py` 的两个值:`ORG_DEPARTMENT`
|
||||
(对 MOM `sys_user.department`)与 `MATERIAL_CATEGORY_PREFIX`(对 MOM
|
||||
`material_base.category` 前缀)。**同一份代码部署给别的部门只需改这两处。**
|
||||
|
||||
过滤点共四处,全部服务端钉死,客户端传什么参数都不采纳:
|
||||
|
||||
| 位置 | 过滤条件 |
|
||||
|---|---|
|
||||
| 登录 `auth_service.login` | `department = ORG_DEPARTMENT OR role = SUPER_ADMIN` |
|
||||
| 人员列表 `endpoints/users.py` | `department = ORG_DEPARTMENT` |
|
||||
| 物料 `endpoints/materials.py` | `category LIKE 'IRIS/%'`(`groups` 与 `items` 都要加) |
|
||||
| MOM 出库单 `services/mom_outbound_service.py` | 同上前缀 + 跨部门领用人例外(见下) |
|
||||
|
||||
- **登录是四处里唯一区分角色的**:`SUPER_ADMIN` 跨部门放行(IRIS 超管也能登
|
||||
LICA 实例,反之亦然),供运维在两个实例之间切换。其余角色(INBOUND /
|
||||
SUPERVISOR / WAREHOUSE_MGR / SALES)必须严格属于本部门。
|
||||
- ⚠️ 物料必须用**前缀** `LIKE 'IRIS/%'`,不能反推成 `ILIKE '%IRIS%'`:MOM 里
|
||||
LICA 的物料是 `LICA/<中文>`(生产配件 687 / 销售产品 89 / 维修服务 16…),
|
||||
而本部门分类树里另有 `IRIS/成品/LICA/…`(野外便携 59 / 无人机 38 /
|
||||
实验室内 34 / 高塔监测 30,共 171 条)—— 那是**挂在 IRIS 名下、给 LICA 做的
|
||||
成品**,本来就属于本部门。前缀匹配天然把前者排除、后者包含,不需要特例。
|
||||
- 刻意**不做**「查询失败退回全表」的降级 —— 那是跨部门数据泄漏。宁可查不出,
|
||||
不可查过头。
|
||||
- 已实测:人员列表 20 人(IRIS 部门)、物料 100 个分类 / 2248 条、
|
||||
`LICA/` 与 `IRIS/` 前缀交叉命中 0 条;4 个 LICA 普通账号全部登不进来,
|
||||
2 个超管(含 LICA 的)正常放行。
|
||||
|
||||
### 出库单的跨部门领用人例外
|
||||
|
||||
`config.EXTRA_VISIBLE_CONSUMERS`(默认 `依锐思,石利LICA`)里的领用人,其在 MOM
|
||||
`trans_outbound.consumer_name` 名下的单据,**即使物料分类不属于本部门也放行** ——
|
||||
他们跨两个部门领料,只按物料前缀过滤会把他们的单整批漏掉。
|
||||
|
||||
⚠️ 这是**放行**条件(SQL 里是 `OR`),与界面筛选(`AND`,只收窄)方向相反,
|
||||
两者的集合运算在 `mom_outbound_service` 里必须分开写。名单为空时整段不拼,
|
||||
退化成纯前缀过滤。已实测:空白名单 438 单 → 填入一个真实跨部门领用人后 440 单,
|
||||
填入不存在的名字仍是 438 单(不放大范围)。
|
||||
|
||||
## 出料功能(2026-09 新增)
|
||||
|
||||
回答两个问题:**这台设备对应 MOM 的哪张出库单**、**这个任务用了哪些出库物料**。
|
||||
|
||||
- `product_outbounds` —— 产品 ↔ 出库单存档。两条写入路径共用一张表,靠 `source`
|
||||
区分:`webhook`(MOM 出库回调自动存档)/ `manual`(人在界面上挂的)。
|
||||
一次出库一行;**撤回只置 `is_revoked` 不删行**(「出过又撤了」也是历史)。
|
||||
唯一约束是 `(serial_number, outbound_no)` 而非只约束单号 —— MOM 的批量出库
|
||||
是多个商品共用一个单号,只约束单号会把正常的批量单误杀。
|
||||
- `task_outbound_materials` —— 任务挂载的出库物料,**明细级快照**(一行 = MOM
|
||||
`trans_outbound` 的一行)。为什么存快照而不只存单号:MOM 的物料名/规格要经
|
||||
`COALESCE` 三表 JOIN(`stock_buy`/`stock_semi`/`stock_product` → `material_base`)
|
||||
才能解析,Track 跨库 JOIN 不了,只存单号则 MOM 一挂就看不到已挂内容。
|
||||
纯引用**不记用量**(`quantity` 是出库单原值,**不是**本任务用量)。
|
||||
- `mom_outbounds.py` / `mom_outbound_service.py` —— 直连 MOM 库的只读查询,供选择器
|
||||
搜索用。不走 MOM 现成的 `GET /api/v1/outbound`:那个接口要 JWT +
|
||||
`permission_required`,且对非特权账号按 `consumer_name` 做行级隔离,Track 用
|
||||
服务账号调只能拿到该账号名下的单,不是全量。分页必须**两段式**(先
|
||||
`GROUP BY outbound_no` 分页拿单号,再 `WHERE outbound_no IN (…)` 捞明细),
|
||||
绝不能对 join 后的宽表直接分页 —— 那是明细行数不是单据数。
|
||||
- ⚠️ 快照一律由后端拿 ID 去 MOM 现查,**不接受前端传入**,否则前端可伪造单据。
|
||||
- ⚠️ 写挂载行时**不要** `append` 到 ORM 集合(`task.outbound_materials`):集合在
|
||||
flush 后处于「未加载」态,碰它会触发懒加载,异步 session 下直接抛
|
||||
`MissingGreenlet`。只写 FK,响应构造前走一次真正的查询。
|
||||
|
||||
**与 LICA 实例(`~/track-lica`)的差异**:LICA 那边出库单还要按**业务分组数据
|
||||
范围**再收敛一层(范围 ∩ 组 ∩ 个人),本实例没有分组体系,故 `mom-outbounds`
|
||||
**没有 `group_id` 参数**,挂载时也不做额外的可见性校验。不要为了「对齐」加回来 ——
|
||||
那会引入一份没有数据支撑的过滤。
|
||||
|
||||
⚠️ 两套实例共用同一个 MOM 库,但**数据卷相互独立**。永远不要在 `/home/yueli/track`
|
||||
下执行 `docker compose down -v`。
|
||||
|
||||
## 本地起环境(关键,踩过的坑都在这)
|
||||
|
||||
1. **本机没有 Postgres 时需要先装**(容器内 `sudo` 可用):
|
||||
|
||||
Reference in New Issue
Block a user