You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Spring Boot采购ERP中PO生成及支付流程技术问询

VendorBridge采购ERP项目:报价转PO流程瓶颈及支付流程疑问

我正在开发名为VendorBridge的个人采购ERP项目,用于管理端到端供应链,项目始于黑客松,目标是拓展自身技术能力。当前技术栈:前端采用React+Tailwind CSS(部署在Vercel)、后端为Java+Spring Boot(部署在Railway)、数据库用MySQL(部署在Railway)。目前卡在报价审批至采购订单(PO)生成的流程转换环节。

行业标准采购流程

  1. 采购专员创建询价单(RFQ)并分配给多个供应商;
  2. 供应商收到通知并提交报价;
  3. 采购专员对比报价后选定目标报价并提交审批;
  4. 报价进入经理/审批人队列进行最终验证;
  5. 经理审批通过后,系统自动生成采购订单(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等。

技术实现逻辑

  1. 监听vendor_quote表的approval_status变更,当状态变为已通过时触发PO生成逻辑;
  2. 从vendor_quote中提取供应商ID、总金额、报价明细等数据,同时关联rfq表的采购需求信息;
  3. 生成唯一PO编号(可采用PO+YYYYMMDD+随机数格式);
  4. 将主数据插入purchase_order表,明细数据插入po_item表;
  5. 触发后续通知(如给供应商推送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对应的货物/服务已验收合格之后,具体步骤:

  1. 供应商按PO发货,仓库确认收货并验收合格,更新PO状态为已验收;
  2. 财务部门完成PO、收货单、供应商发票的三方匹配校验;
  3. 校验通过后发起支付申请,进入财务审批队列;
  4. 审批通过后对接企业支付网关/银行系统完成支付;
  5. 支付完成后更新PO状态为已支付。

Spring Boot模拟实现流程

  1. 新增状态枚举:

    • PO状态:GENERATED → SENT_TO_VENDOR → DELIVERED → ACCEPTED → PAYMENT_REQUESTED → PAYMENT_APPROVED → PAID
    • 支付请求状态:PENDING_APPROVAL → COMPLETED → FAILED
  2. 支付流程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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 01:20:53