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);
This commit is contained in:
@ -157,6 +157,38 @@ def get_purchase_detail(purchase_id):
|
||||
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 3.5 按批次查询采购申请(同一批提交的多条记录)
|
||||
# GET /api/v1/purchase/batch/<batch_no>
|
||||
# --------------------------------------------------------
|
||||
@purchase_bp.route('/batch/<path:batch_no>', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_purchase_by_batch(batch_no):
|
||||
"""按批次号查询同一批提交的所有采购申请记录"""
|
||||
try:
|
||||
from app.models.purchase import PurchaseRequest
|
||||
records = PurchaseRequest.query.filter(
|
||||
PurchaseRequest.batch_no == batch_no
|
||||
).order_by(PurchaseRequest.id.asc()).all()
|
||||
|
||||
user_id = get_current_user_id()
|
||||
has_perm = _user_has_purchase_perm()
|
||||
|
||||
items = []
|
||||
for r in records:
|
||||
d = r.to_dict()
|
||||
# 非本人且无权限时,价格字段剥离
|
||||
if d['requester_id'] != user_id and not has_perm:
|
||||
for k in ('unit_price', 'total_price', 'tax_rate'):
|
||||
d.pop(k, None)
|
||||
items.append(d)
|
||||
|
||||
return jsonify({'code': 200, 'msg': '获取成功', 'data': {'batch_no': batch_no, 'items': items}}), 200
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 4. 审批采购申请
|
||||
# PATCH /api/v1/purchase/<id>/approve
|
||||
@ -214,7 +246,9 @@ def batch_approve_purchase():
|
||||
|
||||
请求体:
|
||||
{
|
||||
"ids": [1, 2, 3],
|
||||
"ids": [1, 2, 3], // 按 ID 列表审批
|
||||
或
|
||||
"batch_no": "BATCH-xxx", // 按批次号审批整批(同一批提交的记录)
|
||||
"action": "approve" | "reject",
|
||||
"reject_reason": "驳回原因(reject 时必填)"
|
||||
}
|
||||
@ -223,11 +257,23 @@ def batch_approve_purchase():
|
||||
user_id = get_current_user_id()
|
||||
data = request.get_json() or {}
|
||||
ids = data.get('ids', [])
|
||||
batch_no = data.get('batch_no')
|
||||
action = data.get('action', 'approve')
|
||||
reject_reason = data.get('reject_reason')
|
||||
|
||||
# 若传 batch_no,则查出该批次的所有待审批记录 ID
|
||||
if batch_no:
|
||||
from app.models.purchase import PurchaseRequest
|
||||
records = PurchaseRequest.query.filter(
|
||||
PurchaseRequest.batch_no == batch_no,
|
||||
PurchaseRequest.status == 0
|
||||
).all()
|
||||
ids = [r.id for r in records]
|
||||
if not ids:
|
||||
return jsonify({'code': 400, 'msg': '该批次没有待审批的记录'}), 400
|
||||
|
||||
if not ids or not isinstance(ids, list):
|
||||
return jsonify({'code': 400, 'msg': 'ids 不能为空'}), 400
|
||||
return jsonify({'code': 400, 'msg': 'ids 或 batch_no 不能为空'}), 400
|
||||
if action not in ('approve', 'reject'):
|
||||
return jsonify({'code': 400, 'msg': '无效的审批操作'}), 400
|
||||
if action == 'reject' and not reject_reason:
|
||||
|
||||
@ -11,6 +11,8 @@ class PurchaseRequest(db.Model):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
request_no = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||
# [新增] 采购批次号:同一批循环提交的多条记录共享,用于分组查看/统计
|
||||
batch_no = db.Column(db.String(100), index=True, comment='采购批次号(同一批提交的多条记录共享)')
|
||||
# [新增] 硬关联基础物料表,打通"按单入库"链路
|
||||
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), index=True, comment='关联基础物料ID')
|
||||
name = db.Column(db.String(255), nullable=False, comment='采购名称')
|
||||
@ -79,6 +81,7 @@ class PurchaseRequest(db.Model):
|
||||
return {
|
||||
'id': self.id,
|
||||
'request_no': self.request_no,
|
||||
'batch_no': self.batch_no or '',
|
||||
'base_id': self.base_id,
|
||||
'name': self.name,
|
||||
'spec_model': self.spec_model or '',
|
||||
|
||||
@ -20,6 +20,18 @@ class PurchaseService:
|
||||
.filter(PurchaseRequest.request_no.like(f"{prefix}%")).scalar()
|
||||
return f"{prefix}{(existing_count + 1):04d}"
|
||||
|
||||
@staticmethod
|
||||
def generate_batch_no():
|
||||
"""生成采购批次号: BATCH-yyyyMMdd-HHmm-当日流水(4位)
|
||||
同一批循环提交的多条记录共享同一 batch_no"""
|
||||
import uuid
|
||||
beijing_tz = timezone(timedelta(hours=8))
|
||||
now = datetime.now(beijing_tz)
|
||||
date_str = now.strftime('%Y%m%d')
|
||||
time_str = now.strftime('%H%M')
|
||||
# 用时间戳 + 随机段确保唯一
|
||||
return f"BATCH-{date_str}-{time_str}-{uuid.uuid4().hex[:4].upper()}"
|
||||
|
||||
@staticmethod
|
||||
def auto_fill_from_material(keyword: str):
|
||||
"""
|
||||
@ -67,8 +79,12 @@ class PurchaseService:
|
||||
if material:
|
||||
base_id = material.id
|
||||
|
||||
# [新增] 批次号:前端同一批循环提交时传入同一 batch_no,无则自动生成
|
||||
batch_no = data.get('batch_no') or PurchaseService.generate_batch_no()
|
||||
|
||||
purchase = PurchaseRequest(
|
||||
request_no=request_no,
|
||||
batch_no=batch_no, # [新增]
|
||||
base_id=base_id, # [新增]
|
||||
name=data['name'],
|
||||
spec_model=data.get('spec_model', ''),
|
||||
|
||||
Reference in New Issue
Block a user