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函数
This commit is contained in:
yueli
2026-07-17 13:07:12 +08:00
parent 2e903cff2c
commit 3c0954598c
12 changed files with 259 additions and 265 deletions

View File

@ -102,6 +102,11 @@ class PermissionService:
else:
element_codes.append(p.target_code)
# ★ 诊断:打印入库操作权限
inbound_ops = [c for c in element_codes if 'inbound_buy' in c or 'inbound_semi' in c or 'inbound_product' in c]
if inbound_ops:
print(f"[诊断] {role_code} 拥有的入库操作权限: {inbound_ops}")
return {
'menus': menu_codes,
'elements': element_codes
@ -637,7 +642,19 @@ class PermissionService:
)
db.session.add(new_perm)
db.session.commit()
# ★ 入库模块操作权限元素(之前缺失导致"可编辑"勾了也不能入库)
inbound_op_elements = [
('inbound_buy', 'inbound_buy:operation', '可编辑', 'operation'),
('inbound_semi', 'inbound_semi:operation', '可编辑', 'operation'),
('inbound_product', 'inbound_product:operation', '可编辑', 'operation'),
('inbound_service', 'inbound_service:operation', '可编辑', 'operation'),
]
for menu_code, code, name, etype in inbound_op_elements:
existing = SysElement.query.filter_by(menu_code=menu_code, code=code).first()
if not existing:
db.session.add(SysElement(menu_code=menu_code, name=name, code=code, element_type=etype))
print(f"✅ 入库操作元素已创建: {code}")
# ★ 采购申请权限元素
purchase_elements = [
('inbound_purchase:operation', '可编辑', 'operation'),
@ -656,6 +673,17 @@ class PermissionService:
))
print(f"✅ 采购申请元素已创建: {code}")
db.session.commit()
# ★ 诊断:打印入库模块的元素和权限状态
for mc in ('inbound_buy', 'inbound_semi', 'inbound_product', 'inbound_service'):
elems = SysElement.query.filter_by(menu_code=mc).all()
codes = [e.code for e in elems]
print(f"[诊断] {mc} 的 sys_element: {codes}")
role_count = SysRolePermission.query.filter_by(
target_code=f'{mc}:operation', type='element'
).count()
print(f"[诊断] {mc}:operation 已分配给 {role_count} 个角色")
print(f"✅ 所有菜单初始化完成")
return True