Commit Graph

294 Commits

Author SHA1 Message Date
1dbf74b7bc feat(borrow): 借库审批补「完结」能力(对齐出库 status=4)
- mark_completed 改为 1→4 已完结(真正扫码借出完成仍为 status=3)
- 前端审批页新增「已完结」筛选项与状态映射;审批信息列对 3/4 显示审批人
2026-09-10 09:47:07 +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
044e6dbd98 feat(borrow,outbound): 库管(WAREHOUSE_MGR)代建申请强制审批
- submit/create 新增 force_approval:库管建单无视物料是否需审批,一律走审批
- 路由按当前角色为 WAREHOUSE_MGR 传 true;未选审批人返回明确业务错误
2026-09-09 16:19:38 +08:00
2a9e2e4560 fix(bom): BOM 三态互斥 + active_only排除归档 + 详情主备注
- get_bom_list active_only 补 is_archived==False,出库/借库“按BOM套餐”不再泄漏归档版本
- get_bom_detail 返回顶层 remark,详情弹窗主备注可回显
- 状态互斥:update_enabled 切启/停即清归档;update_archived 归档→废弃(enabled=False)、取消归档→启用;停用视图排除归档
2026-09-09 13:00:41 +08:00
eff9b62224 fix(borrow,outbound): 普通用户记录只看本人——按领用人/借用人姓名(不含账号前缀)匹配
- 出库记录/借还记录:非管理者视角时按 领用人(consumer_name)/借用人(borrower_name) 过滤
- 匹配取登录名 username.split(/)[0] 的姓名;兼容库里存“姓名/xiaolongxia”全名(姓名+/前缀)
- 修复:管理员替员工创建、领用人=员工 的单,员工登录可见
2026-09-09 11:19:15 +08:00
aa9322bcbc feat(borrow): 借库申请后端必填校验——申请原因、预计归还日期(长期借用除外)
- submit_approval 校验:申请原因非空;未勾长期借用时必须有 expected_return_time,否则 ValueError 拦截
2026-09-09 10:53:12 +08:00
d06ce45a72 feat(borrow,outbound): 需审批判定收敛——ID优先 + 名称精确AND核心规格(斜杠前段)
- approval_control 重写:带 base_id 按主键绝对判定;无 id 才名称100%一致 AND 核心规格(_code_of)一致兜底,杜绝同名不同规误杀
- 借/出库服务命中需审批但未选审批人时,报错列出需审批物料名/规格
2026-09-09 10:47:24 +08:00
4cd3eefdf4 feat(borrow,outbound): 默认免审批改造——命中物料需审批才走原流程
- models/base.py 加 is_approval_required 列及 isApprovalRequired 序列化;field_permissions 登记
- 新增 POST /inbound/base/batch-approval(批量设需审批,仿批量质检)
- borrow_service.submit_approval / outbound_service.create_request:明细含需审批物料→须选审批人走原审批;否则创建即 status=1(待库管执行)、不发审批邮件
- 判定按 (name,spec_model) 反查启用物料
2026-09-09 09:31:18 +08:00
2b0e335790 fix(inbound/buy): 批号自增改为按物料精准取最近一条
- 新增 get_latest_batch_record_by_base_id:SQL 直接 filter(base_id)+order by in_date/id desc first(),O(1) 不分页
- 新增 GET /inbound/buy/latest-record
- 修复原“拉全表前1000条再前端过滤”导致的历史被截断→批号退回000001/重复入库被拦(如 CCAB0029)
2026-09-08 18:16:04 +08:00
66b3e4897f fix(bom): get_bom_summary 关键词与 list 对齐 + 状态更新方法健壮化
- get_bom_summary 关键词过滤改为与 get_bom_list 同款子查询(同时匹配 bom_no、父件名/规格、子件名/规格),任意关键词下 summary 计数与 list 明细严格一致,消除“标题有数、表格为空”
- update_enabled/update_archived 对 bom_no/version 做 strip 规范化,并细化“BOM不存在”报错(带上实际参数便于排查)
2026-09-08 13:58:43 +08:00
02f74278df feat(bom): 归档业务闭环——状态筛选+归档/取消归档入口
- get_bom_list/get_bom_summary 状态过滤支持 archived,enabled 语义改为“启用且未归档”
- get_bom_list 输出补 is_archived;新增 update_archived 与 POST /bom/archive(整组切换归档、清缓存、审计)
- “未指定版本自动取最新”硬化为仅认启用且未归档(get_bom_detail / get_bom_no_by_parent)
- 前端:状态按钮组加“归档”;列表状态列区分归档;操作列加“归档/取消归档”按钮
2026-09-08 11:13:38 +08:00
91fb6c1a03 feat(bom): 自制件子件选版本落库/强校验 + 独立启停 + 状态筛选
- save_bom 落库 child_bom_no/child_bom_version;自制件必选且须属于启用未归档配方集,外购件置空;跨版本查重纳入引用版本
- get_bom_detail 改为读存储列,空则回退最新启用配方;候选/校验/回退读取均排除 is_archived
- 新增 GET /bom/self-bom-versions(子件候选版本);新增 POST /bom/status 独立启停(update_enabled 清缓存)
- 草稿链路 save_draft/get_draft_detail/publish 持久化引用版本
- get_bom_list/get_bom_summary 支持 status(enabled/disabled) 过滤
2026-09-08 10:14:44 +08:00
3dd8458de0 feat: BOM 子件展示自制件 BOM 版本号——子件列表与出库/借库 BOM 清单打印均显示 2026-09-07 18:01:16 +08:00
478b90fe50 feat: 出库类型贯穿申请→扫码出库——申请单加 outbound_type,申请时选类型、扫码出库自动带出 2026-09-07 14:01:13 +08:00
73510d3a71 feat: 借还记录默认排序重构——有限期单按预计归还时间升序靠前,无限期单按借出时间升序靠后(优先关注快到期/逾期) 2026-09-04 16:17:42 +08:00
0daf50ac21 fix: 成品/半成品/维修列表第二层跨域判断补认 crossDomain(解决库管配跨域仍看不到双边) 2026-09-04 13:01:44 +08:00
1527d552d4 feat: 出库/借库记录记录并展示库位快照(DB加列+后端写入+记录页展示) 2026-09-04 11:42:44 +08:00
e5c49c5177 fix: 入库 search-base 物料选择加公司隔离,恢复半成品批号按本公司历史自动递增 2026-09-04 09:42:49 +08:00
1b760f351e fix: 出库/借库扫码查库存与 BOM 子件物料选择加行级公司隔离(超管/跨域不受限) 2026-09-04 09:40:00 +08:00
c68ffdca0f fix: 入库成品/半成品/采购编辑其它字段时不再误清空图片——仅显式携带图片字段才更新 2026-09-03 14:14:24 +08:00
7fe861b4a7 fix: 基础信息修改其它字段时不再误清空 product_image(图片消失)——仅显式携带 generalImage 才更新图片 2026-09-03 14:12:15 +08:00
bf7b2cc6d3 feat: BOM 库存接口支持按版本查询,修复同一编号多版本下拉回显错乱 2026-09-02 18:39:17 +08:00
ee22927660 fix: 解耦 Webhook 身份证与库存序列号,恢复批号入库纯净
- 前端:新增临时字段 form.track_id 存 16位身份证,Batch 时 form.serial_number 清空
- 前端:submitForm 不再把身份证补进 payload.serial_number,改为透传 payload.track_id
- 后端:StockSemi 仅存 serial_number(Batch 为空),notify_track 优先用 track_id
2026-09-01 16:44:48 +08:00
fbb4160787 feat: 采购申请新增库管'完结'功能(仿出库审批)
- 后端:approve 接口支持 action=close(已通过→已完结 status 1→4),模型 status_text 支持已完结
- 前端:采购申请状态tab加'已完结',已通过单显示'完结'按钮(库管)
2026-09-01 16:15:48 +08:00
2c092c4c01 feat: MOM 出库联动 Track 标记已出库
- 发货出库时反查库存表 serial_number(Track 身份证),commit 后通知 Track mom-outbound
- notify_track 支持指定 webhook url(TRACK_OUTBOUND_WEBHOOK_URL)
2026-09-01 13:53:00 +08:00
bee037f925 feat: MOM 扫码入库对接 Track 产品身份证自动填充
- 半成品/成品入库弹窗顶部新增'扫码入库'按钮,打开扫码面板(扫码枪/摄像头)
- 扫码 Track 身份证后自动填充物料信息与序列号,不再二次搜索
- 后端新增 track_query_service(httpx 调 Track lookup)、track-lookup 接口
- 入库成功后 notify_track 通知 Track 闭环
2026-09-01 13:52:56 +08:00
1000deda2f feat(purchase): 采购单号改为 批次-批内序号 格式,显式体现同批
按用户原始逻辑还原并扩展:
- 单号格式: PUR-日期-时间-批次-批内序号
  例: PUR-20260831-1021-0001-0001(今日第1次采购的第1条)
