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 | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 161 insertions(+), 18 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 08d647c..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,9 +5,14 @@
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;
@@ -38,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;
@@ -57,7 +63,15 @@
*/
@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;
@@ -86,6 +100,8 @@
private AdminUserApi adminUserApi;
@Resource
private StorageAttachmentApi storageAttachmentApi;
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -106,9 +122,9 @@
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 淇濆瓨鏍搁攢鏄庣粏锛堟湭浼犳椂鎸夊叧鑱斿紑绁ㄨ嚜鍔ㄧ敓鎴愪竴鏉★級
syncReceivableItems(receivable, createReqVO.getItems());
@@ -266,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());
@@ -278,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(
@@ -365,9 +386,11 @@
// 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. 鏇存柊鍥炴
@@ -396,30 +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. 鍥炴璁″垝鍥炲啓瀹為檯鍥炴閲戦涓庣姸鎬�
- if (receivable.getPlanId() != null) {
- if (approve) {
- receivablePlanService.updateReceivablePlanReceipted(receivable.getPlanId(),
- receivable.getPrice(), receivable.getReturnTime());
- } else {
- receivablePlanService.resetReceivablePlanReceipted(receivable.getPlanId());
- }
+ 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());
}
}
--
Gitblit v1.9.3