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-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 346 insertions(+), 6 deletions(-)
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
index e805567..b2cb267 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
@@ -14,17 +14,26 @@
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceEntryReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceImportExcelVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceImportRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaleOutRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoiceSaveReqVO;
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.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.dal.mysql.invoice.CrmInvoiceMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
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.CrmPaymentStageEnum;
+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.permission.CrmPermissionService;
@@ -46,9 +55,12 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -77,8 +89,36 @@
*/
private static final String INVOICE_RECORD_TYPE = "crm_invoice";
+ /**
+ * ERP 閿�鍞鍗曞鏍搁�氳繃鐘舵�侊紝瀵瑰簲 cn.iocoder.yudao.module.erp.enums.ErpAuditStatus#APPROVE
+ */
+ private static final Integer ERP_SALE_ORDER_STATUS_APPROVE = 20;
+
+ /**
+ * MES 閿�鍞嚭搴撳崟宸插畬鎴愮姸鎬侊紝瀵瑰簲 cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum#FINISHED
+ */
+ private static final Integer MES_PRODUCT_SALES_STATUS_FINISHED = 4;
+
+ /**
+ * MES 閿�鍞嚭搴撳崟宸插彇娑堢姸鎬�
+ */
+ private static final Integer MES_PRODUCT_SALES_STATUS_CANCELED = 5;
+
+ /**
+ * 寮�绁ㄥ崰鐢ㄩ搴︾殑瀹℃壒鐘舵�侊細瀹℃壒涓� + 瀹℃壒閫氳繃
+ */
+ private static final List<Integer> INVOICE_OCCUPY_STATUSES = List.of(
+ CrmAuditStatusEnum.PROCESS.getStatus(), CrmAuditStatusEnum.APPROVE.getStatus());
+
@Resource
private CrmInvoiceMapper invoiceMapper;
+ @Resource
+ private CrmReceivablePlanMapper receivablePlanMapper;
+
+ @Resource
+ private ErpSaleOrderApi saleOrderApi;
+ @Resource
+ private MesWmProductSalesApi productSalesApi;
@Resource
private CrmNoRedisDAO noRedisDAO;
@@ -100,25 +140,31 @@
@LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_CREATE_SUB_TYPE, bizNo = "{{#invoice.id}}",
success = CRM_INVOICE_CREATE_SUCCESS)
public Long createInvoice(CrmInvoiceSaveReqVO createReqVO) {
- // 1.1 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
- validateInvoicePriceExceedsLimit(createReqVO);
- // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
+ // 1.1 鏍¢獙鍏宠仈鏁版嵁瀛樺湪锛堝悎鍚� -> 瀹㈡埛锛�
validateRelationDataExists(createReqVO);
- // 1.3 鐢熸垚寮�绁ㄧ紪鍙�
+ // 1.2 鏍¢獙閿�鍞鍗曘�侀攢鍞嚭搴撳崟锛屽苟鍥炲~鍑哄簱鍗曞彿
+ validateSaleOrderAndOut(createReqVO);
+ // 1.3 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
+ validateInvoicePriceExceedsLimit(createReqVO);
+ // 1.4 鐢熸垚寮�绁ㄧ紪鍙�
String no = noRedisDAO.generate(CrmNoRedisDAO.INVOICE_PREFIX);
if (invoiceMapper.selectByNo(no) != null) {
throw exception(INVOICE_NO_EXISTS);
}
- // 2.1 鎻掑叆寮�绁�
+ // 2.1 鎻掑叆寮�绁細宸插~鍐欏彂绁ㄥ彿鐮佽涓哄凡褰曞叆鍙戠エ
CrmInvoiceDO invoice = BeanUtils.toBean(createReqVO, CrmInvoiceDO.class)
.setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
+ applyEntryStatus(invoice, createReqVO.getInvoiceNo(), null);
invoiceMapper.insert(invoice);
// 2.2 缁戝畾鍙戠エ闄勪欢
if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
storageAttachmentApi.bindAttachments("file", INVOICE_RECORD_TYPE, invoice.getId(), createReqVO.getBlobIds());
}
+
+ // 2.3 鍚屾鏀舵鑺傜偣锛堝悎鍚屾/棰勪粯娆�/鍒拌揣娆�/楠屾敹娆撅級
+ syncPaymentNodes(invoice, createReqVO.getPaymentNodes());
// 3. 鍒涘缓鏁版嵁鏉冮檺
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_INVOICE.getType())
@@ -390,6 +436,212 @@
}
}
+ /**
+ * 鏍¢獙閿�鍞鍗曘�侀攢鍞嚭搴撳崟锛屽苟鍥炲~鍑哄簱鍗曞彿
+ *
+ * 鏍¢獙椤哄簭锛氶攢鍞鍗曞瓨鍦� -> 宸插鏍� -> 瀹㈡埛/鍚堝悓涓�鑷� -> 璁㈠崟鍓╀綑鍙紑绁紱
+ * 鍐嶆牎楠屽嚭搴撳崟瀛樺湪 -> 宸插畬鎴� -> 灞炰簬璇ヨ鍗� -> 瀹㈡埛涓�鑷� -> 鍑哄簱鍗曞墿浣欏彲寮�绁ㄣ��
+ */
+ private void validateSaleOrderAndOut(CrmInvoiceSaveReqVO reqVO) {
+ if (reqVO.getSaleOrderId() == null) {
+ return;
+ }
+ // 1. 閿�鍞鍗�
+ ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(reqVO.getSaleOrderId()).getCheckedData();
+ if (saleOrder == null) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_ORDER_NOT_EXISTS, reqVO.getSaleOrderId());
+ }
+ if (ObjectUtil.notEqual(saleOrder.getStatus(), ERP_SALE_ORDER_STATUS_APPROVE)) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_ORDER_NOT_APPROVE, saleOrder.getNo(),
+ erpAuditStatusName(saleOrder.getStatus()));
+ }
+ if (ObjectUtil.notEqual(saleOrder.getCustomerId(), reqVO.getCustomerId())
+ || (saleOrder.getContractId() != null
+ && ObjectUtil.notEqual(saleOrder.getContractId(), reqVO.getContractId()))) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_ORDER_MISMATCH, saleOrder.getNo(),
+ saleOrder.getCustomerId(), saleOrder.getContractId(), reqVO.getCustomerId(), reqVO.getContractId());
+ }
+ // 1.1 璁㈠崟鍓╀綑鍙紑绁ㄩ噾棰� = 璁㈠崟閲戦 - 璇ヨ鍗曞凡鍗犵敤寮�绁ㄩ噾棰濓紙瀹℃壒涓� + 瀹℃壒閫氳繃锛�
+ BigDecimal orderTotalPrice = zeroIfNull(saleOrder.getTotalPrice());
+ BigDecimal orderInvoicedPrice = sumInvoicePrice(invoiceMapper.selectListBySaleOrderIdAndStatus(
+ reqVO.getSaleOrderId(), INVOICE_OCCUPY_STATUSES), reqVO.getId());
+ BigDecimal orderRemainPrice = orderTotalPrice.subtract(orderInvoicedPrice);
+ if (reqVO.getPrice() != null && reqVO.getPrice().compareTo(orderRemainPrice) > 0) {
+ throw exception(INVOICE_CREATE_FAIL_PRICE_EXCEEDS_SALE_ORDER, saleOrder.getNo(),
+ formatPrice(orderRemainPrice), formatPrice(orderTotalPrice), formatPrice(orderInvoicedPrice));
+ }
+
+ // 2. 閿�鍞嚭搴撳崟锛堝彲閫夛級
+ if (reqVO.getSaleOutId() == null) {
+ return;
+ }
+ MesWmProductSalesRespDTO saleOut = productSalesApi.getProductSales(reqVO.getSaleOutId());
+ if (saleOut == null) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_OUT_NOT_EXISTS, reqVO.getSaleOutId());
+ }
+ if (ObjectUtil.notEqual(saleOut.getStatus(), MES_PRODUCT_SALES_STATUS_FINISHED)) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_OUT_NOT_FINISHED, saleOut.getCode(),
+ productSalesStatusName(saleOut.getStatus()));
+ }
+ if (saleOut.getSaleOrderId() != null && ObjectUtil.notEqual(saleOut.getSaleOrderId(), reqVO.getSaleOrderId())) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_OUT_MISMATCH_ORDER, saleOut.getCode(),
+ saleOrder.getNo(), saleOut.getSaleOrderId());
+ }
+ if (saleOut.getClientId() != null && ObjectUtil.notEqual(saleOut.getClientId(), reqVO.getCustomerId())) {
+ throw exception(INVOICE_CREATE_FAIL_SALE_OUT_MISMATCH_CUSTOMER, saleOut.getCode(),
+ saleOut.getClientId(), reqVO.getCustomerId());
+ }
+ // 2.1 鍑哄簱鍗曞墿浣欏彲寮�绁ㄩ噾棰� = 鍑哄簱閲戦 - 璇ュ嚭搴撳崟宸插崰鐢ㄥ紑绁ㄩ噾棰濓紙瀹℃壒涓� + 瀹℃壒閫氳繃锛�
+ BigDecimal saleOutTotalPrice = zeroIfNull(saleOut.getTotalPrice());
+ BigDecimal saleOutInvoicedPrice = sumInvoicePrice(invoiceMapper.selectListBySaleOutIdAndStatus(
+ reqVO.getSaleOutId(), INVOICE_OCCUPY_STATUSES), reqVO.getId());
+ BigDecimal saleOutRemainPrice = saleOutTotalPrice.subtract(saleOutInvoicedPrice);
+ if (reqVO.getPrice() != null && reqVO.getPrice().compareTo(saleOutRemainPrice) > 0) {
+ throw exception(INVOICE_CREATE_FAIL_PRICE_EXCEEDS_SALE_OUT, saleOut.getCode(),
+ formatPrice(saleOutRemainPrice), formatPrice(saleOutTotalPrice), formatPrice(saleOutInvoicedPrice));
+ }
+ reqVO.setSaleOutCode(saleOut.getCode());
+ }
+
+ private BigDecimal sumInvoicePrice(List<CrmInvoiceDO> invoices, Long excludeInvoiceId) {
+ if (CollUtil.isEmpty(invoices)) {
+ return BigDecimal.ZERO;
+ }
+ return invoices.stream()
+ .filter(invoice -> excludeInvoiceId == null || ObjectUtil.notEqual(invoice.getId(), excludeInvoiceId))
+ .map(invoice -> zeroIfNull(invoice.getPrice()))
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ /**
+ * 鏍规嵁鍙戠エ鍙风爜鏄惁濉啓锛岃缃彂绁ㄥ綍鍏ョ姸鎬�
+ */
+ private void applyEntryStatus(CrmInvoiceDO invoice, String invoiceNo, CrmInvoiceDO oldInvoice) {
+ if (StrUtil.isBlank(invoiceNo)) {
+ invoice.setEntryStatus(0);
+ return;
+ }
+ invoice.setEntryStatus(10);
+ boolean changed = oldInvoice == null || !StrUtil.equals(invoiceNo, oldInvoice.getInvoiceNo())
+ || !Integer.valueOf(10).equals(oldInvoice.getEntryStatus());
+ if (changed) {
+ invoice.setEntryUserId(getLoginUserId()).setEntryTime(LocalDateTime.now());
+ }
+ }
+
+ /**
+ * 鍚屾寮�绁ㄥ崟鐨勬敹娆捐妭鐐瑰埌鍥炴璁″垝锛坈rm_receivable_plan锛�
+ *
+ * 浼� null 琛ㄧず鏈涓嶄慨鏀硅妭鐐癸紱浼犵┖鍒楄〃琛ㄧず娓呯┖鑺傜偣銆�
+ */
+ private void syncPaymentNodes(CrmInvoiceDO invoice, List<CrmInvoiceSaveReqVO.PaymentNode> nodes) {
+ if (nodes == null) {
+ return;
+ }
+ validatePaymentNodes(nodes);
+ // 宸蹭骇鐢熷洖娆剧殑鑺傜偣涓嶅厑璁歌瑕嗙洊
+ for (CrmReceivablePlanDO oldPlan : receivablePlanMapper.selectListByInvoiceId(invoice.getId())) {
+ if (oldPlan.getReceivableId() != null || zeroIfNull(oldPlan.getReceiptedPrice()).compareTo(BigDecimal.ZERO) > 0) {
+ throw exception(INVOICE_PAYMENT_NODE_ALREADY_RECEIPTED,
+ CrmPaymentStageEnum.getNameByStage(oldPlan.getStage()),
+ formatPrice(zeroIfNull(oldPlan.getReceiptedPrice())));
+ }
+ }
+ deletePaymentNodesByInvoiceId(invoice.getId());
+
+ Long ownerUserId = invoice.getOwnerUserId() != null ? invoice.getOwnerUserId() : getLoginUserId();
+ for (CrmInvoiceSaveReqVO.PaymentNode node : nodes) {
+ CrmReceivablePlanDO maxPeriodPlan = receivablePlanMapper.selectMaxPeriodByContractId(invoice.getContractId());
+ int period = maxPeriodPlan == null ? 1 : maxPeriodPlan.getPeriod() + 1;
+ int remindDays = 3;
+ CrmReceivablePlanDO plan = CrmReceivablePlanDO.builder()
+ .period(period).customerId(invoice.getCustomerId()).contractId(invoice.getContractId())
+ .ownerUserId(ownerUserId).returnTime(node.getReturnTime())
+ .remindDays(remindDays).remindTime(node.getReturnTime().minusDays(remindDays))
+ .price(node.getAmount()).stage(node.getStage()).invoiceId(invoice.getId())
+ .saleOrderId(invoice.getSaleOrderId())
+ .receiptedPrice(BigDecimal.ZERO).status(0).remark(node.getRemark())
+ .build();
+ receivablePlanMapper.insert(plan);
+ permissionService.createPermission(new CrmPermissionCreateReqBO()
+ .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(plan.getId())
+ .setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
+ }
+ }
+
+ private void validatePaymentNodes(List<CrmInvoiceSaveReqVO.PaymentNode> nodes) {
+ if (CollUtil.isEmpty(nodes)) {
+ return;
+ }
+ Set<Integer> stages = new HashSet<>();
+ for (int i = 0; i < nodes.size(); i++) {
+ CrmInvoiceSaveReqVO.PaymentNode node = nodes.get(i);
+ int row = i + 1;
+ CrmPaymentStageEnum stageEnum = node == null ? null : CrmPaymentStageEnum.valueOf(node.getStage());
+ if (stageEnum == null) {
+ throw exception(INVOICE_PAYMENT_NODE_STAGE_INVALID, row, node == null ? "鏈~鍐�" : node.getStage());
+ }
+ if (!stages.add(stageEnum.getStage())) {
+ throw exception(INVOICE_PAYMENT_NODE_STAGE_DUPLICATE, stageEnum.getName());
+ }
+ if (node.getAmount() == null || node.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+ throw exception(INVOICE_PAYMENT_NODE_INVALID, row, stageEnum.getName(),
+ node.getAmount() == null ? "鏈~鍐�" : formatPrice(node.getAmount()));
+ }
+ if (node.getReturnTime() == null) {
+ throw exception(INVOICE_PAYMENT_NODE_TIME_REQUIRED, stageEnum.getName());
+ }
+ }
+ }
+
+ private void deletePaymentNodesByInvoiceId(Long invoiceId) {
+ for (CrmReceivablePlanDO plan : receivablePlanMapper.selectListByInvoiceId(invoiceId)) {
+ receivablePlanMapper.deleteById(plan.getId());
+ permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), plan.getId());
+ }
+ }
+
+ private String erpAuditStatusName(Integer status) {
+ if (Objects.equals(status, ERP_SALE_ORDER_STATUS_APPROVE)) {
+ return "宸插鏍�";
+ }
+ if (status == null) {
+ return "鏈煡鐘舵��";
+ }
+ return switch (status) {
+ case 0 -> "鏈彁浜�";
+ case 10 -> "鏈鏍�";
+ case 15 -> "瀹℃壒涓�";
+ case 30 -> "宸蹭綔搴�";
+ case 35 -> "宸查┏鍥�";
+ default -> "鐘舵�佸�� " + status;
+ };
+ }
+
+ private String productSalesStatusName(Integer status) {
+ if (status == null) {
+ return "鏈煡鐘舵��";
+ }
+ return switch (status) {
+ case 0 -> "鑽夌";
+ case 1 -> "寰呮娴�";
+ case 2 -> "寰呮嫞璐�";
+ case 3 -> "寰呮墽琛屽嚭搴�";
+ case 4 -> "宸插畬鎴�";
+ case 5 -> "宸插彇娑�";
+ case 10 -> "寰呭~鍐欒繍鍗�";
+ default -> "鐘舵�佸�� " + status;
+ };
+ }
+
+ private static BigDecimal zeroIfNull(BigDecimal value) {
+ return value == null ? BigDecimal.ZERO : value;
+ }
+
+ private static String formatPrice(BigDecimal value) {
+ return (value == null ? BigDecimal.ZERO : value).stripTrailingZeros().toPlainString();
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -410,15 +662,25 @@
StrUtil.blankToDefault(CrmAuditStatusEnum.getNameByStatus(oldInvoice.getAuditStatus()), "鏈煡鐘舵��"));
}
- // 1.3 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
+ // 1.3 鏍¢獙閿�鍞鍗曘�侀攢鍞嚭搴撳崟锛屽苟鍥炲~鍑哄簱鍗曞彿
+ validateSaleOrderAndOut(updateReqVO);
+ // 1.4 鏍¢獙鍙紑绁ㄩ噾棰濊秴杩囦笂闄�
validateInvoicePriceExceedsLimit(updateReqVO);
// 2. 鏇存柊寮�绁�
CrmInvoiceDO updateObj = BeanUtils.toBean(updateReqVO, CrmInvoiceDO.class);
+ applyEntryStatus(updateObj, updateReqVO.getInvoiceNo(), oldInvoice);
invoiceMapper.updateById(updateObj);
+ // 鍙戠エ鍙风爜琚竻绌烘椂锛屼竴骞舵竻绌哄綍鍏ヤ汉銆佸綍鍏ユ椂闂达紙updateById 浼氬拷鐣� null 瀛楁锛岄渶鏄惧紡娓呯┖锛�
+ if (StrUtil.isBlank(updateReqVO.getInvoiceNo())) {
+ invoiceMapper.clearEntryInfo(updateReqVO.getId());
+ }
// 3. 澧為噺鏇存柊鍙戠エ闄勪欢
storageAttachmentApi.updateAttachments("file", INVOICE_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
+
+ // 3.1 鍚屾鏀舵鑺傜偣锛堜粎鍓嶇浼犱簡 paymentNodes 鎵嶈鐩栵紝閬垮厤涓嶄紶鏃惰鍒狅級
+ syncPaymentNodes(oldInvoice, updateReqVO.getPaymentNodes());
// 4. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
updateReqVO.setOwnerUserId(oldInvoice.getOwnerUserId()); // 閬垮厤鎿嶄綔鏃ュ織鍑虹幇鈥滃垹闄よ礋璐d汉鈥濈殑鎯呭喌
@@ -461,6 +723,8 @@
invoiceMapper.deleteById(id);
// 2.3 鍒犻櫎鏁版嵁鏉冮檺
permissionService.deletePermission(CrmBizTypeEnum.CRM_INVOICE.getType(), id);
+ // 2.4 鍒犻櫎寮�绁ㄧ敓鎴愮殑鏀舵鑺傜偣锛堝鎵归�氳繃鐨勫紑绁ㄤ笉鍏佽鍒犻櫎锛屾晠姝ゅ鑺傜偣蹇呯劧鏈骇鐢熷洖娆撅級
+ deletePaymentNodesByInvoiceId(id);
// 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
LogRecordContext.putVariable("invoice", invoice);
@@ -555,4 +819,80 @@
return invoiceMapper.selectCountByContractId(contractId);
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @LogRecord(type = CRM_INVOICE_TYPE, subType = CRM_INVOICE_ENTRY_SUB_TYPE, bizNo = "{{#reqVO.id}}",
+ success = CRM_INVOICE_ENTRY_SUCCESS)
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_INVOICE, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.WRITE)
+ public void entryInvoice(CrmInvoiceEntryReqVO reqVO) {
+ // 1.1 鏍¢獙瀛樺湪
+ CrmInvoiceDO invoice = validateInvoiceExists(reqVO.getId());
+ // 1.2 鍙湁瀹℃壒閫氳繃鐨勫紑绁ㄧ敵璇锋墠鑳藉綍鍏ュ彂绁�
+ if (ObjectUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
+ throw exception(INVOICE_ENTRY_FAIL_NOT_APPROVE, invoice.getNo(),
+ StrUtil.blankToDefault(CrmAuditStatusEnum.getNameByStatus(invoice.getAuditStatus()), "鏈煡鐘舵��"));
+ }
+ // 1.3 鍙戠エ鍙风爜涓嶅彲涓庡叾浠栧紑绁ㄥ崟閲嶅
+ String invoiceNo = reqVO.getInvoiceNo().trim();
+ CrmInvoiceDO invoiceNoExists = invoiceMapper.selectByInvoiceNo(invoiceNo);
+ if (invoiceNoExists != null && ObjectUtil.notEqual(invoiceNoExists.getId(), invoice.getId())) {
+ throw exception(INVOICE_ENTRY_FAIL_INVOICE_NO_EXISTS, invoiceNo, invoiceNoExists.getNo());
+ }
+
+ // 2. 褰曞叆鍙戠エ淇℃伅锛岀姸鎬佺疆涓哄凡褰曞叆
+ invoiceMapper.updateById(new CrmInvoiceDO().setId(invoice.getId())
+ .setInvoiceNo(invoiceNo).setInvoiceTitle(reqVO.getInvoiceTitle())
+ .setInvoiceTime(reqVO.getInvoiceTime()).setRemark(reqVO.getRemark())
+ .setEntryStatus(10).setEntryUserId(getLoginUserId()).setEntryTime(LocalDateTime.now()));
+ // 3. 鏇存柊鍙戠エ闄勪欢
+ if (reqVO.getBlobIds() != null) {
+ storageAttachmentApi.updateAttachments("file", INVOICE_RECORD_TYPE, invoice.getId(), reqVO.getBlobIds());
+ }
+ // 4. 鍚屾鏀舵鑺傜偣
+ syncPaymentNodes(invoice, reqVO.getPaymentNodes());
+
+ // 5. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
+ LogRecordContext.putVariable("invoiceNo", invoice.getNo());
+ LogRecordContext.putVariable("entryInvoiceNo", invoiceNo);
+ }
+
+ @Override
+ public List<CrmInvoiceSaleOutRespVO> getSaleOutListForInvoice(Long saleOrderId, Long excludeInvoiceId) {
+ if (saleOrderId == null) {
+ return ListUtil.empty();
+ }
+ List<MesWmProductSalesRespDTO> saleOutList = productSalesApi.getProductSalesListBySaleOrderId(saleOrderId);
+ if (CollUtil.isEmpty(saleOutList)) {
+ return ListUtil.empty();
+ }
+ List<CrmInvoiceSaleOutRespVO> result = new ArrayList<>();
+ for (MesWmProductSalesRespDTO saleOut : saleOutList) {
+ // 宸插彇娑堢殑鍑哄簱鍗曚笉鍙紑绁�
+ if (ObjectUtil.equal(saleOut.getStatus(), MES_PRODUCT_SALES_STATUS_CANCELED)) {
+ continue;
+ }
+ BigDecimal totalPrice = zeroIfNull(saleOut.getTotalPrice());
+ BigDecimal invoicedPrice = sumInvoicePrice(invoiceMapper.selectListBySaleOutIdAndStatus(
+ saleOut.getId(), INVOICE_OCCUPY_STATUSES), excludeInvoiceId);
+ CrmInvoiceSaleOutRespVO vo = new CrmInvoiceSaleOutRespVO();
+ vo.setId(saleOut.getId());
+ vo.setCode(saleOut.getCode());
+ vo.setSaleOrderId(saleOut.getSaleOrderId());
+ vo.setSalesOrderCode(saleOut.getSalesOrderCode());
+ vo.setSalesDate(saleOut.getSalesDate());
+ vo.setStatus(saleOut.getStatus());
+ vo.setStatusName(productSalesStatusName(saleOut.getStatus()));
+ vo.setTotalPrice(totalPrice);
+ vo.setInvoicedPrice(invoicedPrice);
+ vo.setRemainInvoicePrice(totalPrice.subtract(invoicedPrice));
+ result.add(vo);
+ }
+ return result;
+ }
+
+ @Override
+ public List<CrmReceivablePlanDO> getPaymentNodeListByInvoiceId(Long invoiceId) {
+ return receivablePlanMapper.selectListByInvoiceId(invoiceId);
+ }
+
}
--
Gitblit v1.9.3