From 8c65f898ed6e5b0c7a94c792c4562f6ec831dccd Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 07 十一月 2026 08:50:53 +0800
Subject: [PATCH] feat: 完善质检、生产、销售、财务与人事模块并接入审批流程

---
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java |  331 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 318 insertions(+), 13 deletions(-)

diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
index 91b18a7..8922271 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -5,20 +5,32 @@
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableItemDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableItemMapper;
 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
 import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableBizTypeEnum;
+import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
+import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
+import cn.iocoder.yudao.module.mes.api.productsales.MesWmProductSalesApi;
+import cn.iocoder.yudao.module.mes.api.productsales.dto.MesWmProductSalesRespDTO;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService;
@@ -31,6 +43,7 @@
 import com.mzt.logapi.service.impl.DiffParseFunction;
 import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -50,10 +63,25 @@
  */
 @Service
 @Validated
+@Slf4j
 public class CrmReceivableServiceImpl implements CrmReceivableService {
+
+    /**
+     * BPM 鍥炴瀹℃壒娴佺▼鏍囪瘑
+     *
+     * 瀵瑰簲 act_re_procdef.KEY_ = crm-receivable-audit锛汢PM 鍒嗙被銆屽洖娆惧鎵广�嶇殑缂栫爜涓� 1111锛坆pm_category.id=76锛�
+     */
+    public static final String BPM_PROCESS_DEFINITION_KEY = "crm-receivable-audit";
 
     @Resource
     private CrmReceivableMapper receivableMapper;
+    @Resource
+    private CrmReceivableItemMapper receivableItemMapper;
+
+    @Resource
+    private ErpSaleOrderApi saleOrderApi;
+    @Resource
+    private MesWmProductSalesApi productSalesApi;
 
     @Resource
     private CrmNoRedisDAO noRedisDAO;
@@ -72,6 +100,8 @@
     private AdminUserApi adminUserApi;
     @Resource
     private StorageAttachmentApi storageAttachmentApi;
+    @Resource
+    private BpmProcessInstanceApi bpmProcessInstanceApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -92,11 +122,12 @@
             throw exception(RECEIVABLE_NO_EXISTS);
         }
 
-        // 2.1 鎻掑叆鍥炴锛堝垱寤哄悗鍗充负鏈鏍哥姸鎬侊級
+        // 2.1 鎻掑叆鍥炴锛堝垱寤哄悗涓烘湭鎻愪氦锛岄渶鏄惧紡銆屾彁浜ゅ鎵广�嶆墠杩涘叆 BPM 瀹℃壒涓級
         CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class)
-                .setNo(no).setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus());
+                .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
         receivableMapper.insert(receivable);
-        // 2.2
+        // 2.2 淇濆瓨鏍搁攢鏄庣粏锛堟湭浼犳椂鎸夊叧鑱斿紑绁ㄨ嚜鍔ㄧ敓鎴愪竴鏉★級
+        syncReceivableItems(receivable, createReqVO.getItems());
 
         // 3. 鍒涘缓鏁版嵁鏉冮檺
         permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
@@ -112,6 +143,121 @@
         LogRecordContext.putVariable("receivable", receivable);
         LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
         return receivable.getId();
