yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -145,6 +145,7 @@ ErrorCode INVOICE_SUBMIT_FAIL_NOT_EDITABLE = new ErrorCode(1_020_016_004, "åªæè稿æå®¡æ ¸ä¸éè¿ç¶æçå¼ç¥¨æè½æäº¤å®¡æ¹"); ErrorCode INVOICE_AUDIT_FAIL_NOT_PROCESS = new ErrorCode(1_020_016_005, "åªæå®¡æ¹ä¸ç¶æçå¼ç¥¨æè½å®¡æ ¸"); ErrorCode INVOICE_AUDIT_REJECT_REASON_REQUIRED = new ErrorCode(1_020_016_013, "å®¡æ ¸ä¸éè¿æ¶å¿ 须填åä¸éè¿åå "); ErrorCode INVOICE_CREATE_FAIL_MATERIAL_NOT_ALLOWED = new ErrorCode(1_020_016_014, "ç©æä¸å 许å¼ç¥¨"); ErrorCode INVOICE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_016_006, "å建å¼ç¥¨å¤±è´¥ï¼åå ï¼åå䏿¯å®¡æ ¸éè¿ç¶æ"); ErrorCode INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_016_007, "å建å¼ç¥¨å¤±è´¥ï¼åå ï¼å¼ç¥¨éé¢è¶ åºååéé¢ï¼ç®åå¯å¼ç¥¨ï¼{} å "); ErrorCode RECEIVABLE_CREATE_FAIL_INVOICE_NOT_EXISTS = new ErrorCode(1_020_016_008, "åå»ºåæ¬¾å¤±è´¥ï¼åå ï¼å ³èçå¼ç¥¨ä¸åå¨"); yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImpl.java
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.invoice.vo.invoice.CrmInvoicePageReqVO; 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.contract.CrmContractProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.invoice.CrmInvoiceDO; import cn.iocoder.yudao.module.crm.dal.mysql.invoice.CrmInvoiceMapper; import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; @@ -30,6 +31,9 @@ import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; @@ -87,6 +91,8 @@ private CrmContractService contractService; @Resource private CrmPermissionService permissionService; @Resource private MdmItemApi mdmItemApi; @Resource private AdminUserApi adminUserApi; @@ -104,6 +110,7 @@ public Long createInvoice(CrmInvoiceSaveReqVO createReqVO) { // 1.1 æ ¡éªå ³èæ°æ®åå¨ï¼å¼ç¥¨éé¢ä¸ååååéé¢éå¶ï¼ä» å端æç¤ºå¯å¼ç¥¨éé¢ï¼ validateRelationDataExists(createReqVO); validateContractItemsInvoiceable(createReqVO.getContractId()); // 1.3 çæå¼ç¥¨ç¼å· String no = noRedisDAO.generate(CrmNoRedisDAO.INVOICE_PREFIX); if (invoiceMapper.selectByNo(no) != null) { @@ -140,6 +147,29 @@ throw exception(INVOICE_CREATE_FAIL_CONTRACT_NOT_APPROVE); } reqVO.setCustomerId(contract.getCustomerId()); // 设置客æ·ç¼å· } } /** * ååä¸å å«ç©ææ¶ä¸å 许å¼ç¥¨ï¼åªæäº§åç±»éå®å 容æå¯è¿å ¥å¼ç¥¨æµç¨ã */ void validateContractItemsInvoiceable(Long contractId) { if (contractId == null) { return; } List<CrmContractProductDO> contractProducts = contractService.getContractProductListByContractId(contractId); if (CollUtil.isEmpty(contractProducts)) { return; } Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( CollectionUtils.convertSet(contractProducts, CrmContractProductDO::getItemId)); boolean containsMaterial = contractProducts.stream() .map(CrmContractProductDO::getItemId) .map(itemMap::get) .filter(ObjectUtil::isNotNull) .anyMatch(item -> MdmItemTypeEnum.MATERIAL.getType().equals(item.getItemType())); if (containsMaterial) { throw exception(INVOICE_CREATE_FAIL_MATERIAL_NOT_ALLOWED); } } @@ -210,6 +240,7 @@ && ObjUtil.notEqual(invoice.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) { throw exception(INVOICE_SUBMIT_FAIL_NOT_EDITABLE); } validateContractItemsInvoiceable(invoice.getContractId()); // 2. æ ¡éªå®¡æ¹é 置已å¯ç¨ä¸é ç½®äºææå®¡æ¹äººï¼æªé ç½®æ¶æåºå¸¦æç¡®æç¤ºçä¸å¡å¼å¸¸ï¼ approvalConfigApi.validateApprovalEnabledAndGetApprovers(INVOICE_APPROVE_BIZ_TYPE); yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/invoice/CrmInvoiceServiceImplTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,60 @@ package cn.iocoder.yudao.module.crm.service.invoice; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.mdm.enums.MdmItemTypeEnum; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CrmInvoiceServiceImplTest { @Mock private CrmContractService contractService; @Mock private MdmItemApi mdmItemApi; @InjectMocks private CrmInvoiceServiceImpl invoiceService; @Test void validateContractItemsInvoiceable_rejectsMaterial() { mockContractItem(MdmItemTypeEnum.MATERIAL); ServiceException exception = assertThrows(ServiceException.class, () -> invoiceService.validateContractItemsInvoiceable(1L)); assertEquals("ç©æä¸å 许å¼ç¥¨", exception.getMessage()); } @Test void validateContractItemsInvoiceable_allowsProduct() { mockContractItem(MdmItemTypeEnum.PRODUCT); assertDoesNotThrow(() -> invoiceService.validateContractItemsInvoiceable(1L)); } private void mockContractItem(MdmItemTypeEnum itemType) { CrmContractProductDO contractProduct = CrmContractProductDO.builder().itemId(10L).build(); MdmItemRespDTO item = new MdmItemRespDTO(); item.setId(10L); item.setItemType(itemType.getType()); when(contractService.getContractProductListByContractId(1L)).thenReturn(List.of(contractProduct)); when(mdmItemApi.getItemMap(Set.of(10L))).thenReturn(Map.of(10L, item)); } }