Commit Graph

39 Commits

Author SHA1 Message Date
fd084bf8fd fix(material): 附件备注纳入写权限管控,废止「不在映射中→默认允许」的兜底
问题
----
POST /inbound/base/ 与 PUT /inbound/base/<id> 的 field_to_perm 里**没有**
productImageRemark / manualLinkRemark,于是这两个字段落入
「不在映射中 → 默认允许」的兜底分支 —— 任何能调通该接口的角色都能改。
配合读侧引用幽灵权限码,形成「谁都能写、除超管没人能读」的错位。

改动
----
两处映射(创建 + 修改)同时显式补入:
    'productImageRemark': 'material_list:remark_edit',
    'manualLinkRemark':   'material_list:remark_edit',
无写权限者的请求不会携带该字段进入服务层 —— 是「丢弃本次修改」而非
「写成空值」,因此不会误清既有内容。

★ 超管不受影响:base.py 的 get_current_user_permissions() 对超管返回的
  硬编码列表以 'material_list:*' 开头,命中通配符分支后整段过滤被跳过。

验证(6 个角色 × 读写,12 项断言全通过)
    超管 / 主管 / 库管    → 读✓ 写✓
    入库 / 出库 / 销售    → 读✓ 写✗
2026-09-17 11:07:57 +08:00
97ff63523c fix(export): 公司隔离移出 if filters 判定,确保无条件执行
export_excel 的行级公司隔离原本嵌套在 `if filters:` 内部:
只要调用方不传或传空筛选条件,整段隔离会被跳过且不抛错 —— 静默失效。
(实测: 修复前 export_excel({}, None) 会导出跨公司全部 1816 行。)

现把 get_current_company_filter() 及其 filter_conditions.append 提到
if filters: 之前,无论有无筛选条件都绝对执行。

同时清理路由里 filters 字典的 'company' 死键 —— export_excel 从不消费它,
真正生效的是 get_current_company_filter() 直接读 request.args 上的公司标识。

注意:前端 handleExport 的 company 参数必须保留(已在代码中加注说明)。
实测 WAREHOUSE_MGR 带 ?company=IRIS 导出 1126 行仅 IRIS,不带则 1816 行
涵盖 IRIS+LICA —— 跨域角色按公司收窄范围完全依赖这个 query 参数。

实测(修复后):
  SALES/IRIS          filters={}          → 1126 行, 仅 IRIS
  SALES/IRIS          filters={keyword:''}→ 1126 行, 仅 IRIS
  WAREHOUSE_MGR       ?company=IRIS       → 1126 行, 仅 IRIS
  WAREHOUSE_MGR       (无参数)            → 1816 行, IRIS+LICA
2026-09-11 12:41:27 +08:00
4808a48594 refactor(audit): 审计架构清理——复活白名单监听器、停用噪声监听器、清除僵尸装饰器
一、统一为单一监听器实现
  原先两套 SQLAlchemy 事件监听器并存:
    · app/utils/audit_events.py   —— 全局监听 db.Model、无白名单、无请求上下文守卫(实际在跑)
    · app/core/audit_listener.py  —— 白名单制、有守卫、有模型级开关(从未生效)
  后者失效的根因:注册代码写在 extensions.py 的 init_extensions() 内,
  而该函数全仓库只有定义、没有任何调用(create_app 直接内联调用 db.init_app 等)。

  现统一由 app/core/audit_listener.py 承担,并在 create_app() 中显式注册。
  extensions.py 的死函数 init_extensions 整体删除,避免后人误以为它是有效入口。

二、修复监听器三处致命缺陷(此前注册了也写不进数据)
  1. 事件回调第二个参数是 Connection,原代码却调用 Connection.add()(不存在),
     每次写日志都抛 AttributeError 并被 except 吞掉 → 改为 connection.execute()
  2. register_audit_listeners 从 app.models 批量 import 多个未导出的模型,
     ImportError 被上层 try/except 吞掉 → 改为按表名从 db.metadata 取模型
  3. 本项目有 31 处函数体内延迟导入模型(如 scrap.py 内部才 import ScrapApproval),
     一次性注册会静默漏表 → 增加 ensure_audit_listeners() 惰性补绑,
     并在模型预加载段补全审批单/BOM/采购等模型