+    }
+
+    /**
+     * 淇濆瓨鏍搁攢鏄庣粏
+     *
+     * 浼� null 鏃讹細鑻ュ叧鑱斿紑绁紝鑷姩鐢熸垚涓�鏉°�屽紑绁ㄣ�嶆牳閿�鏄庣粏锛涘惁鍒欎笉鐢熸垚銆�
+     */
+    private void syncReceivableItems(CrmReceivableDO receivable,
+                                     List<CrmReceivableSaveReqVO.ReceivableItem> items) {
+        if (items == null) {
+            if (receivable.getInvoiceId() == null) {
+                return;
+            }
+            CrmReceivableSaveReqVO.ReceivableItem autoItem = new CrmReceivableSaveReqVO.ReceivableItem();
+            autoItem.setBizType(CrmReceivableBizTypeEnum.SALE_INVOICE.getType());
+            autoItem.setBizId(receivable.getInvoiceId());
+            autoItem.setReceiptPrice(receivable.getPrice());
+            items = List.of(autoItem);
+        }
+        // 1. 鏍¢獙骞惰В鏋愭瘡琛屽叧鑱斿崟鎹�
+        List<ResolvedReceivableItem> resolvedItems = new ArrayList<>(items.size());
+        BigDecimal itemPriceSum = BigDecimal.ZERO;
+        for (int i = 0; i < items.size(); i++) {
+            CrmReceivableSaveReqVO.ReceivableItem item = items.get(i);
+            int row = i + 1;
+            CrmReceivableBizTypeEnum bizType = item == null ? null
+                    : CrmReceivableBizTypeEnum.valueOf(item.getBizType());
+            if (bizType == null) {
+                throw exception(RECEIVABLE_ITEM_BIZ_TYPE_INVALID, row,
+                        item == null ? "鏈~鍐�" : item.getBizType());
+            }
+            if (item.getReceiptPrice() == null || item.getReceiptPrice().compareTo(BigDecimal.ZERO) <= 0) {
+                throw exception(RECEIVABLE_ITEM_PRICE_INVALID, row, bizType.getName(),
+                        item.getBizId(), item.getReceiptPrice() == null ? "鏈~鍐�"
+                                : item.getReceiptPrice().stripTrailingZeros().toPlainString());
+            }
+            ResolvedReceivableItem resolved = resolveReceivableItem(row, bizType, item.getBizId());
+            // 1.1 宸叉牳閿�閲戦锛堜笉鍚湰鍥炴鍗曪紝閬垮厤缂栬緫鏃舵妸鑷繁绠楄繘鍘伙級
+            BigDecimal receiptedPrice = receivableItemMapper.selectListByBiz(bizType.getType(), item.getBizId()).stream()
+                    .filter(old -> ObjectUtil.notEqual(old.getReceivableId(), receivable.getId()))
+                    .map(old -> old.getReceiptPrice() == null ? BigDecimal.ZERO : old.getReceiptPrice())
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            BigDecimal remainPrice = resolved.totalPrice.subtract(receiptedPrice);
+            if (item.getReceiptPrice().compareTo(remainPrice) > 0) {
+                throw exception(RECEIVABLE_ITEM_PRICE_EXCEEDS, row, bizType.getName(), resolved.bizNo,
+                        item.getReceiptPrice().stripTrailingZeros().toPlainString(),
+                        remainPrice.stripTrailingZeros().toPlainString(),
+                        resolved.totalPrice.stripTrailingZeros().toPlainString(),
+                        receiptedPrice.stripTrailingZeros().toPlainString());
+            }
+            resolved.receiptedPrice = receiptedPrice;
+            resolved.receiptPrice = item.getReceiptPrice();
+            resolved.remark = item.getRemark();
+            resolvedItems.add(resolved);
+            itemPriceSum = itemPriceSum.add(item.getReceiptPrice());
+        }
+        // 2. 鏄庣粏鍚堣蹇呴』绛変簬鍥炴閲戦
+        if (itemPriceSum.compareTo(receivable.getPrice()) != 0) {
+            throw exception(RECEIVABLE_ITEM_SUM_MISMATCH,
+                    itemPriceSum.stripTrailingZeros().toPlainString(),
+                    receivable.getPrice().stripTrailingZeros().toPlainString());
+        }
+        // 3. 閲嶅缓鏄庣粏
+        receivableItemMapper.deleteByReceivableId(receivable.getId());
+        for (ResolvedReceivableItem resolved : resolvedItems) {
+            receivableItemMapper.insert(CrmReceivableItemDO.builder()
+                    .receivableId(receivable.getId()).bizType(resolved.bizType.getType())
+                    .bizId(resolved.bizId).bizNo(resolved.bizNo).totalPrice(resolved.totalPrice)
+                    .receiptedPrice(resolved.receiptedPrice).receiptPrice(resolved.receiptPrice)
+                    .remark(resolved.remark).build());
+        }
+    }
+
+    private ResolvedReceivableItem resolveReceivableItem(int row, CrmReceivableBizTypeEnum bizType, Long bizId) {
+        ResolvedReceivableItem resolved = new ResolvedReceivableItem();
+        resolved.bizType = bizType;
+        resolved.bizId = bizId;
+        switch (bizType) {
+            case SALE_ORDER -> {
+                ErpSaleOrderRespDTO saleOrder = bizId == null ? null : saleOrderApi.getSaleOrder(bizId).getCheckedData();
+                if (saleOrder == null) {
+                    throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId);
+                }
+                resolved.bizNo = saleOrder.getNo();
+                resolved.totalPrice = saleOrder.getTotalPrice() == null ? BigDecimal.ZERO : saleOrder.getTotalPrice();
+            }
+            case SALE_OUT -> {
+                MesWmProductSalesRespDTO saleOut = bizId == null ? null : productSalesApi.getProductSales(bizId);
+                if (saleOut == null) {
+                    throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId);
+                }
+                resolved.bizNo = saleOut.getCode();
+                resolved.totalPrice = saleOut.getTotalPrice() == null ? BigDecimal.ZERO : saleOut.getTotalPrice();
+            }
+            case SALE_INVOICE -> {
+                CrmInvoiceDO invoice = bizId == null ? null : invoiceService.getInvoice(bizId);
+                if (invoice == null) {
+                    throw exception(RECEIVABLE_ITEM_BIZ_NOT_EXISTS, row, bizType.getName(), bizId);
+                }
+                resolved.bizNo = invoice.getNo();
+                resolved.totalPrice = invoice.getPrice() == null ? BigDecimal.ZERO : invoice.getPrice();
+            }
+            default -> throw exception(RECEIVABLE_ITEM_BIZ_TYPE_INVALID, row, bizType.getType());
+        }
+        return resolved;
+    }
+
+    private static class ResolvedReceivableItem {
+        private CrmReceivableBizTypeEnum bizType;
+        private Long bizId;
+        private String bizNo;
+        private BigDecimal totalPrice;
+        private BigDecimal receiptedPrice;
+        private BigDecimal receiptPrice;
+        private String remark;
     }
 
     private void validateReceivablePriceExceedsLimit(CrmReceivableSaveReqVO reqVO) {
@@ -136,9 +282,10 @@
      * 鏍¢獙鍏宠仈寮�绁ㄥ悎鏍硷細瀛樺湪銆佸鎵归�氳繃銆佸凡涓婁紶鍙戠エ闄勪欢銆佷笌鎵�閫夊悎鍚屼竴鑷�
      */
     private void validateInvoiceForReceivable(CrmReceivableSaveReqVO reqVO) {
-        // 鏈叧鑱斿紑绁ㄦ椂鐩存帴璺宠繃锛堝厑璁稿厛鍥炴鍚庡紑绁級
+        // 鍥炴蹇呴』鍏堝叧鑱斿凡瀹℃壒涓斿凡褰曞叆鐨勫紑绁ㄥ崟銆�
         if (reqVO.getInvoiceId() == null) {
-            return;
+            throw new ServiceException(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_EXISTS.getCode(),
+                    "鍥炴蹇呴』鍏堝叧鑱斿紑绁ㄥ崟銆傝鍏堝畬鎴愬紑绁ㄧ敵璇枫�佸鎵瑰拰鍙戠エ褰曞叆锛屽啀鍒涘缓鍥炴銆�");
         }
         // 1. 鏍¢獙寮�绁ㄥ瓨鍦�
         CrmInvoiceDO invoice = invoiceService.getInvoice(reqVO.getInvoiceId());
@@ -148,6 +295,10 @@
         // 2. 鏍¢獙寮�绁ㄥ鎵归�氳繃
         if (ObjectUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
             throw exception(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_APPROVE);
+        }
+        if (!Objects.equals(invoice.getEntryStatus(), 10)) {
+            throw new ServiceException(RECEIVABLE_CREATE_FAIL_INVOICE_NOT_APPROVE.getCode(),
+                    "鍏宠仈寮�绁ㄥ崟灏氭湭褰曞叆鍙戠エ銆傝鍏堝~鍐欏彂绁ㄥ彿鐮併�佹棩鏈熷苟涓婁紶闄勪欢锛屽啀鍒涘缓鍥炴銆�");
         }
         // 3. 鏍¢獙寮�绁ㄥ凡涓婁紶鍙戠エ闄勪欢
         if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(
@@ -235,14 +386,21 @@
         // 1.5 鏍¢獙鍙洖娆鹃噾棰濅笉瓒呰繃璇ュ紑绁ㄥ墿浣欏彲鍥炴閲戦
         validateReceivablePriceExceedsInvoiceRemaining(updateReqVO);
 
-        // 1.6 鍙湁鏈鏍革紝鍙互缂栬緫锛�
-        if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
-            throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
+        // 1.6 浠呮湭鎻愪氦銆佸鏍镐笉閫氳繃鍙互缂栬緫锛涘鎵逛腑绛夊緟 BPM锛屽鏍搁�氳繃闇�鍏堝弽瀹℃牳
+        if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
+                && ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
+            throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED,
+                    StrUtil.blankToDefault(CrmAuditStatusEnum.getNameByStatus(oldReceivable.getAuditStatus()), "鏈煡鐘舵��"));
         }
 
         // 2. 鏇存柊鍥炴
         CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class);
         receivableMapper.updateById(updateObj);
