02e03e6fb6
fix(stocktake): 补上模块级 traceback 导入,修复异常分支的 NameError
...
stock.py 模块级从未 import traceback,却有 3 处裸调 traceback.print_exc():
- scan_stock_by_barcode (/scan) —— 本次改动之前就存在
- get_stocktake_companies (新增)
- get_active_session (新增)
后果是真正的错误被 NameError 盖住:例如 company_name 列不存在时,
日志里只剩「name 'traceback' is not defined」,看不到根因。
其余 4 处在 except 块内写了局部 import 因而一直正常,现统一由模块级提供。
2026-09-11 11:34:23 +08:00
b8d18c71d8
fix(stocktake): 盘点链路按公司隔离,开启新盘点不再清空整表
...
修复三处跨公司数据污染:
1. /draft/start-new 原本执行 StocktakeDraft.query.all() 后逐条 delete,
任一库管点「开启新盘点」就会物理删除全公司所有人的盘点进度。
改为只签发新 session_id,历史数据原样保留;清理走 /draft/clear,
且强制要求 session_id(不传直接 400),杜绝误清整表。
2. get_stock_info() 全局按 barcode/sku 匹配三张库存表,不同公司的同码
物料互相串货。新增 company_name 参数,精确与模糊两段查询均经由 base
关系按 material_base.company_name 过滤;草稿去重键同步改为
(uuid, session_id, company_name)。
3. 盘点相关读写接口统一施加公司隔离:/draft/list、/draft/add、/draft/clear、
/variance-report、/draft/merged-list、/stocktake/all-items、
/stocktake/generate-missing、/stocktake/update-quantity、/export-stocktake、
/adjust、/scan。
顺带修复 /export-stocktake:差异与相符两张 Sheet 原本不按 session 过滤,
过去依赖 start-new 清空整表才恰好等价于当前会话,现显式按 session_id 过滤,
否则历史会话会被一并导出。
新增接口:
- GET /stocktake/companies 盘点页公司下拉(普通用户只返回本公司,
避免复用 /inbound/buy/options 时因缺 inbound_buy 权限而 403)
- GET /draft/active-session 该公司最近一次活跃会话,供多设备加入
2026-09-11 11:33:12 +08:00
f589962e5c
feat(scrap): 新增报废专用库存查询端点,解耦出库选单权限
...
报废申请页原复用 /inbound/stock/list,该端点权限是 outbound_selection。
持有 scrap_apply 的角色目前恰好也都持有该权限,故能跑通,但属隐性耦合:
任一角色授权脱钩就会静默 403,被前端 catch 掩盖成「加载库存失败」。
照抄借库既有先例(transactions.py 的 /borrow/stock-list),新增:
GET /v1/scrap/stock-list @permission_required('scrap_apply')
★ 同步把 'scrap_apply' 登记进 stock.py 的 SELECTION_PREFIXES。
_make_price_stripper 仅当前缀为 None 或已在集合中时才剥离价格,
漏登记会导致价格/成本字段泄露给报废申请人(Fail-Closed 失效)。
实测:OUTBOUND/WAREHOUSE_MGR 均 200,无 token 401,
响应中 unit_price/total_price/sale_price 等价格字段零泄漏。
2026-09-10 11:32:44 +08:00
ffbb2199f0
feat(scrap): 报废申请审批流 + 按单报废(后端)
...
- ScrapApproval 模型 + ScrapApprovalService:提交/列表/审批/执行(锁库存扣 available_quantity 写 TransScrap)
- 新路由 POST /scrap/request、/request/check-approval、GET /request、PATCH /request/<id>/approve、POST /request/<id>/execute;权限码 scrap_apply/scrap_approval/scrap_execute(无角色硬编码)
- 旧 /scrap 直接报废入口保持不变
- /inbound/stock/list 每项返回 source_table,供按单流程精准选库存
2026-09-10 09:46:57 +08:00
b40aa3ab90
fix: 出库选单手动添加库存(/v1/inbound/stock/list)加行级公司隔离(三表按 base 公司过滤,超管/跨域不受限)
2026-09-04 15:55:24 +08:00
893e7250db
fix(api): update-stocktake-quantity 按 session_id 隔离更新,防止跨会话误改
...
后端 update_stocktake_quantity 原只按 stock_id+source_table 查草稿,
不同盘点会话会相互覆盖实盘数。后端增加 session_id 过滤,
前端 handleQuantityChange 配套传入当前会话ID,API 类型同步补全。
2026-08-31 16:00:20 +08:00
eb017d881c
perf(api): draft/list 与 stocktake/all-items 改为数据库级分页,消除 N+1
...
- /draft/list: 废弃 query.all() + 逐条查库存/material_base 的 N+1,
改为原生 SQL 连表(草稿 JOIN 三张库存表 + material_base)+ LIMIT/OFFSET 分页
- /stocktake/all-items: 废弃三表 .all() + item.base 懒加载 + 内存切片,
改为 UNION ALL + LEFT JOIN + LIMIT/OFFSET,COUNT 聚合求总数
2026-08-31 16:00:13 +08:00
6ba20fa3db
fix(stocktake): 修复盘库重复记录 — 去重条件去掉 user_id
...
根因: draft/add 用 (user_id, uuid, session_id) 匹配去重,
同一盘点单多个用户(手机/平板不同账号)操作同一物料时,
每个用户各建一条 → 重复记录。
修复:
- 去重改为 (session_id, uuid) 唯一,不按 user_id 隔离
- 已存在则更新数量并记录最后操作用户
- 清理现有重复数据(保留每组最小id)
- 新增唯一索引 uq_stocktake_draft_session_uuid 防未来再重复
数据库需执行:
DELETE FROM stocktake_draft a USING stocktake_draft b
WHERE a.session_id=b.session_id AND a.uuid=b.uuid AND a.id>b.id;
CREATE UNIQUE INDEX IF NOT EXISTS uq_stocktake_draft_session_uuid
ON stocktake_draft(session_id, uuid);
2026-08-31 15:18:47 +08:00
7a3ffd8ba5
feat(stocktake): 重复扫码提示并确认是否修改
...
问题: 同一个码扫两遍输入两个数,后端 draft/add 直接覆盖,无任何提示
可能误改已盘数量
改进:
- 后端 draft/list 支持 uuid 过滤,可精确查某物料是否已盘
- 扫码弹输入框前,先精确查询该物料是否已盘
- 已盘过 → 弹窗提示『当前实盘为 X,是否修改?』
确认后预填旧值供修改,取消则不改变
2026-08-31 15:13:52 +08:00
c6e8c887ec
perf(stocktake): 应盘清单分页 + 扫码置顶高亮
...
性能修复(解决打开费劲):
- get_all_stocktake_items 改为分页返回 {items(当前页), total, total_scanned}
不再三次 .all() 全量加载 + 内存排序
- 前端 fetchAllStockItems 分页拉取(每页200),stats 用后端返回的 total
不依赖全量数组长度
体验优化:
- 扫码成功后将物品置顶到当前表格第一行并浅绿高亮3秒
库管无需翻页找刚扫的物品,'嘀'一下确认数量即可继续
- 主表格行高亮样式 .just-scanned-row
2026-08-31 14:59:22 +08:00
7e281cf285
fix(stocktake): 盘库扫码改为精确匹配,修复漏匹配与性能问题
...
问题:
1. 准确性 bug: 前端用 getStockList({pageSize:10, keyword}) 模糊搜索 + find()
当 SKU 前缀相同、目标不在前10条时,误报'未找到该物料库存'
2. 性能: 每次扫码全表 ilike %x% 搜索 3 张表,不走索引
修复:
- 后端 get_stock_info 改为精确匹配(==)优先,未命中再回退模糊搜索
- 新增 GET /inbound/stock/scan 扫码精确匹配接口,返回唯一命中
- 前端 onScanSuccess/handleManualInput 改用 scanStockByBarcode
一次请求直接命中,不再依赖 pageSize:10 + find()
2026-08-31 14:49:24 +08:00
2ff0f92f94
fix(stocktake): 修复盘点报告导出 500 错误
...
- 导出盘点报告时第1126行使用了 func.sum()/func.coalesce()
但文件未导入 sqlalchemy 的 func
- 改为 db.func.sum()/db.func.coalesce(),与文件内其他用法一致
2026-08-28 15:53:03 +08:00
8c10bb0903
feat: 盘点合并列表 — 服务端JOIN+DB分页,消除99999全量加载
...
## stock.py
- 新增 GET /draft/merged-list: UNION ALL三表+LEFT JOIN draft
数据库级 LIMIT/OFFSET 分页,不再加载全量到Python内存
- 参数: session_id, keyword, status_filter, page, pageSize
- 返回: list, total, total_scanned (已扫去重数)
## stock.ts
- 新增 getDraftMergedList() API函数
## stocktake/index.vue
- fetchInventoryList: 改用merged-list单次调用替代99999全量+find()
- resumeSession: limit 99999→1 先检查存在,再limit 500加载
2026-07-16 13:08:29 +08:00
c07f25b646
fix: Fail-Closed 字段级安全加固 — 堵住15+端点价格泄露
...
## stock.py
- _make_price_stripper(): 工厂函数,选单前缀自动剥离所有价格/成本字段
- _do_get_stock_list(permission_prefix): 每个item.to_dict()后剥离价格
- /all 端点: 非AI模式自动剥离价格字段
- /list 端点: permission_prefix='outbound_selection' 传递
## outbound.py
- /bom-match-stock: 返回前按stock_type剥离全部价格/成本字段
- /scan: result.pop('price', None)
## scrap.py
- /scan: result.pop('price', None)
- /records: 每条记录剥离 cost_at_scrap, total_loss
## transactions.py
- filter_item_by_permissions: 从空字典恢复完整20字段映射
- /borrow/stock-list: permission_prefix='op_borrow_apply' 传递
## buy.py / semi.py / product.py
- submit 成功响应剥离所有价格/成本字段(不泄露给前端)
## bom.py
- /base/list: 剥离 referencePrice
2026-07-16 11:26:05 +08:00
2556b77530
perf: 系统级性能优化与并发安全修复
...
## 并发安全修复 (4处)
- scrap.py: 报废执行添加 SELECT FOR UPDATE 悲观锁,消除 TOCTOU 竞态
- stock.py (adjust_stock): 盘点调整添加 for_update=True 行锁
- outbound_service.py: 低库存预警 SMTP 调用移到 commit 之后,避免长事务
- trans_service.py: execute_dispatch 按 (source_table, id) 排序 items,消除死锁风险
## N+1 查询优化 (2处)
- inventory_task.py: _prefetch_inventory_map 单条 UNION ALL+GROUP BY 替代循环内逐条查询(N*4次→2次)
- stock.py (export_stocktake): get_borrowed_qty 批量 GROUP BY 替代逐条 TransBorrow 查询(~18000次→1次)
## BOM 列表性能重构
- bom_service.py: get_bom_list 单条 GROUP BY+string_agg+分页,消除 N+1 循环查询
- bom_service.py: 新增 get_bom_summary (轻量 GROUP BY category+COUNT)
- bom.py: 新增 /api/v1/bom/summary 路由,/list 支持 category 过滤
## Odoo 基础信息懒加载
- base_service.py: 新增 get_odoo_summary (GROUP BY category+COUNT)
- base.py: 新增 /api/v1/inbound/base/odoo-summary 路由
- buyOdoo.vue: 懒加载分组架构 (fetchOdooSummary + loadGroupItems)
- material_base.ts: 新增 getOdooSummary API
## 前端 Bug 修复
- BomManage.vue: 懒加载分组 (fetchBomSummary + loadGroupItems + collapse)
- BomManage.vue: 适配新 API 格式 (res.data.items 替代 res.data)
- buyOdoo.vue: 移除 "点击展开加载" 文字
- Selection.vue + borrow/apply/index.vue: openBomSelect 适配新 API 格式
2026-07-15 17:37:57 +08:00
0651eb07fa
fix: 彻底解耦出库/借库权限联动 + 路由去硬编码roles + 补API权限保护
...
根因: 借库选单页面14处硬编码outbound_selection:operation, 导致两模块权限联动
修复:
- borrow/apply: 14处outbound_selection:operation→op_borrow_apply:operation
- stock.py: 拆分_do_get_stock_list裸逻辑, 出库/借库各绑独立权限码
- transactions: 新增/borrow/stock-list端点(@permission(op_borrow_apply))
- transaction.ts: 新增getBorrowStockList前端API函数
- outbound.py: 出库审批4端点改用独立outbound_approval权限码
- transactions.py: 借库审批3端点补@permission(op_borrow_approval)
- purchase.py: 采购管理7端点补@permission(inbound_buy)
- audit.py: 审计日志补@permission(system_audit)
- router: 清除全部6处硬编码roles, 交由动态权限树控制
2026-07-15 14:33:11 +08:00
c1092407ad
V3.50
2026-06-25 15:21:43 +08:00
cf55c94826
feat: 库存接口增加 ai_mode=true 极简返回模式,键名压缩为 n/s/c
2026-05-19 09:53:06 +08:00
183b93012e
4.28
2026-04-28 16:07:11 +08:00
e08eaff40a
feat(outbound): 库存列表按规格+库位聚合 + BOM明细类型修复
2026-04-28 09:23:59 +08:00
40e405becd
4.27
2026-04-27 16:33:54 +08:00
48f2011a38
fix: 盘点草稿已盘数量统计兼容字段名 quantity
2026-04-24 13:32:46 +08:00
996056d46a
fix: 修复库存盘点已盘数量卡在500的问题
2026-04-24 13:19:57 +08:00
a849e14b2c
refactor(orm): 将所有的批量 delete/update 重构为对象级操作,以确保触发 SQLAlchemy 审计事件
2026-04-20 15:43:48 +08:00
0d8f697df4
fix(stocktake): enforce session_id in resume, make missing generation idempotent, update UI to show SN, and fix excel time offset
2026-04-03 09:42:51 +08:00
43e1d0aa55
fix(stocktake): strictly isolate stocktake drafts by session_id in list and finish generation to prevent historical data mixing
2026-04-03 09:25:52 +08:00
6aec571775
fix(stocktake): resolve 500 error caused by missing batch or sn attributes using strict getattr fallback
2026-04-03 09:05:48 +08:00
c361d25ea0
fix(stocktake): display batch number in scanning dialog and fix empty uncounted items in Excel export
2026-04-02 19:06:19 +08:00
a52ced0375
fix(backend): resolve DetachedInstanceError in audit_log, add pessimistic locks for stock adjustments, and eliminate N+1 queries with eager loading
2026-04-02 18:44:12 +08:00
b2ce9d31f8
fix(stocktake): replace hardcoded 'admin' with actual current user in excel export
2026-03-27 09:04:13 +08:00
2a27f2e0df
feat(stocktake): implement strict blind stocktake logic with hidden system qty, editable count and status filters
2026-03-26 17:42:51 +08:00
c8810891d8
fix(api): globally replace invalid material_base/material_name attributes with correct base relationship
2026-03-26 17:14:26 +08:00
93dc375ba4
perf: replace client-side pagination with server-side pagination in stock selection dialog and fix duplicate variable in semi.vue
2026-03-23 14:52:01 +08:00
c5974ff05c
fix: relax permission requirement on stock list api to unblock outbound operations
2026-03-23 14:22:52 +08:00
7c9331d78a
perf: implement optimistic UI for scanner, disable auto-camera, and sort excel by SKU
2026-03-23 09:51:59 +08:00
53c198f363
feat: fix stocktake deletion bug, and add pagination, search, sorting to stocktake lists
2026-03-19 16:21:09 +08:00
b37049a4d7
feat: add generate-missing logic to identify unscanned stock as inventory loss
2026-03-19 15:34:54 +08:00
7867fc5e40
fix: resolve TypeError between float and Decimal in stocktake excel export
2026-03-19 11:47:31 +08:00
a32d4f6b65
fix: resolve 500 error on excel export by aligning headers and data row lengths for remark field
2026-03-19 11:41:53 +08:00
a19167e804
fix: add missing remark header to stocktake excel export
2026-03-19 11:36:48 +08:00
0bc0908b9b
fix: ensure remark field is properly parsed and saved to database during stocktake update
2026-03-19 11:27:40 +08:00
367dceef31
feat: add master summary sheet to stocktake excel export
2026-03-18 14:57:12 +08:00
f6153fc10d
feat: add unscanned items sheet to stocktake export and fix user real name mapping
2026-03-18 14:48:49 +08:00
a5f4d32306
fix: map correct user real name for stocktake excel export
2026-03-18 14:43:24 +08:00
41a4518911
fix: map correct database fields for spec and location in excel export
2026-03-18 14:37:09 +08:00
00781422eb
fix: correct excel export formatting (timezone, spec, user, location) and add auto-polling for collaborative stocktake
2026-03-18 14:22:01 +08:00
ac15ef74db
fix: resolve MaterialBase sku property error and add dynamic refresh after stock adjustment
2026-03-18 14:01:36 +08:00
33969b8336
fix: support stock adjustment for items without stocktake draft
2026-03-18 13:53:55 +08:00
e08012d9dd
fix: add missing imports for openpyxl and patch draft resolution in stock adjustment
2026-03-18 13:24:16 +08:00
49a66f9be3
fix: resolve 500 error in excel export and 404 in stock adjust
2026-03-18 11:46:38 +08:00