package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerImportExcelVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerImportReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.framework.excel.core.AutoPoolEnabledConvert; import cn.iocoder.yudao.module.crm.framework.permission.core.aop.CrmPermissionAspect; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import cn.iocoder.yudao.module.crm.controller.admin.customer.CrmCustomerController; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.idev.excel.metadata.data.ReadCellData; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; class CrmCustomerAutoPoolTest { @Test void excelReadsYesNoAndRejectsInvalidValues() { var converter = new AutoPoolEnabledConvert(); assertTrue(converter.convertToJavaData(new ReadCellData<>("是"), null, null)); assertFalse(converter.convertToJavaData(new ReadCellData<>("否"), null, null)); assertFalse(converter.convertToJavaData(new ReadCellData<>(BigDecimal.ZERO), null, null)); assertNull(converter.convertToJavaData(new ReadCellData<>(" "), null, null)); assertThrows(IllegalArgumentException.class, () -> converter.convertToJavaData(new ReadCellData<>("不确定"), null, null)); assertEquals("否", converter.convertToExcelData(false, null, null).getStringValue()); } @Test void oldTemplateCreatesCustomerWithAutoPoolEnabled() { assertTrue(importCustomer(null, false).getAutoPoolEnabled()); } @Test void importingDisabledCustomerKeepsItDisabled() { assertFalse(importCustomer(false, false).getAutoPoolEnabled()); } @Test void blankImportUpdateDoesNotOverwriteExistingSetting() { // null 由 MyBatis 的非空更新策略忽略,不覆盖已有的 false。 assertNull(importCustomer(null, true).getAutoPoolEnabled()); } @Test void explicitImportUpdateCanDisableAutoPool() { assertFalse(importCustomer(false, true).getAutoPoolEnabled()); } @Test void publicPoolListStillFiltersDisabledCustomersInAllScene() { assertPoolListFilter(false); } @Test void superAdminPoolListIncludesDisabledCustomers() { assertPoolListFilter(true); } @Test void poolCustomersUseSameOwnerAndDepartmentAsCustomerManagement() { var controller = new CrmCustomerController(); var users = AdminUserApi.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{AdminUserApi.class}, (proxy, method, args) -> { assertEquals("getUserMap", method.getName()); return Map.of(10L, new AdminUserRespDTO().setId(10L).setNickname("销售负责人").setDeptId(20L)); })); var departments = DeptApi.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{DeptApi.class}, (proxy, method, args) -> { assertEquals("getDeptMap", method.getName()); return Map.of(20L, new DeptRespDTO().setId(20L).setName("销售部")); })); var config = CrmCustomerPoolConfigService.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CrmCustomerPoolConfigService.class}, (proxy, method, args) -> null)); ReflectionTestUtils.setField(controller, "adminUserApi", users); ReflectionTestUtils.setField(controller, "deptApi", departments); ReflectionTestUtils.setField(controller, "customerPoolConfigService", config); var customer = controller.buildCustomerDetailList(List.of(new CrmCustomerDO().setId(1L) .setOwnerUserId(10L).setAutoPoolEnabled(true))).getFirst(); assertEquals(10L, customer.getOwnerUserId()); assertEquals("销售负责人", customer.getOwnerUserName()); assertEquals("销售部", customer.getOwnerUserDeptName()); } private void assertPoolListFilter(boolean superAdmin) { TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"), CrmCustomerDO.class); var mapper = CrmCustomerMapper.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CrmCustomerMapper.class}, (proxy, method, args) -> { if (method.getName().equals("selectJoinPage")) { var query = (com.github.yulichang.wrapper.MPJLambdaWrapper) args[2]; String sql = query.getSqlSegment(); assertFalse(sql.contains("owner_user_id IS NULL"), sql); assertEquals(!superAdmin, sql.contains("auto_pool_enabled ="), sql); return new cn.iocoder.yudao.framework.common.pojo.PageResult<>(List.of(), 0L); } return java.lang.reflect.InvocationHandler.invokeDefault(proxy, method, args); })); mapper.selectPage(new CrmCustomerPageReqVO().setPool(true) .setSceneType(CrmSceneTypeEnum.ALL.getType()), 1L, superAdmin); } @Test void hiddenCustomerDoesNotGetPublicReadFallback() { var aspect = new CrmPermissionAspect(); var enabled = new AtomicReference<>(false); var mapper = CrmCustomerMapper.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CrmCustomerMapper.class}, (proxy, method, args) -> new CrmCustomerDO().setId(1L).setAutoPoolEnabled(enabled.get()))); ReflectionTestUtils.setField(aspect, "customerMapper", mapper); assertEquals(false, ReflectionTestUtils.invokeMethod(aspect, "isPublicPoolVisible", CrmBizTypeEnum.CRM_CUSTOMER.getType(), 1L)); enabled.set(true); assertEquals(true, ReflectionTestUtils.invokeMethod(aspect, "isPublicPoolVisible", CrmBizTypeEnum.CRM_CUSTOMER.getType(), 1L)); } @Test void automaticPoolQueryRequiresEnabledFlagOutsideTimeoutOrConditions() { TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"), CrmCustomerDO.class); var captured = new AtomicReference>(); var mapper = CrmCustomerMapper.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CrmCustomerMapper.class}, (proxy, method, args) -> { if (method.isDefault()) { return java.lang.reflect.InvocationHandler.invokeDefault(proxy, method, args); } assertEquals("selectList", method.getName()); captured.set((LambdaQueryWrapper) args[0]); return List.of(); })); mapper.selectListByAutoPool(new CrmCustomerPoolConfigDO().setDealExpireDays(30).setContactExpireDays(7)); String sql = captured.get().getSqlSegment(); assertTrue(sql.contains("auto_pool_enabled ="), sql); assertTrue(sql.indexOf("auto_pool_enabled") < sql.indexOf(" OR "), sql); assertTrue(captured.get().getParamNameValuePairs().containsValue(true)); } private CrmCustomerDO importCustomer(Boolean enabled, boolean existing) { var service = new CrmCustomerServiceImpl(); var saved = new AtomicReference(); var mapper = CrmCustomerMapper.class.cast(Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CrmCustomerMapper.class}, (proxy, method, args) -> switch (method.getName()) { case "selectByCustomerName" -> existing ? new CrmCustomerDO().setId(1L).setAutoPoolEnabled(false) : null; case "insert", "updateById" -> { saved.set((CrmCustomerDO) args[0]); yield 1; } default -> throw new AssertionError(method.getName()); })); ReflectionTestUtils.setField(service, "customerMapper", mapper); var result = service.importCustomerList(List.of(new CrmCustomerImportExcelVO() .setName("测试客户").setAutoPoolEnabled(enabled)), CrmCustomerImportReqVO.builder().updateSupport(true).build()); assertTrue(result.getFailureCustomerNames().isEmpty()); return saved.get(); } }