feat(purchase): 新增待采购池接口(防重复采购)
GET /api/v1/purchase/pending-pool:找出「有效供给仍低于预警线」的物料。 有效供给 = 物理总库存 + 在途量 过滤规则 = 有效供给 <= 红/黄阈值 才进池 建议采购量 = max(1, 目标阈值 - 有效供给) ★ 为什么不用「有活跃单就排除」:红线 10、库存 0、某采购员只建了一张 数量 5 的单时,该物料会立刻从池中消失,剩下 5 个缺口永远无人认领。 改为按量计算后它继续留池,suggested_qty 自动降到 5。 ★ 用 <= 而非 < 是与业务方确认后刻意维持的口径(与物料列表预警、预警邮件 同源),勿擅自改成严格小于 —— 只改本接口会造成「列表亮黄灯、池子却排除」 的撕裂,真要改必须三处一起动。代价是恰好等于阈值时建议量落到保底的 1, 前端 tooltip 已单独说明,不展示算不成立的错误算式。 权限:新增 inbound_purchase:pending_pool(注册为 sys_element 而非新建 SysMenu)。因 ensure_default_permissions 在角色已有权限时整段跳过,另写了 存量角色自动迁移,并按源行镜像 company_name 避免跨公司作用域泄漏。
This commit is contained in:
@ -673,6 +673,12 @@ class PermissionService:
|
||||
('inbound_purchase:unit_price', '采购单价', 'column'),
|
||||
('inbound_purchase:total_price', '采购总价', 'column'),
|
||||
('inbound_purchase:tax_rate', '税率', 'column'),
|
||||
# ★ 待采购清单(防重复采购)菜单权限。
|
||||
# 注册成 element 而非新建 SysMenu:侧边栏与路由守卫只读前端
|
||||
# meta.permissions + sys_role_permission,从不读 sys_menu;
|
||||
# 且 init_all_menus 的「冗余子菜单清理」会按 target_code 删权限行,
|
||||
# 新增 SysMenu 等于多一个被该清理逻辑波及的面。
|
||||
('inbound_purchase:pending_pool', '待采购清单', 'operation'),
|
||||
]
|
||||
for code, name, etype in purchase_elements:
|
||||
existing = SysElement.query.filter_by(
|
||||
@ -833,6 +839,48 @@ class PermissionService:
|
||||
print(f"[权限迁移] {role_code}: inbound_buy → inbound_purchase 已自动迁移")
|
||||
total_inserted += 1
|
||||
|
||||
# ★ 自动迁移:已有「采购申请」菜单权限的角色 → 自动获得「待采购清单」权限
|
||||
#
|
||||
# 为什么必须独立成段:上面的默认分配逻辑在角色**已有任何权限**时
|
||||
# 就整段跳过(见上方 existing_count > 0 的分支),存量角色根本走不到
|
||||
# 那里。不写这一段,除了超管之外没有任何角色能拿到新权限。
|
||||
#
|
||||
# 本段依赖 autoflush:上一个循环刚 add 的 inbound_purchase 行尚未
|
||||
# commit,这里的查询必须能看见它们(扩展默认 autoflush=True 保证了这点)。
|
||||
for role_code in known_roles:
|
||||
if role_code.upper() == 'SUPER_ADMIN':
|
||||
continue
|
||||
|
||||
# 以「采购申请菜单权限」为门控 —— 能进采购页的角色就该看到待采购清单。
|
||||
# ★ 同时镜像它的 company_name:若某角色的采购菜单是公司定制的
|
||||
# (company_name='A'),补出的权限必须落在同一作用域;硬写 NULL
|
||||
# 会让其他公司的同角色用户凭空多出这个菜单。
|
||||
src_perm = SysRolePermission.query.filter_by(
|
||||
role_code=role_code, target_code='inbound_purchase', type='menu'
|
||||
).first()
|
||||
if not src_perm:
|
||||
continue
|
||||
|
||||
# 幂等检查也必须带 company_name,否则 A 公司已有行会跳过 B 公司那条
|
||||
already = SysRolePermission.query.filter_by(
|
||||
role_code=role_code,
|
||||
target_code='inbound_purchase:pending_pool',
|
||||
type='element',
|
||||
company_name=src_perm.company_name,
|
||||
).first()
|
||||
if already:
|
||||
continue
|
||||
|
||||
db.session.add(SysRolePermission(
|
||||
role_code=role_code,
|
||||
target_code='inbound_purchase:pending_pool',
|
||||
type='element',
|
||||
company_name=src_perm.company_name,
|
||||
))
|
||||
total_inserted += 1
|
||||
print(f"[权限迁移] {role_code}: 已补充「待采购清单」权限"
|
||||
f"(作用域={src_perm.company_name or '全局'})")
|
||||
|
||||
# ★ SUPERVISOR 主管默认获得采购申请子菜单权限
|
||||
for role_code in known_roles:
|
||||
if role_code.upper() == 'SUPERVISOR':
|
||||
|
||||
Reference in New Issue
Block a user