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:
yueli
2026-09-18 14:30:53 +08:00
parent fc5e62c848
commit dec33b685c
3 changed files with 327 additions and 1 deletions

View File

@ -423,3 +423,37 @@ def search_material_for_purchase():
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': str(e)}), 500
# --------------------------------------------------------
# 9. 待采购池(防重复采购)
# GET /api/v1/purchase/pending-pool?page=1&limit=20&keyword=xxx
# --------------------------------------------------------
@purchase_bp.route('/pending-pool', methods=['GET'])
@jwt_required()
@permission_required('inbound_purchase:pending_pool')
def get_pending_purchase_pool():
"""
待采购清单:库存已触及预警线、且没有活跃采购单的物料。
防重逻辑的核心 —— 物料一旦有在途采购单(待审批/已通过/部分到货未齐),
立即从本列表中消失;单据被驳回或强制结案后才会重新回流。
"""
try:
page = request.args.get('page', 1, type=int)
limit = request.args.get('limit', 20, type=int)
keyword = request.args.get('keyword', '').strip() or None
category = request.args.get('category', '').strip() or None
material_type = request.args.get('type', '').strip() or None
warning_status = request.args.get('warning_status', type=int)
result = PurchaseService.get_pending_purchase_pool(
page=page, per_page=limit, keyword=keyword,
category=category, material_type=material_type,
warning_status=warning_status,
)
return jsonify({'code': 200, 'msg': '获取成功', 'data': result}), 200
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': f'获取失败: {str(e)}'}), 500