From d39e4f7d839167c16425086dfa2f8747bccfdff9 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 05 十一月 2026 17:39:01 +0800
Subject: [PATCH] feat: 打通销售采购财务闭环并完成累计业务优化
---
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 164 insertions(+), 1 deletions(-)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
index 727a59c..8df0874 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
@@ -3,12 +3,19 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceItemReceiptRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
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.mes.api.itemreceipt.MesWmItemReceiptApi;
+import cn.iocoder.yudao.module.mes.api.itemreceipt.dto.MesWmItemReceiptLineRespDTO;
+import cn.iocoder.yudao.module.mes.api.itemreceipt.dto.MesWmItemReceiptRespDTO;
import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
@@ -23,6 +30,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -56,7 +64,20 @@
private ErpPurchaseOrderService purchaseOrderService;
@Resource
+ private MesWmItemReceiptApi mesWmItemReceiptApi;
+
+ @Resource
private StorageAttachmentApi storageAttachmentApi;
+
+ /**
+ * MES 鍏ュ簱鍗曞凡瀹屾垚鐘舵�侊紝瑙� MesWmItemReceiptStatusEnum.FINISHED
+ */
+ private static final Integer ITEM_RECEIPT_STATUS_FINISHED = 4;
+
+ /**
+ * MES 鍏ュ簱鍗曞凡鍙栨秷鐘舵�侊紝瑙� MesWmItemReceiptStatusEnum.CANCELED
+ */
+ private static final Integer ITEM_RECEIPT_STATUS_CANCELED = 5;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -73,6 +94,7 @@
// 2.1 鎻掑叆鏉ョエ锛堝垱寤哄悗鍗充负鏈夋晥鐘舵�侊紝鏃犻渶瀹℃牳锛�
ErpPurchaseInvoiceDO invoice = BeanUtils.toBean(createReqVO, ErpPurchaseInvoiceDO.class).setNo(no);
+ applyItemReceiptCode(invoice);
invoiceMapper.insert(invoice);
// 2.2 缁戝畾鍙戠エ闄勪欢
@@ -90,6 +112,65 @@
throw exception(PURCHASE_INVOICE_CREATE_FAIL_SUPPLIER_MISMATCH);
}
reqVO.setSupplierId(purchaseOrder.getSupplierId()); // 渚涘簲鍟嗕互閲囪喘璁㈠崟涓哄噯
+ // 鏍¢獙鍏ュ簱鍗曪紝骞跺洖濉叆搴撳崟鍙峰揩鐓�
+ validateItemReceipt(reqVO, purchaseOrder);
+ }
+
+ /**
+ * 鏍¢獙鏉ョエ缁戝畾鐨勫叆搴撳崟锛氬繀椤诲瓨鍦ㄣ�佸凡瀹屾垚鍏ュ簱銆佸睘浜庡綋鍓嶉噰璐鍗曞拰渚涘簲鍟�
+ */
+ private void validateItemReceipt(ErpPurchaseInvoiceSaveReqVO reqVO, ErpPurchaseOrderDO purchaseOrder) {
+ if (reqVO.getItemReceiptId() == null) {
+ return; // 瀛橀噺/鏈粦瀹氭椂鍏佽涓虹┖
+ }
+ MesWmItemReceiptRespDTO receipt = mesWmItemReceiptApi.getItemReceipt(reqVO.getItemReceiptId());
+ if (receipt == null) {
+ throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_RECEIPT_NOT_EXISTS.getCode(),
+ "鏉ョエ鍏宠仈鐨勫叆搴撳崟涓嶅瓨鍦細鍏ュ簱鍗� ID=" + reqVO.getItemReceiptId()
+ + "銆傝閲嶆柊閫夋嫨璇ラ噰璐鍗曚笅鐨勫叆搴撳崟锛涜嫢鍒楄〃涓病鏈夊彲閫夐」锛岃鍏堝湪 MES 瀹屾垚鍏ュ簱銆�");
+ }
+ if (!ITEM_RECEIPT_STATUS_FINISHED.equals(receipt.getStatus())) {
+ throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_RECEIPT_NOT_FINISHED.getCode(),
+ "鏉ョエ鍏宠仈鐨勫叆搴撳崟[" + receipt.getCode() + "]褰撳墠鐘舵�佷负銆�" + itemReceiptStatusName(receipt.getStatus())
+ + "銆嶏紝鍙湁銆屽凡瀹屾垚銆嶇殑鍏ュ簱鍗曟墠鑳芥潵绁ㄣ�傝鍏堝湪 MES 瀹屾垚鍏ュ簱鍚庡啀閫夋嫨璇ュ叆搴撳崟銆�");
+ }
+ if (ObjectUtil.notEqual(receipt.getPurchaseOrderId(), reqVO.getPurchaseOrderId())) {
+ throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_RECEIPT_ORDER_MISMATCH.getCode(),
+ "鎵�閫夊叆搴撳崟[" + receipt.getCode() + "]灞炰簬閲囪喘璁㈠崟[" + StrUtil.blankToDefault(receipt.getPurchaseOrderCode(), "ID=" + receipt.getPurchaseOrderId())
+ + "]锛屼笌褰撳墠閫夋嫨鐨勯噰璐鍗昜" + purchaseOrder.getNo() + "]涓嶄竴鑷淬�傝閲嶆柊閫夋嫨璇ラ噰璐鍗曚笅鐨勫叆搴撳崟锛屾垨鍏堟牳瀵归噰璐鍗曞彿銆�");
+ }
+ if (ObjectUtil.notEqual(receipt.getVendorId(), purchaseOrder.getSupplierId())) {
+ throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_RECEIPT_SUPPLIER_MISMATCH.getCode(),
+ "鎵�閫夊叆搴撳崟[" + receipt.getCode() + "]鐨勪緵搴斿晢(ID=" + receipt.getVendorId()
+ + ")涓庨噰璐鍗昜" + purchaseOrder.getNo() + "]鐨勪緵搴斿晢(ID=" + purchaseOrder.getSupplierId()
+ + ")涓嶄竴鑷淬�傝鏍稿鍏ュ簱鍗曟潵婧愶紝鎴栨敼閫夎閲囪喘璁㈠崟涓嬬殑鍏ュ簱鍗曘��");
+ }
+ }
+
+ /**
+ * 淇濆瓨鍏ュ簱鍗曞彿蹇収锛屽垪琛ㄤ笌瀵煎嚭鐩存帴灞曠ず锛岄伩鍏嶈仈琛�
+ */
+ private void applyItemReceiptCode(ErpPurchaseInvoiceDO invoice) {
+ if (invoice.getItemReceiptId() == null) {
+ invoice.setItemReceiptCode(null);
+ return;
+ }
+ MesWmItemReceiptRespDTO receipt = mesWmItemReceiptApi.getItemReceipt(invoice.getItemReceiptId());
+ invoice.setItemReceiptCode(receipt != null ? receipt.getCode() : null);
+ }
+
+ private String itemReceiptStatusName(Integer status) {
+ if (status == null) {
+ return "鏈煡";
+ }
+ return switch (status) {
+ case 0 -> "鑽夌";
+ case 2 -> "寰呬笂鏋�";
+ case 3 -> "寰呮墽琛屽叆搴�";
+ case 4 -> "宸插畬鎴�";
+ case 5 -> "宸插彇娑�";
+ default -> "鐘舵��" + status;
+ };
}
private void validatePurchaseInvoicePriceExceedsLimit(ErpPurchaseInvoiceSaveReqVO reqVO) {
@@ -101,10 +182,67 @@
}
BigDecimal remainPrice = purchaseOrder.getTotalPrice().subtract(
getSumValue(invoices, ErpPurchaseInvoiceDO::getPrice, BigDecimal::add, BigDecimal.ZERO));
- // 2. 鏍¢獙閲戦鏄惁瓒呰繃
+ // 2. 鏍¢獙閲戦鏄惁瓒呰繃閲囪喘璁㈠崟鍙紑绁ㄩ噾棰�
if (reqVO.getPrice().compareTo(remainPrice) > 0) {
throw exception(PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, remainPrice);
}
+ // 3. 缁戝畾浜嗗叆搴撳崟鏃讹紝杩樿鏍¢獙涓嶈秴杩囧叆搴撳崟鍙紑绁ㄩ噾棰濓紙鍏ュ簱閲戦 - 璇ュ叆搴撳崟宸叉潵绁ㄩ噾棰濓級
+ if (reqVO.getItemReceiptId() != null) {
+ MesWmItemReceiptRespDTO receipt = mesWmItemReceiptApi.getItemReceipt(reqVO.getItemReceiptId());
+ BigDecimal receiptTotalPrice = calcItemReceiptTotalPrice(receipt);
+ BigDecimal invoicedPrice = getItemReceiptInvoicedPrice(reqVO.getItemReceiptId(), reqVO.getId());
+ BigDecimal receiptRemainPrice = receiptTotalPrice.subtract(invoicedPrice);
+ if (reqVO.getPrice().compareTo(receiptRemainPrice) > 0) {
+ throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_RECEIPT_PRICE_EXCEEDS_LIMIT.getCode(),
+ "鏈鏉ョエ閲戦 " + reqVO.getPrice().stripTrailingZeros().toPlainString() + " 鍏冿紝瓒呰繃鎵�閫夊叆搴撳崟["
+ + (receipt != null ? receipt.getCode() : "ID=" + reqVO.getItemReceiptId()) + "]鐨勫彲寮�绁ㄩ噾棰� "
+ + receiptRemainPrice.stripTrailingZeros().toPlainString() + " 鍏冿紙鍏ュ簱閲戦 "
+ + receiptTotalPrice.stripTrailingZeros().toPlainString() + " 鍏冿紝宸叉潵绁� "
+ + invoicedPrice.stripTrailingZeros().toPlainString()
+ + " 鍏冿級銆傝璋冩暣鏉ョエ閲戦锛屾垨鏀归�夊叾浠栧叆搴撳崟锛涘闇�鎸夋暣鍗曞紑绁紝璇峰厛鏍稿鍏ュ簱閲戦銆�");
+ }
+ }
+ }
+
+ /**
+ * 璁$畻鍏ュ簱鍗曢噾棰濓細浼樺厛鍙栧叆搴撹閲戦蹇収锛屽叾娆℃寜琛屽崟浠� 脳 鍏ュ簱鏁伴噺锛屾渶鍚庢寜閲囪喘璁㈠崟琛屽崟浠峰厹搴�
+ */
+ private BigDecimal calcItemReceiptTotalPrice(MesWmItemReceiptRespDTO receipt) {
+ if (receipt == null || CollUtil.isEmpty(receipt.getLines())) {
+ return BigDecimal.ZERO;
+ }
+ Map<Long, BigDecimal> orderItemPriceMap = purchaseOrderService
+ .getPurchaseOrderItemListByOrderId(receipt.getPurchaseOrderId()).stream()
+ .filter(item -> item.getProductPrice() != null)
+ .collect(Collectors.toMap(ErpPurchaseOrderItemDO::getId, ErpPurchaseOrderItemDO::getProductPrice, (a, b) -> a));
+ BigDecimal total = BigDecimal.ZERO;
+ for (MesWmItemReceiptLineRespDTO line : receipt.getLines()) {
+ BigDecimal amount = line.getTotalPrice();
+ if (amount == null && line.getPrice() != null && line.getReceivedQuantity() != null) {
+ amount = line.getPrice().multiply(line.getReceivedQuantity());
+ }
+ if (amount == null && line.getPurchaseOrderItemId() != null && line.getReceivedQuantity() != null) {
+ BigDecimal orderItemPrice = orderItemPriceMap.get(line.getPurchaseOrderItemId());
+ if (orderItemPrice != null) {
+ amount = orderItemPrice.multiply(line.getReceivedQuantity());
+ }
+ }
+ if (amount != null) {
+ total = total.add(amount);
+ }
+ }
+ return total;
+ }
+
+ /**
+ * 璁$畻鏌愬叆搴撳崟宸叉潵绁ㄩ噾棰濓紙鍙寜鏉ョエ缂栧彿鎺掗櫎鑷韩锛�
+ */
+ private BigDecimal getItemReceiptInvoicedPrice(Long itemReceiptId, Long excludeInvoiceId) {
+ List<ErpPurchaseInvoiceDO> invoices = invoiceMapper.selectListByItemReceiptId(itemReceiptId);
+ if (excludeInvoiceId != null) {
+ invoices.removeIf(invoice -> Objects.equals(invoice.getId(), excludeInvoiceId));
+ }
+ return getSumValue(invoices, ErpPurchaseInvoiceDO::getPrice, BigDecimal::add, BigDecimal.ZERO);
}
@Override
@@ -119,6 +257,7 @@
// 2. 鏇存柊鏉ョエ
ErpPurchaseInvoiceDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseInvoiceDO.class);
+ applyItemReceiptCode(updateObj);
invoiceMapper.updateById(updateObj);
// 3. 澧為噺鏇存柊鍙戠エ闄勪欢
@@ -188,4 +327,28 @@
Collectors.mapping(ErpPurchaseInvoiceDO::getPrice, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
}
+ @Override
+ public List<ErpPurchaseInvoiceItemReceiptRespVO> getItemReceiptListForInvoice(Long purchaseOrderId, Long excludeInvoiceId) {
+ if (purchaseOrderId == null) {
+ return List.of();
+ }
+ List<MesWmItemReceiptRespDTO> receipts = mesWmItemReceiptApi.getItemReceiptListByPurchaseOrderId(purchaseOrderId);
+ return receipts.stream()
+ .filter(receipt -> !ITEM_RECEIPT_STATUS_CANCELED.equals(receipt.getStatus()))
+ .map(receipt -> {
+ BigDecimal totalPrice = calcItemReceiptTotalPrice(receipt);
+ BigDecimal invoicedPrice = getItemReceiptInvoicedPrice(receipt.getId(), excludeInvoiceId);
+ return new ErpPurchaseInvoiceItemReceiptRespVO()
+ .setId(receipt.getId())
+ .setCode(receipt.getCode())
+ .setName(receipt.getName())
+ .setReceiptDate(receipt.getReceiptDate())
+ .setStatus(receipt.getStatus())
+ .setTotalPrice(totalPrice)
+ .setInvoicedPrice(invoicedPrice)
+ .setRemainInvoicePrice(totalPrice.subtract(invoicedPrice));
+ })
+ .toList();
+ }
+
}
--
Gitblit v1.9.3