三、强约束
  · WHITELIST_TABLES:仅 18 张核心业务表,系统表/草稿表/向量表不再自审
  · has_request_context() 守卫:系统初始化与后台定时任务不再产生 username=system 噪声
  · IGNORE_FIELDS 增加 password/password_hash/salt/token/secret/api_key(安全红线)
  · created_at 显式写 beijing_time(),与全系统时间口径一致

四、清除僵尸装饰器
  @audit_log 早已退化为直接透传的空壳(module/action 参数全被忽略,
  数据库中零星的中文 action 即其历史遗留产物),却仍挂在 38 处路由上。
  连同 13 个文件的 import 一并移除;audit_events.register_audit_events 改为空操作。

验证:应用上下文中的写操作不产生日志;HTTP 请求产生 5 条日志,
对象为业务单号(APR-SCRAP-... / SKU),模块中文,操作人真实,时间为北京时间。
2026-09-10 14:16:27 +08:00
4146f35010 feat(permission): “出库/借库需审批”开关权限化——仅主管/超管可见可改
- 新增权限码 material_list:isApprovalRequired,授予仅 SUPER_ADMIN/SUPERVISOR(收回其它角色)
- 物料列表该列仅持码角色可见;后端批量端点与字段脱敏均改按新码校验,其余角色看不到值也无法改
2026-09-09 10:47:36 +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
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
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
8f468a0a39 fix: 修复全局字段权限导致数据显示为空的问题
根因: /permissions/role/<role_code> 接口要求 system_permission,
非管理员角色无法获取自身权限 → 权限数组为空 → 前后端字段全被过滤

修复:
- permission.py: 查自己角色不再需要 system_permission, 解除权限死锁
- 5个 inbound API: 恢复完整 field_to_perm 映射, 每个字段均可独立权限管控
- 补齐遗漏字段(qty_inbound/qty_stock/qty_available/request_id/request_no)
- buy.vue: 入库表单价格字段加入 hasFormFieldPermission 守卫
2026-07-14 16:09:41 +08:00
760f78e016 feat: 添加参考价格列 + 修复公司筛选跨域问题 + CLIP模型持久化
## 新增功能
- material_base 表新增 reference_price 列(NUMERIC(10,2))
- 基础信息 list.vue / buyOdoo.vue 页面增加「参考价格」列展示和编辑
- 新增 material_list:referencePrice 权限元素,支持按角色控制可见性

## Bug 修复
- 入库三页面(buy/product/semi)公司筛选:非超管用户 company=ALL 不再传给旧版后端
- 基础信息两页面(list/buyOdoo):buyOdoo 增加 v-if=isSuperAdmin 与 list.vue 行为统一
- company=ALL 默认值在各页面 getList/fetchData 中自动过滤,兼容旧版后端

## 运维优化
- docker-compose.prod.yml 增加 models_prod 卷挂载,CLIP模型持久化免重复上传
- deploy_code.sh 增加 models_prod 目录检查与模型文件存在性告警
2026-07-13 17:30:52 +08:00
4f5965db02 feat: JWT多租户数据权限隔离 & 主管系统管理权限 & 含税单价补齐
## 多租户公司数据隔离
- 新增 get_current_company_filter() 工具函数 (decorators.py)
  SUPER_ADMIN: 可传company_name参数过滤或传ALL看全量
  其他角色: 强制隔离到JWT中的company_name
- 重构 base_service.py / buy_service.py: 用集中式函数替换内联公司过滤
- SysRolePermission 表新增 company_name 字段,支持同角色不同公司权限
- get_user_permissions() 新增 company_name 参数,查公司定制+全局模板权限
- permission.py API 新增 @permission_required 拦截 + 公司过滤
- 19个API/service文件传递 company_name 到权限查询

## 主管系统管理权限
- delete_user() 允许SUPERVISOR删除同公司用户 (原仅SUPER_ADMIN)
- get_all_users() 新增 company_name 参数过滤
- 用户列表/权限分配 API 应用 get_current_company_filter()
- 前端 UserCreate.vue: 超管可见公司下拉框,主管隐藏部门字段

## 前端多租户适配
- material/list.vue / buy.vue: 公司下拉框仅超管可见,默认ALL
- UserCreate.vue: 新增搜索栏公司筛选,部门字段按角色显隐
- auth.ts: getUserList() 支持 params 参数

