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:
2026-09-23 15:18:06 +08:00
parent edd43fec29
commit e45c97bd1f
22 changed files with 2000 additions and 48 deletions

View File

@ -15,6 +15,7 @@ from app.core.security import (
)
from app.core.mom_database import MomSessionLocal
from app.core.logging import user_var
from app.core.roles import SUPER_ADMIN
from app.schemas.user import LoginResponse, UserResponse
security = HTTPBearer()
@ -24,15 +25,29 @@ def login(username: str, password: str) -> LoginResponse:
"""登录 — 签发双 Token(Access + Refresh)"""
db = MomSessionLocal()
try:
# 1. 普通用户:LIKE '%/username' 模糊匹配 MOM sys_user 表
# 1. LIKE '%/username' 定位 MOM sys_user 账号,再按部门收敛 ——
# 两套 Track 实例共用同一个 MOM 库,不加部门条件的话,另一个部门的
# 普通账号也能登进来。
#
# ⚠️ 唯一的例外是 SUPER_ADMIN:超管**跨部门放行**,供运维/管理员在
# 两个实例之间切换。其余角色(INBOUND / SUPERVISOR / WAREHOUSE_MGR /
# SALES)必须严格属于本部门(settings.ORG_DEPARTMENT)。
#
# 不匹配时统一报「用户名或密码错误」,不区分「账号不存在」与「存在但
# 不属于本部门」,避免给账号探测者提供线索。
from sqlalchemy import text
result = db.execute(
text(
"SELECT id, username, department, role, password_hash "
"FROM sys_user "
"WHERE username LIKE :pattern"
"WHERE username LIKE :pattern "
" AND (department = :dept OR role = :super_admin)"
),
{"pattern": f"%/{username}"},
{
"pattern": f"%/{username}",
"dept": settings.ORG_DEPARTMENT,
"super_admin": SUPER_ADMIN,
},
)
row = result.fetchone()