+        // 2.1 鏇存柊鏍搁攢鏄庣粏锛氫紶浜� items 浠� items 涓哄噯锛涙湭浼犱笖鍏宠仈寮�绁ㄦ椂鎸夊紑绁ㄩ噸寤猴紱鍚﹀垯淇濇寔涓嶅彉
+        if (updateReqVO.getItems() != null
+                || (updateReqVO.getInvoiceId() != null && ObjectUtil.notEqual(updateReqVO.getInvoiceId(), oldReceivable.getInvoiceId()))) {
+            syncReceivableItems(updateObj, updateReqVO.getItems());
+        }
 
         // 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
         updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()); // 閬垮厤鎿嶄綔鏃ュ織鍑虹幇鈥滃垹闄よ礋璐d汉鈥濈殑鎯呭喌
@@ -261,20 +419,150 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
+            success = CRM_RECEIVABLE_SUBMIT_SUCCESS)
+    public void submitReceivable(Long id, String remark, Long userId) {
+        // 1.1 鏍¢獙鍥炴瀛樺湪
+        CrmReceivableDO receivable = validateReceivableExists(id);
+        // 1.2 浠呮湭鎻愪氦銆佸鏍镐笉閫氳繃鍙互鎻愪氦瀹℃壒
+        if (ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())
+                && ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) {
+            throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT,
+                    StrUtil.blankToDefault(CrmAuditStatusEnum.getNameByStatus(receivable.getAuditStatus()), "鏈煡鐘舵��"));
+        }
+
+        // 1.3 鎻愪氦鍓嶉噸鏂版牎楠屽紑绁ㄣ�佸悎鍚�/寮�绁ㄥ墿浣欏彲鍥炴閲戦锛岄伩鍏嶈崏绋挎湡闂撮搴﹁鍏朵粬鍗曟嵁鍗犵敤
+        CrmReceivableSaveReqVO submitReqVO = new CrmReceivableSaveReqVO()
+                .setId(receivable.getId()).setCustomerId(receivable.getCustomerId())
+                .setContractId(receivable.getContractId()).setInvoiceId(receivable.getInvoiceId())
+                .setPrice(receivable.getPrice());
+        validateInvoiceForReceivable(submitReqVO);
+        validateReceivablePriceExceedsLimit(submitReqVO);
+        validateReceivablePriceExceedsInvoiceRemaining(submitReqVO);
+
+        // 2. 鍏ㄥ眬鍏嶅鎵癸細涓氬姟鏍¢獙閫氳繃鍚庣洿鎺ユ墽琛屽鎵归�氳繃閫昏緫锛屾棤闇�娴佺▼妯″瀷
+        if (bpmProcessInstanceApi.isGlobalAutoApprove()) {
+            receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId("")
+                    .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+            updateReceivableAuditStatus(id, BpmProcessInstanceStatusEnum.APPROVE.getStatus());
+            LogRecordContext.putVariable("receivableNo", receivable.getNo());
+            return;
+        }
+
+        // 3. 鍒涘缓 BPM 鍥炴瀹℃壒娴佺▼瀹炰緥
+        Map<String, Object> variables = new HashMap<>();
+        if (StrUtil.isNotBlank(remark)) {
+            variables.put("submitRemark", remark);
+        }
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY)
+                        .setBusinessKey(String.valueOf(id))
+                        .setVariables(variables.isEmpty() ? null : variables));
+
+        // 4. 鏇存柊宸ヤ綔娴佺紪鍙� + 瀹℃壒鐘舵��
+        receivableMapper.updateById(new CrmReceivableDO().setId(id)
+                .setProcessInstanceId(processInstanceId)
+                .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+
+        // 5. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+        LogRecordContext.putVariable("receivableNo", receivable.getNo());
+        LogRecordContext.putVariable("submitRemark", remark);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateReceivableAuditStatus(Long id, Integer bpmResult) {
+        // 1.1 鏍¢獙瀛樺湪锛氬洖娆惧彲鑳藉湪瀹℃壒鏈熼棿琚垹闄わ紝姝ゆ椂闈欓粯蹇界暐锛岄伩鍏嶅奖鍝� BPM 娴佺▼瀹屾垚
+        CrmReceivableDO receivable = receivableMapper.selectById(id);
+        if (receivable == null) {
+            log.warn("[updateReceivableAuditStatus][鍥炴({}) 涓嶅瓨鍦紝蹇界暐 BPM 瀹℃壒缁撴灉({})]", id, bpmResult);
+            return;
+        }
+        // 1.2 鍙湁瀹℃壒涓墠澶勭悊锛氶伩鍏嶆祦绋嬩簨浠堕噸澶嶆姇閫掋�佹垨浜哄伐鍙嶅鏍稿悗杩熷埌鐨勬祦绋嬩簨浠跺鑷撮噸澶嶅洖鍐�
+        if (ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
+            log.warn("[updateReceivableAuditStatus][鍥炴({}) 褰撳墠鐘舵��({}) 涓嶆槸瀹℃壒涓紝蹇界暐 BPM 瀹℃壒缁撴灉({})]",
+                    id, receivable.getAuditStatus(), bpmResult);
+            return;
+        }
+
+        // 2. 杞崲 BPM 瀹℃壒缁撴灉锛堜笉澶嶇敤 CrmAuditStatusUtils锛氬叾 CANCEL 鍒嗘敮璇繑鍥� BPM 鐘舵�� 4锛岃�岄潪 CRM 鐘舵�� 40锛�
+        Integer auditStatus;
+        if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            auditStatus = CrmAuditStatusEnum.APPROVE.getStatus();
+        } else if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            auditStatus = CrmAuditStatusEnum.REJECT.getStatus();
+        } else if (BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            auditStatus = CrmAuditStatusEnum.CANCEL.getStatus();
+        } else {
+            log.warn("[updateReceivableAuditStatus][鍥炴({}) 鐨� BPM 瀹℃壒缁撴灉({}) 涓嶆槸缁堟�侊紝蹇界暐]", id, bpmResult);
+            return;
+        }
+
+        // 3. 鏉′欢鏇存柊锛岀‘淇濆苟鍙�/閲嶅浜嬩欢鍙湁涓�娆$敓鏁�
+        int updateCount = receivableMapper.updateByIdAndStatus(id, CrmAuditStatusEnum.PROCESS.getStatus(),
+                new CrmReceivableDO().setAuditStatus(auditStatus));
+        if (updateCount == 0) {
+            log.warn("[updateReceivableAuditStatus][鍥炴({}) 鐘舵�佸凡琚叾浠栨搷浣滃彉鏇达紝蹇界暐 BPM 瀹℃壒缁撴灉({})]", id, bpmResult);
+            return;
+        }
+
+        // 4. 浠呭鎵归�氳繃鏃跺洖鍐欏洖娆捐鍒掞紱椹冲洖銆佸彇娑堜笉鍥炲啓閲戦
+        if (ObjectUtil.equal(auditStatus, CrmAuditStatusEnum.APPROVE.getStatus())) {
+            syncReceivablePlanReceipted(receivable, true);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateReceivableStatus(Long id, Integer status) {
         boolean approve = CrmAuditStatusEnum.APPROVE.getStatus().equals(status);
         // 1.1 鏍¢獙瀛樺湪
         CrmReceivableDO receivable = validateReceivableExists(id);
-        // 1.2 鏍¢獙鐘舵��
+        // 1.2 宸叉彁浜� BPM 鍗忓悓瀹℃壒鐨勫洖娆撅紝瀹℃壒缁撴灉鐢辨祦绋嬬洃鍚櫒椹卞姩锛岀姝㈡墜宸ュ鏍革紝閬垮厤閲戦鍙岄噸鍥炲啓
+        if (StrUtil.isNotBlank(receivable.getProcessInstanceId())) {
+            if (approve) {
+                throw exception(RECEIVABLE_MANUAL_AUDIT_FAIL_BPM_PROCESSING, receivable.getProcessInstanceId());
+            }
+            // 瀹℃壒涓笉鑳藉弽瀹℃牳锛屽惁鍒欎細瑙i櫎娴佺▼鍏宠仈锛屽鑷存祦绋嬪畬鎴愬悗鏃犳硶鍥炲啓锛涘鎵归�氳繃鍚庣殑鍙嶅鏍镐粛淇濈暀
+            if (ObjectUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
+                throw exception(RECEIVABLE_MANUAL_REVERSE_FAIL_BPM_PROCESSING, receivable.getProcessInstanceId());
+            }
+        }
+        // 1.3 鏍¢獙鐘舵��
         if (receivable.getAuditStatus().equals(status)) {
             throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL);
         }
 
-        // 2. 鏇存柊鐘舵��
-        int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(),
-                new CrmReceivableDO().setAuditStatus(status));
+        // 2. 鏇存柊鐘舵�侊紱鍙嶅鏍告椂瑙i櫎 BPM 鍏宠仈锛屽洖褰掓墜宸ュ鏍稿吋瀹规祦绋�
+        CrmReceivableDO updateObj = new CrmReceivableDO().setAuditStatus(status);
+        if (!approve) {
+            updateObj.setProcessInstanceId("");
+        }
+        int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(), updateObj);
         if (updateCount == 0) {
             throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL);
