fix(crm): 修复客户公海数据显示问题
- 修改客户公海查询逻辑,允许显示已有负责人的客户
- 移除公海查询中对 owner_user_id 为空的限制条件
- 更新 CrmCustomerPageReqVO 中 pool 字段的描述说明
- 在权限切面中增加公海可见性检查逻辑
- 添加单元测试验证公海客户数据的正确显示
- 修复测试断言逻辑确保查询条件正确性
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | from read_log_times import connect |
| | | from pathlib import Path |
| | | import json |
| | | import re |
| | | |
| | | HERE = Path(__file__).resolve().parent |
| | | ROOT = HERE.parents[1] |
| | | ADDITIONS = { |
| | | 'type': "int DEFAULT 1 COMMENT 'æé¡¹ç±»åï¼mes_wm_misc_receipt_type' AFTER `name`", |
| | | 'source_doc_type': "varchar(64) DEFAULT NULL COMMENT 'æ¥æºåæ®ç±»å' AFTER `type`", |
| | | 'source_doc_id': "bigint DEFAULT NULL COMMENT 'æ¥æºåæ®ID' AFTER `source_doc_type`", |
| | | 'source_doc_code': "varchar(100) DEFAULT NULL COMMENT 'æ¥æºåæ®ç¼ç ' AFTER `source_doc_id`", |
| | | } |
| | | |
| | | with connect() as c: |
| | | with c.cursor() as q: |
| | | q.execute('SHOW CREATE TABLE mes_wm_misc_receipt') |
| | | old_schema = q.fetchall() |
| | | q.execute('SHOW COLUMNS FROM mes_wm_misc_receipt') |
| | | columns = {r['Field'] for r in q.fetchall()} |
| | | q.execute('SELECT * FROM mes_wm_misc_receipt ORDER BY id') |
| | | before = q.fetchall() |
| | | missing = [name for name in ADDITIONS if name not in columns] |
| | | backup = HERE/'misc-receipt-schema-before.json' |
| | | if missing: |
| | | with backup.open('x', encoding='utf-8') as f: |
| | | json.dump(dict(target='36.213.150.237:10000/mom-hm', schema=old_schema, |
| | | rows=before, missing=missing), f, ensure_ascii=False, indent=2, default=str) |
| | | sql = 'ALTER TABLE mes_wm_misc_receipt '+', '.join( |
| | | 'ADD COLUMN `'+name+'` '+ADDITIONS[name] for name in missing) |
| | | q.execute(sql) |
| | | # éªè¯ä¸ä¸ªæ°æ®å¯¹è±¡çå
¨é¨æ å°å段é½è½æ¥è¯¢ã |
| | | counts = {} |
| | | for suffix, class_name in [('', 'MesWmMiscReceiptDO'), ('_line', 'MesWmMiscReceiptLineDO'), |
| | | ('_detail', 'MesWmMiscReceiptDetailDO')]: |
| | | source = (ROOT / ('yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscreceipt/'+class_name+'.java')).read_text(encoding='utf-8') |
| | | fields = re.findall(r'private\s+\w+\s+(\w+);', source) |
| | | mapped = [re.sub(r'(?<!^)([A-Z])', r'_\1', name).lower() for name in fields] |
| | | mapped += ['create_time', 'update_time', 'creator', 'updater', 'deleted'] |
| | | table = 'mes_wm_misc_receipt'+suffix |
| | | q.execute('SELECT '+','.join('`'+field+'`' for field in mapped)+ |
| | | ' FROM '+table+' WHERE deleted=0 ORDER BY id DESC LIMIT 10') |
| | | counts[table] = len(q.fetchall()) |
| | | q.execute('SELECT * FROM mes_wm_misc_receipt ORDER BY id') |
| | | after = q.fetchall() |
| | | assert [{k: row[k] for k in columns} for row in after] == before, 'Existing data changed' |
| | | result = dict(target='36.213.150.237:10000/mom-hm', added_columns=missing, |
| | | original_data_unchanged=True, query_verified=counts, backup=str(backup)) |
| | | (HERE/'misc-receipt-schema-result.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8') |
| | | print(json.dumps(result, ensure_ascii=False)) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | from read_log_times import connect |
| | | import json |
| | | |
| | | with connect() as c: |
| | | with c.cursor() as q: |
| | | queries = [ |
| | | "SHOW COLUMNS FROM crm_customer LIKE 'auto_pool_enabled'", |
| | | "SELECT COUNT(*) total, SUM(owner_user_id IS NULL) no_owner, SUM(owner_user_id=0) zero_owner, SUM(auto_pool_enabled=1) enabled, SUM(auto_pool_enabled=0) disabled, SUM(auto_pool_enabled IS NULL) unset FROM crm_customer WHERE deleted=0", |
| | | "SELECT owner_user_id IS NULL no_owner, auto_pool_enabled+0 enabled, COUNT(*) count FROM crm_customer WHERE deleted=0 GROUP BY owner_user_id IS NULL, auto_pool_enabled", |
| | | "SELECT id,code,status FROM system_role WHERE code IN ('super_admin','crm_admin') AND deleted=0", |
| | | ] |
| | | for sql in queries: |
| | | q.execute(sql) |
| | | print(json.dumps({'sql': sql, 'rows': q.fetchall()}, ensure_ascii=False, default=str)) |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | from read_log_times import connect |
| | | import json |
| | | |
| | | with connect() as c: |
| | | with c.cursor() as q: |
| | | for sql in [ |
| | | "SHOW CREATE TABLE mes_wm_misc_receipt_line", |
| | | "SHOW CREATE TABLE mes_wm_misc_receipt_detail", |
| | | "SHOW CREATE TABLE mes_wm_misc_receipt", |
| | | "SHOW TABLES LIKE 'mes_wm_misc_receipt%'", |
| | | "SELECT COUNT(*) count FROM mes_wm_misc_receipt", |
| | | "SELECT id,code,name,status FROM mes_wm_misc_receipt ORDER BY id DESC LIMIT 3", |
| | | "SELECT label,value,status FROM system_dict_data WHERE dict_type='mes_wm_misc_receipt_type' AND deleted=0", |
| | | ]: |
| | | q.execute(sql) |
| | | print(json.dumps({'sql': sql, 'rows': q.fetchall()}, ensure_ascii=False, default=str)) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä¿®å¤æé¡¹å
¥åºä¸»è¡¨ä¸ MesWmMiscReceiptDO çåæ®µå·®å¼ã |
| | | -- åéåºåè¡¥å
å对åºç±»ååå
¸ï¼1 = åºåè°æ´ï¼æ¥æºåæ®åæ®µä¿æå¯ç©ºã |
| | | ALTER TABLE `mes_wm_misc_receipt` |
| | | ADD COLUMN `type` int DEFAULT 1 COMMENT 'æé¡¹ç±»åï¼mes_wm_misc_receipt_type' AFTER `name`, |
| | | ADD COLUMN `source_doc_type` varchar(64) DEFAULT NULL COMMENT 'æ¥æºåæ®ç±»å' AFTER `type`, |
| | | ADD COLUMN `source_doc_id` bigint DEFAULT NULL COMMENT 'æ¥æºåæ®ID' AFTER `source_doc_type`, |
| | | ADD COLUMN `source_doc_code` varchar(100) DEFAULT NULL COMMENT 'æ¥æºåæ®ç¼ç ' AFTER `source_doc_id`; |
| | |
| | | @InEnum(CrmSceneTypeEnum.class) |
| | | private Integer sceneType; // åºæ¯ç±»åï¼ä¸º null æ¶å表示å
¨é¨ |
| | | |
| | | @Schema(description = "æ¯å¦ä¸ºå
¬æµ·æ°æ®", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") |
| | | private Boolean pool; // null åè¡¨ç¤ºä¸ºä¸æ¯å
¬æµ·æ°æ® |
| | | @Schema(description = "æ¯å¦æ¥è¯¢å
¬æµ·å¯è§å®¢æ·ï¼å
å«å·²æè´è´£äººç客æ·ï¼", example = "false") |
| | | private Boolean pool; |
| | | |
| | | @Schema(description = "èç³»ç¶æ", example = "1") |
| | | private Integer contactStatus; // backlog æ¥è¯¢æ¡ä»¶ |
| | |
| | | default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long ownerUserId, |
| | | boolean superAdmin) { |
| | | MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>(); |
| | | // å
¬æµ·æ¥è¯¢å¿
é¡»ä¼å
è¿æ»¤ï¼ä¸è½è¢«âå
¨é¨æ°æ®âåºæ¯ç»è¿ã |
| | | // å
¬æµ·å±ç¤ºå
许å
±äº«ç客æ·ï¼å
å«å·²æè´è´£äººï¼ï¼ä¸å®¢æ·ç®¡ç使ç¨åä¸ä»½æ°æ®ã |
| | | if (Boolean.TRUE.equals(pageReqVO.getPool())) { |
| | | query.isNull(CrmCustomerDO::getOwnerUserId) |
| | | .eq(!superAdmin, CrmCustomerDO::getAutoPoolEnabled, true); |
| | | query.eq(!superAdmin, CrmCustomerDO::getAutoPoolEnabled, true); |
| | | } else if (CrmSceneTypeEnum.isAll(pageReqVO.getSceneType())) { |
| | | // åºæ¯åï¼å
¨é¨æ°æ®ï¼ä¸åºåå
¬æµ·/éå
¬æµ·ï¼ç± appendPermissionCondition ç»ä¸å¤ç |
| | | CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), |
| | |
| | | |
| | | default Long selectPoolCount() { |
| | | return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>() |
| | | .isNull(CrmCustomerDO::getOwnerUserId) |
| | | .eq(!CrmPermissionUtils.isSuperAdmin(), CrmCustomerDO::getAutoPoolEnabled, true)); |
| | | } |
| | | |
| | |
| | | if (CrmPermissionUtils.isCrmAdmin()) { |
| | | return; |
| | | } |
| | | // å
¬æµ·å
许æ¥çæè´è´£äººç客æ·ï¼åªæäºè¯»åæéï¼ä¸å½±ååå
¥ã转移æé¢åæéã |
| | | if (CrmBizTypeEnum.CRM_CUSTOMER.getType().equals(bizType) |
| | | && CrmPermissionLevelEnum.isRead(permissionLevel) && isPublicPoolVisible(bizType, bizId)) { |
| | | return; |
| | | } |
| | | // ç¹æ®ï¼æ²¡ææ°æ®æéçæ
åµï¼é对 READ çç¹æ®å¤ç |
| | | if (CollUtil.isEmpty(bizPermissions)) { |
| | | // 1.1 å
¬æµ·æ°æ®ï¼å¦ææ²¡æå¢éæåï¼å¤§å®¶ä¹åºè¯¥æ READ æéæå¯¹ |
| | |
| | | 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 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.*; |
| | |
| | | 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(), |
| | |
| | | if (method.getName().equals("selectJoinPage")) { |
| | | var query = (com.github.yulichang.wrapper.MPJLambdaWrapper<?>) args[2]; |
| | | String sql = query.getSqlSegment(); |
| | | assertTrue(sql.contains("owner_user_id IS NULL"), sql); |
| | | 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); |
| | | } |