- generate_request_no 支持 batch_seq 参数生成批次+批内序号
- 新增 GET /purchase/next-batch-seq 返回今日下一个批次号
- 前端提交时获取批次号,同批所有行共享,循环提交生成连续批内序号
- 批次识别: 单号去掉末尾 -批内序号 即为批次前缀
- 保留旧格式兼容(无 batch_seq 时回退 PUR-日期-时间-流水)
2026-08-31 13:54:03 +08:00
f499346f74 refactor(purchase): 批次概念改为基于 request_no 前缀推导,撤销数据库字段
按用户反馈:不需要新增数据库字段,现有 request_no (PUR-20260831-1021-0001)
本身就能推导批次——去掉末尾4位流水号即为批次前缀。

- 撤销 purchase_request.batch_no 字段(模型/数据库)
- 撤销 create 接口的 batch_no 参数与 generate_batch_no
- 批次标识 = request_no 去掉末尾 -XXXX 段
  getBatchKey('PUR-20260831-1021-0001') = 'PUR-20260831-1021'
- 按批次查询/批量审批接口改用 batch_key (request_no LIKE 前缀)
- 前端列表批次号列、整批弹窗、一键审批本批均基于前缀推导
- 零数据库迁移,历史数据立即可用
2026-08-31 13:49:11 +08:00
664127ee30 feat(purchase): 采购批次号支持,同一批提交的记录可分组查看/统计
背景: 循环提交把一批物品生成多条独立申请,无法识别'哪些是同一批采购'

