|
|
3851ebd7bc
|
fix(inbound): 导入采购单改为单价优先,修复多发/少发时单价被摊薄
原逻辑是总价优先(hasTotalPrice 分支在前):用「申请单总价 ÷ 本次入库数量」
反算单价。但申请单的 total_price 是「申请数量」的钱,实际入库数量未必相同
—— 厂家怕出问题多发(申请 100 个、实发 104 个)是常态,用总价除以实际数量
会把单价摊薄或抬高。
改为单价优先、总价仅作兜底,总价由「单价 × 实际入库数量」得出。与后端
buy_service 的补价口径一致。
顺带修一处隐患:导入新单前清掉 postTaxTotalManuallySet / postTaxTotalInput。
此前若上一单手工改过含税总价,这两个 ref 会残留,updatePrices 末尾会拿着
旧总价回头覆盖新导入的价格 —— 连续导入两张单时必现。
影响面:库管本就拿不到价格(接口剥离),此段对其一直是空转;受影响的是有
价格权限的角色在页面上导入采购单时的计算口径。
|
2026-09-22 14:09:09 +08:00 |
|
|
|
d99396ecb1
|
fix(inbound): 按单入库时从采购申请单补回成本价
库管角色没有 inbound_purchase:unit_price / total_price 权限,采购单接口
(api/v1/purchase.py:46-53)会把价格字段整个 pop 掉,前端导入时因此带不出价
—— buy.vue 的 hasUnitPrice / hasTotalPrice 双双落空,两个分支都不进,入库
成本被记成 0。
而前端 buy.vue:1936 的注释写得很清楚:"无论前端是否对当前角色显示价格,导入
都应把采购单价格带入(隐藏字段,用于成本)"。前端想带、后端不给,价格就断在
中间——不是谁写错了,是两边对"隐藏"的理解不同。
改为在落库前从采购申请单补回:价格不经过前端,库管依旧看不到采购价,权限
边界不变,但成本可追溯。仅在前端未带价时补,有权限的角色手动填过价则尊重
提交值。采购申请单的 unit_price 存的是**含税**单价(见 purchase/index.vue
列名"含税单价"),需反算不含税。
顺带清理 buy_service.py:221 的函数内局部 import —— 局部 import 会让 Python
把 PurchaseRequest 视为本函数的局部变量,导致位于其之前的引用抛
UnboundLocalError(本次新增的补价逻辑正踩到这个,测试时暴露)。改用文件顶部
的全局导入。
实测:测试申请单 含税 12.5 / 税率 13%,以库管身份提交且**故意不带任何价格
字段**,落库结果 税前 11.0619 (=12.5/1.13) / 含税 12.5 / 总价 110.6195 /
税率 13.00,全部正确;响应体经 filter_item_by_permissions 后仍不含价格字段,
权限边界未变。
另:已有 6 条历史入库单(走采购申请但单价为 0)已按「单价 × 实际入库数量」
回填,不回填总价。
|
2026-09-22 14:09:08 +08:00 |
|
|
|
91db54982c
|
feat(track): 入库/出库 webhook 按公司路由到对应实例
三个触发点此前都不带 company_name,无从路由:
· product_service / semi_service:入库通知补 material.company_name
· outbound_service:track_notifications 元组加 company(取自
stock_record.base.company_name;维修单走 repair.base,两处
relationship 均已定义),发送时带进 payload
出库路径同时**去掉**了显式传入的 url=TRACK_OUTBOUND_WEBHOOK_URL ——
url 参数优先级最高,保留它会让出库永远打到默认实例,company 路由形同
虚设。url 参数本身保留,需要强制覆盖时仍可用。
实测:
LICA 入库:待仓库收货 → 已入库,IRIS 库该 SN 0 行
LICA 出库:已入库 → 已出库,IRIS 库该 SN 0 行
IRIS 回归:对已入库设备重发入库通知,状态幂等不变
精确隔离:发一条 IRIS 通知,lica_backend 日志 +0 行、track_backend +19 行
注意:不做"两个实例都推一遍"——两个 Track 的 webhook key 是同一把、
两边都会鉴权通过,而 Track 按 serial_number/sku 匹配,相同型号设备会
同时命中两库,把 IRIS 的设备误标成已入库,且产生 2 倍审计与流转节点。
|
2026-09-22 13:13:54 +08:00 |
|
|
|
0f929468a8
|
fix(track): track-lookup 按公司路由,修复 LICA 扫码误报"物料不存在"
lookup_product() 此前写死读全局 TRACK_API_URL(指向 IRIS)。LICA 员工在
LICA 业务里扫码 → 请求打到 IRIS 库 → 查不到 → MOM 报"物料不存在"。
改用 get_current_company_filter() + resolve_track_route(…, 'api') 选实例,
复用既有工具而非另写一套。无请求/JWT 上下文时(脚本、后台任务)包一层
异常保护回落默认实例,不阻断查询。
实测(身份证 0000000000000001 只存在于 LICA 库):
LICA 员工 → company_filter='LICA' → 命中 sn='0000000000000001'
IRIS 员工 → company_filter='IRIS' → 未命中
改造前两者都会打到 IRIS,LICA 员工必然查不到。
|
2026-09-22 13:13:53 +08:00 |
|
|
|
b85be2dfdf
|
feat(track): 新增 resolve_track_route,按公司解析 Track 实例地址
resolve_track_route(company_name, channel) 支持 api / inbound / outbound
三个通道。未命中一律回落到扁平变量,**绝不返回空**——漏配一个 key 就
静默断链,比配置写错更难排查。
非空但不在路由表内的值(含 get_current_company_filter() 的
'__NO_COMPANY__' 哨兵、拼写异常的公司名)会打 WARNING 暴露出来,但不
阻断业务——这类值属于主数据问题,交给业务确认,代码不猜。
notify_track() 相应扩展:新增 channel 参数(不传则按 payload['event']
推断 inbound/outbound),url 仍为最高优先级(显式指定时不做公司路由)。
实测各分支:IRIS/LICA 各通道命中正确;None/空串/未知公司/缺通道/空路由表
全部按预期回落。
|
2026-09-22 13:13:53 +08:00 |
|
|
|
de34a939d0
|
feat(track): 新增 TRACK_ROUTES 多实例路由表配置
Track 现在有两个独立实例(IRIS / LICA),MOM 此前所有 Track 配置都写死
指向 IRIS,LICA 的业务拿不到入库/出库联动,且 /track-lookup 对 LICA
员工是坏的。
新增 TRACK_ROUTES 环境变量(JSON),按 material_base.company_name 决定
业务落到哪个实例:
{"IRIS": {"api":..., "inbound":..., "outbound":...}, "LICA": {...}}
原有三个扁平变量(TRACK_API_URL / TRACK_WEBHOOK_URL /
TRACK_OUTBOUND_WEBHOOK_URL)保留为「未命中路由表」时的回落默认值;
TRACK_ROUTES 缺省为空表时,行为与改造前逐字节一致(向后兼容)。
⚠️ JSON 解析失败直接抛异常让进程起不来(fail fast)——路由表写坏却静默
回落,会造成"某些公司悄悄断链",比启动报错难排查得多。
docker-compose.yml 用容器名互访(inventory_api / track_backend /
lica_backend 三个容器同属 projects_default 网络,不走宿主机端口)。
判定维度用 company_name 而非 category:实测 category 两个方向都会错
(10 条 LICA 物料挂在 IRIS/ 分类下;IRIS 分类里含 "IRIS/成品/LICA/..."
那是产品线名不是部门名,共 171 条)。
|
2026-09-22 13:13:52 +08:00 |
|
|
|
3b777af87d
|
V3.90,9.21推送
|
2026-09-21 14:26:07 +08:00 |
|
|
|
2319a053df
|
fix(bom): 备注框移到子件列表上方,改为 textarea
原位置在编号/版本号那一行的 span=6 里,850px 弹窗扣掉 label-width:120px
和 gutter 后,输入框实际只剩 60 余像素,稍长一点的备注根本看不清。
改为独立整行 textarea(rows=2, maxlength=500, show-word-limit),放在
「子件列表」标题上方;编号行改为 span 10+14 填满。
连带修两处相关缺陷:
· 暂存草稿的 payload 补 remark —— 此前根本没往后端传
· getDraftHash 补 remark —— 否则"只改了备注"会被防呆逻辑拦成
"草稿数据无变动,无需重复暂存"
|
2026-09-21 14:13:15 +08:00 |
|
|
|
6a7761abbb
|
fix(bom): 草稿链路透传 BOM 级备注,发布后不丢失
save_draft / get_draft_detail / publish_draft 三处此前都不带 BOM 级备注,
暂存后再恢复、或草稿直接发布,备注都会丢。
同时给 /save 的权限清洗表补上顶级 remark(与子件级 remark 共用
bom_manage:remark 权限码)。
注意:运行时真正生效的草稿入口是 bom.py:521 的 /draft/save。
app/api/v1/bom_draft.py 里的 bom_draft_bp 从未在 app/__init__.py 注册,
是死代码(两份都调同一个 BomDraftService,本次两处都改以保持同步)。
实测(临时 BOM,验证后已清理):
草稿暂存带备注 → 读回 '草稿BOM级备注-测试'
草稿 → 发布 → 正式表 读回 '草稿备注-发布后应保留'
|
2026-09-21 14:13:14 +08:00 |
|
|
|
0abc5850d8
|
fix(bom): save_bom 落库 BOM 级备注,get_bom_detail 改读 bom_remark
save_bom() 此前从未读取 data['remark'],父件级备注被静默丢弃;
get_bom_detail() 则用 first.BomTable.remark(第一个子件行的备注)冒充
主表备注——所以填了保存后再打开必为空,偶尔还会显示出某个子件的备注
(库里 1196 行只有 1 行有 remark,值是"白色")。
这正是"BOM 备注看不见"的根因。
|
2026-09-21 14:13:14 +08:00 |
|
|
|
cc80fdd9c9
|
fix(bom): 增加 bom_remark 列,为 BOM 级备注提供存储位置
bom_table 是子件明细表(一行 = 一个子件),表里只有子件级的 remark 列,
BOM 级备注没有任何地方可存。前端一直有备注输入框、提交时也确实放进了
payload,但后端 save_bom 收到后无处可写,只能静默丢弃。
沿用 parent_id / is_enabled 既有的冗余写法:同一 BOM 版本的每一行存同一
份值,读取时取首行。bom_draft_table 同步加列,保证草稿暂存与发布期间
不丢失。与既有 remark(子件级)语义分离,互不干扰。
存量数据无法回填——历史上填过的备注从未入库,只能从本迁移之后开始记录。
执行方式: docker exec -i inventory_db psql -U test -d inventory_system < db_migrations/add_bom_remark.sql
|
2026-09-21 14:13:13 +08:00 |
|
|
|
b6ce754ba7
|
V3.89,9.21推送
|
2026-09-21 12:03:07 +08:00 |
|
|
|
e0ea946e08
|
fix(outbound): 出库列表补传日期参数,修复日期筛选静默失效
前端 el-date-picker 一直传 start_date/end_date(outbound/index.vue:472),
service 层 get_grouped_list() 也早有完整实现(签名接收 + 10 位日期补全
时分秒 + outbound_time 过滤),但中间 API 层从未读取这两个参数:
outbound.py: get_outbound_list() 只读 page/limit/keyword/search_type/
company/advancedFilters,调用 get_grouped_list() 时也没传日期。
get_grouped_list 全项目仅此一个调用方,因此 start_date/end_date 恒为
None,service 层 `if start_date and end_date:` 永远为假——用户在页面上
选了日期,后端直接忽略,结果集与不筛选时完全一致,且不报错。
修复:
· outbound.py 读取 start_date/end_date 并透传给 service
· outbound_service.py 把 BETWEEN 改为分别判断(原写法只传一侧时
整段条件静默失效,与 /returns 等接口的边界处理对齐)
实测验证(SUPER_ADMIN 全量口径,与 SQL count(distinct outbound_no) 逐项对齐):
无日期 494 (基准)
2026-08-24 单日 3 SQL=3
2026-05-11 单日 9 SQL=9
2026-08 整月 88 SQL=88
只传 start_date 229 SQL=229
修复前上述四项均恒等于 494。运行中容器经 --reload 重载后实测一致。
影响范围:仅出库主列表。其余带日期筛选的接口(/returns、transactions、
scrap、purchase、audit、inbound_summary)均已正确读取参数,未受影响。
|
2026-09-21 10:33:47 +08:00 |
|
|
|
8db33fdf40
|
V3.88,9.20推送
|
2026-09-20 17:01:52 +08:00 |
|
|
|
3bb5fc6eb8
|
fix(outbound): 执行校验兼容无 base_id 的历史单据,修复老单必然被拒
2026-09-10 预占改造(b57c21a)之前建的单,items_json 里没有 base_id。
这类单只要在改造上线时仍处于「已通过待执行」状态,就再也执行不了:
批准侧 build_approval_index() → identity_key(base_id, name, spec)
无 base_id → 退化成 ('name', 名称, 规格)
扫码侧 stock_identity() → ('base', id)
两侧键不同构,**永远不相等**,verify_scanned() 必然抛
「扫码物料【…】不在该申请单的批准明细中,禁止出库」——批的就是这件货。
identity_key() 的文档本就把 (name, spec_model) 写作「历史数据的兜底」,
只是校验时只有批准侧降了级、扫码侧没有,两侧因此错开。
实测(APR-OUT-20260909-1313-0007,建于 09-09,改造上线前一天):
批准侧 ('name', '派里肯安全箱1600(黑色)', 'PS-9640B001-black')
扫码侧 ('base', 3012)
修复:
· 新增 stock_name_spec() —— 库存行 → 名称型身份键,刻意丢掉 base_id
· 新增 build_legacy_approval_index() —— 无 base_id 的历史明细旁路索引
· verify_scanned() —— 主索引落空时用库存行名称+规格再比一次;命中则
改用**批准侧的键**继续,使 acc 累计与 approved_idx 的批准量同口径
安全边界(关键):降级只对真正来自老单据的名称型键放行,白名单是
legacy_idx 而非 approved_idx 本身——build_legacy_approval_index() 只收
identity_key() 产出名称型键的明细,有 base_id 的一律跳过;名称与规格皆空
的明细直接丢弃,绝不退化成「任意物料都能匹配」。无老单时该索引为空集,
降级分支恒不命中,行为与改造前逐字节一致。
影响范围:当时处于 status=1 的老单全库仅 1 张(即上述单号)。其余 358 张
老单(已完成 327 / 已驳回 9 / 已完结 22)均为终态,不受影响——它们在改造
上线前就已执行完毕。
实测验证:
T1 老单 360 + 扫批准范围内物料 修复前必拒 → 修复后通过
T2 老单 360 + 扫单外物料 仍拒绝
T3 老单 360 + 数量超批准 仍拒绝
T4 现代单 + 扫批准范围内物料 通过(旁路索引 0 键,未受影响)
T5 现代单 + 扫单外物料 仍拒绝
副作用改善:降级后标签改用批准侧的键,超量报错从「物料#3012」变为
「派里肯安全箱1600(黑色)(PS-9640B001-black)」,可读性提升。
|
2026-09-20 16:43:37 +08:00 |
|
|
|
4f922a55f3
|
V3.87,9.20推送
|
2026-09-20 15:11:10 +08:00 |
|
|
|
d7f7548cee
|
fix(outbound): 库存分配按 base_id 合并需求,修复重复行导致的假性库存不足
_allocate_bom_requirements 在整轮 for req 循环中复用同一份可用量快照
(rows_by_base 建好后不再更新),而 for 循环本身不按 base_id 去重。同一
物料以多行进入时,每行都从同一份快照重新分配一遍,把同一个 stock_id
重复分配 N 次,累计分配量可超过该行真实可用量。
超配在分配阶段不会暴露,直到 reserve_for_items 的二次校验
(take > avail)才抛错,报「可用库存不足(需 X,实剩 Y)」,而实际库存
充足。实剩为 0.0 是当最大候选批次的可用量恰好等于单行需求时的收尾形态。
重复行来自正常业务,非脏数据:
- 购物车里同一物料的多批次就是多行(Selection.vue 提交时只带
base_id + quantity,stock_id/source_table 被丢弃);
- BOM 明细里同一子件被多处引用(前端 requirements 按 child_id 不去重);
- 调拨 / 补发等拆行场景。
修复:在函数入口按 base_id 合并 reqs、required_qty 求和。分配语义不变
—— 分配器本就按 base_id 拉全量批次行、降序分配,输入里的 stock_id 从来
就被忽略。
选在此处收口而非合并调用方的 items:_allocate_bom_requirements 是全系统
库存分配的唯一权威入口(出库与借库都经 reserve_for_items 走到这里),
一处修改同时覆盖两条链路。
实测(base_id=711,四个批次可用量合计 124):
三行各 30 修复前 ERROR(需30/实剩10) → 修复后 OK,2117 出 70 + 1182 出 20
两行各 30 修复前静默超配(两行都绑 2117) → 修复后 OK,合并为 2117 出 60
十行各 30 修复后报真实缺口「需 300,可用 124」(走正常缺料分支)
|
2026-09-20 15:09:14 +08:00 |
|
|
|
9ea79a55f8
|
V3.86,9.18推送
|
2026-09-18 14:41:53 +08:00 |
|
|
|
26a1857acb
|
fix(purchase): 待采购清单的参考价格改按 material_list:referencePrice 管控
修复一个权限旁路:待采购清单的「参考单价」原本后端无条件返回、前端列也没有
任何门控,而 material_list:referencePrice 只授予 SUPER_ADMIN 与 SUPERVISOR。
结果是 WAREHOUSE_MGR(库管) / INBOUND(入库员) / SALES(销售) 虽然都持有
inbound_purchase:pending_pool、能打开待采购清单,就会看到参考价格 —— 而这个
数字他们在物料列表里是被挡住的。等于本页面成了绕过该权限的后门。
参考价格来自 material_base.reference_price,与物料列表同源,因此复用同一个
权限码管控,不另造新码(同源数据用同码,口径才不会漂)。
改动:
- 后端 get_pending_purchase_pool 新增 include_reference_price 参数,
fail-closed 默认 False;无权限时**整个字段不返回**而不是给 None
- 新增 _has_material_reference_price_perm(),判定方式与既有的
_filter_purchase_prices 保持一致(超管/主管放行 + 逐个权限码比对)
- 前端「参考单价」列加 hasPermission 门控,TS 类型改为可选
★ 只挡前端等于没挡(接口仍然裸奔),前后端必须同时改。
|
2026-09-18 14:36:02 +08:00 |
|
|
|
970c03fb44
|
feat(material): 基础信息双胞胎页面:移除人工「标记已采购」,改自动「采购在途」
list.vue 与 buyOdoo.vue 同步改造。这两个文件是手工维护的副本、不是共享组件,
改动必须两边都做 —— 本项目已多次因只改单边导致同一 bug 在另一页长期存活
(参考价格脏检查、表单禁用守卫、发起采购申请按钮等都曾漏同步)。
改动:
- 移除「标记已采购」按钮、handleMarkOrdered 函数与 markWarningOrdered 引用
- 操作列改为只读 el-tag「采购在途」,由后端 isPurchasing 派生
- 预警状态列降级:缺货但已有在途采购单时显示蓝色「缺货(已采购)」而非红/黄
★ 降级只改前端展示,后端 warningStatus 原样保留 —— 预警强排
(enableWarningSort 的 order_by case())依赖它,改后端会连带影响排序。
★ 顺带修掉一个历史问题:前端 `scope.row.warningOrdered` 这个字段后端从未输出过
(后端只给 isOrdered,且列表接口根本没查这一列),所以那个 disabled 的
「采购在途」按钮从来不会显示,用户标记后永远看不到已标记状态。
|
2026-09-18 14:31:22 +08:00 |
|
|
|
70dd257392
|
feat(purchase): 采购申请弹窗支持批量建单与商家链接一键跳转
批量建单:
openCreateDialog 新增 batchItems 分支 —— 待采购清单勾选多个物料时,
明细表一次性铺成 N 行,实现一单多品的合并采购。单条与批量共用
fillRowFromPrefill 做字段映射,不各写一份。
商家链接一键跳转:
输入框加后置按钮,点击在新标签页打开,方便采购员比对价格。
★ 链接先过 safeHref 白名单(只放行 http/https)—— 该字段由用户自由填写,
直接交给 window.open 会让 javascript: 之类的伪协议被当成代码执行。
非法时按钮置灰,不做静默失败。
交接方式:
单行与批量统一走 localStorage(query 里只传一次性 key,取完即删),
不经过 URL。原因:material_base.purchase_link 是 text,实测已有 516 字符的
真实外链,走 URL 会撞上长度上限并被静默截断。曾经单行走 query 时被迫引入
一个「链接多长就不带」的硬编码阈值,导致同一物料单行跳转带链接、批量跳转
不带 —— 现在两条路径统一,无长度上限。
|
2026-09-18 14:31:14 +08:00 |
|
|
|
690ee2c81d
|
feat(purchase): 新增「待采购清单」页面
路由 router/index.ts:
/purchase 下新增子路由 pending(name: PurchasePendingPool)。
★ 父路由刻意不加 permissions —— 加了会让没有 pending_pool 权限的用户
整个「采购管理」模块从侧边栏消失。
页面 views/purchase/PendingPool.vue:
筛选栏 + 表格 + 分页,列含产品图、规格、当前库存、在途、有效供给、
预警、建议采购量(tooltip 展示完整算式)、参考单价、采购链接、操作。
采购链接渲染走 safeHref 白名单,非法地址不渲染成可点击链接。
api/purchase.ts:
新增 getPendingPurchasePool 与 PendingPoolItem 类型。
★ 需告知业务方的可见变化:「采购管理」从扁平单项变为可展开子菜单
(Sidebar 在子路由只有 1 个时渲染 el-menu-item,2 个时渲染 el-sub-menu)。
|
2026-09-18 14:31:07 +08:00 |
|
|
|
b4445f07d9
|
feat(material): 物料列表新增「采购在途」自动标识,预警邮件改用活跃单静音
base_service.get_list:
新增 isPurchasing 字段,与待采购池共用同一份活跃单判定。放在预警权限
判断之外 —— 能看到物料列表的人都该知道这个物料已经在采购了。
inventory_task:
静音条件从人工标记 is_ordered 改为「该物料无活跃采购单」。
★ 为什么必须同时改邮件:预警邮件由出库执行流程触发(outbound_service 出库
完成后的调用),功能是活的;而它的静音入口(前端人工「标记已采购」)随本次
改造一并移除。若不改这里,用户会彻底失去静音能力,每个缺货物料在每次出库后
都会发信。自动判定在建单时静默、单据驳回或强制结案后自动恢复,比人工标记准。
两张表的判定口径都由 utils/purchase_activity.py 提供,不各写一遍。
|
2026-09-18 14:31:00 +08:00 |
|
|
|
dec33b685c
|
feat(purchase): 新增待采购池接口(防重复采购)
GET /api/v1/purchase/pending-pool:找出「有效供给仍低于预警线」的物料。
有效供给 = 物理总库存 + 在途量
过滤规则 = 有效供给 <= 红/黄阈值 才进池
建议采购量 = max(1, 目标阈值 - 有效供给)
★ 为什么不用「有活跃单就排除」:红线 10、库存 0、某采购员只建了一张
数量 5 的单时,该物料会立刻从池中消失,剩下 5 个缺口永远无人认领。
改为按量计算后它继续留池,suggested_qty 自动降到 5。
★ 用 <= 而非 < 是与业务方确认后刻意维持的口径(与物料列表预警、预警邮件
同源),勿擅自改成严格小于 —— 只改本接口会造成「列表亮黄灯、池子却排除」
的撕裂,真要改必须三处一起动。代价是恰好等于阈值时建议量落到保底的 1,
前端 tooltip 已单独说明,不展示算不成立的错误算式。
权限:新增 inbound_purchase:pending_pool(注册为 sys_element 而非新建
SysMenu)。因 ensure_default_permissions 在角色已有权限时整段跳过,另写了
存量角色自动迁移,并按源行镜像 company_name 避免跨公司作用域泄漏。
|
2026-09-18 14:30:53 +08:00 |
|
|
|
fc5e62c848
|
feat(purchase): 新增「活跃采购单」判定与在途量计算模块
防重复采购的唯一真相源。刻意回答两个不同的问题,别混用:
- active_purchase_exists() 有没有人在买? → 展示用
- in_transit_subquery() 已经买了多少还没到? → 算术用
活跃单定义:status 0/1,或 status=3 且累计入库 < 采购量。
status=4(强制结案)刻意不在其中 —— 短交不补发、来料拒收不补发这类
异常单永远达不到采购量,靠库管强制结案解锁,避免物料被永久占用。
「部分到货」无需改表即可算出:StockBuy.request_id 早已存在,且
StockBuy.in_quantity 是入库原始量(不被出库扣减,区别于 stock_quantity),
按 request_id 聚合即得每张单的累计到货量。
|
2026-09-18 14:30:40 +08:00 |
|
|
|
caaa1571d4
|
V3.85,9.18推送
|
2026-09-18 10:17:05 +08:00 |
|
|
|
1b6a6f5c19
|
fix(return): 还库签名文案统一为「领用人」,消除与页面标题的口径冲突
同一签名位的四处文案不一致:页面标题(第 7 行)写「领用人签字」,
而正文提示(125 行)与校验报错(391 行)写「库管员」、占位文案
(158 行)写「库管签名」,用户无法判断该谁签。
全部统一为「领用人」。其中第 125 行原为「签字确认入库」,与本节
分节标题「还库确认」自身矛盾,一并改为「签字确认还库」。
|
2026-09-18 10:15:18 +08:00 |
|
|
|
b4e43d641a
|
V3.84,9.18推送
|
2026-09-18 10:10:12 +08:00 |
|
|
|
ac22c9959b
|
feat(material): 基础信息(Odoo)补回「出库/借库需审批」列
buyOdoo.vue 是 list.vue 的手工副本。93f1497 / 4146f35 给 list.vue 加了
「出库/借库需审批」列,未同步到 Odoo 页 —— isApprovalRequired 在
list.vue 出现 9 次,在 buyOdoo.vue 为 0 次,该页既看不到也改不了这个开关。
补上表格列(带切换开关与操作权限守卫)、列显示开关、columns 与
permissionMap 条目、toggleApprovalRequired 函数及 batchSetApprovalRequired
接口引用。
|
2026-09-18 10:07:27 +08:00 |
|
|
|
825c088e98
|
feat(material): 基础信息(Odoo)补回「发起采购申请」入口
buyOdoo.vue 是 list.vue 的手工副本。31903bd 给 list.vue 弹窗加了
「发起采购申请」,未同步到 Odoo 页,两个页面的编辑弹窗头部不一致。
补上弹窗头部链接、createPurchaseForMaterial 函数及 ShoppingCart 图标。
|
2026-09-18 10:07:24 +08:00 |
|
|
|
f2bb95becc
|
fix(material): 基础信息(Odoo)补回表单禁用权限守卫
buyOdoo.vue 是 list.vue 的手工副本。68fd93b 给 list.vue 加了 formDisabled
(无 material_list:operation 权限时整个表单只读),未同步到 Odoo 页。
列表页的「编辑/新增」按钮虽有权限守卫,但 URL 深链 ?edit_id= 不受守卫,
可直接打开 Odoo 页弹窗:字段能改、确定能点,提交后才被后端
@permission_required('material_list:operation') 拦下,用户白填一遍。
补上 formDisabled computed 及表单、确定按钮两处绑定,与 list.vue 对齐。
|
2026-09-18 10:07:21 +08:00 |
|
|
|
243355bc68
|
fix(material): 基础信息(Odoo)补回参考价格脏检查白名单
buyOdoo.vue 是 list.vue 的手工副本。8791914 修复「参考价格不可改」时只改了
list.vue,同一个 bug 在 Odoo 页存活至今:
- 只改参考价格:payload 无变更,提示「没有检测到数据变更,无需保存」
- 连带改其他字段:提示「修改成功」,但 payload 不含 referencePrice,价格未入库
compareFields 补上 referencePrice,与 list.vue 对齐。
|
2026-09-18 10:07:18 +08:00 |
|
|
|
eafdf992c7
|
fix(borrow): 借还记录「归还人」列错标成了经手库管,拆成两列
问题
----
列表里那一列标着「归还人」,读的却是 trans_borrow.return_operator —— 而该字段
存的是**办理还库的库管**,不是来还东西的人。两者本就是不同的人:
· returner_id(trans_borrow_return)—— 把东西交回窗口的人,已校验 == 当时持有人
· return_operator —— 经手办理的库管
实测(借用行 120):return_operator = 杜邢宸/duxingchen(库管),
而实际归还人是 returner_id = 21(测试)—— 页面却显示成了「杜邢宸」。
改动
----
一、后端 get_records 增补 returners:从 trans_borrow_return 取 returner_id 并
反查 sys_user 得到姓名(去重按明细挂回)。批量查一次,不做 N+1。
二、前端拆成两列,各自名副其实:
「归还人」 ← returners(后端新增)
「经手库管」 ← return_operators(原列改为正确标签)
三、顺带修展示口径:return_operator 存的是**完整 username**(高闯/gaochuang),
未按全站口径截断。_display_borrow_operator 现统一归一到展示名
(数字 id 反查 / 姓名、斜杠前段 / 已是展示名 原样),并修正其 docstring
—— 它原本也把该字段称作「归还人」,是同一个误解的源头。
★ 历史数据的现实
实际归还人流水是二期才建的,**历史归还没有这个记录**。这部分行的「归还人」
显示为空并挂 tooltip 说明「该笔归还发生在实际归还人记录上线之前」——
刻意不拿库管的名字顶上,那正是本次要修的错。
验证
BOR-20260918-0001 → 归还人=['测试']、经手库管='杜邢宸' ✓ 两者分开
BOR-20260914-0001 → 归还人=[](历史)、经手库管='高闯' ✓
归一化:'高闯/gaochuang'→'高闯'、'21'→'测试' ✓
前端 vite build 通过;本次无需 DB 迁移。
|
2026-09-18 09:43:25 +08:00 |
|
|
|
e3fe1fc12a
|
feat(borrow): 借还记录「已归还」页签改为按归还时间倒序
问题
----
三个页签共用同一套排序(有限期单在前 → 最早应还时间 ASC → 最早借出时间 DESC),
这套「优先关注快到期/逾期」的逻辑对「未归还」是对的,但对「已归还」正好**
反了**:已归还列表要回答的是「最近还了哪几笔」,而按应还时间排会让最近刚还的
那几笔排到最后。
改动
----
order_subq 增加 max_return_time(单号内**最晚**一次归还时间)作为排序键,
并按页签分流:
· 已归还 → nullslast(max_return_time DESC),borrow_no DESC 兜底保证稳定
· 全部/未归还 → 原三级排序不变
★ 为什么取「最晚」而不是「最早」一次归还时间
多明细分批归还时,整单结清的那一刻才是有意义的节点;且与主行「归还时间」列
的展示口径一致(前端同样取 latest),排序依据与可见值不会打架。
验证(真实数据)
已归还页签:09-15 11:41 → 09-14 16:02 → 09-09 11:46 → 09-08 13:22 →
09-04 17:26 → 09-04 09:45 → 09-03 15:25,第 2 页续 08-27 → …,
严格递减且**跨页连续**;
未归还页签:排序与改动前完全一致(回归确认)。
|
2026-09-18 09:34:45 +08:00 |
|
|
|
ccdd6e7306
|
fix(purchase): 商家地址链接放宽为 text,修超长链接保存失败
现象
----
采购管理新建采购申请时粘贴电商商品链接报错。实测该链接 **516 字符**,而
purchase_request.supplier_link 是 varchar(500) —— 超出 16 个字符,
PostgreSQL 直接拒绝(value too long for type character varying(500))。
★ 为什么用 text 而不是把 500 调大
电商外链(淘宝/1688)普遍带很长的跟踪参数,长度没有稳定上界:本例已 516,
再叠加一轮营销参数就可能破千。任何有限的 varchar(N) 都只是把报错往后推,
而且**截断是静默的** —— 用户只会觉得「链接打不开」。
同 material_base.purchase_link 的处理(那边一开始就用 text)。
附带确认(全部扫描,无需改动)
从数据库层面扫了所有「有长度上限、且列名像 link/url/photo/image/
signature/path」的列:
· purchase_request.supplier_link(500) ← 本次唯一真正有问题的
· sys_menu.path(200)、sys_warehouse_location.full_path(500) 内部生成数据
· stock_adjustment.linked_sku(100)/linked_outbound_no(50) SKU 与单号
用户粘贴外链的列仅此一处。
迁移用 DO 块先判断当前类型,可重复执行;含核对段与回滚段。
验证:把那条 516 字符的真实链接写入再读回,长度一致、内容未截断。
|
2026-09-18 09:34:44 +08:00 |
|
|
|
1eaa203eba
|
fix(db): 修正存量「归还时间/报废时间」的 8 小时时区偏差(附自检)
问题
----
process_return 与 scrap_sources.deduct 原先用 datetime.now()(容器本地时间,
Docker 默认 UTC)写 return_time / operation_time,而同一行的 borrow_time 由
beijing_time() 写入 —— 同一行两个时钟,差 8 小时。
实测(开发库)trans_borrow 有 53 行呈现「归还时间早于借出时间」这种物理上
不可能的数据,例如 id=80 借出 09-09 11:04、归还 09-09 03:46。
代码侧已修(f7c49f4),新数据已是北京时间 —— 本次只处理存量。
★ 脚本刻意做了「先自检、后修正」的两段式
服务器 API 容器的 TZ 未必是 UTC(若本就是 Asia/Shanghai,datetime.now()
一直是北京时间,则**根本无需修正**)。若不做自检直接 +8h,会把正确的时间
改错。故第 0 步先输出:异常行数、以及按分界切出的「UTC 段/空档/北京段」
三段计数,由使用人据结果决定是否执行第 2 步。
★ 分界与幂等性都在脚本里写明
· 分界 = 修复代码在该服务器**部署生效的时刻**(不是提交时刻),需按实际调整;
· 脚本**不幂等** —— 重复执行会再加 8 小时,已显式警示并给出回滚写法。
开发库已按此逻辑修正:53 行 trans_borrow + 1 行 trans_scrap,
修正后「归还早于借出」为 0,抽样时间落在正常工作时段。
|
2026-09-18 09:27:17 +08:00 |
|
|
|
91c4ae85db
|
V3.83,9.17推送
|
2026-09-17 13:40:56 +08:00 |
|
|
|
191724a176
|
fix(outbound): 修复扫码出库 500 —— applicant_id 用了尚未赋值的 approval
现象
----
所有扫码出库报 500(前端 create.vue 提交即失败)。
根因
----
上一个提交把 'applicant_id': approval.applicant_id 写进了 common_data,
但 common_data 构造在第 234 行,而 approval 直到第 269 行(「强制按单出库」
那一段)才查询赋值 —— 典型的变量先用后赋,直接 UnboundLocalError。
它影响的是**每一条**出库请求,属于必然复现而非偶发。
修复
----
把赋值移到 approval 取出并校验**之后**:common_data['applicant_id'] = ...
并在原处留注释说明为什么不能写在字典字面量里,避免后人搬回去。
★ 我的测试为什么没抓到
上一轮只测了「退回 → 补发」这条路径,而 applicant_id 的写入在**扫码出库**
路径上 —— 两条路径不重合,所以漏了。本次补测了完整的
「出库申请(预占) → 扫码出库(扣减)」链路。
验证(6 项断言全通过,走真实接口链路)
申请单创建(status=1,申请人=12,预占 stock_buy#2058 两件)
→ 扫码出库不再抛 500
→ 出库明细生成且 applicant_id = 12(不再是 NULL)
→ consumer_name 照常写入
→ 审批单置为已完成
→ 库存实际扣减 2
清理后库存与数据零残留。
★ 顺带在真实数据上得到印证:生产库中已有一笔由业务方重试成功的出库
(OUT-20260917-1333-0001),其 applicant_id 正确等于所关联申请单的申请人。
|
2026-09-17 13:35:47 +08:00 |
|
|
|
07567d2f76
|
fix(outbound): 「补发给谁」改为必选,并按原申请人精确预填
· 预填顺序改为:① 原出库明细记录的 applicant_id(创建出库时从审批单带出,
主键无歧义)→ ② 历史单据该字段为 NULL 时,退而按原领用人姓名**唯一命中**
预填;重名一律留空。
· 「补发给谁」改为**必选**:提交前校验,未选即拦下并提示。
不再有「留空则挂当前操作人」的兜底 —— 那会把别人的需求记到库管名下。
· 文案同步:明确写「必须选择:补发是原申请人的需求,不能挂到办理退回的库管名下」。
|
2026-09-17 12:07:21 +08:00 |
|
|
|
0005a689dc
|
fix(outbound): 补发申请人取原申请人,绝不再回退为库管
问题
----
上一版在库管未指定「补发给谁」时,把补发单申请人回退成了**当前操作人(库管)**。
但补发是**原申请人的需求**,挂到库管名下逻辑不通 —— 那张单会出现在库管的
「我的申请」里,而真正该拿东西的人什么也看不到。
根因
----
trans_outbound 只有 consumer_name(**扫码时前端自由填写**的领用人/客户名,
既不可靠也可能是外部客户),没有任何指回原审批单的关联,所以当时只能退而求其次。
根治
----
一、trans_outbound 新增 applicant_id,**创建出库时从关联审批单带出**
(request_id 已强制必填、approval 恒非 None,故新单据必然有值)。
落这一列后,「退回 → 补发」即可自动找回真正的原申请人。
⚠ 存量行为 NULL —— 存量出库与其来源审批单之间没有任何可用关联,无从回填。
刻意留 NULL 而不是按姓名猜(consumer_name 是自由文本,会重名错绑),
与 dispatch_operator 同一取舍:宁可留空,也不猜。
二、退回接口的申请人优先级改为:
① 前端显式指定 reissue_applicant_id
② 原出库明细记录的 applicant_id(真实原申请人)
③ 都没有 → **报错要求指定**
★ 彻底移除「回退为当前操作人」—— 那正是本次要修的逻辑错误。
三、出库列表明细返回 applicant_id,供前端精确预填。
验证(9 项断言全通过)
· 新单据 → 申请人 = 原申请人(12),绝不是库管(7)
· 显式指定优先于原申请人
★ 历史单据未指定 → 接口拒绝、要求选择「补发给谁」、整笔回滚、
且**未生成任何挂在库管名下的补发单**
· 历史单据 + 显式指定 → 正常
库存与数据零残留。
|
2026-09-17 12:07:21 +08:00 |
|
|
|
05524c988a
|
feat(outbound): 退回弹窗增加「补发给谁」选择器
· 勾选「需要补发」后出现「补发给谁」下拉(filterable + clearable),
并自动带上补发数量。
· 预填策略:按原领用人 consumer_name **唯一命中**才预选;重名一律留空让现场
自己选 —— 与借用人回填同口径,宁可多一步也不猜错人(补发单会挂到错的人名下)。
· 留空时后端回退为当前操作人,文案已写明。
· 人员名单**勾选补发时才加载**(多数退回不需要补,避免无谓请求),加载后缓存。
· 新增 api/common/users.ts 的 getActiveUsers():走中性的
/v1/common/active-users,而不是复用借库专用路径。
|
2026-09-17 12:01:12 +08:00 |
|
|
|
27e5589a5e
|
feat(outbound,common): 补发可指定「补发给谁」+ 抽出通用人员名单接口
一、补发申请人可选择(原单退回)
退回接口新增 reissue_applicant_id:
① 前端指定 → 校验用户存在后落库;
② 未指定 → 回退为**当前操作人**(原行为不变,向后兼容)。
为何不自动推断原申请人:trans_outbound **没有申请人字段,也没有指回原审批单
的关联**(扫码出库时只把审批单状态置为 3),按 consumer_name 反查会重蹈
「重名错绑」的覆辙(借用人姓名回填那轮刚踩过)。故把选择权交给现场,不猜。
二、抽出中性人员名单 GET /api/v1/common/active-users
实现抽到 common.active_user_options(),借库的 /transactions/borrow/users
改为调同一函数 —— 实现只有一份,但出库补发走**中性路径**,不再出现
「出库为什么在调借库的接口」这种跨模块语义错位。
仅要求登录、只返回 id 与姓名(与 /auth/users/approvers 同一处理)。
★ 本次无需 DB 迁移:未新增任何列,补发申请人是复用已有的
outbound_approval.applicant_id。
验证(打桩/真实 token 直连接口,12 项断言全通过)
· 名单只含 id/name,无邮箱/角色/部门;借库原路径返回值与新路径完全一致
· 指定「补发给谁」→ 补发单申请人 = 指定的人;备注仍含原领用人
· 不指定 → 回退为当前操作人
★ 指定不存在的用户 → 被拒,且整笔退回回滚(流水未落库)
库存与数据零残留。
|
2026-09-17 12:01:11 +08:00 |
|
|
|
9d187593eb
|
feat(outbound): 退回弹窗增加「需要补发」勾选与补发数量
· 退回对话框新增「退回后自动生成补发单」勾选框 + 补发数量(勾选时默认 = 本次
退回量,可调小;上限即退回量)。
· 文案明写两条关键后果:生成的是**免审批**出库单;**库存不足时整笔退回会一并
取消**,取消勾选即可只做退回过账 —— 避免库管对着失败提示发懵。
· 提交前做同口径前置校验(数量 > 0 且 <= 退回量),不白跑一趟。
· resetReturnDialog / openReturnDialog 同步重置这两个字段。
默认**不勾选**:有些退回是项目结束退还,根本不需要补,不该默认给所有人挂上。
|
2026-09-17 11:56:39 +08:00 |
|
|
|
7071c89305
|
feat(outbound): 原单退回后可自动生成补发单
背景
----
原单退回只做两件事:良品加回库存 / 不良品转在管台账。但**申请人的需求并没有
被满足** —— 东西交回来了(甚至还是坏的),系统却不提醒任何人、无单据承载
「我要重新领一份」,退回与后续再出库之间也毫无关联。现场只能靠人记住再手建
一张出库申请,而那张单与原单看不出任何关系。
改动
----
· outbound_approval 新增 source_return_id(非空 = 补发单),把「退回 → 补发」
串成闭环。
· POST /inbound/stock/return-from-outbound 新增 need_reissue / reissue_qty:
勾选即自动生成一张**免审批**出库单(status=1,直接进入待执行),
沿用原单出库类型,并关联回本笔退回。
★ 为什么只存退回单 ID,不加 is_reissue 布尔列
「是不是补发」完全由来源是否存在决定,再加一列就是同一事实的两处存储,
必然有不同步的一天。也不冗余存原出库单 ID:trans_return 已有 outbound_id。
★ 库存不足 → 整笔回滚(关键取舍)
补发走 reserve_for_items(strict=True),与出库申请同一口径。不足时抛错,
退回也一并回滚 —— 若只让补发静默失败,「需要补发」的意图就丢了,
那正是本功能要解决的问题。库管看到提示后取消勾选即可只做退回过账。
★ 一个被发现的数据约束(改变了原设计)
原打算把补发单的申请人设为「原出库单的申请人」,但 **trans_outbound 既没有
申请人字段,也没有指回原审批单的关联**(扫码出库时只把审批单状态置为 3)。
按 consumer_name 反查会重蹈「重名错绑」的覆辙。故申请人取**当前操作人**,
原领用人写入备注供人工追溯。
⚠ 若业务要求补发单挂在原领用人名下,需要前端在退回弹窗里加一个「补发给谁」
的人员选择 —— 请确认是否需要。
验证(打桩 JWT 直连真实接口,22 项断言全通过)
勾选补发 → 免审批单生成、关联退回、预占库存、原领用人入备注;
不勾选 → 不生成补发单、良品正常回库;
★ 库存不足 → 接口拒绝且**退回流水/补发单/退回额度全部未落库**(整笔回滚);
补发量 > 退回量被拒;库存与数据零残留。
|
2026-09-17 11:56:38 +08:00 |
|
|
|
483fb5336a
|
V3.82,9.17推送
|
2026-09-17 11:41:22 +08:00 |
|
|
|
a739f9c889
|
feat(material): 采购链接在编辑弹窗内可直接跳转,无需复制网址
体验问题
----
原先只能在编辑弹窗的输入框里看到网址,要跳转得复制出来、另开标签页、粘贴 ——
多三步,且现场场景下很容易贴错。
改动
----
输入框追加一个常驻的「打开」按钮:点一下在新标签页打开,同时输入框始终可编辑。
非法链接(非 http/https)时按钮置灰 + title 提示;误点给出明确 ElMessage,
不做静默失败。两处(list.vue / buyOdoo.vue)交互一致。
★ 为什么不做「双击解锁编辑」
1) 双击是**隐藏交互**,没有任何视觉提示,用户不会发现;
2) 双击在输入框内与「选中文字」冲突(复制片段时会误触发);
3) 追加按钮零学习成本,且不牺牲可编辑性 —— 两者目标相同,代价更低。
链接的「只看不改」需求若有,应走权限(只读渲染),而不是靠双击切换模式。
|
2026-09-17 11:33:15 +08:00 |
|
|
|
938051fe29
|
feat(material): 采购链接前端(表单 + 列表列),并修复 Odoo 页保存后丢搜索
一、采购链接 UI(list.vue 基础信息 / buyOdoo.vue 基础信息(Odoo))
各补齐 6 处:columns、permissionMap、列设置复选框、表格列、表单字段、compareFields。
· 表格列带双重守卫(columns.X.visible && hasColPermission),无权限者整列不渲染
· 表单字段套 hasFieldPermission('purchaseLink'),无权限者整项不显示
· ★ compareFields 必须加:编辑走的是「差异比对后只提交变更字段」,
漏了这一项则改了链接也不会被提交(静默失败,排查成本极高)
二、采购链接的 href 白名单
新增 safeHref():只放行 http/https。
★ 该字段由用户自由填写,直接绑到 href 上时 `javascript:` 伪协议会被浏览器
当成可执行链接 —— 点一下就执行脚本。非法值的行显示「格式无效」而非链接。
三、修复 Odoo 页「保存后列表变空、必须重新搜索才恢复」
根因:fetchOdooSummary() **无条件清空 groupCache**,却只在关键词变化时重置
activeCategories。保存编辑后关键词未变 → 分组仍显示为「已展开」、缓存却空了,
而 loadGroupItems 只由展开事件触发、不会重跑 —— 面板保持展开却无数据。
修法:关键词未变时,清缓存后把**仍展开的分组重新拉一遍**(与文件内批量操作
处既有的 delete + loadGroupItems 模式一致);关键词变了才折叠全部分组。
基础信息页(list.vue)不受影响:它的 getList 会完整带上 queryParams。
|
2026-09-17 11:26:42 +08:00 |
|
|
|
6cb30a21fd
|
feat(material): 采购链接接入读写映射与服务层
· models/base.py:新增 purchase_link 列(text)并加入 to_dict(purchaseLink)
· field_permissions.py:读过滤映射 purchaseLink → material_list:purchaseLink
· inbound/base.py:POST 与 PUT 两处 field_to_perm 同时补入(漏掉任一处,
新增/修改就会各缺一半)
· base_service.py:create_material 写入、update_material 按字段存在与否更新
★ 写侧刻意**显式纳入映射**而非依赖「不在映射中→默认允许」的兜底分支 ——
那个分支正是上一轮附件备注「谁都能写」的成因,新字段不再走它。
验证(6 个角色)
SUPER_ADMIN / SUPERVISOR / WAREHOUSE_MGR / INBOUND → 读✓ 写✓
OUTBOUND / SALES → 读✗ 写✗
读写逐角色一致;超管走 material_list:* 通配分支、过滤整段跳过(已单独复核)。
|
2026-09-17 11:26:36 +08:00 |
|
|
|
2f658407c3
|
feat(db): 基础信息新增「采购链接」字段并注册权限
需求:物料主数据增加一个可直接跳转的补货地址(淘宝/1688 等),纳入字段权限管控。
★ 设计取舍:读写**共用同一个权限码** material_list:purchaseLink
上一轮刚修完「写用 A 码、读用 B 码」造成的读写错位(附件备注),新字段刻意
沿用 referencePrice 的既有模式 —— 一个码同时进 field_permissions.py(读过滤)
与 base.py 的 field_to_perm(写过滤)。单码最大的好处是**结构上不可能错位**:
能读必然能写、反之亦然,不会再有「填了看不到」或「看得到改不了」。
若将来要求「能看不能改」,需拆两个码并同步改三处,届时一并回归验证。
★ 列类型用 text 而非 varchar(N):外链常带很长的查询串,截断后是打不开的地址,
且截断是静默的 —— 用户只会觉得「链接坏了」。
默认授予 SUPER_ADMIN / SUPERVISOR / WAREHOUSE_MGR / INBOUND(覆盖实际补货的人
与管理物料的人)。⚠ 这是本次取的默认值,业务可按需增删 —— 改 VALUES 再执行一次即可。
幂等;不含 psql 元命令,DataGrip 可直接整段执行;含核对段与回滚段。
|
2026-09-17 11:26:36 +08:00 |
|
|
|
2f7a81ff3d
|
fix(material): 物料列表列加双重守卫,消除「有表头无数据」的空列
现象
----
收紧某字段读权限后,后端已把值抹成 null,但表格列照常渲染 —— 留下一列
全是「-」的空表头,白占屏幕宽度。反馈的「专业名称」即属此类。
两处前提与代码不符,先澄清
----
· 列展示设置里**已有**「专业名称」选项(list.vue:190),columns.commonName
也**已在** columns 数组中(默认 visible: true)—— 无需补充。
该选项与表格列都走 hasColPermission('commonName'),没有权限者看不到选项,
与「没权限就看不见这列」的目标一致,并非缺陷。
· 真正的缺陷在同一处:**表格列只有 columns.X.visible,没有权限守卫**。
16 个数据列里仅 isApprovalRequired 有(且用错了码)。
改动
----
一、给全部数据列补上 && hasColPermission('<key>')(两文件各 15 列;
isApprovalRequired 原先写成 userStore.hasPermission('material_list:isApprovalRequired'),
改为同一函数,避免复选框与表格列各用一套口径)。
二、修正 permissionMap 两处与后端读过滤的口径错位:
isInspectionRequired: material_list:operation → material_list:isInspectionRequired
isApprovalRequired: material_list:operation → material_list:isApprovalRequired
(以 app/utils/field_permissions.py 为准)
★ 为什么第 2 步是必须的:前端按 operation 判断会以为「有权限」,而后端其实
按另一个码把字段抹成了 null —— 于是渲染出一列全是「-」的空表头。
前端判定码必须与后端读过滤码**逐字段一致**,否则修了守卫也照样是空列。
验证
----
· 两个文件的数据列 100% 带权限守卫(grep 复核)
· 前端 permissionMap 与后端 STOCK_FIELD_RBAC_MAPPING 逐字段比对:
16 项中 14 项完全一致;id / isEnabled 两项前端更严(后端为公开字段)——
方向安全,不会产生空列
· 前端 vite build 通过
|
2026-09-17 11:20:59 +08:00 |
|