+        }
+
+        // 3. 鍥炴璁″垝鍥炲啓瀹為檯鍥炴閲戦涓庣姸鎬�
+        syncReceivablePlanReceipted(receivable, approve);
+    }
+
+    /**
+     * 鍚屾鍥炴璁″垝鐨勫凡鍥炴閲戦涓庣姸鎬�
+     *
+     * @param receivable 鍥炴鍗�
+     * @param approve    鏄惁瀹℃壒閫氳繃锛歵rue 鍥炲啓宸插洖娆撅紱false 閲嶇疆宸插洖娆�
+     */
+    private void syncReceivablePlanReceipted(CrmReceivableDO receivable, boolean approve) {
+        if (receivable.getPlanId() == null) {
+            return;
+        }
+        if (approve) {
+            receivablePlanService.updateReceivablePlanReceipted(receivable.getPlanId(),
+                    receivable.getPrice(), receivable.getReturnTime());
+        } else {
+            receivablePlanService.resetReceivablePlanReceipted(receivable.getPlanId());
         }
     }
 
@@ -297,7 +585,9 @@
 
         // 2.1 鍒犻櫎鍥炴
         receivableMapper.deleteById(id);
-        // 2.2 鍒犻櫎鏁版嵁鏉冮檺
+        // 2.2 鍒犻櫎鏍搁攢鏄庣粏
+        receivableItemMapper.deleteByReceivableId(id);
+        // 2.3 鍒犻櫎鏁版嵁鏉冮檺
         permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
 
         // 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
@@ -333,6 +623,21 @@
     }
 
     @Override
+    public List<CrmReceivableItemDO> getReceivableItemList(Long receivableId) {
+        return receivableItemMapper.selectListByReceivableId(receivableId);
+    }
+
+    @Override
+    public List<CrmReceivableItemDO> getReceivableItemListByCustomerId(Long customerId) {
+        List<CrmReceivableDO> receivables = receivableMapper.selectListByCustomerId(customerId);
+        if (CollUtil.isEmpty(receivables)) {
+            return ListUtil.empty();
+        }
+        return receivableItemMapper.selectListByReceivableIds(
+                receivables.stream().map(CrmReceivableDO::getId).toList());
+    }
+
+    @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
     public PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) {
         return receivableMapper.selectPageByCustomerId(pageReqVO);

--
Gitblit v1.9.3