|
|
9778b8e4b2
|
feat(audit): MOM 回调归因到实际操作人,不再显示「未认证」
外部回调走 X-API-Key 鉴权、没有 JWT,JWT 依赖不执行,审计中间件读到的
request.state.audit_user 永远是空 —— 操作审计里就出现一堆没有归属的
「外部系统对接」记录,看不出是谁扫的码。
MOM 载荷里本来就带着实际操作人(operator,即 MOM 侧扫码的那位),写进
request.state 即可让审计归因到人;顺手解析中文姓名(查不到也不影响审计,
前端会回退显示账号)。
⚠️ 调用位置必须在 X-API-Key 校验【之后】:密钥不对说明载荷本身就不可信,
此时把 operator 写进审计等于允许伪造人。放在部门校验之后同样有意为之 ——
被拦下的外来消息不该留下任何归属痕迹。
取不到操作人时写 "MOM系统" 而非留空:「MOM系统」至少说明这是一次机器回调,
比继续显示「未认证」(读起来像"一个匿名的人")更准确。
本函数与 IRIS 实例(~/track)代码体逐行一致,只差 docstring —— 两侧审计口径
必须一样,否则排查时日志对不上。约定已记入 AGENTS.md。
实测:MOM 回调后审计记录显示实际操作人姓名;无 operator 时显示「MOM系统」。
|
2026-09-22 13:45:00 +08:00 |
|
|
|
24a15126d0
|
fix(webhook): 忽略原因与 IRIS 实例统一为 ignored_company
IRIS 侧用的是 ignored_company,LICA 侧我原先写的是 org_mismatch ——
MOM 不解析这个字段,但排查时两边日志要对着看,字段名不一致会白白浪费时间。
统一成 IRIS 已在用的 ignored_company(IRIS 在线上,不该为这点小事动它)。
IRIS 侧实测建议里说的「语法编译检查证明不了行为」是对的,我这边也是按
真实载荷逐个验的。
顺带把约定写进 AGENTS.md —— 包括「两边规则刻意相反、不要统一」这条,
很容易被后来者顺手改掉。
实测:
LICA -> {"ok":true,"matched":false} (无 reason = 通过校验)
IRIS/空串/缺失 -> {"ok":true,"matched":false,"reason":"ignored_company"}
outbound 同规则
|
2026-09-22 13:14:24 +08:00 |
|
|
|
a68b2bbca0
|
feat(webhook): 部门校验 —— 只认 company_name == "LICA"
MOM 现在同时对接 IRIS 与 LICA 两个 Track 实例,按载荷里的 company_name 分流。
本实例采取**严格白名单**:
· company_name == "LICA"(strip 后)→ 正常处理
· 空白 / 缺失 / "IRIS" / 未知值 → 忽略
⚠️ 与 IRIS 实例的策略**刻意相反**,别"顺手统一成一样":
IRIS 对空白值要放行 —— MOM 判定不出公司时会回落到指向 IRIS 的扁平配置,
不收就彻底丢了。
LICA 没有兜底角色,空白值只可能来自「MOM 没判定出公司」,那本就该由 IRIS 兜。
**宁可漏,不可误收** —— 误收会把别的部门的设备状态改掉,那是数据污染,
比漏一条通知严重得多。
实现:
· MomInboundPayload / MomOutboundPayload 补 company_name 字段
(不补的话会被 Pydantic 静默丢弃,校验无从谈起)
· 新增 _belongs_to_this_org(),在**鉴权之后、匹配产品之前**拦截
· 拦截时返回 200 + matched=False + reason=org_mismatch —— 与「未命中」保持
同一契约,避免 MOM 侧把它当成故障去重试
· 顺带补上 outbound 一直在发、但此前被丢弃的 outbound_type 字段
实测:
· 7 种 company_name 取值全部符合预期
("LICA" / "LICA " 通过;"IRIS" / "" / 缺失 / null / "UNKNOWN" 全部忽略)
· 无 X-API-Key 仍返回 401(鉴权没有被绕过)
· 真实闭环:LICA 入库回调 → 产品「待仓库收货」→「已入库」✅
· 对照:对同一产品发 IRIS 的回调 → 状态纹丝不动 ✅
· 测试数据已还原为原始值
|
2026-09-22 13:09:22 +08:00 |
|
|
|
af8dcc9e0a
|
fix(分组): 候选人排除已在本组的成员
反馈:某人明明已经在成员名单里,点「添加成员」却还能再选他一次 ——
界面自相矛盾,看着就像分组没生效。
member-candidates 新增 group_id 参数:传了就把该组已有成员排除掉。
⚠️ MOM 与 Track 是**两个独立的库**,没法 JOIN,所以先在本库查出该组成员
集合,再在内存里排掉。LICA 只有十几人,这个取舍是划算的(已加注释)。
前端配合:
· openAddMember 传 selectedId —— 面板一打开,候选就是干净的
· 添加成功后**保持面板打开并重拉候选**:刚加进去的人立刻从方块里消失,
既能连续加人,也不会出现「刚加完还能再选他一次」
实测:
生产小组(成员=魏会军):不带 group_id 17 人含他;带 group_id=3 → 16 人已排除
维修大组同理排除齐海建
加「李婧」后她立即从候选方块消失;测试数据已清理
|
2026-09-21 17:52:12 +08:00 |
|
|
|
9598c3fb9f
|
feat(分组): 成员改为标签云展示 + 添加成员支持多选批量
1) 成员不再一人一行
原先用 antd Table:成员一多就要滚很久,右侧还大片留白。
改为标签云(flex-wrap),一行能放好几个。
交互一并简化:
· 点标签正文 → 切换组长(悬停有提示)
· 点 × → 移出分组
· 无权限时两者都不出现,退化成纯展示
组长用金色标签 + 皇冠图标,一眼可辨。
2) 添加成员支持多选
Select 加 mode="multiple" + maxTagCount="responsive",可搜可多选、批量提交。
后端 POST /groups/{id}/members 由单个 user_id 改为 user_ids 数组,
返回 {added, skipped, skipped_names}:
· 已在组内的**静默跳过**而非整批 409 —— 多选时难免选中已在组里的人,
为此让整批失败体验很差;前端会把跳过的名单说清楚,
否则用户会疑惑「明明选了 5 个,怎么只多了 3 个」
· 批量里混入超管则**整批拒绝并指名道姓**,不制造「部分成功」这种
难以解释的中间状态
实测(后端):
批量加 3 人 → added 3
重复提交(2 旧 1 新) → added 1 / skipped 2,整批仍成功
混入超管 → 400「以下账号是超级管理员,业务分组对其不生效:sunxia」
空列表 → 422
测试成员已清理
|
2026-09-21 17:44:06 +08:00 |
|
|
|
5095756571
|
fix(分组): 把超管排除在分组之外 + 添加成员改用带搜索的下拉
1) 超管不进分组
超管的数据范围是硬编码全厂(resolve_data_scope 规则 1),业务分组对他
根本不生效。把他放进成员名单会造成两处矛盾:
· 界面上「他在这个组里」暗示他受该组约束,但实际不受;
· 若再给他打组长标记,会出现「组长却不受组范围限制」的怪状态。
所以:
· member-candidates 的 SQL 加 `COALESCE(role,'') <> 'SUPER_ADMIN'`
(LICA 19 人 → 17 人)
· add_member 再挡一道并给出明确原因,防止绕过界面直接调接口
· _is_super_admin_account 在 MOM 查询失败时**保守当作超管拦下** ——
误拦只是加不进去,误放会留下脏数据
2) 添加成员的下拉改用 antd Select
原先是原生 <select>:LICA 近 20 人,展开会整屏铺开、且不能搜索,又长又难选。
改为带 showSearch 的 Select(按姓名或账号过滤,optionFilterProp=label),
并在未选人时禁用「添加」按钮,避免无意义报错。
实测:
候选人数 19 → 17,超管已不在列表中
直接 POST 加 sunxia / xingyouwu → 400 并给出原因
普通成员 duwensheng 加入 → 201,移出 → 204(对照组正常)
|
2026-09-21 17:41:17 +08:00 |
|
|
|
15097baa2b
|
feat(权限): 业务分组对全员开放(操作分层)+ 操作审计仅超管可见
需求:业务分组开放给所有人,主管可操作、其余人只读;操作审计前端不显示。
⚠️ 这里有个必须收窄的边界:按数据范围规则,被分进组的 SUPERVISOR 会从
「全厂」降级为只看本组。若允许他改可见范围,他把自己那组改成「生产+售后」
就恢复全厂视野;若允许建组,他新建一个组再把自己塞进去,同样绕过。
**「能管成员」与「能配范围」必须分开** —— 前者安全(组长给自己加组会被
唯一约束挡住、移出自己只是失去权限),后者是提权入口。
因此分层如下:
| 谁 | 看 | 改 |
|------------------|--------------|------------------------------------|
| SUPER_ADMIN | 所有组 | 全部(建/改/删组、配范围、管成员) |
| 主管 / 本组组长 | 自己所属的组 | **仅本组成员**(加人/移人/设组长) |
| 普通成员 | 自己所属的组 | 无(只读) |
后端(groups.py):
· 去掉路由级的 require_roles(SUPER_ADMIN),改为逐端点校验
· list_groups / get_group:非超管只返回自己所属的组;看别组详情 403
(连「这个组存在但你没份」都不暴露,避免被拿来推测组织架构)
· create/update/delete:_require_super —— 这三个 + 配范围是提权入口
· add/update/remove member:_can_manage_members(超管 / 主管 / 本组组长)
· GroupOut 新增 can_manage_members / can_manage_group / is_my_leader
—— 由服务端算好下发,前端不按 role 自行推导(组长身份是按组算的)
前端:
· AdminGroupsPage 去掉整页超管门禁,改为按能力显示按钮:
无权限时「组长」列退化成纯展示而非可点按钮
没有任何组时给「你还没有被分配到任何业务分组」的引导提示
· AdminLayout 的 MENU 支持 superOnly 标记,「操作审计」只对超管显示
(后端 /audit/* 本就挂了 require_admin,这里只是不给入口,
否则普通用户点进去只会看到一堆 403)
实测(四类账号):
建组/改范围:仅超管通过,其余全 403
管成员:超管/主管/本组组长通过;非本组 403;跨组组长 403
可见性:主管未分组看到 0 个组、维修组长只看到维修大组、生产组员只看到生产小组
详情页能力标记正确下发;越权查别组详情 403
|
2026-09-21 17:38:07 +08:00 |
|
|
|
394e1e39c3
|
feat(分组权限): 统计接口接入数据范围(dashboard / analytics / screen 共 20 个路由)
这三组接口此前是「上帝视角」且**匿名可访问**,现在全部挂 get_data_scope ——
既要求登录、又按业务分组范围过滤。顺带堵上了 AGENTS.md 点名的风险:
/dashboard/people-history/export 此前匿名即可批量导出全员工时台账。
dashboard(11 个函数 / 24 处注入)
· Product 主体 → product_where();Task、TaskLog 主体 → 先 join(Product) 再 task_where()
· 「卡片数字」与「下钻明细」成对出现的地方用同一谓词,避免「按钮显示 2、点开却是 0 条」
· get_user_operations 的 func.count() 改为 func.count(TaskLog.id) —— 显式化,不依赖
join 形状(当前是 many-to-one 不会放大,但这样写更稳)
· unread_notif **刻意不过滤**:Notification.task_id 可空,按 Product 过滤会漏掉
无任务关联的提醒(就地注释说明)
· get_my_stats 本轮不动 —— 它按本人归因,语义上不受分组影响
analytics(4 个函数 / 9 条语句)
· get_analytics_options 的 4 条独立语句全部处理 —— 它是筛选栏下拉的选项源,
不过滤的话维修组能在下拉里看到生产组的人(最易漏的一处)
· get_device_records 的 product_id 查号是安全闸:范围外 SN 查不出 → 直接返回 []
screen(3 个函数)
· month_production **不做特判** —— 维修组的「本月生产数」本来就该是 0
· get_wip_distribution 按范围裁剪工序柱子,但坚持「恒 0 才裁、有数必现」,
保证 total == sum(items) 在任何 scope 下都成立
实测(17 个端点):超管全部 200、匿名全部 401。
造 1 生产 + 1 售后产品后:
超管 products=2 / wip-matrix 2 行 / options 2 个型号
生产组 products=1 / wip-matrix 1 行 / options 1 个型号
维修组 products=1 / wip-matrix 1 行 / options 1 个型号
列表与统计口径一致;未分组用户在过渡期开关下仍走 ungrouped_fallback。
测试数据已还原。
|
2026-09-21 17:22:01 +08:00 |
|
|
|
3c1c5d6fb5
|
feat(分组权限): 分组管理接口 + 管理页 + 端到端验收
后端 endpoints/groups.py(**仅 SUPER_ADMIN**):
· 组的 CRUD(两级,子组不配范围则继承父组 —— 生产大组配一次,下面的
生产/测试小组都不用再配)
· 成员增删 / 设组长 / 候选人下拉(复用 MOM 查询口径,部门已钉死为 ORG_DEPARTMENT)
· **删组仅限空组**:级联删是一次静默的批量权限变更,误点一下一批人就突然
看不到数据了;强制「先移人再删组」多一步,但出错时是可见的
· 停用组的语义写死在接口文档:成员**立即**退回未分组状态
为什么只有超管能管分组(不是偏好,是必须):
被显式分进组的 SUPERVISOR 会从「全厂」降级为只看本组;若允许主管管理分组,
他把自己移出组就能恢复全厂视野 —— 这是一条现成的提权路径,分组对他无效。
前端:
· AdminGroupsPage:组列表 + 可见范围勾选 + 成员管理 + 组长标记 + 二次确认
· AdminLayout 加菜单项,页头显示数据范围徽标 —— 空范围(未分组)用橙色显眼
提示,否则用户看到空列表会以为系统坏了,这是最难排查的一类反馈
· AuthContext 登录后补拉一次 /auth/me 拿 scope(登录接口不查库、不返回它)
· constants/task.ts 新增 isSuperAdmin,不手写 === 比较
端到端验收(实测):先造 1 生产 + 1 售后产品,然后
生产组员 → 1 条,全 PRODUCTION;范围经「生产小组 → 生产大组」继承而来
维修组员 → 1 条,全 AFTER_SALES
超管 → 2 条,全量
任务列表 total 与 returned 一致(验证 count/select 双过滤)
扫码跨组仍 200(符合「能看、不能操作」的既定决策)
停用维修大组 → 成员立即退回未分组
上述测试数据已还原
|
2026-09-21 17:10:33 +08:00 |
|
|
|
cfcfca7269
|
feat(分组权限): 业务分组模型 + DataScope 判定 + 列表接口接入
第一阶段:模型 → 判定 → /auth/me → 列表。统计接口与前端管理页随后。
1) 模型与迁移(head 从 k1l2m3n4o5p6 推进到 l1m2n3o4p5q6)
· business_groups 组定义,parent_id 表达「大组 > 小组」
· business_group_phases 可见范围,独立成表以支持多选 —— 需求要求
「范围可配置、不要写死」,单列存不下多个 phase
· business_group_members 成员,一人可属多组(这是「同时看生产+维修」的实现)
只建表、不写种子数据,所以可以先部署代码再建组。
2) DataScope 判定模块(app/services/data_scope_service.py)
全仓库唯一的权限谓词来源,业务代码里不准再出现 lifecycle_phase 过滤。
两条红线照抄部门隔离的教训:
· None(不限) 与 frozenset()(空) 语义相反,绝不共用一个哨兵值
· 空集合必须显式 false() —— SQLAlchemy 对 in_(()) 生 成 IN (NULL),
一旦退化成不过滤就是全量泄漏
解析优先级:SUPER_ADMIN 硬放行(不可被分组覆盖)
> 显式分组(分组优先于角色)
> 未分组 SUPERVISOR 默认全厂
> 未分组普通用户
3) 过渡期开关 DATA_SCOPE_UNGROUPED(默认 ALL)
直接上严格模式会让所有未分组工人当场看不到自己的任务、现场停摆。
默认 ALL 先放行并打 WARNING 记录「谁还没分组」,配好组后再改 NONE。
4) /auth/me 返回 scope,phase 中文标签由服务端下发
—— 前端已有两份 phase 词表副本,不再加第三份。
5) 列表接口接入
· get_all_products:过滤加在 offset/limit 之前(其下有 6 段基于 product_ids
的批量预计算,过滤晚了等于算完再丢)
· get_all_tasks:谓词进【共享 filters】,保证 count 与 select 两条独立语句
同时生效,否则 total 与实际页不一致、移动端 hasMore 判断跟着错
· 扫码 get_product_by_serial 刻意不过滤,理由写死在 docstring 里
实测:空 scope 生成 false、受限 scope 生成 JOIN + IN 谓词;
/products 返回 2 条、/tasks 的 total 与 returned 一致。
|
2026-09-21 17:07:46 +08:00 |
|
|
|
f9f3d90f96
|
fix: 二维码接口去鉴权(修复破图)+ 产品序列号加部门前缀
1) 二维码破图
/products/qrcode/{sn} 带了 Depends(get_current_user),而前端是用
<img src="/api/v1/products/qrcode/{sn}"> 引用它的 —— <img> 无法携带
Authorization 头,请求必然 401,页面上就是破图(同时刷大量 401 审计)。
该接口不查库、只把调用方传进来的字符串渲染成二维码,没有数据泄露面,
故去掉鉴权。刻意不做 ?token= 兜底:JWT 进 URL 会渗进访问日志、浏览器
历史与 Referer,比它想解决的问题更糟。
实测:HTTP 200 / image/png / 300x300。
2) 序列号部门前缀
新增 config.SERIAL_PREFIX(LICA 为 "L"),counter_service 生成
{前缀}{15 位 HEX},总长仍严格 16 位 —— products.serial_number 是
String(16),前端 TaskTreeViewer / ManualInput / ScanPage 多处按 16 位
校验,不能改总长。IRIS 实例该值为空串,格式保持原样。
实测 LICA 生成 L000000000000002。
前端本就兼容带字母的序列号:TaskTreeViewer 的占位符示例就是
X20260801000001,ScanPage 的 replace(/[^a-zA-Z0-9]/g,"") 也不会滤掉 L。
|
2026-09-21 16:34:56 +08:00 |
|
|
|
347b7b6a68
|
feat: LICA 部门独立实例 — 组织隔离与端口/标识改造
派生自 IRIS 实例的 feature/ai-audit-update @ 192c8ee,在同一台机器上独立运行。
隔离机制(开关集中在 app/core/config.py 的 ORG_DEPARTMENT / MATERIAL_CATEGORY_PREFIX):
- 登录:sys_user 查询增加 department 条件,非本部门账号一律 401
- 人员列表:服务端钉死部门、忽略客户端传参;删除「异常退回全表」的降级分支
- 物料:groups 与 items 都增加 category LIKE 'LICA/%' 前缀过滤
- 人员操作统计:把硬编码的 department='IRIS' 改为配置项
物料为什么用前缀而不是 LIKE '%LICA%':
MOM 里存在 171 条 IRIS/成品/LICA/...(无人机/野外便携/高塔监测等),
模糊匹配会把这些 IRIS 物料漏给 LICA。实测前缀匹配命中 795 条 / 5 个分组。
部署隔离:
- 端口 8030/8031/8032,容器名 lica_*,卷 lica_pgdata(与 IRIS 完全独立)
- 服务名改为 lica_backend,避免在 projects_default 网络上与 IRIS 的 backend
重名 —— 否则将来任何一方写 http://backend:8000 会随机打到另一个部门
- SECRET_KEY 重新生成:实测两边 token 互不通用(双向 401)
客户端标识(不改会导致两个部门的客户端互相覆盖):
- Tauri identifier 改 com.lica.production(否则桌面端互相覆盖安装,且共用
WebView 数据目录会让 track_admin_token 串号)
- uni-app appid 改 __UNI__D2F4A19(否则同机 APK 互相覆盖、wgt 热更新串号)
- uni-app 地址端口 8011 → 8031(收敛在 utils/config.js 单一来源)
- sync-watch.sh 的 DST 指向 LICA 专属 HBuilderX 目录(否则会把源码灌进 IRIS 工程)
排除项:未复制 deploy.sh / deploy_full.sh / docker-compose.prod.yml ——
它们写死了 IRIS 的生产服务器,误跑会覆盖线上系统。
|
2026-09-21 16:10:52 +08:00 |
|
|
|
3286a11bc7
|
chore: fork from IRIS track 供 LICA 部门独立运行
- 复制来源: /home/yueli/track @ 192c8ee (feature/ai-audit-update)
- 组织隔离目标: LICA
- 端口规划: 前端 8030 / 后端 8031 / 数据库 8032
- 已排除 deploy.sh、deploy_full.sh、docker-compose.prod.yml(IRIS 生产发布脚本)
- 已排除工作区未提交改动,取干净的 192c8ee 状态
|
2026-09-21 15:56:52 +08:00 |
|