feat(purchase): 采购单号改为 批次-批内序号 格式,显式体现同批
按用户原始逻辑还原并扩展: - 单号格式: PUR-日期-时间-批次-批内序号 例: PUR-20260831-1021-0001-0001(今日第1次采购的第1条) - generate_request_no 支持 batch_seq 参数生成批次+批内序号 - 新增 GET /purchase/next-batch-seq 返回今日下一个批次号 - 前端提交时获取批次号,同批所有行共享,循环提交生成连续批内序号 - 批次识别: 单号去掉末尾 -批内序号 即为批次前缀 - 保留旧格式兼容(无 batch_seq 时回退 PUR-日期-时间-流水)
This commit is contained in:
@ -237,6 +237,18 @@ def approve_purchase_request(purchase_id):
|
|||||||
# 5. 获取可选审批人列表
|
# 5. 获取可选审批人列表
|
||||||
# GET /api/v1/purchase/approvers
|
# GET /api/v1/purchase/approvers
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
|
@purchase_bp.route('/next-batch-seq', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def get_next_batch_seq():
|
||||||
|
"""获取今日下一个采购批次号(前端提交一批时调用,同批所有行共享)"""
|
||||||
|
try:
|
||||||
|
next_seq = PurchaseService.get_next_batch_seq()
|
||||||
|
return jsonify({'code': 200, 'msg': '获取成功', 'data': {'batch_seq': next_seq}}), 200
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@purchase_bp.route('/batch-approve', methods=['POST'])
|
@purchase_bp.route('/batch-approve', methods=['POST'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
@permission_required('inbound_purchase:operation')
|
@permission_required('inbound_purchase:operation')
|
||||||
|
|||||||
@ -9,16 +9,58 @@ from app.models.base import MaterialBase
|
|||||||
class PurchaseService:
|
class PurchaseService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_request_no():
|
def generate_request_no(batch_seq: int = None):
|
||||||
"""生成采购单号: PUR-yyyyMMdd-HHmm-当日流水(4位)"""
|
"""
|
||||||
|
生成采购单号: PUR-yyyyMMdd-HHmm-批次-批内序号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
batch_seq: 今日第几次采购批次(前端提交一批时传入,同一批共享)
|
||||||
|
None 时回退为旧格式 PUR-日期-时间-流水
|
||||||
|
|
||||||
|
返回:
|
||||||
|
- 有 batch_seq: PUR-20260831-1021-0001-0001(批次号-批内序号)
|
||||||
|
- 无 batch_seq: PUR-20260831-1021-0001(旧格式兼容)
|
||||||
|
"""
|
||||||
|
beijing_tz = timezone(timedelta(hours=8))
|
||||||
|
now = datetime.now(beijing_tz)
|
||||||
|
date_str = now.strftime('%Y%m%d')
|
||||||
|
time_str = now.strftime('%H%M')
|
||||||
|
|
||||||
|
if batch_seq is not None:
|
||||||
|
# 批次前缀: PUR-日期-时间-批次
|
||||||
|
batch_prefix = f"PUR-{date_str}-{time_str}-{batch_seq:04d}"
|
||||||
|
# 批内序号: 该批次前缀下的记录数 + 1
|
||||||
|
item_count = db.session.query(func.count(func.distinct(PurchaseRequest.request_no))) \
|
||||||
|
.filter(PurchaseRequest.request_no.like(f"{batch_prefix}-%")).scalar()
|
||||||
|
return f"{batch_prefix}-{(item_count + 1):04d}"
|
||||||
|
|
||||||
|
# 旧格式兼容: PUR-日期-时间-流水
|
||||||
|
prefix = f"PUR-{date_str}-{time_str}-"
|
||||||
|
existing_count = db.session.query(func.count(func.distinct(PurchaseRequest.request_no))) \
|
||||||
|
.filter(PurchaseRequest.request_no.like(f"{prefix}%")).scalar()
|
||||||
|
return f"{prefix}{(existing_count + 1):04d}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_next_batch_seq():
|
||||||
|
"""
|
||||||
|
返回今日下一个采购批次号(今日第几次)
|
||||||
|
统计今日已有的不同批次(单号第4段)数量,+1
|
||||||
|
"""
|
||||||
beijing_tz = timezone(timedelta(hours=8))
|
beijing_tz = timezone(timedelta(hours=8))
|
||||||
now = datetime.now(beijing_tz)
|
now = datetime.now(beijing_tz)
|
||||||
date_str = now.strftime('%Y%m%d')
|
date_str = now.strftime('%Y%m%d')
|
||||||
time_str = now.strftime('%H%M')
|
time_str = now.strftime('%H%M')
|
||||||
prefix = f"PUR-{date_str}-{time_str}-"
|
prefix = f"PUR-{date_str}-{time_str}-"
|
||||||
existing_count = db.session.query(func.count(func.distinct(PurchaseRequest.request_no))) \
|
|
||||||
.filter(PurchaseRequest.request_no.like(f"{prefix}%")).scalar()
|
# 查询今日所有单号,提取第4段(批次号)去重
|
||||||
return f"{prefix}{(existing_count + 1):04d}"
|
rows = db.session.query(PurchaseRequest.request_no) \
|
||||||
|
.filter(PurchaseRequest.request_no.like(f"{prefix}%")).all()
|
||||||
|
batch_seqs = set()
|
||||||
|
for (rn,) in rows:
|
||||||
|
parts = rn.split('-')
|
||||||
|
if len(parts) >= 4:
|
||||||
|
batch_seqs.add(parts[3])
|
||||||
|
return len(batch_seqs) + 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def auto_fill_from_material(keyword: str):
|
def auto_fill_from_material(keyword: str):
|
||||||
@ -47,7 +89,8 @@ class PurchaseService:
|
|||||||
data 包含: name, spec_model, quantity, purchase_date, supplier_link, remark, images,
|
data 包含: name, spec_model, quantity, purchase_date, supplier_link, remark, images,
|
||||||
unit_price, total_price, approver_id, base_id (可选)
|
unit_price, total_price, approver_id, base_id (可选)
|
||||||
"""
|
"""
|
||||||
request_no = PurchaseService.generate_request_no()
|
batch_seq = data.get('batch_seq')
|
||||||
|
request_no = PurchaseService.generate_request_no(batch_seq=batch_seq)
|
||||||
|
|
||||||
purchase_date = data.get('purchase_date')
|
purchase_date = data.get('purchase_date')
|
||||||
if isinstance(purchase_date, str):
|
if isinstance(purchase_date, str):
|
||||||
|
|||||||
@ -109,6 +109,14 @@ export function getPurchaseByBatch(batchKey: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取今日下一个采购批次号(提交一批时调用,同批所有行共享)
|
||||||
|
export function getNextBatchSeq() {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/next-batch-seq',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 获取可选审批人列表
|
// 获取可选审批人列表
|
||||||
export function getPurchaseApprovers() {
|
export function getPurchaseApprovers() {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -362,7 +362,7 @@ import { searchMaterialPurchase } from '@/api/purchase'
|
|||||||
import {
|
import {
|
||||||
getPurchaseList, createPurchase, getPurchaseDetail,
|
getPurchaseList, createPurchase, getPurchaseDetail,
|
||||||
approvePurchase, batchApprovePurchase, getPurchaseApprovers, autoFillPurchase,
|
approvePurchase, batchApprovePurchase, getPurchaseApprovers, autoFillPurchase,
|
||||||
getPurchaseByBatch
|
getPurchaseByBatch, getNextBatchSeq
|
||||||
} from '@/api/purchase'
|
} from '@/api/purchase'
|
||||||
import { uploadFile, deleteFile } from '@/api/common/upload'
|
import { uploadFile, deleteFile } from '@/api/common/upload'
|
||||||
import type { FormInstance } from 'element-plus'
|
import type { FormInstance } from 'element-plus'
|
||||||
@ -951,12 +951,21 @@ const submitForm = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 逐行提交(每行携带公共信息)
|
// 3. 逐行提交(每行携带公共信息 + 同一批次号)
|
||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
let successCount = 0
|
let successCount = 0
|
||||||
|
// ★ 获取今日批次号:同一批所有行共享,单号体现 PUR-日期-时间-批次-批内序号
|
||||||
|
let batchSeq: number | undefined
|
||||||
|
try {
|
||||||
|
const seqRes: any = await getNextBatchSeq()
|
||||||
|
batchSeq = seqRes.data?.batch_seq
|
||||||
|
} catch (e) {
|
||||||
|
// 获取批次号失败不阻断,回退旧格式
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
for (const row of validItems) {
|
for (const row of validItems) {
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
|
batch_seq: batchSeq,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
spec_model: row.spec_model,
|
spec_model: row.spec_model,
|
||||||
quantity: row.quantity,
|
quantity: row.quantity,
|
||||||
|
|||||||
Reference in New Issue
Block a user