|
|
8db33fdf40
|
V3.88,9.20推送
|
2026-09-20 17:01:52 +08:00 |
|
|
|
4f922a55f3
|
V3.87,9.20推送
|
2026-09-20 15:11:10 +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 |
|
|
|
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 |
|
|
|
91c4ae85db
|
V3.83,9.17推送
|
2026-09-17 13:40:56 +08:00 |
|
|
|
07567d2f76
|
fix(outbound): 「补发给谁」改为必选,并按原申请人精确预填
· 预填顺序改为:① 原出库明细记录的 applicant_id(创建出库时从审批单带出,
主键无歧义)→ ② 历史单据该字段为 NULL 时,退而按原领用人姓名**唯一命中**
预填;重名一律留空。
· 「补发给谁」改为**必选**:提交前校验,未选即拦下并提示。
不再有「留空则挂当前操作人」的兜底 —— 那会把别人的需求记到库管名下。
· 文案同步:明确写「必须选择:补发是原申请人的需求,不能挂到办理退回的库管名下」。
|
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 |
|
|
|
9d187593eb
|
feat(outbound): 退回弹窗增加「需要补发」勾选与补发数量
· 退回对话框新增「退回后自动生成补发单」勾选框 + 补发数量(勾选时默认 = 本次
退回量,可调小;上限即退回量)。
· 文案明写两条关键后果:生成的是**免审批**出库单;**库存不足时整笔退回会一并
取消**,取消勾选即可只做退回过账 —— 避免库管对着失败提示发懵。
· 提交前做同口径前置校验(数量 > 0 且 <= 退回量),不白跑一趟。
· resetReturnDialog / openReturnDialog 同步重置这两个字段。
默认**不勾选**:有些退回是项目结束退还,根本不需要补,不该默认给所有人挂上。
|
2026-09-17 11:56:39 +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 |
|
|
|
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 |
|
|
|
70071e111a
|
fix(material): 附件备注输入框按写权限置灰,消除 UI 与后端口径撕裂
背景
----
读权限(material_list:files,6 角色)决定「产品图」整块是否显示;
但写权限已收紧到 material_list:remark_edit(3 角色)。若前端不区分,
入库员/出库员/销售会看到可输入的备注框,填完却被后端静默丢弃 ——
正是本次要消灭的「能填却存不进去」的撕裂。
改动
----
list.vue(基础信息)与 buyOdoo.vue(基础信息(Odoo))各新增 canEditRemark,
两个备注输入框绑定 :disabled="!canEditRemark",并把占位文案切换为
「无编辑权限,仅可查看」,避免用户对着灰框发懵。
三处口径必须一致,已交叉核对:
前端 list.vue / buyOdoo.vue 的 canEditRemark
后端 base.py 的 field_to_perm(写)
后端 field_permissions.py 的映射(读)
|
2026-09-17 11:07:58 +08:00 |
|
|
|
1ebd06e68c
|
style(borrow): 时间线转交节点去掉冗余的「发起人」
转交已收紧为「仅当前持有人本人可发起」,发起人恒等于该节点已显示的 from_name
(「A → B」)—— 同一人出现两次是纯冗余,且第二次还是完整 username 格式
(杜邢宸/duxingchen),与全站展示名口径不一致。去掉。
数据不受影响:operator_name 仍在流水里存档,仅不再展示 —— 审计追溯仍可查。
归还 / 报废节点的「经手库管」保持不变:那里确实存在独立的窗口经手人
(归还人是 returner_id,经手库管是 operator_name),措辞正确。
|
2026-09-17 10:47:25 +08:00 |
|
|
|
b5f96384e9
|
style(borrow): 流转时间线区分成败 —— 被拒转交红点 + 红标签 + 原因独立成行
问题(截图反馈)
----
被拒收的转交和正常转交一样是黄色节点、黄色「转交」标签,拒绝原因又粗暴地
拼在备注后面(「备注: 3333 [拒绝原因] 5555」)—— 完全看不出资产交接失败。
改动
----
一、节点颜色:被拒收的转交 el-timeline-item 的 type 设为 danger(红色圆点);
待接收(PENDING)保持 warning(进行中,不是失败);
其余沿用动作类型配色。
二、动作标签:被拒的显示红色「转交被拒」,不再显示黄色「转交」。
三、原因分行:转交备注灰色单独一行,拒收原因另起一行、红色加粗前缀,
彻底解决文字挤在一起的问题。
顺带修正标签措辞
----
「经手库管」改为「发起人」:转交已收紧为「仅当前持有人本人可发起」,
该字段存的就是发起人本人,标成库管是错的。
(归还节点的「经手库管」保持不变 —— 那里确实是独立的窗口经手人。)
★ 一个可议之处:转交节点现在会同时显示「A → B」和「· 发起人:A」,
同一个人出现两次(字段格式不同:一个展示名、一个完整 username)。
若觉得冗余,可去掉后者 —— 但那属于展示取舍,未擅自决定。
|
2026-09-17 10:46:32 +08:00 |
|
|
|
f4f887c2b4
|
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」
提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类:
① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上
② 待我接收 —— 别人转给我、等我确认
弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断):
标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数),
按钮【去处理】(跳借还记录) /【知道了】。
★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。
前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办;
本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack),
避免两个 Modal 叠加打扰。
★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条,
从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次,
也不能漏)。
防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。
顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交
不再与成功的长得一模一样。
验证(node 复刻判定链)
既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack;
下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。
拒绝优先、不叠加、ack 后待接收提醒正常补上。
|
2026-09-17 10:44:07 +08:00 |
|
|
|
b53e22f536
|
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩)
背景
----
右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略,
待接收的物品就一直在系统里悬着。
改动
----
ElNotification -> ElMessageBox.confirm:
· 屏幕正中 + 灰色半透明遮罩,强制打断注意力;
· 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」;
· 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'),
后者仅收起弹窗,不阻断当前工作;
· closeOnClickModal=false(点遮罩不关,避免误触即消失),
但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。
★ 防叠加(两道,模块级标志 + DOM 探测)
reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载
(HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。
另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。
★ 一处必须修正的时序隐患:记录时机
原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后,
弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹,
**这条提醒被永久吞掉**。
现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出,
没弹出就不记账,留给下一轮轮询重试。
验证(node 复刻判定链,全场景通过)
3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 →
收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。
弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
|
2026-09-17 10:40:00 +08:00 |
|
|
|
c566d1b728
|
style(borrow): 借还记录表格响应式改造,消除横向滚动与操作列不可达
背景
----
主表共 11 列,原先除 2 列外用 width 硬编码,最小总宽约 1528px;
1440 笔记本可用宽度只有约 1240px(屏幕 - 180 侧边栏 - 20 内边距),
必然撑出横向滚动条,操作按钮被推出屏幕,用户得拖滚动条才能点转交。
一、固定操作列
----
主表操作列原本已有 fixed="right";**明细表的操作列没有**,本次补上。
子表嵌在主表展开区内,容器更窄(两侧各留 40px),不固定更容易被挤出屏幕。
操作列宽度 250 -> 230(实际按钮最多 3 个并存,230 足够)。
二、弹性列宽
----
按「内容长度是否确定」划界:
用 min-width(可伸缩)—— 单号 150 / 借用人 90 / 当前持有人 120 /
归还人 90 / 归还时间·预计 200
用 width(定长) —— 借出时间 160 / 借出物品 80 / 状态 110 /
电子签名 100(原 140,两个小标签用不了那么宽)
展开列本身约 48px,不可控
明细表同步:SKU 120 -> min-width 110;当前持有人 130 -> min-width 120。
主表最小总宽由约 1528px 降到 1378px。
三、长文本省略
----
物料名称、SKU、单号均已带 show-overflow-tooltip —— 超长物料名自动省略号,
悬浮显示全称,不会因单条数据撑爆整表。
★ 如实说明:11 列的表格在 1600px 以上屏幕可完整铺开;1440/1366 笔记本上
仍会保留一条横向滚动条(最小 1378px vs 可用约 1240px),这是列数决定的,
不删列就无法根除。但操作列两级都已吸附最右侧,**用户不再需要滚动即可操作** ——
这正是本次要解决的核心痛点。若要做到零滚动条,需另议「窄屏隐藏次要列
(电子签名/借出物品)」或侧边栏可折叠。
|
2026-09-17 10:35:11 +08:00 |
|
|
|
2c732a9a3a
|
feat(borrow): 全局待办强提醒(接收人不再处于盲区)
新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在
已登录区域渲染,天然保证「有 token 才查」)。
UI
----
ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉):
标题:待办通知:借库转交
内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】
点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。
message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。
防骚扰(两道)
----
· 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。
用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。
· 数量归零时清掉记录,下次新转交能重新提醒。
★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它):
需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的
用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底,
轮询不会造成重复打扰。不需要的话删掉定时器即可。
错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。
验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复,
归零后再来新转交能重新提醒。
|
2026-09-17 10:30:13 +08:00 |
|
|
|
4bd6765ab4
|
feat(borrow): 转交发起收紧为「仅当前持有人本人」(责任链隔离)
背景
----
此前【转交】只要持有 borrow_transfer 权限就可见可调,与「当前持有人」无关 ——
任何库管都能把别人保管的资产转给第三方,责任链形同虚设。业务方确认改为
**只有该物品的当前持有人本人可以发起**。
改动(前后端同改,缺一不可)
----
· service.transfer_borrow 新增 caller_user_id,强校验其 == 该明细
current_holder_id;传 None 一律拒绝,不做「系统内部调用」的隐式放行。
· get_records 为每条明细附加 can_transfer(当前持有人 == 我)—— 前端
localStorage 里只有 username 没有 user_id,故与 is_mine 一样由后端判定。
· 前端明细行【转交】改判 can_transfer;主行【转交】改为「该单下存在由我持有
的未还物品」时才出现;弹窗候选也过滤为「由我持有」,不是我的不列进来
(后端会拒,列出来只会误导)。
★ 连带调整:移除 route 上的 permission_required('borrow_transfer')
责任链规则既然是「持有人本人」,而持有人是普通员工、通常不持有库管权限,
再加一道库管权限,实际能发起的人变成「持有人 ∩ 库管」,绝大多数持有人
反而发不了 —— 功能形同虚设。这与 accept/reject 同级:员工处置自己名下资产。
真正的边界是 service 层的 caller_user_id 强校验,不是界面遮挡。
⚠ 由此 borrow_transfer 权限码已无任何代码引用(sys_element 中的定义与
4 个角色的授权仍在,属无害冗余)。若后续需要「管理员代办」入口,
可在此基础上加豁免;若确定不需要,该权限码可择期下线。
验证(13 项断言全通过)
----
· 非持有人发起被拒;未传调用者被拒;持有人转给自己被拒
· 持有人本人发起成功,from_user_id 正确记为持有人
· can_transfer:持有人 True / 接收人 False;接收转移后新持有人变 True
· 接收环节不受影响;库存零副作用、数据零残留
|
2026-09-17 10:20:34 +08:00 |
|
|
|
5f0cdc3adb
|
fix(borrow): 转交弹窗文案纠偏 + 表头一键全选
一、修掉弹窗内的过期文案(与「按件转交」自相矛盾)
----
顶部 Alert 仍写着上一版「整单转交」的描述,而下方的交互早已改为按件勾选。
现按业务方给的文案改为:
标题:「按件转交,对方确认后责任正式转移」
描述:「发起后将生成「待接收」记录,对方在系统中确认接收前,物品仍挂在您的名下。」
同时修掉同处一处过期注释(「整单转交 + 双向握手」)。
二、勾选改用 Element Plus 标准 selection 列
----
原先逐行手写 el-checkbox + computed 收集,**没有表头全选**。现改用
<el-table-column type="selection" width="55" :selectable="..." />:
· 表头自带一键全选 / 取消全选;
· selectable 钩子把「已有待接收」的明细排除在可选之外 ——
表头全选也会自动跳过它们,不会出现「整列勾上却提交失败」;
· 选中状态交由 el-table 内部托管(@selection-change),不再与数据源字段
两套并存导致不同步。
★ 预选通过表格 API(toggleRowSelection)在 nextTick 后写入,不直接改数据源:
弹窗是 destroy-on-close,每次打开表格都是新建的,故每次都要重新预选。
|
2026-09-17 10:17:48 +08:00 |
|
|
|
1bb3f59bb7
|
feat(borrow): 前端适配明细行粒度转交(部分转交)
主行聚合
----
「当前持有人」列改为三态:
· 单内只有 1 个持有人 → 显示该姓名(转过交时蓝色加粗)
· 单内有多个持有人 → 灰色标签「多人持有」
· 全部回库 → 「已回库」
★ 多持有人不再是异常:它是「借 2 件只转了 1 件」的正常形态。主行无法一一
列清谁拿哪件,故只做提示,具体归属下沉到明细行。
明细行
----
· 新增「当前持有人」列:能看到**具体哪件物品在谁手里**,待接收的另挂
「转交待确认」小标签。
· 新增操作列:【转交】(权限 borrow_transfer)、【接收】【拒绝】(待我接收时)——
转交粒度既然下沉到明细,这里才是精准入口。
转交弹窗(主行/明细行共用)
----
· 由「整单转交」改为**勾选式**:列出该单未还明细,可勾选其中若干件一并转交,
也可只转一件。已有待接收的明细置灰不可选。
· 从明细行进入时只预选该件;从主行进入时默认全选可转交项。
· 逐项发起、逐项成立流水:刻意不做「全部成功才算成功」的伪原子 ——
物理上每件物品本就是独立交接的,部分成功同样有意义。失败项由拦截器
提示原因,列表刷新后未成功的仍显示为可转交。
接收/拒绝
----
明细行按钮处理自己那一条;主行按钮聚合处理该单下所有属于我的待接收
(部分转交下单内可能有多项),并汇总提示「已接收 N/M 项」。
|
2026-09-17 10:12:36 +08:00 |
|
|
|
263f7ba3a1
|
feat(borrow): 前端适配整单转交与双向握手
· 转交弹窗由「选择明细」改为**整单**:列出该单全部未还物品(只读),
明确告知「发起后对方确认,责任才转移」。原先让用户挑一行,挑中就只转
那一行 —— 正是单内撕裂的入口,现从 UI 上根除。
· 操作栏新增【接收转交】【拒绝】:仅对 pending_transfer.is_mine 的行显示,
即「待我接收」的转交。已有 PENDING 时不再显示【转交】,避免重复发起。
· 状态列新增「转交待确认」:此时主表 current_holder 未变(东西还在原持有人
手上),必须用状态列把「责任正在转移中」显式表达出来,否则界面上看不出
任何变化。
· 新增 acceptBorrowTransfer / rejectBorrowTransfer API;
拒绝时用 prompt 收集可选原因,写入转交流水备注。
错误提示仍交由 request 拦截器统一 toast,不在本地重复弹出。
|
2026-09-17 10:04:16 +08:00 |
|
|
|
0eca1805b6
|
style(borrow): 当前持有人列改为常亮蓝色,转交时加浅蓝底与细边框
背景
----
上一轮按「只有发生过转交才高亮」实现后,业务方反馈「当前持有人那里也应该
高亮才对」—— 该列回答的是「东西现在在谁手上」,比「当初谁借的」更需要一眼
看到,不该只在转交时才着色。本轮按新要求调整。
改动
----
· 基础态:当前持有人一律主题色加粗(不再区分是否转交);
· 转交态:额外加浅蓝底 + 细边框(--el-color-primary-light-9 / light-5),
强调「经手人已变更,归还时必须由他本人来还」。
用浅蓝而非实心色块,保持上一轮确立的降噪基调,只做一层轻标记。
覆盖三处「当前持有人」:借还记录列表、还库页、转交弹窗(弹窗按单条明细比对,
故新增 isRowTransferred;组行仍用 currentHolderList)。
★ 数据侧的事实(供验收参考):trans_borrow_transfer 目前 0 笔,34 条未还
记录的「当前持有人 = 借用人」、53 条已还记录的持有人为空。
也就是说转交态(浅蓝框)在产生第一笔真实转交前不会出现。
|
2026-09-17 09:53:31 +08:00 |
|
|
|
b1f39acb97
|
style(borrow): 持有人高亮统一为条件蓝色,姓名按两字补全角空格等宽
一、当前持有人高亮策略统一
----
还库页(return.vue)该列原为橙色标签(type="warning"),与列表页在上一轮
已完成的条件高亮不一致。现统一为:
· 当前持有人 ≠ 借用人(确曾转交)→ 主题色 var(--el-color-primary) 加粗
· 两者同一人(常态)→ 普通深色文本,不做任何高亮
比对前把两侧都归一化到「斜杠前段」:历史数据可能混入「姓名/拼音」的完整
username 写法,不归一化会把同一人误判成转交,让整列无故飘蓝。
(records.vue 该列在 ba5394f 已是此策略,本轮仅同步调用 formatName。)
二、姓名两字补全角空格,实现等宽对齐
----
新增 formatName()(放在 src/utils/format.ts,而非各组件内复制 —— 列表页与
还库页都要用,规则改动只需改一处):
'高雪' -> '高 雪' U+3000 全角空格,占一个汉字宽
'郭俊玥' -> '郭俊玥'
'欧阳娜娜' -> '欧阳娜娜'
'AB' -> 'AB' 纯拉丁不补,否则渲染出 'A B' 怪相
null/'测试01'/混合字符 -> 原样
★ 纯展示层:只在模板 {{ formatName(row.borrower_name) }} 中调用,
row.borrower_name 等底层对象**一个字节都没动**。
姓名是转交/归还责任链的匹配键(records.vue 的 currentHolderList、
return.vue 的 isTransferred),往数据里插不可见字符会让比对、检索、
导出、复制全部带上它 —— 这正是上一轮拒绝「插空格」的原因,本轮改用
渲染期格式化,视觉要求与数据纯洁性同时满足。
★ 为什么用 U+3000 而非普通空格:普通空格宽度随字体浮动(约半个汉字),
补进去反而难对齐;U+3000 稳定占一个汉字宽,与「1 汉字 ≈ 1em」一致。
三、配套
----
· 归还人 / 当前持有人 / 借用人三列(含转交弹窗的持有人列)统一走 formatName;
· .name-fixed 增加 margin-right,避免同一格内多个姓名(同一单持有人/归还人
不同)紧贴成「高 雪张 三」。
|
2026-09-17 09:45:34 +08:00 |
|
|
|
ba5394f70c
|
style(borrow): 借还记录页视觉降噪、移除前端假排序、主行改按整单汇总
一、视觉降噪
----
· 「当前持有人」由橙色标签改为纯文本:
未转交 → 普通文本;发生过转交(当前持有人 ≠ 借用人)→ 主题色加粗。
原先一律用色块,一屏几十个标签会把真正需要关注的「已转交」淹没。
· 「无限期」由蓝色标签改为灰色小字:它不是异常,不该和红色的「已逾期」抢
视觉重心。(沿用系统既有术语「无限期」,与借出页「无限期/长期借用」一致)
· 转交弹窗内的持有人列同步降噪,保持同一表达。
二、移除前端假排序
----
「借出时间」列的 sortable 是 Element Plus 的**前端排序**,只会重排当前页的
10 条,反而打乱后端按单号聚合的「逾期优先」排序(有限期在前、按最早应还
升序、无限期按最早借出升序)。已移除。若将来需要按其他字段排序,应改用
sortable="custom" 并由后端接管。
三、主行改按整单汇总
----
主行字段原样取「首条明细」(groupMap 首次遇到即定型),多明细单会失真。
现对四项做整单聚合:
· 状态 —— 全部报废→已报废;全部结清→已还;仍有未还且已还过一部分→
部分归还;全部未还→未还。
修复「首条已还清、其余未还 → 主行显示已还,却仍出现在未归还
页签」的口径矛盾(该页签由后端按整单聚合过滤)。
· 归还人 —— 去重汇总(原只显示首条明细的经手人)
· 归还时间 —— 取**最晚**(整单结清的那一刻;原显示最早那笔)
· 应还时间 —— 取**最早**(与后端排序键 min(expected_return_time) 对齐)
四、姓名定宽对齐
----
中文 1 字 ≈ 1em,故姓名统一占 3em(.name-fixed):「石利」与「郭俊玥」占同样
横向空间,一列名字纵向对齐。
★ 刻意用 CSS 而非往数据里插全角空格:插字符会污染真实数据(复制、导出、检索、
姓名比对全带着它),而转交/归还的责任链恰恰依赖姓名比对。
★ 如实说明:上述聚合里,状态/归还人/应还时间三项在当前数据上**尚无可见变化**
—— 实测单号内状态完全一致、归还人同为一人、应还时间因同批发货天然相同。
它们是防住「部分归还 + 多明细」组合的前瞻性修正。真正改变显示的只有「归还
时间取最晚」:BOR-20260616-0001(21 条明细、21 个不同归还时间)等 2 张单。
|
2026-09-17 09:38:19 +08:00 |
|
|
|
8d83d4404e
|
fix(borrow): 恢复选中审批单后的「一键带出」,并补上领用人自动回填
现象
----
借库执行页选中一张已通过的审批单后,下方的「预计归还日期」「备注说明」
不再自动带出;「领用人」在改为用户下拉后也恒为空,库管每次都得手工补填。
根因(不在前端下拉框,而在后端持久化)
----
borrow_service.submit_approval 会把 reserve_for_items() 的返回值直接
set_items() 落库 —— 即审批单的 items_json **就是预占结果**。
而 reserve_for_items 内部是 `reserved.append({...})` **重建**字典,只保留
库存定位与数量,把申请端按明细传上来的 expected_return_time / is_indefinite
全部丢弃。执行页读的正是 firstItem.expected_return_time,读不到就落到
「清空」分支。
这是 2026-09-10 库存预占改造引入的回退:
b57c21a fix 前: approval.set_items(items) # 原始申请明细
b57c21a fix 后: approval.set_items(reserved_items) # 重建字典,字段丢失
数据完全吻合该提交的上线时刻(09-10 14:59):
最后一张「有日期」的审批单 id=40 创建于 09-10 09:30
第一张「丢失日期」的审批单 id=41 创建于 09-10 16:20
修复
----
1) inventory_reservation.reserve_for_items:透传申请「意图字段」
(expected_return_time / is_indefinite / remark)到预占结果。
· 一条申请明细会被拆到多个批次行(同物料多批次),故按 base_id 建索引,
把同一份意图回填到它拆分出的每一行;
· **只透传意图,不透传库存定位与数量** —— 实扫可能换批次,
这里写什么执行页就按什么释放,绝不能覆盖分配结果;
· 跳过 None:无限期借用提交的 expected_return_time 本就是 null。
2) borrow.vue handleApprovalChange:实现领用人自动回填
· 口径差异:审批单上的 borrower_name 是**完整 username**
(「杜邢宸/duxingchen」),人员名单返回的是展示名(「杜邢宸」)。
两边归一化到「斜杠前段」再比对,否则永远匹配不上;
· borrower_name 优先,匹配不到才回退 applicant_id —— 库管代建时申请人是
库管本人,回退到它会选错人;
· 名单加载改为可重复 await:选中单据时要拿它反查,名单没回来就比对会
误判为「找不到」而清空;
· 匹配不到时保持未选,不回退到自由文本 —— 借用人 ID 是转交/归还责任链的
唯一锚点,宁可让库管手选也不能猜。
可编辑性
----
三个字段均保持可改:领用人下拉、备注文本框无 disabled;日期选择器仅在勾选
「无限期/长期借用」时置灰(原有语义,取消勾选即可重新填写)。
影响与验证
----
· 存量:仅 1 张待执行审批单(id=46)受影响,其日期在库中已无任何留存,
无法回填,需库管手工补一次;已完结单据不受影响。
· 回归:reserve_for_items 透传 9 项断言、submit_approval 端到端 5 项断言
全部通过;库存精确还原、无残留数据。
|
2026-09-17 09:26:42 +08:00 |
|
|
|
ece5dd8d72
|
feat(borrow): 前端转交/流转明细 UI 与借用人选人适配
转交 UI(借还记录页)
----
· 操作列新增【转交】,v-permission="'borrow_transfer'" + 按钮 loading 防抖
· 弹窗明示「仅支持整单全部转交」与「转交后须由接收人本人归还」,并在确认框
复述接收人与转交数量
· 列表是 borrow_no 主子表结构,trans_borrow.id 在**明细行**上,而一张单的明细
可各有不同持有人(转交是逐条明细进行的),故弹窗先让用户选定明细;单条明细
时自动选中(实测 54/60 的单号只有 1 条,等于零额外点击)。
该「单号按钮 → 弹窗选明细」范式沿用本页既有的「申请报废」。
流转明细时间线
----
· 操作列新增【流转记录】,无特殊权限要求(页面本身已由 op_records 把守)
· Drawer + el-timeline 倒序展示 借出 → 转交(可多次) → 归还 → 报废,
每节点显示时间、动作、当事人(借用人 / 转出→接收 / 实际归还人)与经手库管,
转交备注一并展示
· 主列表新增「当前持有人」列:转交后可能与「借用人」不是同一人;一张单的明细
持有人可能不同,去重后逐个展示,已全部回库显示「已回库」
身份锚点适配
----
· 借出页:借用人由「自由输入姓名」改为「用户下拉」,提交 borrower_id
(姓名无法唯一锚定一个人,重名即责任链断裂)
· 归还页:新增「实际归还人」下拉,默认取首件物品的当前持有人,提交 returner_id;
主表新增「当前持有人」列提示库管该由谁来还
· 错误提示不再本地重复弹出 —— request 拦截器已按后端 msg 全局 toast,
再弹一次会双份
验证:vite build 通过;接口契约经后端回归用例校验。
|
2026-09-17 09:18:06 +08:00 |
|
|
|
f4a01be951
|
V3.81,9.16推送
|
2026-09-16 17:51:32 +08:00 |
|
|
|
d1337d12e1
|
fix(scrap): 修复扫码时库存行遮蔽在管不良品导致的「不在批准明细中」
现象
----
报废申请单指向在管不良品时,待执行清单里明明能看到该 SKU,扫码却报
「不在该报废申请单的批准明细中,禁止报废」。
根因
----
在管不良品的 SKU 是从原库存行**复制**的(退回时 sku=getattr(stock_row,'sku','')),
因此同一个条码可能同时命中 stock_buy#M 与 trans_defective_goods#N —— 这是
**两个不同的实物**。
而 ScrapService.get_stock_by_barcode 原实现按固定顺序
(stock_product → stock_semi → stock_buy → trans_defective_goods)返回
**首个命中**。原库存行只要还在,就永远遮蔽在管不良品,扫码结果恒为
stock_buy。改造前双方都用 SKU 作为匹配键,遮蔽不暴露问题;上一轮给匹配键
加上来源表后,`sku:trans_defective_goods:X` ≠ `sku:stock_buy:X`,
问题才浮出水面。
实测复现(业务报障的 SKU 0000000590):
stock_buy#589 在库 6 件
trans_defective_goods#24 在管 1 件(同一 SKU)
批准明细期望 -> ('trans_defective_goods', 24)
扫码实际返回 -> stock_buy#589 → 键不匹配 → 报错
修复
----
条码本身无法区分这两个实物,**只有单据上下文能决定该扫到哪一个**,故把
单据上下文引入扫码解析:
- get_stock_by_barcode(barcode, prefer_pairs=None):改为**收集全部候选**,
再按 prefer_pairs(当前申请单批准明细的 (source_table, stock_id) 集合)
优先命中;无上下文时退回固定顺序,行为与改造前一致
- GET /scrap/scan 新增可选 request_id:据此加载该单的批准明细构造优先集。
优先集构造失败不阻断扫码,仅告警并回退默认选路
- 前端 scanBarcode(barcode, requestId) 与 create.vue 调用处带上当前申请单 id
验证(隔离数据端到端,非仅单元):
同一 SKU 建于 stock_buy#2202 与 trans_defective_goods#25
不带上下文扫码 -> stock_buy#2202 (命中批准明细? False ← 旧行为)
带上下文扫码 -> trans_defective_goods#25(命中批准明细? True)
执行后:在管 2→0、状态=已报废;**库存行 10/10 分毫未动**(未误扣错误实物)
|
2026-09-16 17:22:51 +08:00 |
|
|
|
6520f1c97c
|
feat(scrap): 前端适配报废审批收口
配合后端把三条报废旁路收口到审批流。
【删除】api/scrap.ts 的 createScrap()
对应后端已删的 POST /api/v1/scrap。该函数此前已是死代码(全仓无调用方),
页面早已切换到申请-审批-按单执行流程。
【改造】不良品看板 views/stock/defective/index.vue
「报废销毁」→「申请报废」:弹窗新增「指定审批人」(必填,复用
getApproversList)。API 由 scrapDefective 改为 submitDefectiveScrapRequest。
★ 弹窗与成功提示都明确写出「在管数量不会立即扣减,待审批通过并执行后才
扣减」—— 申请不预占 remaining_qty,行数据看起来毫无变化,不说明会诱发
重复提交。
【改造】借还记录页 views/transaction/records.vue
「报废」→「申请报废」:弹窗新增「指定审批人」(必填)。原先内联的
request() 调用挪到 api/transaction.ts 成为 submitBorrowScrapRequest,
与全仓 API 分层一致。确认框文案改为「提交后进入审批流程」。
顺带清理 canScrap 里硬编码的 `username === 'IRIS'` 后门 —— 该账号在
sys_user 表中根本不存在,属死代码。
【改造】按单报废执行页 views/operation/scrap/create.vue
按 scrap_mode 把批准明细分流:
· scan 项(库存行 / 在管不良品)—— 走原有扫码购物车,逻辑不变
· auto 项(借出未还)—— 实物在借用人手上、无法扫码,放入**只读区块**
展示,不进购物车(购物车语义是「扫码证据」,混入会让扫码校验失效)
纯 auto 单隐藏整个扫码区并提示「本单无可扫码物料,将按批准数量直接执行」;
扫码进度只统计 scan 项(否则进度条永远满不了,会让操作员误以为没扫完);
提交守卫放宽为「购物车与 auto 项都为空才拦」;确认框追加
「另有 N 项将按批准数量自动执行」,避免操作员误以为只报废了扫到的那些。
matchKey 同步加入来源表(与后端 _match_key 保持逐字同口径),
sourceLabel 补两种新来源的中文名。
存量单据没有 scrap_mode 字段,前端回落为 scan,行为与改造前完全一致。
|
2026-09-16 17:14:29 +08:00 |
|
|
|
511e822d12
|
feat(ui): 按钮级权限守卫与数量显示格式化
按钮级权限(与后端 @permission_required 用同一批权限码):
- 出库记录页「退回」按钮 -> v-permission="'outbound_return'"
- 不良品看板「修复回库」 -> v-permission="'defective_restock'"
- 不良品看板「报废销毁」 -> v-permission="'defective_scrap'"
两个处置按钮是**独立权限**,可能有人只有其一,故分别判定、分别隐藏;
两者都无时显示「无处置权限」。均保留响应式的 v-if 兜底——v-permission
只在 mounted 执行一次,而 Element Plus 表格行会重渲染,两者用同一权限码
判定一致。hasPermission() 内部已对 SUPER_ADMIN 放行,与后端装饰器的
超管旁路对齐。
另外去掉了出库记录页原本套用的 username === 'IRIS' 特例放行:退回是实物
交接的 SOP 动作,后端不认这个特例,前端也不该认,否则会出现「按钮可见但
接口 403」的错位。
数量显示格式化(新增 utils/format.ts,该文件原本是 0 字节空文件):
- formatQty() 显示用:1.0000 -> '1',2.5000 -> '2.5'
- normalizeQty() 数值用:同上但返回 number,供 el-input-number 的
v-model / :max —— 喂字符串会让步进与边界比较退化
应用于 不良品看板(退回总数、剩余待处理、两个弹窗的在管数量与输入框)、
出库记录页(已退 N 提示、退回弹窗三项数字)、退回记录页(退回数量列)。
同时移除三个 el-input-number 上的 :precision="4" —— 它会把 1 强制渲染成
1.0000,是截图上多余小数点的直接来源。数量本身仍支持小数(后端按
numeric(19,4) 收),只是不再强制补零。
表格列原本不受影响(接口返回 JSON 数字,1.0 经 JS 解析即 1),但后端有一批
运算是 float 累加,可能吐出 0.30000000000000004 这类尾巴,格式化按
numeric(19,4) 量纲先收敛到 4 位再做归一。
|
2026-09-16 16:46:20 +08:00 |
|
|
|
27436f5efe
|
feat(ui): 菜单权限过滤与退回记录页
侧边栏支持按权限隐藏菜单(Sidebar/index.vue):
- 新增 canSee():读取 meta.permissions(数组,满足任一即可)
- ★ 同时修了一个既有缺陷:原实现只过滤顶层路由,**子路由的权限判定不生效**,
未授权用户仍能在菜单里看到入口。现改为递归过滤,父菜单的子项被过滤光后
父菜单本身也不再显示。
- ★ 刻意只认 meta.permissions(新键),历史遗留的 meta.permission(单数,
如「维修管理」的 inbound_repair)保持不生效——一旦开始消费它,会让若干
菜单对部分角色突然消失,属于超出本次范围的静默行为变更。两者若要统一,
建议单独排期逐个核对影响面。
路由守卫(router/index.ts):
- 新增 meta.permissions 校验,未授权时提示并跳首页。侧边栏负责「菜单看不见」,
守卫负责「直接敲 URL 也进不去」,后端负责「绕过前端直接调接口也不行」,
三层任一失效都不至于越权。
- 顺带修了原守卫的分支结构:roles 通过时直接 next(),会跳过后续检查;
改为不通过才中断,落到末尾统一 next()。
新增「退回记录」页(views/outbound/returns/index.vue):
- 纯只读台账,无任何操作按钮,供库管核对历史退回
- 展示 退回时间 / 原出库单号 / 物料名称 / 规格 / SKU / 退回数量 /
退回类型 / 退回原因 / 操作人
- 退回类型用标签区分(不良品=红,良品=绿)
- 源库存行或原单被删除时显示占位文案而非空白,台账不留哑行
api/inbound/return.ts 补 getReturnList()。
|
2026-09-16 16:46:02 +08:00 |
|
|
|
fd99d33a0d
|
feat(return): 前端退回入口与不良品在管台账看板
出库记录页(views/outbound/index.vue):
- 明细行新增「退回」列,returnable_quantity <= 0 时按钮置灰,行内显示「已退 N」
- 对话框展示 原出库数量 / 已退回数量 / 本次可退最大,默认带入可退最大值
- 退回类型用下拉单选:良品(加回库存)/ 不良品(转入异常待处理)
- 退回原因必填,提交前三重校验;按钮 :loading + 函数内 submitting 双保险防抖
- 成功后刷新列表。错误提示不重复弹——request 拦截器对 HTTP 400 已取
data.msg 展示,对话框 catch 只收尾
配套后端(services/outbound_service.py):
- get_grouped_list 的出库明细补 id / returned_quantity / returnable_quantity。
原先明细不带 id,退回接口无从指定 outbound_id
新增页面(views/stock/defective/index.vue,路由 /inventory/defective):
- 展示 物料名称/规格/SKU/退回时间/操作人/退回总数/剩余待处理/状态
- 顶部 alert 提示在管总量,并明确「这批实物不在库存表中,盘点请以本台账
为准」——在管坏件对盘点不可见是本方案的固有盲区,必须在页面上主动提醒
- 仅对 remaining_qty > 0 的行提供「修复回库」「报废销毁」,终态行显示已结案
- 两个弹窗均带数量上限约束与 loading 防抖,成功后刷新
新增 api/inbound/return.ts 承载四个逆向物流接口。
侧边栏由 router/index.ts 驱动,加路由即入菜单;注意侧边栏只过滤
meta.hidden、不看 meta.permission,故菜单对所有角色可见,访问控制由后端
接口负责(与既有「维修管理」一致)。
|
2026-09-16 15:45:40 +08:00 |
|
|
|
ffd3e2652c
|
V3.80,9.15推送
|
2026-09-15 13:27:42 +08:00 |
|
|
|
fe54d01391
|
fix(outbound): 序列号物料出库被误判超出计划数量
现象:申请单里同一个物料有 2 个,扫第 2 个时报
「超出计划数量(计划: 1,已扫: 1,本次: 1)」。
根因:计划侧与已扫侧用了**不同的聚合方式**。
已扫侧 .filter().reduce() → 把【所有】匹配行加总
计划侧 .find() → 只取【第一条】匹配行
序列号物料在申请单里是「一物一行、每行 quantity=1」(因为每个序列号对应
一条独立库存行),于是 planQty 只拿到 1,而 alreadyScanned 是全部之和,
1 + 1 > 1 直接判超发。普通数量制物料计划单只有一行,两边一致,所以一直
没暴露 —— 只有序列号物料会踩中。
同一缺陷还有第二处:unscannedList 逐行比较 planQty 与「累计已扫」,
导致两行各自都被同一笔扫码"满足",扫 1 个就把整个物料判为扫完、未扫清单漏报。
修复:
- validateAgainstPlan:计划侧改为 .filter().reduce() 汇总所有匹配行
- unscannedList:先按「名称+规格」合并计划行,再与同口径汇总的已扫数比较
用申请单 410(Opt1025 两行各 1 个)的真实数据模拟验证:
修复前 扫第2个 → 超出计划数量(计划:1,已扫:1,本次:1) ← 与现场报错逐字一致
修复后 扫第2个 → 通过
修复后 扫第3个 → 仍被拦(计划:2,已扫:2,本次:1),超发保护未失效
|
2026-09-15 13:21:36 +08:00 |
|
|
|
b55119acbc
|
V3.79,9.14推送
|
2026-09-14 17:35:07 +08:00 |
|
|
|
0481f917ee
|
fix(stocktake): 修正页面容器高度 —— 真正解决按钮被裁的真根因
前几轮都在 .idle-card 内层打转(min-height:0 / overflow / padding / 去居中),
全都没打到点上。真根因在页面容器**之外**:
.app-wrapper { height:100vh; overflow:hidden } App.vue
.app-content { flex:1; min-height:0; overflow:hidden } ← 高度 = 100vh − 90px
.app-main { position:relative } ← 无高度
.app-container { height:100vh } ← 就是本页
App.vue 里 .app-header 是 60px、.app-footer 是 30px,所以 .app-content 的实际
高度是 100vh − 90px。而本页容器写死 height:100vh,**比父容器高出整整 90px**,
超出的那一截被 .app-content 的 overflow:hidden 裁掉 —— 【开始新盘点】按钮
恰好就在那一截里,所以看不见也滚不到(滚动条属于被裁掉的那部分)。
改法:height 改为 calc(100vh - 60px - 30px),两个高度提成 CSS 变量并注明
对应 App.vue 的哪个类,便于外壳改动时同步。
为何不用 height:100%:.app-main 是 auto 高度,百分比无从解析,会退化成 auto。
同时保留前几轮的成果(它们各自都是对的,只是不足以单独解决问题):
- .idle-card { min-height:0; display:block; overflow:hidden auto }
- .idle-content { margin:0 auto; padding:20px 20px 60px }
- .scope-columns 高度 380 → 260,再省 120px
|
2026-09-14 11:10:42 +08:00 |
|
|
|
66e0728957
|
fix(stocktake): 欢迎页补足上下安全边距,显式压过 el-card 的 overflow:hidden
在 b7cdd5e 的 min-height:0 基础上补两处:
1. 顶部安全边距缺失:内容超高时 .idle-content 的 margin:auto 会塌缩为 0,
文字直接贴着屏幕顶端。改用**容器自身的 padding: 40px 0** —— padding
不参与 auto 塌缩,滚到顶/底都稳定保留 40px 呼吸空间。
2. 显式声明两轴 overflow:Element Plus 的 .el-card 默认 `overflow: hidden`
(实测其 dist/index.css 确有该声明)。原先只写 overflow-y: auto,
虽然 scoped 选择器(.idle-card[data-v-x],特异性 0,2,0)能压过
.el-card(0,1,0),但意图不够显式。改为 `overflow: hidden auto` 一次写清
「横向裁掉、纵向可滚」。
实测 style 块编译通过(12799 字符),min-height:0 / padding:40px 0 /
overflow:hidden auto / margin:auto 均已进入产物。
|
2026-09-14 11:05:12 +08:00 |
|