From 3b2a3dd0ea75b4288b615436af0c1c98f32e1118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=8B=E9=81=93=E6=BA=90=E7=A0=81?= Date: Mon, 9 Jun 2025 06:36:11 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20ERP=E7=BB=9F=E8=AE=A1=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=9C=A8=E5=A4=9A=E7=A7=9F=E6=88=B7=E5=85=B3=E9=97=AD=E6=97=B6?= =?UTF-8?q?=E7=9A=84NullPointerException=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复ErpSaleStatisticsMapper.xml中硬编码使用getRequiredTenantId()导致的空指针异常 - 修复ErpPurchaseStatisticsMapper.xml中硬编码使用getRequiredTenantId()导致的空指针异常 - 使用条件判断getTenantId() != null来决定是否添加租户条件 - 添加单元测试验证多租户开启和关闭时的统计查询功能 - 确保向后兼容,多租户开启时正常工作,关闭时不报错 --- .../ErpPurchaseStatisticsMapper.xml | 8 +- .../statistics/ErpSaleStatisticsMapper.xml | 8 +- .../statistics/ErpStatisticsServiceTest.java | 155 ++++++++++++++++++ 3 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 yudao-module-erp/src/test/java/cn/iocoder/yudao/module/erp/service/statistics/ErpStatisticsServiceTest.java diff --git a/yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml b/yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml index 699286b5d..a88fb3a88 100644 --- a/yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml +++ b/yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml @@ -10,7 +10,9 @@ AND in_time < #{endTime} - AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()} + + AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} + AND deleted = 0) - (SELECT IFNULL(SUM(total_price), 0) FROM erp_purchase_return @@ -18,7 +20,9 @@ AND return_time < #{endTime} - AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()} + + AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} + AND deleted = 0) diff --git a/yudao-module-erp/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml b/yudao-module-erp/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml index 324cbd432..3e04ee706 100644 --- a/yudao-module-erp/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml +++ b/yudao-module-erp/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml @@ -10,7 +10,9 @@ AND out_time < #{endTime} - AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()} + + AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} + AND deleted = 0) - (SELECT IFNULL(SUM(total_price), 0) FROM erp_sale_return @@ -18,7 +20,9 @@ AND return_time < #{endTime} - AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()} + + AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} + AND deleted = 0) diff --git a/yudao-module-erp/src/test/java/cn/iocoder/yudao/module/erp/service/statistics/ErpStatisticsServiceTest.java b/yudao-module-erp/src/test/java/cn/iocoder/yudao/module/erp/service/statistics/ErpStatisticsServiceTest.java new file mode 100644 index 000000000..c96f2d35f --- /dev/null +++ b/yudao-module-erp/src/test/java/cn/iocoder/yudao/module/erp/service/statistics/ErpStatisticsServiceTest.java @@ -0,0 +1,155 @@ +package cn.iocoder.yudao.module.erp.service.statistics; + +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.erp.dal.mysql.statistics.ErpPurchaseStatisticsMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.statistics.ErpSaleStatisticsMapper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; + +import jakarta.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * ERP 统计服务测试类 + * 主要测试在多租户关闭情况下,统计查询是否能正常工作 + * + * @author 芋道源码 + */ +@SpringBootTest +@ActiveProfiles("unit-test") +public class ErpStatisticsServiceTest { + + @Resource + private ErpSaleStatisticsService saleStatisticsService; + + @Resource + private ErpPurchaseStatisticsService purchaseStatisticsService; + + @MockBean + private ErpSaleStatisticsMapper saleStatisticsMapper; + + @MockBean + private ErpPurchaseStatisticsMapper purchaseStatisticsMapper; + + @BeforeEach + void setUp() { + // 清理租户上下文 + TenantContextHolder.clear(); + } + + @AfterEach + void tearDown() { + // 清理租户上下文 + TenantContextHolder.clear(); + } + + @Test + void testSaleStatisticsWithoutTenant() { + // 准备参数 + LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0); + LocalDateTime endTime = LocalDateTime.of(2024, 1, 31, 23, 59, 59); + BigDecimal expectedPrice = new BigDecimal("1000.00"); + + // Mock 返回值 + when(saleStatisticsMapper.getSalePrice(any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(expectedPrice); + + // 测试:在没有租户ID的情况下调用销售统计 + assertDoesNotThrow(() -> { + BigDecimal result = saleStatisticsService.getSalePrice(beginTime, endTime); + assertEquals(expectedPrice, result); + }, "在多租户关闭时,销售统计查询应该能正常工作"); + } + + @Test + void testPurchaseStatisticsWithoutTenant() { + // 准备参数 + LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0); + LocalDateTime endTime = LocalDateTime.of(2024, 1, 31, 23, 59, 59); + BigDecimal expectedPrice = new BigDecimal("800.00"); + + // Mock 返回值 + when(purchaseStatisticsMapper.getPurchasePrice(any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(expectedPrice); + + // 测试:在没有租户ID的情况下调用采购统计 + assertDoesNotThrow(() -> { + BigDecimal result = purchaseStatisticsService.getPurchasePrice(beginTime, endTime); + assertEquals(expectedPrice, result); + }, "在多租户关闭时,采购统计查询应该能正常工作"); + } + + @Test + void testSaleStatisticsWithTenant() { + // 设置租户ID + Long tenantId = 1L; + TenantContextHolder.setTenantId(tenantId); + + // 准备参数 + LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0); + LocalDateTime endTime = LocalDateTime.of(2024, 1, 31, 23, 59, 59); + BigDecimal expectedPrice = new BigDecimal("1500.00"); + + // Mock 返回值 + when(saleStatisticsMapper.getSalePrice(any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(expectedPrice); + + // 测试:在有租户ID的情况下调用销售统计 + assertDoesNotThrow(() -> { + BigDecimal result = saleStatisticsService.getSalePrice(beginTime, endTime); + assertEquals(expectedPrice, result); + }, "在多租户开启时,销售统计查询应该能正常工作"); + + // 验证租户ID是否正确设置 + assertEquals(tenantId, TenantContextHolder.getTenantId()); + } + + @Test + void testPurchaseStatisticsWithTenant() { + // 设置租户ID + Long tenantId = 2L; + TenantContextHolder.setTenantId(tenantId); + + // 准备参数 + LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0); + LocalDateTime endTime = LocalDateTime.of(2024, 1, 31, 23, 59, 59); + BigDecimal expectedPrice = new BigDecimal("1200.00"); + + // Mock 返回值 + when(purchaseStatisticsMapper.getPurchasePrice(any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(expectedPrice); + + // 测试:在有租户ID的情况下调用采购统计 + assertDoesNotThrow(() -> { + BigDecimal result = purchaseStatisticsService.getPurchasePrice(beginTime, endTime); + assertEquals(expectedPrice, result); + }, "在多租户开启时,采购统计查询应该能正常工作"); + + // 验证租户ID是否正确设置 + assertEquals(tenantId, TenantContextHolder.getTenantId()); + } + + @Test + void testTenantContextHolderMethods() { + // 测试 getTenantId() 在没有设置租户时返回 null + assertNull(TenantContextHolder.getTenantId(), "未设置租户时应该返回 null"); + + // 设置租户ID + Long tenantId = 3L; + TenantContextHolder.setTenantId(tenantId); + assertEquals(tenantId, TenantContextHolder.getTenantId(), "设置租户后应该能正确获取"); + + // 清理租户上下文 + TenantContextHolder.clear(); + assertNull(TenantContextHolder.getTenantId(), "清理后应该返回 null"); + } +}