后端:
- purchase_request 模型新增 batch_no 字段(同一批提交共享)
- create 接口接收 batch_no,无则自动生成 BATCH-yyyyMMdd-HHmm-随机
- 新增 GET /purchase/batch/<batch_no> 按批次查询所有记录
- 批量审批接口支持按 batch_no 一键审批整批

前端:
- 提交时生成 batch_no 共享给所有行
- 列表新增批次号列(可点击)
- 新增'整批查看'弹窗:显示同批次所有记录 + 一键批量通过本批
- 操作列新增'整批'按钮

数据库需执行: ALTER TABLE purchase_request ADD COLUMN batch_no VARCHAR(100);
2026-08-31 13:46:46 +08:00
3a07f1a272 fix(bom): BOM 详情查询改为 LEFT JOIN,子件物料被删后仍返回父件结构
- get_bom_detail 使用 outerjoin 替代 join
- 子件物料被删除后 child_name 显示 [已删除物料] 而非误报 BOM 不存在
- 修复按 BOM 出库时因子件删除导致 404 BOM 不存在
2026-08-31 10:28:22 +08:00
36e9f500aa feat(borrow): 借库未归还直接报废功能(关联报废单流程)
后端:
- trans_service 新增 scrap_borrow 方法:未归还借用标记为 scrapped,
  扣减总库存 stock_quantity(可用已在借出时冻结),生成 TransScrap 报废记录
- transactions.py 新增 POST /borrow/scrap 接口(复用 op_return:operation 权限,库管/主管可操作)
- scrap.py query_records 支持 trans_borrow 来源的物料名解析 + 行级隔离

前端:
- records.vue 未归还记录新增「报废」按钮(库管/主管可见)
- 报废弹窗:勾选明细 + 填报废原因 + 二次确认
- 状态显示新增「已报废」标签;整单所有明细报废时标记为 scrapped
2026-08-31 10:21:28 +08:00
39f3f8af45 feat(outbound): 审批单手动完结/作废功能
- 后端: outbound_service 新增 close_request 方法(状态 1-已通过 → 4-已完结)
- 后端: 新增 POST /api/v1/outbound/request/<id>/close 接口
- 模型: status_text 数组增加「已完结」
- 前端: create.vue 审批单选择区新增「强制完结此单」按钮
  完结后刷新列表,该单从已通过下拉中移除
