React+Spring Boot采购ERP中PO生成及支付流程技术问询
VendorBridge采购ERP项目:报价转PO流程瓶颈及支付流程疑问
我正在开发名为VendorBridge的个人采购ERP项目,用于管理端到端供应链,项目始于黑客松,目标是拓展自身技术能力。当前技术栈:前端采用React+Tailwind CSS(部署在Vercel)、后端为Java+Spring Boot(部署在Railway)、数据库用MySQL(部署在Railway)。目前卡在报价审批至采购订单(PO)生成的流程转换环节。
行业标准采购流程
- 采购专员创建询价单(RFQ)并分配给多个供应商;
- 供应商收到通知并提交报价;
- 采购专员对比报价后选定目标报价并提交审批;
- 报价进入经理/审批人队列进行最终验证;
- 经理审批通过后,系统自动生成采购订单(PO)。
当前困惑点及解决方案
1. 如何从已审批报价生成PO?数据库Schema设计最佳实践
核心数据库表设计
- rfq表:存储询价单基础信息,字段示例:
rfq_id(主键)、buyer_id(采购专员ID)、created_at、status(待报价/已结束)等。 - vendor_quote表:存储供应商报价,关联
rfq_id,字段示例:quote_id(主键)、vendor_id、rfq_id(外键)、total_amount、approval_status(待审批/已通过/已驳回)、approved_by、approved_at等。 - purchase_order表:存储PO主数据,关联已通过的
quote_id,字段示例:po_id(主键)、po_number(唯一编号)、vendor_id、quote_id(外键)、total_amount、status(已生成/已发送/已收货/已支付)、created_at等。 - po_item表:存储PO明细,关联
po_id,字段示例:item_id(主键)、po_id(外键)、product_name、quantity、unit_price等。
技术实现逻辑
- 监听
vendor_quote表的approval_status变更,当状态变为已通过时触发PO生成逻辑; - 从
vendor_quote中提取供应商ID、总金额、报价明细等数据,同时关联rfq表的采购需求信息; - 生成唯一PO编号(可采用
PO+YYYYMMDD+随机数格式); - 将主数据插入
purchase_order表,明细数据插入po_item表; - 触发后续通知(如给供应商推送PO、给采购专员发送生成成功提醒)。
Spring Boot代码示例(Service层)
@Service public class PurchaseOrderService { @Autowired private VendorQuoteRepository vendorQuoteRepository; @Autowired private PurchaseOrderRepository purchaseOrderRepository; @Autowired private PoItemRepository poItemRepository; @Transactional public PurchaseOrder generatePOFromApprovedQuote(Long quoteId) { // 查询已审批通过的报价 VendorQuote approvedQuote = vendorQuoteRepository.findByIdAndApprovalStatus(quoteId, ApprovalStatus.APPROVED) .orElseThrow(() -> new RuntimeException("未找到已审批的有效报价")); // 构建PO主表数据 PurchaseOrder po = new PurchaseOrder(); po.setVendorId(approvedQuote.getVendorId()); po.setTotalAmount(approvedQuote.getTotalAmount()); po.setPoNumber(generateUniquePoNumber()); po.setRfqId(approvedQuote.getRfqId()); po.setStatus(POStatus.GENERATED); po.setCreatedAt(LocalDateTime.now()); PurchaseOrder savedPO = purchaseOrderRepository.save(po); // 转换报价明细为PO明细并保存 List<PoItem> poItems = approvedQuote.getQuoteItems().stream() .map(quoteItem -> { PoItem poItem = new PoItem(); poItem.setPoId(savedPO.getPoId()); poItem.setProductName(quoteItem.getProductName()); poItem.setQuantity(quoteItem.getQuantity()); poItem.setUnitPrice(quoteItem.getUnitPrice()); return poItem; }) .collect(Collectors.toList()); poItemRepository.saveAll(poItems); return savedPO; } // 生成唯一PO编号 private String generateUniquePoNumber() { DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); String dateSegment = LocalDateTime.now().format(dateFormatter); String randomSegment = String.format("%06d", new Random().nextInt(999999)); return String.format("PO-%s-%s", dateSegment, randomSegment); } }
2. 企业支付触发节点及Spring Boot实现
企业实际支付触发节点
主流流程中,支付触发节点为PO对应的货物/服务已验收合格之后,具体步骤:
- 供应商按PO发货,仓库确认收货并验收合格,更新PO状态为已验收;
- 财务部门完成PO、收货单、供应商发票的三方匹配校验;
- 校验通过后发起支付申请,进入财务审批队列;
- 审批通过后对接企业支付网关/银行系统完成支付;
- 支付完成后更新PO状态为已支付。
Spring Boot模拟实现流程
新增状态枚举:
- PO状态:
GENERATED→SENT_TO_VENDOR→DELIVERED→ACCEPTED→PAYMENT_REQUESTED→PAYMENT_APPROVED→PAID - 支付请求状态:
PENDING_APPROVAL→COMPLETED→FAILED
- PO状态:
支付流程Service代码示例:
@Service public class PaymentService { @Autowired private PurchaseOrderRepository purchaseOrderRepository; @Autowired private PaymentRequestRepository paymentRequestRepository; @Transactional public PaymentRequest initiatePayment(Long poId) { // 仅允许已验收的PO发起支付 PurchaseOrder po = purchaseOrderRepository.findByIdAndStatus(poId, POStatus.ACCEPTED) .orElseThrow(() -> new RuntimeException("PO未完成验收,无法发起支付")); PaymentRequest paymentRequest = new PaymentRequest(); paymentRequest.setPoId(poId); paymentRequest.setAmount(po.getTotalAmount()); paymentRequest.setStatus(PaymentStatus.PENDING_APPROVAL); paymentRequest.setInitiatedAt(LocalDateTime.now()); return paymentRequestRepository.save(paymentRequest); } @Transactional public void approveAndExecutePayment(Long paymentRequestId) { PaymentRequest request = paymentRequestRepository.findByIdAndStatus(paymentRequestId, PaymentStatus.PENDING_APPROVAL) .orElseThrow(() -> new RuntimeException("支付申请不存在或未处于待审批状态")); // 模拟调用支付网关(实际场景替换为真实支付接口) boolean paymentSuccess = simulatePaymentGatewayCall(request.getAmount()); if (paymentSuccess) { request.setStatus(PaymentStatus.COMPLETED); paymentRequestRepository.save(request); // 更新PO状态为已支付 PurchaseOrder po = purchaseOrderRepository.findById(request.getPoId()).orElseThrow(); po.setStatus(POStatus.PAID); purchaseOrderRepository.save(po); } else { request.setStatus(PaymentStatus.FAILED); paymentRequestRepository.save(request); throw new RuntimeException("支付操作失败,请重试"); } } // 模拟支付网关调用逻辑 private boolean simulatePaymentGatewayCall(BigDecimal amount) { // 此处可根据需求模拟成功/失败场景,默认返回成功 return true; } }
架构指导建议
- 事件驱动解耦:采用Spring Event或消息队列(如RabbitMQ)处理报价审批通过、PO验收完成等关键事件,避免模块间强耦合;
- 状态流转管控:核心实体(RFQ、报价、PO、支付请求)通过状态字段明确流程阶段,减少逻辑混乱;
- 权限校验:审批环节(报价审批、支付审批)添加角色权限控制,确保操作合规;
- 审计日志:记录关键操作(如审批人、操作时间),便于后续追溯与合规检查。
内容的提问来源于stack exchange,提问作者Taksh Diyora
相关产品推荐
相关产品推荐

