liu
17 小时以前 c4a98bdfe1ed9a54d103c5729d83b64bb53cc28f
feat(erp): 采购订单审批接入协同办公(BPM)

- 采购订单创建后自动提交协同办公审批(审批中),不在采购页面直接审批
- 新增 ErpPurchaseOrderStatusListener,协同办公审批结果回写订单状态
- 驳回结果映射为已驳回(35),新增 ErpAuditStatus.REJECT
- 新增反审批接口 POST /erp/purchase-order/reverse-approve,协同办公已办任务中撤回已审核订单并重新进入待办审批

Co-Authored-By: Claude <noreply@anthropic.com>
已添加1个文件
已修改5个文件
107 ■■■■■ 文件已修改
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -13,6 +13,7 @@
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderSourceTypeEnum;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
@@ -40,7 +41,9 @@
import java.util.Map;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -170,6 +173,23 @@
        return success(purchaseOrderService.confirmReceipt(id));
    }
    @PostMapping("/reverse-approve")
    @Operation(summary = "反审批(协同办公已办任务中撤回已审核订单,直接重新进入待办审批)")
    @Parameter(name = "processInstanceId", description = "流程实例编号", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
    public CommonResult<Boolean> reverseApprove(@RequestParam("processInstanceId") String processInstanceId) {
        // 1. æ ¹æ®æµç¨‹å®žä¾‹ç¼–号找到采购订单
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrderByProcessInstanceId(processInstanceId);
        if (purchaseOrder == null) {
            throw exception(PURCHASE_ORDER_NOT_EXISTS);
        }
        // 2. åå®¡æ ¸ï¼šå·²å®¡æ ¸ -> æœªå®¡æ ¸ï¼ˆå«å…¥åº“/退货校验)
        purchaseOrderService.updatePurchaseOrderStatus(purchaseOrder.getId(), ErpAuditStatus.PROCESS.getStatus());
        // 3. è‡ªåŠ¨é‡æ–°æäº¤å®¡æ‰¹ -> è®¢å•直接回到协同办公待办任务
        purchaseOrderService.submitPurchaseOrder(purchaseOrder.getId(), "purchase_order_approve", getLoginUserId());
        return success(true);
    }
    private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult) {
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -74,6 +74,10 @@
        return selectOne(ErpPurchaseOrderDO::getNo, no);
    }
    default ErpPurchaseOrderDO selectByProcessInstanceId(String processInstanceId) {
        return selectOne(ErpPurchaseOrderDO::getProcessInstanceId, processInstanceId);
    }
    default List<ErpPurchaseOrderDO> selectList(Long supplierId, Integer status) {
        return selectList(new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
                .eqIfPresent(ErpPurchaseOrderDO::getSupplierId, supplierId)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -20,7 +20,8 @@
    PROCESS(10, "未审核"), // æœªå®¡æ ¸/草稿
    APPROVING(15, "审批中"), // å®¡æ‰¹ä¸­
    APPROVE(20, "已审核"), // å®¡æ ¸é€šè¿‡
    CANCEL(30, "已作废"); // å·²ä½œåºŸ/已撤销
    CANCEL(30, "已作废"), // å·²ä½œåºŸ/已撤销
    REJECT(35, "已驳回"); // å®¡æ‰¹è¢«é©³å›ž
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpAuditStatus::getStatus).toArray(Integer[]::new);
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -98,6 +98,14 @@
    ErpPurchaseOrderDO getPurchaseOrder(Long id);
    /**
     * æ ¹æ® BPM æµç¨‹å®žä¾‹ç¼–号查询采购订单(用于协同办公反审批)
     *
     * @param processInstanceId æµç¨‹å®žä¾‹ç¼–号
     * @return é‡‡è´­è®¢å•
     */
    ErpPurchaseOrderDO getPurchaseOrderByProcessInstanceId(String processInstanceId);
    /**
     * æ ¡éªŒé‡‡è´­è®¢å•,已经审核通过
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -41,6 +41,7 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
@@ -56,7 +57,11 @@
    /**
     * BPM é‡‡è´­è®¢å•审批分类编码
     */
    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_request_approve";
    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_order_approve";
    /**
     * BPM é‡‡è´­è®¢å•审批流程定义 Key(创建订单后自动提交到协同办公审批)
     */
    private static final String PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY = "purchase_order_approve";
    @Resource
    private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -119,6 +124,10 @@
        // 2.3 ç»‘定附件
        storageAttachmentApi.bindAttachments("file", "erp_purchase_order",
                purchaseOrder.getId(), createReqVO.getBlobIds());
        // 3. åˆ›å»ºåŽè‡ªåŠ¨æäº¤å®¡æ‰¹åˆ°ååŒåŠžå…¬ï¼ˆå®¡æ‰¹åœ¨ååŒåŠžå…¬å®Œæˆï¼Œä¸åœ¨æœ¬é¡µé¢å®¡æ‰¹ï¼‰
        submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
        return purchaseOrder.getId();
    }
@@ -288,7 +297,7 @@
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.PROCESS.getStatus();
            return ErpAuditStatus.REJECT.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.CANCEL.getStatus();
        }
@@ -406,6 +415,11 @@
    }
    @Override
    public ErpPurchaseOrderDO getPurchaseOrderByProcessInstanceId(String processInstanceId) {
        return purchaseOrderMapper.selectByProcessInstanceId(processInstanceId);
    }
    @Override
    public ErpPurchaseOrderDO validatePurchaseOrder(Long id) {
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.erp.service.purchase.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * é‡‡è´­è®¢å•审批状态监听器
 *
 * ç›‘听 BPM æµç¨‹å®žä¾‹çŠ¶æ€å˜æ›´äº‹ä»¶ï¼Œæ›´æ–°é‡‡è´­è®¢å•çš„å®¡æ‰¹çŠ¶æ€ï¼ˆååŒåŠžå…¬å®¡æ‰¹åŽå›žå†™ï¼‰
 *
 * @author è¶…级管理员
 */
@Component
@Slf4j
public class ErpPurchaseOrderStatusListener extends BpmProcessInstanceStatusEventListener {
    /**
     * é‡‡è´­è®¢å•审批流程定义 Key
     */
    private static final String PROCESS_DEFINITION_KEY = "purchase_order_approve";
    @Resource
    private ErpPurchaseOrderService purchaseOrderService;
    @Override
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long orderId;
        try {
            orderId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„采购订单编号", event.getBusinessKey());
            return;
        }
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,orderId={}, status={}", orderId, event.getStatus());
        try {
            purchaseOrderService.updatePurchaseOrderAuditStatus(orderId, event.getStatus());
            log.info("[onEvent] é‡‡è´­è®¢å•({}) å®¡æ‰¹çŠ¶æ€æ›´æ–°å®Œæˆ", orderId);
        } catch (Exception e) {
            log.error("[onEvent] æ›´æ–°é‡‡è´­è®¢å•审批状态失败,orderId={}", orderId, e);
        }
    }
}