## Bug修复: 含税单价字段补齐
- buy.vue: 表格列/高级筛选/排序/权限映射新增 post_tax_unit_price
- buy_service.py: allowed_fields/sort_field_map 新增 post_tax_unit_price
2026-07-13 15:12:22 +08:00
DXC
bac670ef7a 基础信息页:计量单位改 el-select(下拉历史+手动输入);表单排版重排为 4 行(类别占满行);类别末级英文后缀自动填规格型号 2026-06-04 13:22:51 +08:00
dxc
259f3a7e0d 4.29扫码获取库位小工具接口 2026-04-29 15:40:43 +08:00
DXC
c7ac092be4 feat(material): add global floating helper to track latest specification codes with smart grouping 2026-04-13 08:28:27 +08:00
DXC
032479fe38 fix: capture and persist target object names for delete, outbound, and borrow operations in audit logs 2026-03-20 15:47:13 +08:00
DXC
b08bbba718 fix: enforce Beijing Time (UTC+8) globally by replacing datetime.utcnow 2026-03-20 15:23:20 +08:00
DXC
c1c494893f feat: implement dynamic inspection requirement logic based on material master data 2026-03-17 11:56:04 +08:00
DXC
d0fa474ee4 \"fix: auto enable warning sort based on permission when no manual sort specified\" 2026-03-11 17:21:31 +08:00
DXC
4bf5a23d31 \"fix: add missing imports for db and MaterialBase in base.py\" 2026-03-11 17:10:07 +08:00
DXC
6b932f79a8 fix: Ð޸´Ԥ¾¯ÉèÖÃÖÐãÐֵΪ null µ¼Öºó¶Ë 500 ±ÀÀ£µÄÎÊÌ⣬²¢Ôö¼Óǰºó¶˰²ȫУÑé 2026-03-11 17:06:19 +08:00
dxc
b1cc280a71 feat: 重构全局搜索框为复合条件选择,支持按名称、俗名、规格进行精准查杂 2026-03-11 13:37:52 +08:00
dxc
e224a07a47 feat: 升级预警批量设置交互,引入批量操作模式切换,提升界面整洁度与体验 2026-03-11 13:28:11 +08:00
dxc
d2d9abe201 全局审计日fix: 使用鸭子类型强制安全解包 SQLAlchemy Row 对象,彻底解决 to_dict 报错志 2026-03-11 13:11:16 +08:00
DXC
ea28ee1c86 feat: 为核心业务 API 全面挂载审计日志装饰器 2026-03-10 17:16:57 +08:00
dxc
ec8bdb2476 feat: sync advanced filter fields with column permissions
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:53:52 +08:00
dxc
c5872aed3c feat: add advanced filtering and full-field sorting to material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 15:22:04 +08:00
dxc
c3e2494b3e fix: correct default sorting and export desensitization logic
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:23:00 +08:00
dxc
fed85e51c5 feat: add sorting and export desensitization to material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:09:02 +08:00
dxc
89620b2445 fix: case-insensitive super admin role check and wildcard permission
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 16:34:23 +08:00
dxc
079987e7f3 feat: enforce field-level permissions for material creation and update
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:41:27 +08:00
dxc
d6d9621bf3 fix: resolve global permission code collision with material_list prefix
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:43:32 +08:00
dxc
f178b9cd00 fix: correct permission codes in inbound base API
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:36:28 +08:00
dxc
b3e1ac6245 feat: implement permission checking and field-level data masking
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:20:09 +08:00
dxc
73ee163352 feat: add MaterialBase permission control with field-level filtering
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:16:43 +08:00
dxc
63a3cf269d 库存资产excel文件导出 2026-02-25 09:55:25 +08:00
dxc
5532c87684 基础信息展示以及搜索逻辑进行修复 2026-02-11 13:12:05 +08:00
dxc
e027ebd4a9 盘库操作初设计 2026-02-06 10:16:37 +08:00
dxc
06ba2d7563 采购件,半成品,产品页面初步完成 2026-01-29 09:27:56 +08:00
dxc
7a4ea8acfb 基础信息读取错误,未修改完成 2026-01-28 08:54:11 +08:00