From e2729587d4a8b9c2e3e2c4dd50a25adedb337e98 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 14 八月 2026 15:58:15 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business
---
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 206 insertions(+), 18 deletions(-)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
index e4c6b89..5fdd106 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
@@ -1,27 +1,39 @@
package cn.iocoder.yudao.module.erp.service.finance;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO;
+import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceService;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceServiceImpl;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -30,11 +42,17 @@
/**
* ERP 浠樻鍗� Service 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
+@Slf4j
public class ErpFinancePaymentServiceImpl implements ErpFinancePaymentService {
+
+ /**
+ * BPM 浠樻鍗曞鎵瑰垎绫荤紪鐮�
+ */
+ private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "erp_finance_payment_approve";
@Resource
private ErpFinancePaymentMapper financePaymentMapper;
@@ -48,9 +66,21 @@
private ErpSupplierService supplierService;
@Resource
private ErpAccountService accountService;
+ @Resource
+ private ErpPurchaseInvoiceService invoiceService;
@Resource
private AdminUserApi adminUserApi;
+
+ @Resource
+ private StorageAttachmentApi storageAttachmentApi;
+
+ @Resource
+ private BpmProcessInstanceApi processInstanceApi;
+ @Resource
+ private BpmCategoryService bpmCategoryService;
+ @Resource
+ private BpmProcessDefinitionService bpmProcessDefinitionService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -68,7 +98,13 @@
if (createReqVO.getFinanceUserId() != null) {
adminUserApi.validateUser(createReqVO.getFinanceUserId());
}
- // 1.5 鐢熸垚浠樻鍗曞彿锛屽苟鏍¢獙鍞竴鎬�
+ // 1.5 鏍¢獙鍏宠仈鏉ョエ
+ validateInvoiceForPayment(createReqVO.getInvoiceId(), createReqVO.getSupplierId());
+ // 1.6 鏍¢獙浠樻閲戦涓嶈秴杩囪鏉ョエ鍓╀綑鍙粯閲戦
+ validatePaymentPriceExceedsInvoiceRemaining(createReqVO.getInvoiceId(),
+ getSumValue(paymentItems, ErpFinancePaymentItemDO::getPaymentPrice, BigDecimal::add, BigDecimal.ZERO)
+ .subtract(createReqVO.getDiscountPrice()), null);
+ // 1.7 鐢熸垚浠樻鍗曞彿锛屽苟鏍¢獙鍞竴鎬�
String no = noRedisDAO.generate(ErpNoRedisDAO.FINANCE_PAYMENT_NO_PREFIX);
if (financePaymentMapper.selectByNo(no) != null) {
throw exception(FINANCE_PAYMENT_NO_EXISTS);
@@ -83,6 +119,10 @@
paymentItems.forEach(o -> o.setPaymentId(payment.getId()));
financePaymentItemMapper.insertBatch(paymentItems);
+ // 2.3 缁戝畾闄勪欢
+ storageAttachmentApi.bindAttachments("file", "erp_finance_payment",
+ payment.getId(), createReqVO.getBlobIds());
+
// 3. 鏇存柊閲囪喘鍏ュ簱銆侀��璐х殑浠樻閲戦鎯呭喌
updatePurchasePrice(paymentItems);
return payment.getId();
@@ -95,6 +135,9 @@
ErpFinancePaymentDO payment = validateFinancePaymentExists(updateReqVO.getId());
if (ErpAuditStatus.APPROVE.getStatus().equals(payment.getStatus())) {
throw exception(FINANCE_PAYMENT_UPDATE_FAIL_APPROVE, payment.getNo());
+ }
+ if (ErpAuditStatus.APPROVING.getStatus().equals(payment.getStatus())) {
+ throw exception(FINANCE_PAYMENT_UPDATE_FAIL_APPROVING, payment.getNo());
}
// 1.2 鏍¢獙渚涘簲鍟�
supplierService.validateSupplier(updateReqVO.getSupplierId());
@@ -109,6 +152,12 @@
// 1.5 鏍¢獙浠樻鍗曢」鐨勬湁鏁堟��
List<ErpFinancePaymentItemDO> paymentItems = validateFinancePaymentItems(
updateReqVO.getSupplierId(), updateReqVO.getItems());
+ // 1.6 鏍¢獙鍏宠仈鏉ョエ
+ validateInvoiceForPayment(updateReqVO.getInvoiceId(), updateReqVO.getSupplierId());
+ // 1.7 鏍¢獙浠樻閲戦涓嶈秴杩囪鏉ョエ鍓╀綑鍙粯閲戦锛堟帓闄よ浠樻鍗曡嚜韬綋鍓嶅凡浠橀噾棰濓級
+ validatePaymentPriceExceedsInvoiceRemaining(updateReqVO.getInvoiceId(),
+ getSumValue(paymentItems, ErpFinancePaymentItemDO::getPaymentPrice, BigDecimal::add, BigDecimal.ZERO)
+ .subtract(updateReqVO.getDiscountPrice()), payment.getPaymentPrice());
// 2.1 鏇存柊浠樻鍗�
ErpFinancePaymentDO updateObj = BeanUtils.toBean(updateReqVO, ErpFinancePaymentDO.class);
@@ -116,6 +165,8 @@
financePaymentMapper.updateById(updateObj);
// 2.2 鏇存柊浠樻鍗曢」
updateFinancePaymentItemList(updateReqVO.getId(), paymentItems);
+ // 2.3 鏇存柊闄勪欢
+ storageAttachmentApi.updateAttachments("file", "erp_finance_payment", updateReqVO.getId(), updateReqVO.getBlobIds());
}
private void calculateTotalPrice(ErpFinancePaymentDO payment, List<ErpFinancePaymentItemDO> paymentItems) {
@@ -125,21 +176,105 @@
@Override
@Transactional(rollbackFor = Exception.class)
- public void updateFinancePaymentStatus(Long id, Integer status) {
- boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
- // 1.1 鏍¢獙瀛樺湪
+ public void submitFinancePayment(Long id, String processDefinitionKey, Long userId) {
+ // 1. 鏍¢獙瀛樺湪 + 鏈鏍哥姸鎬�
ErpFinancePaymentDO payment = validateFinancePaymentExists(id);
- // 1.2 鏍¢獙鐘舵��
- if (payment.getStatus().equals(status)) {
- throw exception(approve ? FINANCE_PAYMENT_APPROVE_FAIL : FINANCE_PAYMENT_PROCESS_FAIL);
+ if (!ErpAuditStatus.PROCESS.getStatus().equals(payment.getStatus())) {
+ throw exception(FINANCE_PAYMENT_SUBMIT_FAIL_STATUS);
}
- // 2. 鏇存柊鐘舵��
- int updateCount = financePaymentMapper.updateByIdAndStatus(id, payment.getStatus(),
- new ErpFinancePaymentDO().setStatus(status));
- if (updateCount == 0) {
- throw exception(approve ? FINANCE_PAYMENT_APPROVE_FAIL : FINANCE_PAYMENT_PROCESS_FAIL);
+ // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥
+ String processInstanceId = processInstanceApi.createProcessInstance(userId,
+ new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(processDefinitionKey)
+ .setBusinessKey(String.valueOf(id)));
+
+ // 3. 鏇存柊浠樻鍗曠姸鎬佷负瀹℃壒涓�
+ financePaymentMapper.updateById(new ErpFinancePaymentDO()
+ .setId(id)
+ .setStatus(ErpAuditStatus.APPROVING.getStatus())
+ .setProcessInstanceId(processInstanceId));
+ }
+
+ @Override
+ public List<Map<String, Object>> getFinancePaymentApproveProcessDefinitionList() {
+ // 1. 鏍¢獙鍒嗙被鍜屾祦绋嬪畾涔夋槸鍚﹀瓨鍦�
+ validateFinancePaymentApproveCategoryAndProcessDefinition();
+
+ // 2. 鑾峰彇鍒嗙被涓嬬殑娴佺▼瀹氫箟淇℃伅
+ List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
+ .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
+ if (CollUtil.isEmpty(definitionInfoList)) {
+ return Collections.emptyList();
}
+
+ // 3. 閬嶅巻鑾峰彇娴佺▼瀹氫箟璇︽儏锛岃繃婊ゆ縺娲荤姸鎬侊紝淇濈暀鏈�鏂扮増鏈�
+ Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+ for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+ ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+ if (pd == null || pd.isSuspended()) {
+ continue;
+ }
+ ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+ if (existing == null || pd.getVersion() > existing.getVersion()) {
+ latestVersionMap.put(pd.getKey(), pd);
+ }
+ }
+
+ // 4. 杩斿洖娴佺▼瀹氫箟鍒楄〃
+ List<Map<String, Object>> result = new ArrayList<>();
+ for (ProcessDefinition pd : latestVersionMap.values()) {
+ Map<String, Object> item = new HashMap<>();
+ item.put("id", pd.getId());
+ item.put("key", pd.getKey());
+ item.put("name", pd.getName());
+ result.add(item);
+ }
+ return result;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateFinancePaymentAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙瀛樺湪
+ ErpFinancePaymentDO payment = validateFinancePaymentExists(id);
+ if (!ErpAuditStatus.APPROVING.getStatus().equals(payment.getStatus())) {
+ log.warn("[updateFinancePaymentAuditStatus] 浠樻鍗�({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+ return;
+ }
+
+ // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+ Integer newStatus = convertBpmResultToAuditStatus(bpmResult);
+ if (newStatus != null) {
+ financePaymentMapper.updateById(new ErpFinancePaymentDO()
+ .setId(id).setStatus(newStatus));
+ }
+ }
+
+ /**
+ * 鏍¢獙浠樻鍗曞鎵瑰垎绫诲拰娴佺▼瀹氫箟鏄惁瀛樺湪
+ */
+ private void validateFinancePaymentApproveCategoryAndProcessDefinition() {
+ // 1. 鏍¢獙鍒嗙被鏄惁瀛樺湪
+ List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
+ Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE));
+ if (CollUtil.isEmpty(categories)) {
+ throw exception(FINANCE_PAYMENT_BPM_CATEGORY_NOT_EXISTS);
+ }
+ }
+
+ /**
+ * 杞崲 BPM 瀹℃壒缁撴灉涓哄鏍哥姸鎬�
+ */
+ private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
+ if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+ return ErpAuditStatus.APPROVE.getStatus();
+ } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+ return ErpAuditStatus.PROCESS.getStatus();
+ } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+ return ErpAuditStatus.CANCEL.getStatus();
+ }
+ return null;
}
private List<ErpFinancePaymentItemDO> validateFinancePaymentItems(
@@ -189,13 +324,18 @@
if (ErpAuditStatus.APPROVE.getStatus().equals(payment.getStatus())) {
throw exception(FINANCE_PAYMENT_DELETE_FAIL_APPROVE, payment.getNo());
}
+ if (ErpAuditStatus.APPROVING.getStatus().equals(payment.getStatus())) {
+ throw exception(FINANCE_PAYMENT_DELETE_FAIL_APPROVING, payment.getNo());
+ }
});
// 2. 閬嶅巻鍒犻櫎锛屽苟璁板綍鎿嶄綔鏃ュ織
payments.forEach(payment -> {
- // 2.1 鍒犻櫎浠樻鍗�
+ // 2.1 鍒犻櫎闄勪欢
+ storageAttachmentApi.deleteAttachmentsByRecord("erp_finance_payment", payment.getId());
+ // 2.2 鍒犻櫎浠樻鍗�
financePaymentMapper.deleteById(payment.getId());
- // 2.2 鍒犻櫎浠樻鍗曢」
+ // 2.3 鍒犻櫎浠樻鍗曢」
List<ErpFinancePaymentItemDO> paymentItems = financePaymentItemMapper.selectListByPaymentId(payment.getId());
financePaymentItemMapper.deleteByIds(convertSet(paymentItems, ErpFinancePaymentItemDO::getId));
@@ -212,6 +352,49 @@
return payment;
}
+ /**
+ * 鏍¢獙浠樻鍗曞叧鑱旂殑鏉ョエ锛氬瓨鍦ㄣ�佸凡涓婁紶鍙戠エ闄勪欢銆佷緵搴斿晢涓庢潵绁ㄤ竴鑷�
+ *
+ * @param invoiceId 鏉ョエ缂栧彿
+ * @param supplierId 渚涘簲鍟嗙紪鍙�
+ */
+ private void validateInvoiceForPayment(Long invoiceId, Long supplierId) {
+ // 1. 鏍¢獙鏉ョエ瀛樺湪
+ ErpPurchaseInvoiceDO invoice = invoiceService.getPurchaseInvoice(invoiceId);
+ if (invoice == null) {
+ throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_EXISTS);
+ }
+ // 2. 鏍¢獙宸蹭笂浼犲彂绁ㄩ檮浠�
+ if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(
+ ErpPurchaseInvoiceServiceImpl.PURCHASE_INVOICE_RECORD_TYPE, invoiceId))) {
+ throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_ATTACHMENT);
+ }
+ // 3. 鏍¢獙渚涘簲鍟嗕笌鏉ョエ涓�鑷达紙闃茶剰鏁版嵁锛�
+ if (ObjectUtil.notEqual(invoice.getSupplierId(), supplierId)) {
+ throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_SUPPLIER_MISMATCH);
+ }
+ }
+
+ /**
+ * 鏍¢獙浠樻閲戦涓嶈秴杩囪鏉ョエ鍓╀綑鍙粯閲戦锛堟潵绁ㄩ噾棰� - 璇ユ潵绁ㄥ凡浠樻閲戦锛�
+ *
+ * @param invoiceId 鏉ョエ缂栧彿
+ * @param paymentPrice 鏈浠樻閲戦
+ * @param oldPaymentPrice 鏇存柊鍦烘櫙涓嬶紝璇ヤ粯娆惧崟鑷韩褰撳墠鐨勪粯娆鹃噾棰濓紙鐢ㄤ簬鎺掗櫎鑷韩锛夛紝鍒涘缓鍦烘櫙浼� null
+ */
+ private void validatePaymentPriceExceedsInvoiceRemaining(Long invoiceId, BigDecimal paymentPrice, BigDecimal oldPaymentPrice) {
+ ErpPurchaseInvoiceDO invoice = invoiceService.getPurchaseInvoice(invoiceId);
+ BigDecimal paidPrice = financePaymentMapper.selectPaymentPriceMapByInvoiceId(List.of(invoiceId))
+ .getOrDefault(invoiceId, BigDecimal.ZERO);
+ if (oldPaymentPrice != null) {
+ paidPrice = paidPrice.subtract(oldPaymentPrice); // 鎺掗櫎璇ヤ粯娆惧崟鑷韩褰撳墠宸蹭粯閲戦
+ }
+ BigDecimal remainingPrice = invoice.getPrice().subtract(paidPrice);
+ if (paymentPrice.compareTo(remainingPrice) > 0) {
+ throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_PRICE_EXCEEDS, remainingPrice);
+ }
+ }
+
@Override
public ErpFinancePaymentDO getFinancePayment(Long id) {
return financePaymentMapper.selectById(id);
@@ -222,6 +405,11 @@
return financePaymentMapper.selectPage(pageReqVO);
}
+ @Override
+ public Map<Long, BigDecimal> getPaymentPriceMapByInvoiceId(Collection<Long> invoiceIds) {
+ return financePaymentMapper.selectPaymentPriceMapByInvoiceId(invoiceIds);
+ }
+
// ==================== 浠樻鍗曢」 ====================
@Override
--
Gitblit v1.9.3