- 权限: 超级管理员/审批人/拥有 outbound_create:operation 的用户可完结(库管可操作)
2026-08-31 10:15:33 +08:00
95057dedf5 refactor(import): 导入服务核心逻辑改造
- 预览阶段增加 Excel 内部重复检测 + 空规格去重 + 字段长度校验
- 执行阶段支持 skip 模式(跳过重复行继续导入)+ update 模式(覆盖更新已有记录)
- update 模式空值保护:Excel 空单元格不覆盖已有数据
- DB commit 异常防御:字段超长等异常优雅返回而非 500
- 去重规则改为仅依据 spec_model(货号唯一键)
- BOM 导入物料查找同步改为 spec-only
2026-08-03 15:53:10 +08:00
65aba4b313 fix: token 刷新时缺少 company_name 导致多租户过滤返回空数据 2026-08-03 09:22:50 +08:00
29de22b575 fix: crossDomain 重复收集 — 真实树中的 system_mgmt/crossDomain 与虚拟 global_privileges 冲突,从真实树中移除 crossDomain 避免双重收集 2026-07-17 16:22:59 +08:00
c45e7b5a52 debug: assign_permissions 加详细日志追踪 crossDomain 删除/插入/提交后状态 2026-07-17 16:18:05 +08:00
de2d13fa68 fix: 全局权限码 crossDomain/global:cross_company_op 强制存为 company_name='',防止按公司隔离失效 2026-07-17 15:50:06 +08:00
efe04c5d42 fix: assign_permissions 删除逻辑对齐 get_user_permissions 查询逻辑 — 公司定制+全局模板一起替换 2026-07-17 15:38:14 +08:00
f3c946d584 fix: 借库审批同样修复 — sys_element 补充 op_borrow_approval:operation,can_approve 增加 RBAC 权限判断 2026-07-17 15:22:54 +08:00
05832e913b fix: can_approve 增加 outbound_approval:operation RBAC 权限判断,前端按钮权限与后端审批权限统一对齐 2026-07-17 15:17:26 +08:00
3c0954598c perf: 综合安全加固 — RBAC严格映射+异步邮件+字段权限白名单+前端对齐+导入模板
本次提交包含本会话所有修改的最终统一提交

## 权限系统重构
- permission_service.py: 添加入库/采购操作元素 + ensure_default_permissions
- field_permissions.py: 严格1-to-1 Default Deny 字段映射(StockBuy/Semi/Product/MaterialBase)
- decorators.py: _expand_operation_perms 双向粒度桥接 + prevent_double_submit
- deploy_production.sql: 修复 sys_element 别名码(qty_inbound→in_quantity)

## 采购模块
- purchase.py: 权限驱动可见性 + inbound_purchase独立权限 + 价格字段过滤
- purchase_service.py: 异步邮件 + 三阶段批量模糊匹配防N+1
- purchase/index.vue: canApprove严格操作权限 + upload重复修复

## 导出/入
- base_service.py: export_excel 流式写入防OOM + get_latest_specs 优化
- import_service.py + import_api.py: Excel批量导入(模板+预览+执行)
- ImportDialog.vue: 三步骤导入弹窗

## 异步邮件
- email_service.py: send_email_async (守护线程)
- inventory_task.py: send_email→send_email_async

## 前端对齐
- product/semi/buy.vue: 列对齐in_quantity/stock_quantity/available_quantity + localStorage缓存V2
- buyOdoo.vue: 排序修复 + 导入按钮 + 移除点击展开加载
- BomManage.vue: 懒加载分组 + 导入按钮
- list.vue: 导入按钮
- Selection.vue + borrow/apply: BOM匹配修复 + 导入按钮
- outbound/create.vue: 出库类型必选
- AppMain.vue: 移除transition白屏修复
- material_base.ts, outbound.ts, bom.ts, stock.ts: 新增API函数
2026-07-17 13:07:12 +08:00
2e903cff2c perf: 规格连号助手优化 — 流式查询+宽松regex+Redis缓存
## base_service.py get_latest_specs
- .all()全量→with_entities().limit(10000) 防OOM
- regex放宽: 支持 LICA-3000/M3x12 等格式(旧版仅匹配OPT12046)
- 移除OPT硬编码→SUB_CATEGORY_PREFIXES可配置集合
- Redis缓存(1h): 首次查询后缓存,后续命中直接返回

## base.py 缓存失效
- 新增 _invalidate_specs_cache() 辅助函数
- create/update/delete 成功后清除缓存
2026-07-16 18:15:43 +08:00
754c46bd59 feat: 采购申请字段级价格权限 — 库管看不到采购价格
## permission_service.py
- init_all_menus: 新增3个采购权限元素
  inbound_purchase:unit_price, :total_price, :tax_rate

## purchase.py
- _filter_purchase_prices(): 无价格权限则pop价格字段
- GET /purchase (列表): 应用价格过滤
- GET /purchase/<id> (详情): 应用价格过滤
- approved-unstocked: 保留价格(入库数据源,前端按inbound_buy权限控制)

## purchase/index.vue
- 列表: 单价/总价/税率列 v-if hasPermission
- 详情: 单价/总价 v-if hasPermission
2026-07-16 17:34:24 +08:00
23fc10a25f fix: Day2就绪 — 删除硬化+索引补全+全局异常处理
## base_service.py — MaterialBase删除硬化
- 新增 BomTable 引用检查(bom_usage_count)
- 物料被BOM引用时禁止删除,提示「请先解除BOM绑定」

## models/base.py + bom.py — 缺失索引补全
- material_base: company_name + is_enabled 添加 index=True
- bom_table: is_enabled 添加 index=True

## __init__.py — 全局异常处理
- @app.errorhandler(ValueError) → 400 + 自定义消息
- @app.errorhandler(404/405) → 标准错误
- @app.errorhandler(Exception) → 500 + 通用消息
  堆栈仅记录服务器日志,不泄露到前端
2026-07-16 14:14:43 +08:00
5411cabd0a feat: Excel批量导入后端 — 模板下载+预览验证+执行导入
## import_service.py (新增)
- generate_template(): 基础信息/BOM Excel模板生成(带样例数据)
- parse_boolean(): 是/否/True/False 稳健布尔解析
- preview_material/bom(): Dry-Run解析+验证(重复/存在性/必填)
- BOM ffill: 父件列自动向下填充(支持合并单元格UX)
- execute_material/bom_import(): 事务包裹批量写入

## import_api.py (新增)
- GET /api/v1/import/template?type=material|bom
- POST /api/v1/import/preview (文件→预览验证)
- POST /api/v1/import/execute (确认导入)

## __init__.py + requirements.txt
- 注册 import 蓝图; 新增 pandas>=1.5.0
2026-07-16 14:00:28 +08:00
3290f206c6 perf: 导出OOM修复(yield_per流式) + 采购模糊匹配安全防护
## base_service.py export_excel
- query.all()全量→yield_per(2000)分块流式读取
- all_rows[]内存收集→write_only=True直写Excel
- all_rows.sort()→ORDER BY数据库排序
- 删除~160行旧Excel样式/脱敏死代码
- 内存: 全量→2000行分块+输出流

## purchase_service.py
- 模糊匹配安全防护:
  len(n.strip())>=2 过滤空格/单字符
  .limit(500) 防全表返回
2026-07-16 13:08:45 +08:00
e313f575bf perf: 系统级异步邮件 — 消除SMTP阻塞导致的10-15秒UI冻结
## email_service.py
- 新增 send_email_async(): threading.Thread(daemon=True) 包装 send_email()
  无需 Flask app_context (send_email仅用os.getenv+smtplib)
- 6处 send_email→send_email_async 替换

## inventory_task.py
- 库存预警 send_email→send_email_async (2处)
2026-07-16 11:26:12 +08:00
128985af17 feat: 采购模块重构 — 权限驱动的可见性 + 严格操作权限
## purchase.py
- GET /purchase (列表): @jwt_required, 有权限看同公司全部/无权限只看自己
- POST /purchase (创建): @jwt_required, 任何登录用户可提交
- GET /purchase/<id> (详情): @jwt_required, 自己的或有权者可见
- PATCH /purchase/<id>/approve (审批): @permission_required('inbound_purchase:operation')
  不再硬编码 SUPERVISOR 角色, 有操作权限即可审批
- GET /purchase/approvers, /auto-fill, /search-material: @jwt_required (开放给创建流程)
- 新增 _user_has_purchase_perm() 统一权限检查
- 移除所有硬编码 role in ('SUPERVISOR','SUPER_ADMIN')

## purchase_service.py
- send_email → send_email_async (采购审批邮件异步发送)

## purchase/index.vue
- canApprove: 仅检查 inbound_purchase:operation (菜单权限≠审批权限)
- customUpload: 修复 v-model:file-list 重复推入导致图片双份显示
- auth.py → purchase.py 权限前缀统一
2026-07-16 11:25:53 +08:00
0b75740ae0 feat: 权限系统重构 — 操作权限双向展开 + 启动时自动补全默认权限
## permission_service.py
- init_all_menus: menu_defs 新增 inbound_purchase(采购申请) 菜单项
- init_all_menus: 创建 inbound_purchase:operation 操作元素
- 新增 ensure_default_permissions(): 启动时从 sys_user 提取活跃角色,
  对空权限角色自动补全默认菜单(按角色类型分级)
- 自动迁移: 已有 inbound_buy 权限的角色 → 自动获得 inbound_purchase
- SUPERVISOR 启动时自动获得 inbound_purchase 菜单权限

## decorators.py
- _expand_operation_perms 改为双向桥接:
  正向(inbound_buy:operation←inbound_buy:write)→通过
  反向(inbound_buy←inbound_buy:operation)→通过(可编辑隐含可读)
- 新增详细诊断日志,记录展开路径和失败原因

## __init__.py
- 启动时调用 PermissionService.ensure_default_permissions()
2026-07-16 11:25:41 +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