Commit bada32750c67282ff7f8c8b1acbad736c3448c28

Authored by 芋道源码
Committed by Gitee
2 parents b0b65448 daf4651a

!795 CRM: 完善线索、商机、联系人、客户、合同、回款、回款计划数据权限

Merge pull request !795 from puhui999/develop
Showing 76 changed files with 1455 additions and 310 deletions
yudao-dependencies/pom.xml
... ... @@ -62,6 +62,7 @@
62 62 <jsch.version>0.1.55</jsch.version>
63 63 <tika-core.version>2.9.1</tika-core.version>
64 64 <ip2region.version>2.7.0</ip2region.version>
  65 + <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
65 66 <!-- 三方云服务相关 -->
66 67 <okio.version>3.5.0</okio.version>
67 68 <okhttp3.version>4.11.0</okhttp3.version>
... ... @@ -100,6 +101,17 @@
100 101 <version>${revision}</version>
101 102 </dependency>
102 103 <dependency>
  104 + <groupId>io.github.mouzt</groupId>
  105 + <artifactId>bizlog-sdk</artifactId>
  106 + <version>${bizlog-sdk.version}</version>
  107 + <exclusions>
  108 + <exclusion> <!-- 排除掉springboot依赖使用项目的 -->
  109 + <groupId>org.springframework.boot</groupId>
  110 + <artifactId>spring-boot-starter</artifactId>
  111 + </exclusion>
  112 + </exclusions>
  113 + </dependency>
  114 + <dependency>
103 115 <groupId>cn.iocoder.boot</groupId>
104 116 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
105 117 <version>${revision}</version>
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
... ... @@ -177,4 +177,14 @@ public class DateUtils {
177 177 return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
178 178 }
179 179  
  180 + /**
  181 + * 是否昨天
  182 + *
  183 + * @param date 日期
  184 + * @return 是否
  185 + */
  186 + public static boolean isYesterday(LocalDateTime date) {
  187 + return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
  188 + }
  189 +
180 190 }
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums;
  2 +
  3 +/**
  4 + * CRM 操作日志枚举
  5 + *
  6 + * @author HUIHUI
  7 + */
  8 +public interface LogRecordConstants {
  9 +
  10 + //======================= 客户模块类型 =======================
  11 + // TODO puhui999: 确保模块命名方式为 module + 子模块名称的方式。统一定义模块名称是为了方便查询各自记录的操作日志,列如说:查询客户【张三的操作日志】就可以 module + bizId
  12 + String CRM_LEADS = "CRM-线索";
  13 + String CRM_CUSTOMER = "CRM-客户";
  14 + String CRM_CONTACT = "CRM-联系人";
  15 + String CRM_BUSINESS = "CRM-商机";
  16 + String CRM_CONTRACT = "CRM-合同";
  17 + String CRM_PRODUCT = "CRM-产品";
  18 + String CRM_RECEIVABLE = "CRM-回款";
  19 + String CRM_RECEIVABLE_PLAN = "CRM-回款计划";
  20 +
  21 + //======================= 客户转移操作日志 =======================
  22 +
  23 + String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  24 + String TRANSFER_CUSTOMER_LOG_FAIL = "";
  25 +
  26 +}
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
... ... @@ -22,7 +22,9 @@ public enum CrmBizTypeEnum implements IntArrayValuable {
22 22 CRM_CONTACT(3, "联系人"),
23 23 CRM_BUSINESS(4, "商机"),
24 24 CRM_CONTRACT(5, "合同"),
25   - CRM_PRODUCT(6, "产品")
  25 + CRM_PRODUCT(6, "产品"),
  26 + CRM_RECEIVABLE(7, "回款"),
  27 + CRM_RECEIVABLE_PLAN(8, "回款计划")
26 28 ;
27 29  
28 30 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizTypeEnum::getType).toArray();
... ...
yudao-module-crm/yudao-module-crm-biz/pom.xml
... ... @@ -60,6 +60,10 @@
60 60 <groupId>cn.iocoder.boot</groupId>
61 61 <artifactId>yudao-spring-boot-starter-excel</artifactId>
62 62 </dependency>
  63 + <dependency>
  64 + <groupId>cn.iocoder.boot</groupId>
  65 + <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
  66 + </dependency>
63 67  
64 68 <!-- Test 测试相关 -->
65 69 <dependency>
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
... ... @@ -122,7 +122,7 @@ public class CrmBusinessController {
122 122 @Operation(summary = "获得商机分页,基于指定客户")
123 123 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
124 124 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
125   - PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomer(pageReqVO, getLoginUserId());
  125 + PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
126 126 // 处理客户名称回显
127 127 // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
128 128 Set<Long> customerIds = pageResult.getList().stream()
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java
... ... @@ -24,7 +24,4 @@ public class CrmBusinessPageReqVO extends PageParam {
24 24 @InEnum(CrmSceneTypeEnum.class)
25 25 private Integer sceneType; // 场景类型,为 null 时则表示全部
26 26  
27   - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
28   - private Boolean pool; // null 则表示为不是公海数据
29   -
30 27 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java
... ... @@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2  
3 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.Data;
6   -
7 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
8 7  
9 8 @Schema(description = "管理后台 - 商机转移 Request VO")
10 9 @Data
11 10 public class CrmBusinessTransferReqVO {
12 11  
13 12 @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
14   - @NotNull(message = "联系人编号不能为空")
  13 + @NotNull(message = "商机编号不能为空")
15 14 private Long id;
16 15  
17 16 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
... ... @@ -88,4 +88,12 @@ public class CrmClueController {
88 88 ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas);
89 89 }
90 90  
  91 + @PutMapping("/transfer")
  92 + @Operation(summary = "线索转移")
  93 + @PreAuthorize("@ss.hasPermission('crm:clue:update')")
  94 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmClueTransferReqVO reqVO) {
  95 + clueService.transferClue(reqVO, getLoginUserId());
  96 + return success(true);
  97 + }
  98 +
91 99 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - 线索转移 Request VO")
  9 +@Data
  10 +public class CrmClueTransferReqVO {
  11 +
  12 + @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  13 + @NotNull(message = "线索编号不能为空")
  14 + private Long id;
  15 +
  16 + /**
  17 + * 新负责人的用户编号
  18 + */
  19 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  20 + @NotNull(message = "新负责人的用户编号不能为空")
  21 + private Long newOwnerUserId;
  22 +
  23 + /**
  24 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  25 + *
  26 + * 关联 {@link CrmPermissionLevelEnum}
  27 + */
  28 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  29 + private Integer oldOwnerPermissionLevel;
  30 +
  31 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
... ... @@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
12   -import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
  12 +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
13 13 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
14 14 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 15 import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
... ... @@ -100,7 +100,7 @@ public class CrmContactController {
100 100 // 3. 直属上级
101 101 List<CrmContactDO> parentContactList = contactService.getContactList(
102 102 Collections.singletonList(contact.getParentId()), getLoginUserId());
103   - return success(ContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList));
  103 + return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList));
104 104 }
105 105  
106 106 @GetMapping("/simple-all-list")
... ... @@ -110,7 +110,7 @@ public class CrmContactController {
110 110 CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
111 111 pageReqVO.setPageSize(PAGE_SIZE_NONE);
112 112 List<CrmContactDO> list = contactService.getContactPage(pageReqVO, getLoginUserId()).getList();
113   - return success(ContactConvert.INSTANCE.convertAllList(list));
  113 + return success(CrmContactConvert.INSTANCE.convertAllList(list));
114 114 }
115 115  
116 116 @GetMapping("/page")
... ... @@ -125,7 +125,7 @@ public class CrmContactController {
125 125 @Operation(summary = "获得联系人分页,基于指定客户")
126 126 public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
127 127 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
128   - PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO, getLoginUserId());
  128 + PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
129 129 return success(convertDetailContactPage(pageResult));
130 130 }
131 131  
... ... @@ -161,7 +161,15 @@ public class CrmContactController {
161 161 // 3. 直属上级
162 162 List<CrmContactDO> parentContactList = contactService.getContactList(
163 163 convertSet(contactList, CrmContactDO::getParentId), getLoginUserId());
164   - return ContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList);
  164 + return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList);
  165 + }
  166 +
  167 + @PutMapping("/transfer")
  168 + @Operation(summary = "联系人转移")
  169 + @PreAuthorize("@ss.hasPermission('crm:contact:update')")
  170 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmContactTransferReqVO reqVO) {
  171 + contactService.transferContact(reqVO, getLoginUserId());
  172 + return success(true);
165 173 }
166 174  
167 175 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java
... ... @@ -39,7 +39,4 @@ public class CrmContactPageReqVO extends PageParam {
39 39 @InEnum(CrmSceneTypeEnum.class)
40 40 private Integer sceneType; // 场景类型,为 null 时则表示全部
41 41  
42   - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
43   - private Boolean pool; // null 则表示为不是公海数据
44   -
45 42 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
... ... @@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 10 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
11   -import cn.iocoder.yudao.module.crm.convert.contract.ContractConvert;
  11 +import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
12 12 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
13 13 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
14 14 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
... ... @@ -80,7 +80,7 @@ public class CrmContractController {
80 80 @PreAuthorize("@ss.hasPermission('crm:contract:query')")
81 81 public CommonResult<ContractRespVO> getContract(@RequestParam("id") Long id) {
82 82 CrmContractDO contract = contractService.getContract(id);
83   - return success(ContractConvert.INSTANCE.convert(contract));
  83 + return success(CrmContractConvert.INSTANCE.convert(contract));
84 84 }
85 85  
86 86 @GetMapping("/page")
... ... @@ -95,7 +95,7 @@ public class CrmContractController {
95 95 @Operation(summary = "获得联系人分页,基于指定客户")
96 96 public CommonResult<PageResult<ContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
97 97 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
98   - PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomer(pageVO, getLoginUserId());
  98 + PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
99 99 return success(convertDetailContractPage(pageResult));
100 100 }
101 101  
... ... @@ -108,7 +108,7 @@ public class CrmContractController {
108 108 PageResult<CrmContractDO> pageResult = contractService.getContractPage(exportReqVO, getLoginUserId());
109 109 // 导出 Excel
110 110 ExcelUtils.write(response, "合同.xls", "数据", CrmContractExcelVO.class,
111   - ContractConvert.INSTANCE.convertList02(pageResult.getList()));
  111 + CrmContractConvert.INSTANCE.convertList02(pageResult.getList()));
112 112 }
113 113  
114 114 /**
... ... @@ -128,7 +128,7 @@ public class CrmContractController {
128 128 // 2. 获取创建人、负责人列表
129 129 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
130 130 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
131   - return ContractConvert.INSTANCE.convertPage(pageResult, userMap, customerList);
  131 + return CrmContractConvert.INSTANCE.convertPage(pageResult, userMap, customerList);
132 132 }
133 133  
134 134 @PutMapping("/transfer")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java
... ... @@ -30,7 +30,4 @@ public class CrmContractPageReqVO extends PageParam {
30 30 @InEnum(CrmSceneTypeEnum.class)
31 31 private Integer sceneType; // 场景类型,为 null 时则表示全部
32 32  
33   - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
34   - private Boolean pool; // null 则表示为不是公海数据 TODO @puhui999:合同没有公海。目前只有【客户】【线索】有公海,其它都没
35   -
36 33 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http
1   -### 请求 /update
2   -GET {{baseUrl}}/crm/customer/page?pageNo=1&pageSize=10&name="张三"
  1 +### 请求 /transfer
  2 +PUT {{baseUrl}}/crm/customer/transfer
  3 +Content-Type: application/json
3 4 Authorization: Bearer {{token}}
4 5 tenant-id: {{adminTenentId}}
5 6  
  7 +{
  8 + "id": 10,
  9 + "newOwnerUserId": 127
  10 +}
6 11  
  12 +### 自定义日志记录结果
  13 +### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9))
  14 +
  15 +### diff 日志
  16 +### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
... ... @@ -11,20 +11,22 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
11 11 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
12 12 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
13 13 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  14 +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  15 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
14 16 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
15 17 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
16 18 import io.swagger.v3.oas.annotations.Operation;
17 19 import io.swagger.v3.oas.annotations.Parameter;
18 20 import io.swagger.v3.oas.annotations.Parameters;
19 21 import io.swagger.v3.oas.annotations.tags.Tag;
  22 +import jakarta.annotation.Resource;
  23 +import jakarta.servlet.http.HttpServletResponse;
  24 +import jakarta.validation.Valid;
20 25 import org.mapstruct.ap.internal.util.Collections;
21 26 import org.springframework.security.access.prepost.PreAuthorize;
22 27 import org.springframework.validation.annotation.Validated;
23 28 import org.springframework.web.bind.annotation.*;
24 29  
25   -import jakarta.annotation.Resource;
26   -import jakarta.servlet.http.HttpServletResponse;
27   -import jakarta.validation.Valid;
28 30 import java.io.IOException;
29 31 import java.util.List;
30 32 import java.util.Map;
... ... @@ -36,6 +38,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
36 38 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
37 39 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
38 40 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  41 +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER;
39 42  
40 43 @Tag(name = "管理后台 - CRM 客户")
41 44 @RestController
... ... @@ -50,6 +53,8 @@ public class CrmCustomerController {
50 53 private DeptApi deptApi;
51 54 @Resource
52 55 private AdminUserApi adminUserApi;
  56 + @Resource
  57 + private OperateLogApi operateLogApi;
53 58  
54 59 @PostMapping("/create")
55 60 @Operation(summary = "创建客户")
... ... @@ -59,7 +64,7 @@ public class CrmCustomerController {
59 64 }
60 65  
61 66 @PutMapping("/update")
62   - @Operation(summary = "更新客户")
  67 + //@Operation(summary = "更新客户")
63 68 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
64 69 public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
65 70 customerService.updateCustomer(updateReqVO);
... ... @@ -123,13 +128,28 @@ public class CrmCustomerController {
123 128 }
124 129  
125 130 @PutMapping("/transfer")
126   - @Operation(summary = "客户转移")
  131 + //@Operation(summary = "客户转移")
127 132 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
128 133 public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
129 134 customerService.transferCustomer(reqVO, getLoginUserId());
130 135 return success(true);
131 136 }
132 137  
  138 + @GetMapping("/operate-log")
  139 + @Operation(summary = "获得客户操作日志")
  140 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  141 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  142 + public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
  143 + // 1. 获取客户
  144 + CrmCustomerDO customer = customerService.getCustomer(id);
  145 + if (customer == null) {
  146 + return success(null);
  147 + }
  148 +
  149 + // 2. 获取操作日志
  150 + return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
  151 + }
  152 +
133 153 // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;
134 154 @PutMapping("/lock")
135 155 @Operation(summary = "锁定/解锁客户")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerBaseVO.java
... ... @@ -3,17 +3,20 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.framework.common.validation.Mobile;
5 5 import cn.iocoder.yudao.framework.common.validation.Telephone;
  6 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 7 import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
  8 +import com.mzt.logapi.starter.annotation.DiffLogField;
7 9 import io.swagger.v3.oas.annotations.media.Schema;
8   -import lombok.Data;
9   -import org.springframework.format.annotation.DateTimeFormat;
10   -
11 10 import jakarta.validation.constraints.Email;
12 11 import jakarta.validation.constraints.NotEmpty;
13 12 import jakarta.validation.constraints.Size;
  13 +import lombok.Data;
  14 +import org.springframework.format.annotation.DateTimeFormat;
  15 +
14 16 import java.time.LocalDateTime;
15 17  
16 18 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  19 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
17 20  
18 21 /**
19 22 * 客户 Base VO,提供给添加、修改、详细的子 VO 使用
... ... @@ -23,57 +26,73 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
23 26 public class CrmCustomerBaseVO {
24 27  
25 28 @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
  29 + @DiffLogField(name = "客户名称")
26 30 @NotEmpty(message = "客户名称不能为空")
27 31 private String name;
28 32  
29 33 @Schema(description = "所属行业", example = "1")
  34 + @DiffLogField(name = "所属行业", function = "getIndustryById")
  35 + @DictFormat(CRM_CUSTOMER_INDUSTRY)
30 36 private Integer industryId;
31 37  
32 38 @Schema(description = "客户等级", example = "2")
  39 + @DiffLogField(name = "客户等级", function = "getLevel")
33 40 @InEnum(CrmCustomerLevelEnum.class)
34 41 private Integer level;
35 42  
36 43 @Schema(description = "客户来源", example = "3")
  44 + @DiffLogField(name = "客户来源", function = "getSource")
37 45 private Integer source;
38 46  
39 47 @Schema(description = "手机", example = "18000000000")
  48 + @DiffLogField(name = "手机")
40 49 @Mobile
41 50 private String mobile;
42 51  
43 52 @Schema(description = "电话", example = "18000000000")
  53 + @DiffLogField(name = "电话")
44 54 @Telephone
45 55 private String telephone;
46 56  
47 57 @Schema(description = "网址", example = "https://www.baidu.com")
  58 + @DiffLogField(name = "网址")
48 59 private String website;
49 60  
50 61 @Schema(description = "QQ", example = "123456789")
  62 + @DiffLogField(name = "QQ")
51 63 @Size(max = 20, message = "QQ长度不能超过 20 个字符")
52 64 private String qq;
53 65  
54   - @Schema(description = "wechat", example = "123456789")
  66 + @Schema(description = "微信", example = "123456789")
  67 + @DiffLogField(name = "微信")
55 68 @Size(max = 255, message = "微信长度不能超过 255 个字符")
56 69 private String wechat;
57 70  
58   - @Schema(description = "email", example = "123456789@qq.com")
  71 + @Schema(description = "邮箱", example = "123456789@qq.com")
  72 + @DiffLogField(name = "邮箱")
59 73 @Email(message = "邮箱格式不正确")
60 74 @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
61 75 private String email;
62 76  
63 77 @Schema(description = "客户描述", example = "任意文字")
  78 + @DiffLogField(name = "客户描述")
64 79 @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
65 80 private String description;
66 81  
67 82 @Schema(description = "备注", example = "随便")
  83 + @DiffLogField(name = "备注")
68 84 private String remark;
69 85  
70 86 @Schema(description = "地区编号", example = "20158")
  87 + @DiffLogField(name = "地区编号", function = "getAreaById")
71 88 private Integer areaId;
72 89  
73 90 @Schema(description = "详细地址", example = "北京市海淀区")
  91 + @DiffLogField(name = "详细地址")
74 92 private String detailAddress;
75 93  
76 94 @Schema(description = "下次联系时间")
  95 + @DiffLogField(name = "下次联系时间")
77 96 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
78 97 private LocalDateTime contactNextTime;
79 98  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
... ... @@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
2 2  
3 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.Data;
6   -
7 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
8 7  
9 8 @Schema(description = "管理后台 - CRM 客户转移 Request VO")
10 9 @Data
11 10 public class CrmCustomerTransferReqVO {
12 11  
13 12 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
14   - @NotNull(message = "联系人编号不能为空")
  13 + @NotNull(message = "客户编号不能为空")
15 14 private Long id;
16 15  
17 16 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
... ... @@ -7,10 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
11   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
13   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.*;
14 11 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert;
15 12 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 13 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -36,6 +33,7 @@ import java.util.Map;
36 33 import java.util.stream.Stream;
37 34  
38 35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  36 +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
39 37 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
40 38 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
41 39 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
... ... @@ -94,7 +92,7 @@ public class CrmReceivableController {
94 92 @Operation(summary = "获得回款分页")
95 93 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
96 94 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
97   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO);
  95 + PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
98 96 return success(convertDetailReceivablePage(pageResult));
99 97 }
100 98  
... ... @@ -102,7 +100,7 @@ public class CrmReceivableController {
102 100 @Operation(summary = "获得回款分页,基于指定客户")
103 101 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
104 102 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
105   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomer(pageReqVO);
  103 + PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
106 104 return success(convertDetailReceivablePage(pageResult));
107 105 }
108 106  
... ... @@ -113,7 +111,8 @@ public class CrmReceivableController {
113 111 @OperateLog(type = EXPORT)
114 112 public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO,
115 113 HttpServletResponse response) throws IOException {
116   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO);
  114 + exportReqVO.setPageSize(PAGE_SIZE_NONE);
  115 + PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
117 116 // 导出 Excel
118 117 ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
119 118 convertDetailReceivablePage(pageResult).getList());
... ... @@ -142,4 +141,12 @@ public class CrmReceivableController {
142 141 return CrmReceivableConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList);
143 142 }
144 143  
  144 + @PutMapping("/transfer")
  145 + @Operation(summary = "回款转移")
  146 + @PreAuthorize("@ss.hasPermission('crm:receivable:update')")
  147 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmReceivableTransferReqVO reqVO) {
  148 + receivableService.transferReceivable(reqVO, getLoginUserId());
  149 + return success(true);
  150 + }
  151 +
145 152 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
... ... @@ -7,10 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
11   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
13   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.*;
14 11 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert;
15 12 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 13 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -38,6 +35,7 @@ import java.util.Map;
38 35 import java.util.stream.Stream;
39 36  
40 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  38 +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
41 39 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
42 40 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
43 41 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
... ... @@ -65,7 +63,7 @@ public class CrmReceivablePlanController {
65 63 @Operation(summary = "创建回款计划")
66 64 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')")
67 65 public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) {
68   - return success(receivablePlanService.createReceivablePlan(createReqVO));
  66 + return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId()));
69 67 }
70 68  
71 69 @PutMapping("/update")
... ... @@ -98,7 +96,7 @@ public class CrmReceivablePlanController {
98 96 @Operation(summary = "获得回款计划分页")
99 97 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
100 98 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
101   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO);
  99 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId());
102 100 return success(convertDetailReceivablePlanPage(pageResult));
103 101 }
104 102  
... ... @@ -106,7 +104,7 @@ public class CrmReceivablePlanController {
106 104 @Operation(summary = "获得回款计划分页,基于指定客户")
107 105 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
108 106 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
109   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomer(pageReqVO);
  107 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
110 108 return success(convertDetailReceivablePlanPage(pageResult));
111 109 }
112 110  
... ... @@ -117,7 +115,8 @@ public class CrmReceivablePlanController {
117 115 @OperateLog(type = EXPORT)
118 116 public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO,
119 117 HttpServletResponse response) throws IOException {
120   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO);
  118 + exportReqVO.setPageSize(PAGE_SIZE_NONE);
  119 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId());
121 120 // 导出 Excel
122 121 ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class,
123 122 convertDetailReceivablePlanPage(pageResult).getList());
... ... @@ -149,4 +148,12 @@ public class CrmReceivablePlanController {
149 148 return CrmReceivablePlanConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList, receivableList);
150 149 }
151 150  
  151 + @PutMapping("/transfer")
  152 + @Operation(summary = "回款计划转移")
  153 + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')")
  154 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmReceivablePlanTransferReqVO reqVO) {
  155 + receivablePlanService.transferReceivablePlan(reqVO, getLoginUserId());
  156 + return success(true);
  157 + }
  158 +
152 159 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
... ... @@ -25,7 +25,4 @@ public class CrmReceivablePlanPageReqVO extends PageParam {
25 25 @InEnum(CrmSceneTypeEnum.class)
26 26 private Integer sceneType; // 场景类型,为 null 时则表示全部
27 27  
28   - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
29   - private Boolean pool; // null 则表示为不是公海数据
30   -
31 28 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
  2 +
  3 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - CRM 回款计划转移 Request VO")
  9 +@Data
  10 +public class CrmReceivablePlanTransferReqVO {
  11 +
  12 + @Schema(description = "回款计划编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  13 + @NotNull(message = "回款计划编号不能为空")
  14 + private Long id;
  15 +
  16 + /**
  17 + * 新负责人的用户编号
  18 + */
  19 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  20 + @NotNull(message = "新负责人的用户编号不能为空")
  21 + private Long newOwnerUserId;
  22 +
  23 + /**
  24 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  25 + *
  26 + * 关联 {@link CrmPermissionLevelEnum}
  27 + */
  28 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  29 + private Integer oldOwnerPermissionLevel;
  30 +
  31 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
... ... @@ -27,7 +27,4 @@ public class CrmReceivablePageReqVO extends PageParam {
27 27 @InEnum(CrmSceneTypeEnum.class)
28 28 private Integer sceneType; // 场景类型,为 null 时则表示全部
29 29  
30   - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
31   - private Boolean pool; // null 则表示为不是公海数据
32   -
33 30 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
  2 +
  3 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - CRM 回款转移 Request VO")
  9 +@Data
  10 +public class CrmReceivableTransferReqVO {
  11 +
  12 + @Schema(description = "回款编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  13 + @NotNull(message = "回款编号不能为空")
  14 + private Long id;
  15 +
  16 + /**
  17 + * 新负责人的用户编号
  18 + */
  19 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  20 + @NotNull(message = "新负责人的用户编号不能为空")
  21 + private Long newOwnerUserId;
  22 +
  23 + /**
  24 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  25 + *
  26 + * 关联 {@link CrmPermissionLevelEnum}
  27 + */
  28 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  29 + private Integer oldOwnerPermissionLevel;
  30 +
  31 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.clue;
2 2  
3   -import java.util.*;
4   -
5 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -
7   -import org.mapstruct.Mapper;
8   -import org.mapstruct.factory.Mappers;
9 4 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
10 5 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
  6 +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.Mapping;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +import java.util.List;
11 12  
12 13 /**
13 14 * 线索 Convert
... ... @@ -29,4 +30,7 @@ public interface CrmClueConvert {
29 30  
30 31 List<CrmClueExcelVO> convertList02(List<CrmClueDO> list);
31 32  
  33 + @Mapping(target = "bizId", source = "reqVO.id")
  34 + CrmPermissionTransferReqBO convert(CrmClueTransferReqVO reqVO, Long userId);
  35 +
32 36 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferRe
9 9 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
10 10 import org.mapstruct.Mapper;
11 11 import org.mapstruct.Mapping;
12   -import org.mapstruct.Mappings;
13 12 import org.mapstruct.factory.Mappers;
14 13  
15 14 import java.util.List;
... ... @@ -26,9 +25,9 @@ import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAnd
26 25 * @author 芋道源码
27 26 */
28 27 @Mapper
29   -public interface ContactConvert {
  28 +public interface CrmContactConvert {
30 29  
31   - ContactConvert INSTANCE = Mappers.getMapper(ContactConvert.class);
  30 + CrmContactConvert INSTANCE = Mappers.getMapper(CrmContactConvert.class);
32 31  
33 32 CrmContactDO convert(CrmContactCreateReqVO bean);
34 33  
... ... @@ -48,10 +47,7 @@ public interface ContactConvert {
48 47  
49 48 List<CrmContactSimpleRespVO> convertAllList(List<CrmContactDO> list);
50 49  
51   - @Mappings({
52   - @Mapping(target = "bizId", source = "reqVO.id"),
53   - @Mapping(target = "newOwnerUserId", source = "reqVO.id")
54   - })
  50 + @Mapping(target = "bizId", source = "reqVO.id")
55 51 CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
56 52  
57 53 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/ContractConvert.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferRe
8 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
9 9 import org.mapstruct.Mapper;
10 10 import org.mapstruct.Mapping;
11   -import org.mapstruct.Mappings;
12 11 import org.mapstruct.factory.Mappers;
13 12  
14 13 import java.util.List;
... ... @@ -23,9 +22,9 @@ import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAnd
23 22 * @author dhb52
24 23 */
25 24 @Mapper
26   -public interface ContractConvert {
  25 +public interface CrmContractConvert {
27 26  
28   - ContractConvert INSTANCE = Mappers.getMapper(ContractConvert.class);
  27 + CrmContractConvert INSTANCE = Mappers.getMapper(CrmContractConvert.class);
29 28  
30 29 CrmContractDO convert(CrmContractCreateReqVO bean);
31 30  
... ... @@ -39,10 +38,7 @@ public interface ContractConvert {
39 38  
40 39 List<CrmContractExcelVO> convertList02(List<CrmContractDO> list);
41 40  
42   - @Mappings({
43   - @Mapping(target = "bizId", source = "reqVO.id"),
44   - @Mapping(target = "newOwnerUserId", source = "reqVO.id")
45   - })
  41 + @Mapping(target = "bizId", source = "reqVO.id")
46 42 CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId);
47 43  
48 44 default PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java
... ... @@ -3,12 +3,15 @@ package cn.iocoder.yudao.module.crm.convert.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
8 9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
9 10 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
  11 +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
10 12 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11 13 import org.mapstruct.Mapper;
  14 +import org.mapstruct.Mapping;
12 15 import org.mapstruct.factory.Mappers;
13 16  
14 17 import java.util.List;
... ... @@ -58,4 +61,7 @@ public interface CrmReceivableConvert {
58 61 findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
59 62 }
60 63  
  64 + @Mapping(target = "bizId", source = "reqVO.id")
  65 + CrmPermissionTransferReqBO convert(CrmReceivableTransferReqVO reqVO, Long userId);
  66 +
61 67 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
... ... @@ -3,13 +3,16 @@ package cn.iocoder.yudao.module.crm.convert.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
8 9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
9 10 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
10 11 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
  12 +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
11 13 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 14 import org.mapstruct.Mapper;
  15 +import org.mapstruct.Mapping;
13 16 import org.mapstruct.factory.Mappers;
14 17  
15 18 import java.util.List;
... ... @@ -63,4 +66,7 @@ public interface CrmReceivablePlanConvert {
63 66 findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
64 67 }
65 68  
  69 + @Mapping(target = "bizId", source = "reqVO.id")
  70 + CrmPermissionTransferReqBO convert(CrmReceivablePlanTransferReqVO reqVO, Long userId);
  71 +
66 72 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
... ... @@ -27,14 +28,23 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
27 28 .set(CrmBusinessDO::getOwnerUserId, ownerUserId));
28 29 }
29 30  
  31 + default PageResult<CrmBusinessDO> selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
  32 + return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
  33 + .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
  34 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
  35 + .orderByDesc(CrmBusinessDO::getId));
  36 + }
  37 +
30 38 default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
31 39 MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
32 40 // 拼接数据权限的查询条件
33   - CrmQueryWrapperUtils.builderPageQuery(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), CrmBusinessDO::getId,
34   - userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  41 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
  42 + CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
  43 + if (!condition) {
  44 + return PageResult.empty();
  45 + }
35 46 // 拼接自身的查询条件
36 47 query.selectAll(CrmBusinessDO.class)
37   - .eqIfPresent(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
38 48 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
39 49 .orderByDesc(CrmBusinessDO::getId);
40 50 return selectJoinPage(pageReqVO, CrmBusinessDO.class, query);
... ... @@ -43,7 +53,7 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
43 53 default List<CrmBusinessDO> selectBatchIds(Collection<Long> ids, Long userId) {
44 54 MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
45 55 // 拼接数据权限的查询条件
46   - CrmQueryWrapperUtils.builderListQueryBatch(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId);
  56 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId);
47 57 return selectJoinList(CrmBusinessDO.class, query);
48 58 }
49 59  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
... ... @@ -30,8 +30,11 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
30 30 default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
31 31 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
32 32 // 拼接数据权限的查询条件
33   - CrmQueryWrapperUtils.builderPageQuery(query, CrmBizTypeEnum.CRM_LEADS.getType(), CrmClueDO::getId,
34   - userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  33 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
  34 + CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  35 + if (!condition) {
  36 + return PageResult.empty();
  37 + }
35 38 // 拼接自身的查询条件
36 39 query.selectAll(CrmClueDO.class)
37 40 .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
... ... @@ -44,7 +47,7 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
44 47 default List<CrmClueDO> selectBatchIds(Collection<Long> ids, Long userId) {
45 48 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
46 49 // 拼接数据权限的查询条件
47   - CrmQueryWrapperUtils.builderListQueryBatch(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId);
  50 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId);
48 51 return selectJoinList(CrmClueDO.class, query);
49 52 }
50 53  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.contact;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
... ... @@ -27,14 +28,28 @@ public interface CrmContactMapper extends BaseMapperX&lt;CrmContactDO&gt; {
27 28 .set(CrmContactDO::getOwnerUserId, ownerUserId));
28 29 }
29 30  
  31 + default PageResult<CrmContactDO> selectPageByCustomerId(CrmContactPageReqVO pageVO) {
  32 + return selectPage(pageVO, new LambdaQueryWrapperX<CrmContactDO>()
  33 + .eq(CrmContactDO::getCustomerId, pageVO.getCustomerId()) // 指定客户编号
  34 + .likeIfPresent(CrmContactDO::getName, pageVO.getName())
  35 + .eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile())
  36 + .eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone())
  37 + .eqIfPresent(CrmContactDO::getEmail, pageVO.getEmail())
  38 + .eqIfPresent(CrmContactDO::getQq, pageVO.getQq())
  39 + .eqIfPresent(CrmContactDO::getWechat, pageVO.getWechat())
  40 + .orderByDesc(CrmContactDO::getId));
  41 + }
  42 +
30 43 default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
31 44 MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
32 45 // 拼接数据权限的查询条件
33   - CrmQueryWrapperUtils.builderPageQuery(query, CrmBizTypeEnum.CRM_CONTACT.getType(), CrmContactDO::getId,
34   - userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  46 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
  47 + CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
  48 + if (!condition) {
  49 + return PageResult.empty();
  50 + }
35 51 // 拼接自身的查询条件
36 52 query.selectAll(CrmContactDO.class)
37   - .eqIfPresent(CrmContactDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
38 53 .likeIfPresent(CrmContactDO::getName, pageReqVO.getName())
39 54 .eqIfPresent(CrmContactDO::getMobile, pageReqVO.getMobile())
40 55 .eqIfPresent(CrmContactDO::getTelephone, pageReqVO.getTelephone())
... ... @@ -48,7 +63,7 @@ public interface CrmContactMapper extends BaseMapperX&lt;CrmContactDO&gt; {
48 63 default List<CrmContactDO> selectBatchIds(Collection<Long> ids, Long userId) {
49 64 MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
50 65 // 拼接数据权限的查询条件
51   - CrmQueryWrapperUtils.builderListQueryBatch(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId);
  66 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId);
52 67 return selectJoinList(CrmContactDO.class, query);
53 68 }
54 69  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.contract;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
... ... @@ -27,13 +28,26 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
27 28 .set(CrmContractDO::getOwnerUserId, ownerUserId));
28 29 }
29 30  
  31 + default PageResult<CrmContractDO> selectPageByCustomerId(CrmContractPageReqVO pageReqVO) {
  32 + return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmContractDO>()
  33 + .eq(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
  34 + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
  35 + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
  36 + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
  37 + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
  38 + .orderByDesc(CrmContractDO::getId));
  39 + }
  40 +
30 41 default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
31 42 MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
32   - // 构建数据权限连表条件
33   - CrmQueryWrapperUtils.builderPageQuery(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), CrmContractDO::getId,
34   - userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  43 + // 拼接数据权限的查询条件
  44 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
  45 + CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
  46 + if (!condition) {
  47 + return PageResult.empty();
  48 + }
  49 + // 拼接自身的查询条件
35 50 mpjLambdaWrapperX.selectAll(CrmContractDO.class)
36   - .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
37 51 .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
38 52 .likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
39 53 .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
... ... @@ -45,7 +59,7 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
45 59 default List<CrmContractDO> selectBatchIds(Collection<Long> ids, Long userId) {
46 60 MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
47 61 // 构建数据权限连表条件
48   - CrmQueryWrapperUtils.builderListQueryBatch(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId);
  62 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId);
49 63 return selectJoinList(CrmContractDO.class, mpjLambdaWrapperX);
50 64 }
51 65  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
... ... @@ -30,8 +30,11 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
30 30 default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
31 31 MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
32 32 // 拼接数据权限的查询条件
33   - CrmQueryWrapperUtils.builderPageQuery(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), CrmCustomerDO::getId,
34   - userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  33 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
  34 + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
  35 + if (!condition) {
  36 + return PageResult.empty();
  37 + }
35 38 // 拼接自身的查询条件
36 39 query.selectAll(CrmCustomerDO.class)
37 40 .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())
... ... @@ -45,7 +48,7 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
45 48 default List<CrmCustomerDO> selectBatchIds(Collection<Long> ids, Long userId) {
46 49 MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
47 50 // 拼接数据权限的查询条件
48   - CrmQueryWrapperUtils.builderListQueryBatch(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, userId);
  51 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, userId);
49 52 return selectJoinList(CrmCustomerDO.class, query);
50 53 }
51 54  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
... ... @@ -46,4 +46,10 @@ public interface CrmPermissionMapper extends BaseMapperX&lt;CrmPermissionDO&gt; {
46 46 .eq(CrmPermissionDO::getId, id).eq(CrmPermissionDO::getUserId, userId));
47 47 }
48 48  
  49 + default int deletePermission(Integer bizType, Long bizId) {
  50 + return delete(new LambdaQueryWrapperX<CrmPermissionDO>()
  51 + .eq(CrmPermissionDO::getBizType, bizType)
  52 + .eq(CrmPermissionDO::getBizId, bizId));
  53 + }
  54 +
49 55 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
... ... @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
  9 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
  10 +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
  11 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 12 import org.apache.ibatis.annotations.Mapper;
9 13  
  14 +import java.util.Collection;
  15 +import java.util.List;
  16 +
10 17 /**
11 18 * 回款 Mapper
12 19 *
... ... @@ -15,15 +22,13 @@ import org.apache.ibatis.annotations.Mapper;
15 22 @Mapper
16 23 public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
17 24  
18   - default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO reqVO) {
19   - return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivableDO>()
20   - .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo())
21   - .eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId())
22   - .eqIfPresent(CrmReceivableDO::getCustomerId, reqVO.getCustomerId())
23   - .orderByDesc(CrmReceivableDO::getId));
  25 + default int updateOwnerUserIdById(Long id, Long ownerUserId) {
  26 + return update(new LambdaUpdateWrapper<CrmReceivableDO>()
  27 + .eq(CrmReceivableDO::getId, id)
  28 + .set(CrmReceivableDO::getOwnerUserId, ownerUserId));
24 29 }
25 30  
26   - default PageResult<CrmReceivableDO> selectPageByCustomer(CrmReceivablePageReqVO reqVO) {
  31 + default PageResult<CrmReceivableDO> selectPageByCustomerId(CrmReceivablePageReqVO reqVO) {
27 32 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivableDO>()
28 33 .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
29 34 .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo())
... ... @@ -31,4 +36,27 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
31 36 .orderByDesc(CrmReceivableDO::getId));
32 37 }
33 38  
  39 + default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
  40 + MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
  41 + // 拼接数据权限的查询条件
  42 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
  43 + CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
  44 + if (!condition) {
  45 + return PageResult.empty();
  46 + }
  47 + // 拼接自身的查询条件
  48 + query.selectAll(CrmReceivableDO.class)
  49 + .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
  50 + .eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId())
  51 + .orderByDesc(CrmReceivableDO::getId);
  52 + return selectJoinPage(pageReqVO, CrmReceivableDO.class, query);
  53 + }
  54 +
  55 + default List<CrmReceivableDO> selectBatchIds(Collection<Long> ids, Long userId) {
  56 + MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
  57 + // 拼接数据权限的查询条件
  58 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId);
  59 + return selectJoinList(CrmReceivableDO.class, query);
  60 + }
  61 +
34 62 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
... ... @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
  9 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
  10 +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
  11 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 12 import org.apache.ibatis.annotations.Mapper;
9 13  
  14 +import java.util.Collection;
  15 +import java.util.List;
  16 +
10 17 /**
11 18 * 回款计划 Mapper
12 19 *
... ... @@ -15,18 +22,40 @@ import org.apache.ibatis.annotations.Mapper;
15 22 @Mapper
16 23 public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO> {
17 24  
18   - default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO reqVO) {
19   - return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivablePlanDO>()
20   - .eqIfPresent(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId())
21   - .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId())
22   - .orderByDesc(CrmReceivablePlanDO::getId));
  25 + default int updateOwnerUserIdById(Long id, Long ownerUserId) {
  26 + return update(new LambdaUpdateWrapper<CrmReceivablePlanDO>()
  27 + .eq(CrmReceivablePlanDO::getId, id)
  28 + .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId));
23 29 }
24 30  
25   - default PageResult<CrmReceivablePlanDO> selectPageByCustomer(CrmReceivablePlanPageReqVO reqVO) {
  31 + default PageResult<CrmReceivablePlanDO> selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) {
26 32 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivablePlanDO>()
27 33 .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
28 34 .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId())
29 35 .orderByDesc(CrmReceivablePlanDO::getId));
30 36 }
31 37  
  38 + default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
  39 + MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
  40 + // 拼接数据权限的查询条件
  41 + boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
  42 + CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
  43 + if (!condition) {
  44 + return PageResult.empty();
  45 + }
  46 + // 拼接自身的查询条件
  47 + query.selectAll(CrmReceivablePlanDO.class)
  48 + .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())
  49 + .eqIfPresent(CrmReceivablePlanDO::getContractId, pageReqVO.getContractId())
  50 + .orderByDesc(CrmReceivablePlanDO::getId);
  51 + return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query);
  52 + }
  53 +
  54 + default List<CrmReceivablePlanDO> selectBatchIds(Collection<Long> ids, Long userId) {
  55 + MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
  56 + // 拼接数据权限的查询条件
  57 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), ids, userId);
  58 + return selectJoinList(CrmReceivablePlanDO.class, query);
  59 + }
  60 +
32 61 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmIndustryParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
  5 +import com.mzt.logapi.service.IParseFunction;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
  10 +
  11 +/**
  12 + * 自定义函数-通过行业编号获取行业信息
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Slf4j
  17 +@Component
  18 +public class CrmIndustryParseFunction implements IParseFunction {
  19 +
  20 + @Override
  21 + public boolean executeBefore() {
  22 + return true; // 先转换值后对比
  23 + }
  24 +
  25 + @Override
  26 + public String functionName() {
  27 + return "getIndustryById";
  28 + }
  29 +
  30 + @Override
  31 + public String apply(Object value) {
  32 + if (value == null) {
  33 + return "";
  34 + }
  35 + if (StrUtil.isEmpty(value.toString())) {
  36 + return "";
  37 + }
  38 +
  39 + // 获取行业信息
  40 + try {
  41 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
  42 + } catch (Exception ignored) {
  43 + }
  44 + return "";
  45 + }
  46 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmLevelParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
  5 +import com.mzt.logapi.service.IParseFunction;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL;
  10 +
  11 +/**
  12 + * 自定义函数-通过客户等级编号获取客户等级信息
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Slf4j
  17 +@Component
  18 +public class CrmLevelParseFunction implements IParseFunction {
  19 +
  20 + @Override
  21 + public boolean executeBefore() {
  22 + return true; // 先转换值后对比
  23 + }
  24 +
  25 + @Override
  26 + public String functionName() {
  27 + return "getLevel";
  28 + }
  29 +
  30 + @Override
  31 + public String apply(Object value) {
  32 + if (value == null) {
  33 + return "";
  34 + }
  35 + if (StrUtil.isEmpty(value.toString())) {
  36 + return "";
  37 + }
  38 +
  39 + // 获取客户等级信息
  40 + try {
  41 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
  42 + } catch (Exception ignored) {
  43 + }
  44 + return "";
  45 + }
  46 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmSourceParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
  5 +import com.mzt.logapi.service.IParseFunction;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE;
  10 +
  11 +/**
  12 + * 自定义函数-通过客户来源编号获取客户来源信息
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Slf4j
  17 +@Component
  18 +public class CrmSourceParseFunction implements IParseFunction {
  19 +
  20 + @Override
  21 + public boolean executeBefore() {
  22 + return true; // 先转换值后对比
  23 + }
  24 +
  25 + @Override
  26 + public String functionName() {
  27 + return "getSource";
  28 + }
  29 +
  30 + @Override
  31 + public String apply(Object value) {
  32 + if (value == null) {
  33 + return "";
  34 + }
  35 + if (StrUtil.isEmpty(value.toString())) {
  36 + return "";
  37 + }
  38 +
  39 + // 获取客户来源信息
  40 + try {
  41 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
  42 + } catch (Exception ignored) {
  43 + }
  44 + return "";
  45 + }
  46 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.bizlog;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
... ... @@ -75,10 +75,9 @@ public interface CrmBusinessService {
75 75 * 数据权限:基于 {@link CrmCustomerDO} 读取
76 76 *
77 77 * @param pageReqVO 分页查询
78   - * @param userId 用户编号
79 78 * @return 联系人分页
80 79 */
81   - PageResult<CrmBusinessDO> getBusinessPageByCustomer(CrmBusinessPageReqVO pageReqVO, Long userId);
  80 + PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO);
82 81  
83 82 /**
84 83 * 商机转移
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
... ... @@ -111,11 +111,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
111 111 }
112 112  
113 113 @Override
114   - public PageResult<CrmBusinessDO> getBusinessPageByCustomer(CrmBusinessPageReqVO pageReqVO, Long userId) {
115   - // 校验客户存在 TODO @puhui999:这里不校验
116   - customerService.validateCustomer(pageReqVO.getCustomerId());
117   - // TODO @puhui999:感觉这里貌似不太复用用 selectPage,因为他可能没商机权限,只是因为能看 customer,所以可以看到列表
118   - return businessMapper.selectPage(pageReqVO, userId);
  114 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
  115 + public PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
  116 + return businessMapper.selectPageByCustomerId(pageReqVO);
119 117 }
120 118  
121 119 @Override
... ... @@ -127,7 +125,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
127 125 // 2.1 数据权限转移
128 126 crmPermissionService.transferPermission(
129 127 CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()));
130   -
131 128 // 2.2 设置新的负责人
132 129 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
133 130  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.service.clue;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
8 9 import jakarta.validation.Valid;
... ... @@ -64,4 +65,12 @@ public interface CrmClueService {
64 65 */
65 66 PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId);
66 67  
  68 + /**
  69 + * 线索转移
  70 + *
  71 + * @param reqVO 请求
  72 + * @param userId 用户编号
  73 + */
  74 + void transferClue(CrmClueTransferReqVO reqVO, Long userId);
  75 +
67 76 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
8 9 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO;
9 10 import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
10 11 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
... ... @@ -101,4 +102,18 @@ public class CrmClueServiceImpl implements CrmClueService {
101 102 return clueMapper.selectPage(pageReqVO, userId);
102 103 }
103 104  
  105 + @Override
  106 + public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
  107 + // 1 校验线索是否存在
  108 + validateClueExists(reqVO.getId());
  109 +
  110 + // 2.1 数据权限转移
  111 + crmPermissionService.transferPermission(
  112 + CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType()));
  113 + // 2.2 设置新的负责人
  114 + clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
  115 +
  116 + // 3. TODO 记录转移日志
  117 + }
  118 +
104 119 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
... ... @@ -3,8 +3,10 @@ package cn.iocoder.yudao.module.crm.service.contact;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 10 import jakarta.validation.Valid;
9 11  
10 12 import java.util.Collection;
... ... @@ -71,12 +73,19 @@ public interface CrmContactService {
71 73 /**
72 74 * 获得联系人分页
73 75 *
74   - * 数据权限:基于 {@link CrmContactDO}
  76 + * 数据权限:基于 {@link CrmCustomerDO}
75 77 *
76 78 * @param pageVO 分页查询
77   - * @param userId 用户编号
78 79 * @return 联系人分页
79 80 */
80   - PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO, Long userId);
  81 + PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO);
  82 +
  83 + /**
  84 + * 联系人转移
  85 + *
  86 + * @param reqVO 请求
  87 + * @param userId 用户编号
  88 + */
  89 + void transferContact(CrmContactTransferReqVO reqVO, Long userId);
81 90  
82 91 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
... ... @@ -3,11 +3,8 @@ package cn.iocoder.yudao.module.crm.service.contact;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBaseVO;
7   -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
8   -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
9   -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
10   -import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
  7 +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
11 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
12 9 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
13 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
... ... @@ -56,7 +53,7 @@ public class CrmContactServiceImpl implements CrmContactService {
56 53 // 1.1 校验
57 54 validateRelationDataExists(createReqVO);
58 55 // 1.2 插入
59   - CrmContactDO contact = ContactConvert.INSTANCE.convert(createReqVO);
  56 + CrmContactDO contact = CrmContactConvert.INSTANCE.convert(createReqVO);
60 57 contactMapper.insert(contact);
61 58  
62 59 // 2. 创建数据权限
... ... @@ -73,7 +70,7 @@ public class CrmContactServiceImpl implements CrmContactService {
73 70 validateContactExists(updateReqVO.getId());
74 71 validateRelationDataExists(updateReqVO);
75 72 // 2. 更新
76   - CrmContactDO updateObj = ContactConvert.INSTANCE.convert(updateReqVO);
  73 + CrmContactDO updateObj = CrmContactConvert.INSTANCE.convert(updateReqVO);
77 74 contactMapper.updateById(updateObj);
78 75 }
79 76  
... ... @@ -134,11 +131,23 @@ public class CrmContactServiceImpl implements CrmContactService {
134 131 }
135 132  
136 133 @Override
137   - public PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO, Long userId) {
138   - // 校验用户存在
139   - customerService.validateCustomer(pageVO.getCustomerId());
140   - // TODO @puhui999:getBusinessPageByCustomer 同理
141   - return contactMapper.selectPage(pageVO, userId);
  134 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageVO.customerId", level = CrmPermissionLevelEnum.READ)
  135 + public PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO) {
  136 + return contactMapper.selectPageByCustomerId(pageVO);
  137 + }
  138 +
  139 + @Override
  140 + public void transferContact(CrmContactTransferReqVO reqVO, Long userId) {
  141 + // 1 校验联系人是否存在
  142 + validateContactExists(reqVO.getId());
  143 +
  144 + // 2.1 数据权限转移
  145 + crmPermissionService.transferPermission(
  146 + CrmContactConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()));
  147 + // 2.2 设置新的负责人
  148 + contactMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
  149 +
  150 + // 3. TODO 记录转移日志
142 151 }
143 152  
144 153 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
... ... @@ -75,12 +75,9 @@ public interface CrmContractService {
75 75 * 数据权限:基于 {@link CrmCustomerDO} 读取
76 76 *
77 77 * @param pageReqVO 分页查询
78   - * @param userId 用户编号
79 78 * @return 联系人分页
80 79 */
81   - default PageResult<CrmContractDO> getContractPageByCustomer(CrmContractPageReqVO pageReqVO, Long userId) {
82   - return getContractPage(pageReqVO, userId);
83   - }
  80 + PageResult<CrmContractDO> getContractPageByCustomerId(CrmContractPageReqVO pageReqVO);
84 81  
85 82 /**
86 83 * 合同转移
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
... ... @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreat
7 7 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
8 8 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
9 9 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractUpdateReqVO;
10   -import cn.iocoder.yudao.module.crm.convert.contract.ContractConvert;
  10 +import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
11 11 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
... ... @@ -44,7 +44,7 @@ public class CrmContractServiceImpl implements CrmContractService {
44 44 @Override
45 45 public Long createContract(CrmContractCreateReqVO createReqVO, Long userId) {
46 46 // 插入
47   - CrmContractDO contract = ContractConvert.INSTANCE.convert(createReqVO);
  47 + CrmContractDO contract = CrmContractConvert.INSTANCE.convert(createReqVO);
48 48 contractMapper.insert(contract);
49 49  
50 50 // 创建数据权限
... ... @@ -61,7 +61,7 @@ public class CrmContractServiceImpl implements CrmContractService {
61 61 // 校验存在
62 62 validateContractExists(updateReqVO.getId());
63 63 // 更新
64   - CrmContractDO updateObj = ContractConvert.INSTANCE.convert(updateReqVO);
  64 + CrmContractDO updateObj = CrmContractConvert.INSTANCE.convert(updateReqVO);
65 65 contractMapper.updateById(updateObj);
66 66 }
67 67  
... ... @@ -105,6 +105,12 @@ public class CrmContractServiceImpl implements CrmContractService {
105 105 }
106 106  
107 107 @Override
  108 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
  109 + public PageResult<CrmContractDO> getContractPageByCustomerId(CrmContractPageReqVO pageReqVO) {
  110 + return contractMapper.selectPageByCustomerId(pageReqVO);
  111 + }
  112 +
  113 + @Override
108 114 @Transactional(rollbackFor = Exception.class)
109 115 public void transferContract(CrmContractTransferReqVO reqVO, Long userId) {
110 116 // 1. 校验合同是否存在
... ... @@ -112,7 +118,7 @@ public class CrmContractServiceImpl implements CrmContractService {
112 118  
113 119 // 2.1 数据权限转移
114 120 crmPermissionService.transferPermission(
115   - ContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()));
  121 + CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()));
116 122 // 2.2 设置负责人
117 123 contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
118 124  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.customer;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 6 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
... ... @@ -15,6 +16,9 @@ import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
15 16 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
16 17 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
17 18 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  19 +import com.mzt.logapi.context.LogRecordContext;
  20 +import com.mzt.logapi.service.impl.DiffParseFunction;
  21 +import com.mzt.logapi.starter.annotation.LogRecord;
18 22 import jakarta.annotation.Resource;
19 23 import org.springframework.stereotype.Service;
20 24 import org.springframework.transaction.annotation.Transactional;
... ... @@ -24,6 +28,8 @@ import java.util.*;
24 28  
25 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 30 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
  31 +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER;
  32 +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.TRANSFER_CUSTOMER_LOG_SUCCESS;
27 33 import static java.util.Collections.singletonList;
28 34  
29 35 /**
... ... @@ -59,11 +65,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
59 65  
60 66 @Override
61 67 @Transactional(rollbackFor = Exception.class)
  68 + @LogRecord(success = "更新了客户{_DIFF{#updateReqVO}}", type = CRM_CUSTOMER, subType = "更新客户", bizNo = "{{#updateReqVO.id}}")
62 69 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
63 70 public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
64 71 // 校验存在
65   - validateCustomerExists(updateReqVO.getId());
  72 + CrmCustomerDO oldCustomerDO = validateCustomerExists(updateReqVO.getId());
66 73  
  74 + // __DIFF 函数传递了一个参数,传递的参数是修改之后的对象,这种方式需要在方法内部向 LogRecordContext 中 put 一个变量,代表是之前的对象,这个对象可以是null
  75 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
67 76 // 更新
68 77 CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
69 78 customerMapper.updateById(updateObj);
... ... @@ -82,10 +91,12 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
82 91 crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
83 92 }
84 93  
85   - private void validateCustomerExists(Long id) {
86   - if (customerMapper.selectById(id) == null) {
  94 + private CrmCustomerDO validateCustomerExists(Long id) {
  95 + CrmCustomerDO customerDO = customerMapper.selectById(id);
  96 + if (customerDO == null) {
87 97 throw exception(CUSTOMER_NOT_EXISTS);
88 98 }
  99 + return customerDO;
89 100 }
90 101  
91 102 @Override
... ... @@ -114,7 +125,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
114 125 */
115 126 @Override
116 127 public void validateCustomer(Long customerId) {
117   - // TODO puhui999: 不返回客户不走校验应该可行
118 128 // 校验客户是否存在
119 129 if (customerId == null) {
120 130 throw exception(CUSTOMER_NOT_EXISTS);
... ... @@ -127,11 +137,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
127 137  
128 138 @Override
129 139 @Transactional(rollbackFor = Exception.class)
  140 + @LogRecord(success = TRANSFER_CUSTOMER_LOG_SUCCESS, type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}")
130 141 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
131 142 public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
132 143 // 1. 校验客户是否存在
133 144 validateCustomer(reqVO.getId());
134   -
  145 + // 添加 crmCustomer 到日志上下文 TODO 日志记录放在 service 里是因为已经过了权限校验查询时不用走两次校验
  146 + LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
135 147 // 2.1 数据权限转移
136 148 crmPermissionService.transferPermission(
137 149 CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
... ... @@ -138,15 +138,13 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
138 138 }
139 139  
140 140 @Override
  141 + @Transactional(rollbackFor = Exception.class)
141 142 public void deletePermission(Integer bizType, Long bizId) {
142   - // TODO @puhui999:这种直接写条件删除;不需要先查询,再删除
143   - List<CrmPermissionDO> permissionList = crmPermissionMapper.selectByBizTypeAndBizId(bizType, bizId);
144   - if (CollUtil.isEmpty(permissionList)) {
145   - return;
146   - }
147   -
148 143 // 删除数据权限
149   - crmPermissionMapper.deleteBatchIds(convertSet(permissionList, CrmPermissionDO::getId));
  144 + int deletedCol = crmPermissionMapper.deletePermission(bizType, bizId);
  145 + if (deletedCol == 0) {
  146 + throw exception(CRM_PERMISSION_NOT_EXISTS);
  147 + }
150 148 }
151 149  
152 150 @Override
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
... ... @@ -3,11 +3,12 @@ package cn.iocoder.yudao.module.crm.service.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 9 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
9   -
10 10 import jakarta.validation.Valid;
  11 +
11 12 import java.util.Collection;
12 13 import java.util.List;
13 14  
... ... @@ -24,7 +25,7 @@ public interface CrmReceivablePlanService {
24 25 * @param createReqVO 创建信息
25 26 * @return 编号
26 27 */
27   - Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO);
  28 + Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId);
28 29  
29 30 /**
30 31 * 更新回款计划
... ... @@ -62,9 +63,10 @@ public interface CrmReceivablePlanService {
62 63 * 数据权限:基于 {@link CrmReceivablePlanDO} 读取
63 64 *
64 65 * @param pageReqVO 分页查询
  66 + * @param userId 用户编号
65 67 * @return 回款计划分页
66 68 */
67   - PageResult<CrmReceivablePlanDO> getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO);
  69 + PageResult<CrmReceivablePlanDO> getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId);
68 70  
69 71 /**
70 72 * 获得回款计划分页,基于指定客户
... ... @@ -74,6 +76,14 @@ public interface CrmReceivablePlanService {
74 76 * @param pageReqVO 分页查询
75 77 * @return 回款计划分页
76 78 */
77   - PageResult<CrmReceivablePlanDO> getReceivablePlanPageByCustomer(CrmReceivablePlanPageReqVO pageReqVO);
  79 + PageResult<CrmReceivablePlanDO> getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO);
  80 +
  81 + /**
  82 + * 回款计划转移
  83 + *
  84 + * @param reqVO 请求
  85 + * @param userId 用户编号
  86 + */
  87 + void transferReceivablePlan(CrmReceivablePlanTransferReqVO reqVO, Long userId);
78 88  
79 89 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
... ... @@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
8 8 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
9 10 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
10 11 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert;
11 12 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
... ... @@ -17,10 +18,12 @@ import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
17 18 import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
18 19 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
19 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  21 +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  22 +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  23 +import jakarta.annotation.Resource;
20 24 import org.springframework.stereotype.Service;
21 25 import org.springframework.validation.annotation.Validated;
22 26  
23   -import jakarta.annotation.Resource;
24 27 import java.util.Collection;
25 28 import java.util.List;
26 29  
... ... @@ -28,7 +31,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
28 31 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
29 32  
30 33 // TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈;
31   -// TODO @puhui999:数据权限
  34 +
32 35 /**
33 36 * 回款计划 Service 实现类
34 37 *
... ... @@ -45,9 +48,11 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
45 48 private CrmContractService contractService;
46 49 @Resource
47 50 private CrmCustomerService customerService;
  51 + @Resource
  52 + private CrmPermissionService crmPermissionService;
48 53  
49 54 @Override
50   - public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO) {
  55 + public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) {
51 56 // 插入
52 57 CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO);
53 58 receivablePlan.setFinishStatus(false);
... ... @@ -55,29 +60,33 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
55 60 checkReceivablePlan(receivablePlan);
56 61  
57 62 receivablePlanMapper.insert(receivablePlan);
  63 + // 创建数据权限
  64 + crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType())
  65 + .setBizId(receivablePlan.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
58 66 // 返回
59 67 return receivablePlan.getId();
60 68 }
61 69  
62 70 private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) {
63 71  
64   - if(ObjectUtil.isNull(receivablePlan.getContractId())){
  72 + if (ObjectUtil.isNull(receivablePlan.getContractId())) {
65 73 throw exception(CONTRACT_NOT_EXISTS);
66 74 }
67 75  
68 76 CrmContractDO contract = contractService.getContract(receivablePlan.getContractId());
69   - if(ObjectUtil.isNull(contract)){
  77 + if (ObjectUtil.isNull(contract)) {
70 78 throw exception(CONTRACT_NOT_EXISTS);
71 79 }
72 80  
73 81 CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId());
74   - if(ObjectUtil.isNull(customer)){
  82 + if (ObjectUtil.isNull(customer)) {
75 83 throw exception(CUSTOMER_NOT_EXISTS);
76 84 }
77 85  
78 86 }
79 87  
80 88 @Override
  89 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
81 90 public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) {
82 91 // 校验存在
83 92 validateReceivablePlanExists(updateReqVO.getId());
... ... @@ -88,6 +97,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
88 97 }
89 98  
90 99 @Override
  100 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
91 101 public void deleteReceivablePlan(Long id) {
92 102 // 校验存在
93 103 validateReceivablePlanExists(id);
... ... @@ -102,6 +112,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
102 112 }
103 113  
104 114 @Override
  115 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.READ)
105 116 public CrmReceivablePlanDO getReceivablePlan(Long id) {
106 117 return receivablePlanMapper.selectById(id);
107 118 }
... ... @@ -115,14 +126,28 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
115 126 }
116 127  
117 128 @Override
118   - public PageResult<CrmReceivablePlanDO> getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO) {
119   - return receivablePlanMapper.selectPage(pageReqVO);
  129 + public PageResult<CrmReceivablePlanDO> getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
  130 + return receivablePlanMapper.selectPage(pageReqVO, userId);
120 131 }
121 132  
122 133 @Override
123 134 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
124   - public PageResult<CrmReceivablePlanDO> getReceivablePlanPageByCustomer(CrmReceivablePlanPageReqVO pageReqVO) {
125   - return receivablePlanMapper.selectPageByCustomer(pageReqVO);
  135 + public PageResult<CrmReceivablePlanDO> getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO) {
  136 + return receivablePlanMapper.selectPageByCustomerId(pageReqVO);
  137 + }
  138 +
  139 + @Override
  140 + public void transferReceivablePlan(CrmReceivablePlanTransferReqVO reqVO, Long userId) {
  141 + // 1 校验回款计划是否存在
  142 + validateReceivablePlanExists(reqVO.getId());
  143 +
  144 + // 2.1 数据权限转移
  145 + crmPermissionService.transferPermission(
  146 + CrmReceivablePlanConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()));
  147 + // 2.2 设置新的负责人
  148 + receivablePlanMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
  149 +
  150 + // 3. TODO 记录转移日志
126 151 }
127 152  
128 153 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
... ... @@ -3,11 +3,12 @@ package cn.iocoder.yudao.module.crm.service.receivable;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 9 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9   -
10 10 import jakarta.validation.Valid;
  11 +
11 12 import java.util.Collection;
12 13 import java.util.List;
13 14  
... ... @@ -62,9 +63,10 @@ public interface CrmReceivableService {
62 63 * 数据权限:基于 {@link CrmReceivableDO} 读取
63 64 *
64 65 * @param pageReqVO 分页查询
  66 + * @param userId 用户编号
65 67 * @return 回款分页
66 68 */
67   - PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO);
  69 + PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId);
68 70  
69 71 /**
70 72 * 获得回款分页,基于指定客户
... ... @@ -74,6 +76,14 @@ public interface CrmReceivableService {
74 76 * @param pageReqVO 分页查询
75 77 * @return 回款分页
76 78 */
77   - PageResult<CrmReceivableDO> getReceivablePageByCustomer(CrmReceivablePageReqVO pageReqVO);
  79 + PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO);
  80 +
  81 + /**
  82 + * 回款转移
  83 + *
  84 + * @param reqVO 请求
  85 + * @param userId 用户编号
  86 + */
  87 + void transferReceivable(CrmReceivableTransferReqVO reqVO, Long userId);
78 88  
79 89 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
9 9 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
10 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
11 12 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert;
12 13 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
... ... @@ -20,10 +21,11 @@ import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
20 21 import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
21 22 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
22 23 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  24 +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  25 +import jakarta.annotation.Resource;
23 26 import org.springframework.stereotype.Service;
24 27 import org.springframework.validation.annotation.Validated;
25 28  
26   -import jakarta.annotation.Resource;
27 29 import java.util.Collection;
28 30 import java.util.List;
29 31  
... ... @@ -48,6 +50,8 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
48 50 private CrmCustomerService customerService;
49 51 @Resource
50 52 private CrmReceivablePlanService receivablePlanService;
  53 + @Resource
  54 + private CrmPermissionService crmPermissionService;
51 55  
52 56 // TODO @liuhongfeng:创建还款后,是不是什么时候,要更新 plan?
53 57 @Override
... ... @@ -70,22 +74,22 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
70 74 // TODO @liuhongfeng:这里的括号要注意排版;
71 75 private void checkReceivable(CrmReceivableDO receivable) {
72 76 // TODO @liuhongfeng:这个放在参数校验合适
73   - if(ObjectUtil.isNull(receivable.getContractId())){
  77 + if (ObjectUtil.isNull(receivable.getContractId())) {
74 78 throw exception(CONTRACT_NOT_EXISTS);
75 79 }
76 80  
77 81 CrmContractDO contract = contractService.getContract(receivable.getContractId());
78   - if(ObjectUtil.isNull(contract)){
  82 + if (ObjectUtil.isNull(contract)) {
79 83 throw exception(CONTRACT_NOT_EXISTS);
80 84 }
81 85  
82 86 CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId());
83   - if(ObjectUtil.isNull(customer)){
  87 + if (ObjectUtil.isNull(customer)) {
84 88 throw exception(CUSTOMER_NOT_EXISTS);
85 89 }
86 90  
87 91 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId());
88   - if(ObjectUtil.isNull(receivablePlan)){
  92 + if (ObjectUtil.isNull(receivablePlan)) {
89 93 throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
90 94 }
91 95  
... ... @@ -129,16 +133,29 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
129 133 return receivableMapper.selectBatchIds(ids);
130 134 }
131 135  
132   - // TODO @芋艿:数据权限
133 136 @Override
134   - public PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO) {
135   - return receivableMapper.selectPage(pageReqVO);
  137 + public PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId) {
  138 + return receivableMapper.selectPage(pageReqVO, userId);
136 139 }
137 140  
138 141 @Override
139 142 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
140   - public PageResult<CrmReceivableDO> getReceivablePageByCustomer(CrmReceivablePageReqVO pageReqVO) {
141   - return receivableMapper.selectPageByCustomer(pageReqVO);
  143 + public PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) {
  144 + return receivableMapper.selectPageByCustomerId(pageReqVO);
  145 + }
  146 +
  147 + @Override
  148 + public void transferReceivable(CrmReceivableTransferReqVO reqVO, Long userId) {
  149 + // 1 校验回款是否存在
  150 + validateReceivableExists(reqVO.getId());
  151 +
  152 + // 2.1 数据权限转移
  153 + crmPermissionService.transferPermission(
  154 + CrmReceivableConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()));
  155 + // 2.2 设置新的负责人
  156 + receivableMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
  157 +
  158 + // 3. TODO 记录转移日志
142 159 }
143 160  
144 161 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java
... ... @@ -6,6 +6,7 @@ import cn.hutool.extra.spring.SpringUtil;
6 6 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
7 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
8 8 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
  9 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
9 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
10 11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11 12 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
... ... @@ -17,7 +18,7 @@ import java.util.List;
17 18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
18 19  
19 20 /**
20   - * CRM 分页查询工具类
  21 + * CRM 查询工具类
21 22 *
22 23 * @author HUIHUI
23 24 */
... ... @@ -26,17 +27,17 @@ public class CrmQueryWrapperUtils {
26 27 /**
27 28 * 构造 CRM 数据类型数据分页查询条件
28 29 *
29   - * @param query 连表查询对象
30   - * @param bizType 数据类型 {@link CrmBizTypeEnum}
31   - * @param bizId 数据编号
32   - * @param userId 用户编号
33   - * @param sceneType 场景类型
34   - * @param pool 公海
  30 + * @param query 连表查询对象
  31 + * @param bizType 数据类型 {@link CrmBizTypeEnum}
  32 + * @param bizId 数据编号
  33 + * @param userId 用户编号
  34 + * @param sceneType 场景类型
  35 + * @param pool 公海
  36 + * @return 是否 (是:需要执行查询,否:不需要查询调用方法直接返回空)
35 37 */
36   - // TODO @puhui999:bizId 直接传递会不会简单点
37   - // TODO @puhui999:builderPageQuery 应该不仅仅适合于分页查询,应该适用于所有的查询;可以改成 appendPermissionCondition
38   - public static <T extends MPJLambdaWrapper<?>, S> void builderPageQuery(T query, Integer bizType, SFunction<S, ?> bizId,
39   - Long userId, Integer sceneType, Boolean pool) {
  38 + // TODO @puhui999:bizId 直接传递会不会简单点 回复:还是需要 SFunction 因为分页连表时不知道 bizId 是多少
  39 + public static <T extends MPJLambdaWrapper<?>, S> boolean appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
  40 + Long userId, Integer sceneType, Boolean pool) {
40 41 // 1. 构建数据权限连表条件
41 42 if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
42 43 query.innerJoin(CrmPermissionDO.class, on ->
... ... @@ -48,52 +49,55 @@ public class CrmQueryWrapperUtils {
48 49 query.eq("owner_user_id", userId);
49 50 }
50 51 // 2.2 场景二:我参与的数据
51   - // TODO @puhui999:参与,指的是有读写权限噢;可以把 1. 的合并到 2.2 里;因为 2.1 不需要;
52 52 if (CrmSceneTypeEnum.isInvolved(sceneType)) {
53   - query.ne("owner_user_id", userId);
  53 + query
  54 + .ne("owner_user_id", userId)
  55 + .and(q -> q.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel())
  56 + .or()
  57 + .eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.WRITE.getLevel()));
  58 +
54 59 }
55 60 // 2.3 场景三:下属负责的数据
56 61 if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
57 62 List<AdminUserRespDTO> subordinateUsers = getAdminUserApi().getUserListBySubordinate(userId);
58   - // TODO @puhui999:如果为空,不拼接,就是查询了所有数据呀?
59   - if (CollUtil.isNotEmpty(subordinateUsers)) {
60   - query.in("owner_user_id", convertSet(subordinateUsers, AdminUserRespDTO::getId));
  63 + if (CollUtil.isEmpty(subordinateUsers)) {
  64 + return false;
61 65 }
  66 + query.in("owner_user_id", convertSet(subordinateUsers, AdminUserRespDTO::getId));
62 67 }
63 68  
64   - // 2. 拼接公海的查询条件
  69 + // 3. 拼接公海的查询条件
65 70 if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一:公海
66 71 query.isNull("owner_user_id");
67 72 } else { // 情况二:不是公海
68 73 query.isNotNull("owner_user_id");
69 74 }
  75 +
  76 + return true;
70 77 }
71 78  
72 79 /**
73 80 * 构造 CRM 数据类型批量数据查询条件
74 81 *
75   - * @param query 连表查询对象
76   - * @param bizType 数据类型 {@link CrmBizTypeEnum}
77   - * @param bizIds 数据编号
78   - * @param userId 用户编号
  82 + * @param query 连表查询对象
  83 + * @param bizType 数据类型 {@link CrmBizTypeEnum}
  84 + * @param bizIds 数据编号
  85 + * @param userId 用户编号
79 86 */
80   - // TODO @puhui999:可以改成 appendPermissionCondition
81   - // TODO @puhui999:S 是不是可以删除
82   - public static <T extends MPJLambdaWrapper<?>, S> void builderListQueryBatch(T query, Integer bizType, Collection<Long> bizIds, Long userId) {
83   - // TODO @puhui999:这里先 if return 简单点
84   - if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
85   - query.innerJoin(CrmPermissionDO.class, on ->
86   - on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds)
87   - .in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
  87 + public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query, Integer bizType, Collection<Long> bizIds, Long userId) {
  88 + if (ObjUtil.equal(validateAdminUser(userId), Boolean.TRUE)) {// 管理员不需要数据权限
  89 + return;
88 90 }
  91 +
  92 + query.innerJoin(CrmPermissionDO.class, on ->
  93 + on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds)
  94 + .in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
89 95 }
90 96  
91   - // TODO @puhui999:需要加个变量,不用每次都拿哈;
92 97 private static AdminUserApi getAdminUserApi() {
93   - return SpringUtil.getBean(AdminUserApi.class);
  98 + return AdminUserApiHolder.ADMIN_USER_API;
94 99 }
95 100  
96   - // TODO @puhui999:需要实现;
97 101 /**
98 102 * 校验用户是否是管理员
99 103 *
... ... @@ -101,7 +105,27 @@ public class CrmQueryWrapperUtils {
101 105 * @return 是/否
102 106 */
103 107 private static boolean validateAdminUser(Long userId) {
  108 + // TODO 查询权限配置表用户的角色信息
  109 + //CrmPermissionConfig permissionConfig = crmPermissionConfigService.getPermissionConfigByUserId(userId);
  110 + //if (permissionConfig == null) {
  111 + // return false;
  112 + //}
  113 + //// 校验是否为管理员
  114 + //if (permissionConfig.getIsAdmin()){
  115 + // return true;
  116 + //}
104 117 return false;
105 118 }
106 119  
  120 + /**
  121 + * 静态内部类实现 AdminUserApi 单例获取
  122 + *
  123 + * @author HUIHUI
  124 + */
  125 + private static class AdminUserApiHolder {
  126 +
  127 + private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class);
  128 +
  129 + }
  130 +
107 131 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java
... ... @@ -7,12 +7,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceiv
7 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
9 9 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
  10 +import jakarta.annotation.Resource;
10 11 import org.junit.jupiter.api.Disabled;
11 12 import org.junit.jupiter.api.Test;
12 13 import org.springframework.context.annotation.Import;
13 14  
14   -import jakarta.annotation.Resource;
15   -
  15 +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
16 16 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
17 17 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
18 18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
... ... @@ -22,6 +22,7 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PL
22 22 import static org.junit.jupiter.api.Assertions.*;
23 23  
24 24 // TODO 芋艿:后续,需要补充测试用例
  25 +
25 26 /**
26 27 * {@link CrmReceivablePlanServiceImpl} 的单元测试类
27 28 *
... ... @@ -43,7 +44,7 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest {
43 44 CrmReceivablePlanCreateReqVO reqVO = randomPojo(CrmReceivablePlanCreateReqVO.class);
44 45  
45 46 // 调用
46   - Long receivablePlanId = receivablePlanService.createReceivablePlan(reqVO);
  47 + Long receivablePlanId = receivablePlanService.createReceivablePlan(reqVO, 1L);
47 48 // 断言
48 49 assertNotNull(receivablePlanId);
49 50 // 校验记录的属性是否正确
... ... @@ -87,8 +88,8 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest {
87 88  
88 89 // 调用
89 90 receivablePlanService.deleteReceivablePlan(id);
90   - // 校验数据不存在了
91   - assertNull(crmReceivablePlanMapper.selectById(id));
  91 + // 校验数据不存在了
  92 + assertNull(crmReceivablePlanMapper.selectById(id));
92 93 }
93 94  
94 95 @Test
... ... @@ -103,34 +104,34 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest {
103 104 @Test
104 105 @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
105 106 public void testGetReceivablePlanPage() {
106   - // mock 数据
107   - CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class, o -> { // 等会查询到
108   - o.setPeriod(null);
109   - o.setReturnTime(null);
110   - o.setRemindDays(null);
111   - o.setRemindTime(null);
112   - o.setCustomerId(null);
113   - o.setContractId(null);
114   - o.setOwnerUserId(null);
115   - o.setRemark(null);
116   - o.setCreateTime(null);
117   - });
118   - crmReceivablePlanMapper.insert(dbReceivablePlan);
119   - // 测试 customerId 不匹配
120   - crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setCustomerId(null)));
121   - // 测试 contractId 不匹配
122   - crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setContractId(null)));
123   - // 准备参数
124   - CrmReceivablePlanPageReqVO reqVO = new CrmReceivablePlanPageReqVO();
125   - reqVO.setCustomerId(null);
126   - reqVO.setContractId(null);
127   -
128   - // 调用
129   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(reqVO);
130   - // 断言
131   - assertEquals(1, pageResult.getTotal());
132   - assertEquals(1, pageResult.getList().size());
133   - assertPojoEquals(dbReceivablePlan, pageResult.getList().get(0));
  107 + // mock 数据
  108 + CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class, o -> { // 等会查询到
  109 + o.setPeriod(null);
  110 + o.setReturnTime(null);
  111 + o.setRemindDays(null);
  112 + o.setRemindTime(null);
  113 + o.setCustomerId(null);
  114 + o.setContractId(null);
  115 + o.setOwnerUserId(null);
  116 + o.setRemark(null);
  117 + o.setCreateTime(null);
  118 + });
  119 + crmReceivablePlanMapper.insert(dbReceivablePlan);
  120 + // 测试 customerId 不匹配
  121 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setCustomerId(null)));
  122 + // 测试 contractId 不匹配
  123 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setContractId(null)));
  124 + // 准备参数
  125 + CrmReceivablePlanPageReqVO reqVO = new CrmReceivablePlanPageReqVO();
  126 + reqVO.setCustomerId(null);
  127 + reqVO.setContractId(null);
  128 + reqVO.setPageSize(PAGE_SIZE_NONE);
  129 + // 调用
  130 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(reqVO, 1L);
  131 + // 断言
  132 + assertEquals(1, pageResult.getTotal());
  133 + assertEquals(1, pageResult.getList().size());
  134 + assertPojoEquals(dbReceivablePlan, pageResult.getList().get(0));
134 135 }
135 136  
136 137 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java
... ... @@ -7,12 +7,11 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.Crm
7 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9 9 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
  10 +import jakarta.annotation.Resource;
10 11 import org.junit.jupiter.api.Disabled;
11 12 import org.junit.jupiter.api.Test;
12 13 import org.springframework.context.annotation.Import;
13 14  
14   -import jakarta.annotation.Resource;
15   -
16 15 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
17 16 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
18 17 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
... ... @@ -133,7 +132,7 @@ public class CrmCrmReceivableServiceImplTest extends BaseDbUnitTest {
133 132 reqVO.setCustomerId(null);
134 133  
135 134 // 调用
136   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(reqVO);
  135 + PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(reqVO, 1L);
137 136 // 断言
138 137 assertEquals(1, pageResult.getTotal());
139 138 assertEquals(1, pageResult.getList().size());
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
... ... @@ -63,7 +63,7 @@ export function export${simpleClassName}Excel(params) {
63 63 responseType: 'blob'
64 64 })
65 65 }
66   -## 特殊:主子表专属逻辑 TODO @puhui999:下面方法的【空格】不太对
  66 +## 特殊:主子表专属逻辑
67 67 #foreach ($subTable in $subTables)
68 68 #set ($index = $foreach.count - 1)
69 69 #set ($subSimpleClassName = $subSimpleClassNames.get($index))
... ... @@ -76,7 +76,7 @@ export function export${simpleClassName}Excel(params) {
76 76  
77 77 // ==================== 子表($subTable.classComment) ====================
78 78 ## 情况一:MASTER_ERP 时,需要分查询页子表
79   - #if ( $table.templateType == 11 )
  79 + #if ($table.templateType == 11)
80 80 // 获得${subTable.classComment}分页
81 81 export function get${subSimpleClassName}Page(params) {
82 82 return request({
... ... @@ -87,7 +87,7 @@ export function export${simpleClassName}Excel(params) {
87 87 }
88 88 ## 情况二:非 MASTER_ERP 时,需要列表查询子表
89 89 #else
90   - #if ( $subTable.subJoinMany )
  90 + #if ($subTable.subJoinMany)
91 91 // 获得${subTable.classComment}列表
92 92 export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {
93 93 return request({
... ... @@ -106,7 +106,7 @@ export function export${simpleClassName}Excel(params) {
106 106 #end
107 107 #end
108 108 ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
109   - #if ( $table.templateType == 11 )
  109 + #if ($table.templateType == 11)
110 110 // 新增${subTable.classComment}
111 111 export function create${subSimpleClassName}(data) {
112 112 return request({
... ... @@ -115,7 +115,6 @@ export function export${simpleClassName}Excel(params) {
115 115 data
116 116 })
117 117 }
118   -
119 118 // 修改${subTable.classComment}
120 119 export function update${subSimpleClassName}(data) {
121 120 return request({
... ... @@ -124,7 +123,6 @@ export function export${simpleClassName}Excel(params) {
124 123 data
125 124 })
126 125 }
127   -
128 126 // 删除${subTable.classComment}
129 127 export function delete${subSimpleClassName}(id) {
130 128 return request({
... ... @@ -132,7 +130,6 @@ export function export${simpleClassName}Excel(params) {
132 130 method: 'delete'
133 131 })
134 132 }
135   -
136 133 // 获得${subTable.classComment}
137 134 export function get${subSimpleClassName}(id) {
138 135 return request({
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
... ... @@ -7,11 +7,10 @@ import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivity
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
8 8 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
9 9 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
  10 +import jakarta.annotation.Resource;
10 11 import org.springframework.stereotype.Service;
11 12 import org.springframework.validation.annotation.Validated;
12 13  
13   -import jakarta.annotation.Resource;
14   -
15 14 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16 15 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_RECORD_NOT_EXISTS;
17 16  
... ... @@ -25,21 +24,21 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINA
25 24 public class CombinationRecordApiImpl implements CombinationRecordApi {
26 25  
27 26 @Resource
28   - private CombinationRecordService recordService;
  27 + private CombinationRecordService combinationRecordService;
29 28  
30 29 @Override
31 30 public void validateCombinationRecord(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
32   - recordService.validateCombinationRecord(userId, activityId, headId, skuId, count);
  31 + combinationRecordService.validateCombinationRecord(userId, activityId, headId, skuId, count);
33 32 }
34 33  
35 34 @Override
36 35 public CombinationRecordCreateRespDTO createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
37   - return CombinationActivityConvert.INSTANCE.convert4(recordService.createCombinationRecord(reqDTO));
  36 + return CombinationActivityConvert.INSTANCE.convert4(combinationRecordService.createCombinationRecord(reqDTO));
38 37 }
39 38  
40 39 @Override
41 40 public boolean isCombinationRecordSuccess(Long userId, Long orderId) {
42   - CombinationRecordDO record = recordService.getCombinationRecord(userId, orderId);
  41 + CombinationRecordDO record = combinationRecordService.getCombinationRecord(userId, orderId);
43 42 if (record == null) {
44 43 throw exception(COMBINATION_RECORD_NOT_EXISTS);
45 44 }
... ... @@ -48,7 +47,7 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
48 47  
49 48 @Override
50 49 public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
51   - return recordService.validateJoinCombination(userId, activityId, headId, skuId, count);
  50 + return combinationRecordService.validateJoinCombination(userId, activityId, headId, skuId, count);
52 51 }
53 52  
54 53 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
7 8 import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO;
8 9 import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
9 10 import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
... ... @@ -47,10 +48,9 @@ public interface MemberSignInRecordConvert {
47 48 // 1. 计算是第几天签到
48 49 configs.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
49 50 MemberSignInConfigDO lastConfig = CollUtil.getLast(configs); // 最大签到天数配置
50   - // 1.2. 计算今天是第几天签到
  51 + // 1.2. 计算今天是第几天签到 (只有连续签到才加否则重置为 1)
51 52 int day = 1;
52   - // TODO @puhui999:要判断是不是昨天签到的;是否是昨天的判断,可以抽个方法到 util 里
53   - if (lastRecord != null) {
  53 + if (lastRecord != null && DateUtils.isYesterday(lastRecord.getCreateTime())) {
54 54 day = lastRecord.getDay() + 1;
55 55 }
56 56 // 1.3 判断是否超出了最大签到配置
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java
... ... @@ -19,14 +19,12 @@ import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
19 19 import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
20 20 import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
21 21 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
  22 +import jakarta.annotation.Resource;
22 23 import org.apache.commons.lang3.StringUtils;
23 24 import org.springframework.stereotype.Service;
24 25 import org.springframework.transaction.annotation.Transactional;
25 26 import org.springframework.validation.annotation.Validated;
26 27  
27   -import jakarta.annotation.Resource;
28   -import java.time.LocalDate;
29   -import java.util.Comparator;
30 28 import java.util.List;
31 29 import java.util.Set;
32 30  
... ... @@ -77,50 +75,15 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
77 75 }
78 76 summary.setTodaySignIn(DateUtils.isToday(lastRecord.getCreateTime()));
79 77  
80   - // 4. 校验今天是否签到,没有签到则直接返回
  78 + // 4.1 校验今天是否签到,没有签到则直接返回
81 79 if (!summary.getTodaySignIn()) {
82 80 return summary;
83 81 }
84   - // 4.1. 判断连续签到天数
85   - // TODO @puhui999:连续签到,可以基于 lastRecord 的 day 和当前时间判断呀?按 day 统计连续签到天数可能不准确
86   - // 1. day 只是记录第几天签到的有可能不连续,比如第一次签到是周一,第二次签到是周三这样 lastRecord 的 day 为 2 但是并不是连续的两天
87   - // 2. day 超出签到规则的最大天数会重置到从第一天开始签到(我理解为开始下一轮,类似一周签到七天七天结束下周又从周一开始签到)
88   - // 1. 回复:周三签到,day 要归 1 呀。连续签到哈;
89   - List<MemberSignInRecordDO> signInRecords = signInRecordMapper.selectListByUserId(userId);
90   - signInRecords.sort(Comparator.comparing(MemberSignInRecordDO::getCreateTime).reversed()); // 根据签到时间倒序
91   - summary.setContinuousDay(calculateConsecutiveDays(signInRecords));
  82 + // 4.2 连续签到天数
  83 + summary.setContinuousDay(lastRecord.getDay());
92 84 return summary;
93 85 }
94 86  
95   - /**
96   - * 计算连续签到天数
97   - *
98   - * @param signInRecords 签到记录列表
99   - * @return int 连续签到天数
100   - */
101   - public int calculateConsecutiveDays(List<MemberSignInRecordDO> signInRecords) {
102   - int consecutiveDays = 1; // 初始连续天数为1
103   - LocalDate previousDate = null;
104   -
105   - for (MemberSignInRecordDO record : signInRecords) {
106   - LocalDate currentDate = record.getCreateTime().toLocalDate();
107   -
108   - if (previousDate != null) {
109   - // 检查相邻两个日期是否连续
110   - if (currentDate.minusDays(1).isEqual(previousDate)) {
111   - consecutiveDays++;
112   - } else {
113   - // 如果日期不连续,停止遍历
114   - break;
115   - }
116   - }
117   -
118   - previousDate = currentDate;
119   - }
120   -
121   - return consecutiveDays;
122   - }
123   -
124 87 @Override
125 88 public PageResult<MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
126 89 // 根据用户昵称查询出用户ids
... ...
yudao-module-system/yudao-module-system-api/pom.xml
... ... @@ -22,6 +22,23 @@
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
24 24  
  25 + <!-- Springboot-注解-通用操作日志组件 -->
  26 + <!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
  27 + <dependency>
  28 + <groupId>io.github.mouzt</groupId>
  29 + <artifactId>bizlog-sdk</artifactId>
  30 + </dependency>
  31 +
  32 + <!--工具类相关-->
  33 + <dependency>
  34 + <groupId>com.fasterxml.jackson.core</groupId>
  35 + <artifactId>jackson-databind</artifactId>
  36 + </dependency>
  37 + <dependency>
  38 + <groupId>com.fasterxml.jackson.core</groupId>
  39 + <artifactId>jackson-core</artifactId>
  40 + </dependency>
  41 +
25 42 <!-- 参数校验 -->
26 43 <dependency>
27 44 <groupId>org.springframework.boot</groupId>
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java
1 1 package cn.iocoder.yudao.module.system.api.logger;
2 2  
3 3 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
4   -
  4 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
5 5 import jakarta.validation.Valid;
6 6  
  7 +import java.util.List;
  8 +
7 9 /**
8 10 * 操作日志 API 接口
9 11 *
... ... @@ -18,4 +20,13 @@ public interface OperateLogApi {
18 20 */
19 21 void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
20 22  
  23 + /**
  24 + * 获取指定模块的指定数据的操作日志
  25 + *
  26 + * @param module 操作模块
  27 + * @param bizId 操作模块编号
  28 + * @return 操作日志
  29 + */
  30 + List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId);
  31 +
21 32 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2RespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.logger.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import com.fasterxml.jackson.annotation.JsonFormat;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
  11 +
  12 +/**
  13 + * 系统操作日志 Resp DTO
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Data
  18 +public class OperateLogV2RespDTO {
  19 +
  20 + /**
  21 + * 链路追踪编号
  22 + *
  23 + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
  24 + */
  25 + private String traceId;
  26 + /**
  27 + * 用户编号
  28 + *
  29 + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
  30 + */
  31 + private Long userId;
  32 + /**
  33 + * 用户类型
  34 + *
  35 + * 关联 {@link UserTypeEnum}
  36 + */
  37 + private Integer userType;
  38 + /**
  39 + * 操作模块
  40 + */
  41 + private String module;
  42 + /**
  43 + * 操作名
  44 + */
  45 + private String name;
  46 + /**
  47 + * 操作模块业务编号
  48 + */
  49 + private Long bizId;
  50 + /**
  51 + * 操作内容,记录整个操作的明细
  52 + * 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  53 + */
  54 + private String content;
  55 + /**
  56 + * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
  57 + * 例如说,记录订单编号,{ orderId: "1"}
  58 + */
  59 + private String extra;
  60 +
  61 + /**
  62 + * 请求方法名
  63 + */
  64 + private String requestMethod;
  65 + /**
  66 + * 请求地址
  67 + */
  68 + private String requestUrl;
  69 + /**
  70 + * 用户 IP
  71 + */
  72 + private String userIp;
  73 + /**
  74 + * 浏览器 UA
  75 + */
  76 + private String userAgent;
  77 +
  78 + /**
  79 + * 创建时间
  80 + */
  81 + // TODO puhui999: 木得效果怎么肥事
  82 + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
  83 + private LocalDateTime createTime;
  84 + /**
  85 + * 创建者,关联 AdminUserDO#getId
  86 + */
  87 + private String creator;
  88 + /**
  89 + * 创建者名称
  90 + */
  91 + private String creatorName;
  92 +
  93 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java
1 1 package cn.iocoder.yudao.module.system.api.logger;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
3 5 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
  6 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
4 9 import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
  10 +import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  11 +import jakarta.annotation.Resource;
5 12 import org.springframework.stereotype.Service;
6 13 import org.springframework.validation.annotation.Validated;
7 14  
8   -import jakarta.annotation.Resource;
  15 +import java.util.Collections;
  16 +import java.util.List;
  17 +import java.util.Map;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  20 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
9 21  
10 22 /**
11 23 * 操作日志 API 实现类
... ... @@ -18,10 +30,31 @@ public class OperateLogApiImpl implements OperateLogApi {
18 30  
19 31 @Resource
20 32 private OperateLogService operateLogService;
  33 + @Resource
  34 + private AdminUserService adminUserService;
21 35  
22 36 @Override
23 37 public void createOperateLog(OperateLogCreateReqDTO createReqDTO) {
24 38 operateLogService.createOperateLog(createReqDTO);
25 39 }
26 40  
  41 + @Override
  42 + public List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId) {
  43 + List<OperateLogV2DO> logList = operateLogService.getOperateLogByModuleAndBizId(module, bizId);
  44 + if (CollUtil.isEmpty(logList)) {
  45 + return Collections.emptyList();
  46 + }
  47 +
  48 + // 获取用户
  49 + List<AdminUserDO> userList = adminUserService.getUserList(convertSet(logList, item -> Long.parseLong(item.getCreator())));
  50 + Map<Long, AdminUserDO> userMap = convertMap(userList, AdminUserDO::getId);
  51 + return convertList(logList, item -> {
  52 + OperateLogV2RespDTO bean = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
  53 + findAndThen(userMap, Long.parseLong(item.getCreator()), user -> {
  54 + bean.setCreatorName(user.getNickname());
  55 + });
  56 + return bean;
  57 + });
  58 + }
  59 +
27 60 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogV2DO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.logger;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +
  11 +/**
  12 + * 操作日志表 V2
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@TableName(value = "system_operate_log_v2", autoResultMap = true)
  17 +@KeySequence("system_operate_log_seq_v2") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +public class OperateLogV2DO extends BaseDO {
  21 +
  22 + /**
  23 + * 日志主键
  24 + */
  25 + @TableId
  26 + private Long id;
  27 + /**
  28 + * 链路追踪编号
  29 + *
  30 + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
  31 + */
  32 + private String traceId;
  33 + /**
  34 + * 用户编号
  35 + *
  36 + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
  37 + */
  38 + private Long userId;
  39 + /**
  40 + * 用户类型
  41 + *
  42 + * 关联 {@link UserTypeEnum}
  43 + */
  44 + private Integer userType;
  45 + /**
  46 + * 操作模块
  47 + */
  48 + private String module;
  49 + /**
  50 + * 操作名
  51 + */
  52 + private String name;
  53 + /**
  54 + * 操作模块业务编号
  55 + */
  56 + private Long bizId;
  57 + /**
  58 + * 操作内容,记录整个操作的明细
  59 + * 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  60 + */
  61 + private String content;
  62 + /**
  63 + * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
  64 + * 例如说,记录订单编号,{ orderId: "1"}
  65 + */
  66 + private String extra;
  67 + /**
  68 + * 请求方法名
  69 + */
  70 + private String requestMethod;
  71 + /**
  72 + * 请求地址
  73 + */
  74 + private String requestUrl;
  75 + /**
  76 + * 用户 IP
  77 + */
  78 + private String userIp;
  79 + /**
  80 + * 浏览器 UA
  81 + */
  82 + private String userAgent;
  83 +
  84 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/logger/OperateLogV2Mapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.logger;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
  13 +@Mapper
  14 +public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
  15 +
  16 + default PageResult<OperateLogV2DO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
  17 + LambdaQueryWrapperX<OperateLogV2DO> query = new LambdaQueryWrapperX<OperateLogV2DO>()
  18 + .likeIfPresent(OperateLogV2DO::getModule, reqVO.getModule())
  19 + .inIfPresent(OperateLogV2DO::getUserId, userIds);
  20 + query.orderByDesc(OperateLogV2DO::getId); // 降序
  21 + return selectPage(reqVO, query);
  22 + }
  23 +
  24 + default List<OperateLogV2DO> selectListByModuleAndBizId(String module, Long bizId) {
  25 + return selectList(new LambdaQueryWrapperX<OperateLogV2DO>()
  26 + .eq(OperateLogV2DO::getModule, module)
  27 + .eq(OperateLogV2DO::getBizId, bizId)
  28 + .orderByDesc(OperateLogV2DO::getCreateTime));
  29 + }
  30 +
  31 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/config/YudaoOperateLogV2Configuration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.bizlog.config;
  2 +
  3 +import com.mzt.logapi.starter.annotation.EnableLogRecord;
  4 +import org.springframework.context.annotation.Configuration;
  5 +
  6 +/**
  7 + * mzt-biz-log 配置类
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@Configuration(proxyBeanMethods = false)
  12 +@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
  13 +public class YudaoOperateLogV2Configuration {
  14 +
  15 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AdminUserParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.bizlog.function;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  6 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  7 +import com.mzt.logapi.service.IParseFunction;
  8 +import jakarta.annotation.Resource;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +/**
  13 + * 自定义函数-通过用户编号获取用户信息
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Slf4j
  18 +@Component
  19 +public class AdminUserParseFunction implements IParseFunction {
  20 +
  21 + @Resource
  22 + private AdminUserApi adminUserApi;
  23 +
  24 + @Override
  25 + public String functionName() {
  26 + return "getAdminUserById";
  27 + }
  28 +
  29 + @Override
  30 + public String apply(Object value) {
  31 + if (value == null) {
  32 + //log.warn("(getAdminUserById) 解析异常参数为 null");
  33 + return "";
  34 + }
  35 + if (StrUtil.isEmpty(value.toString())) {
  36 + //log.warn("(getAdminUserById) 解析异常参数为空");
  37 + return "";
  38 + }
  39 +
  40 + // 获取用户信息
  41 + AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString()));
  42 + if (user == null) {
  43 + log.warn("(getAdminUserById) 获取用户信息失败,参数为:{}", value);
  44 + return "";
  45 + }
  46 + // 返回格式 芋道源码(13888888888)
  47 + String nickname = user.getNickname();
  48 + if (ObjUtil.isNotEmpty(user.getMobile())) {
  49 + return nickname.concat("(").concat(user.getMobile()).concat(")");
  50 + }
  51 + return nickname;
  52 + }
  53 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AreaParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.bizlog.function;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  5 +import com.mzt.logapi.service.IParseFunction;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 自定义函数-通过区域编号获取区域信息
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +@Slf4j
  15 +@Component
  16 +public class AreaParseFunction implements IParseFunction {
  17 +
  18 + @Override
  19 + public boolean executeBefore() {
  20 + return true; // 先转换值后对比
  21 + }
  22 +
  23 + @Override
  24 + public String functionName() {
  25 + return "getAreaById";
  26 + }
  27 +
  28 + @Override
  29 + public String apply(Object value) {
  30 + if (value == null) {
  31 + return "";
  32 + }
  33 + if (StrUtil.isEmpty(value.toString())) {
  34 + return "";
  35 + }
  36 +
  37 + return AreaUtils.format(Integer.parseInt(value.toString()));
  38 + }
  39 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.bizlog;
0 2 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/service/ILogRecordServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.bizlog.service;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
  4 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  5 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  6 +import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
  7 +import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
  8 +import com.mzt.logapi.beans.LogRecord;
  9 +import com.mzt.logapi.service.ILogRecordService;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.servlet.http.HttpServletRequest;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +import java.util.Collections;
  16 +import java.util.List;
  17 +
  18 +/**
  19 + * 操作日志 ILogRecordService 实现类
  20 + *
  21 + * 基于 {@link OperateLogService} 实现,记录操作日志
  22 + *
  23 + * @author HUIHUI
  24 + */
  25 +@Slf4j
  26 +@Service
  27 +public class ILogRecordServiceImpl implements ILogRecordService {
  28 +
  29 + @Resource
  30 + private OperateLogService operateLogService;
  31 +
  32 + @Override
  33 + public void record(LogRecord logRecord) {
  34 + OperateLogV2CreateReqBO reqBO = new OperateLogV2CreateReqBO();
  35 + // 补全通用字段
  36 + reqBO.setTraceId(TracerUtils.getTraceId());
  37 + // 补充用户信息
  38 + fillUserFields(reqBO);
  39 + // 补全模块信息
  40 + fillModuleFields(reqBO, logRecord);
  41 + // 补全请求信息
  42 + fillRequestFields(reqBO);
  43 + // 异步记录日志
  44 + operateLogService.createOperateLogV2(reqBO);
  45 + log.info("操作日志 ===> {}", reqBO);
  46 + }
  47 +
  48 + private static void fillUserFields(OperateLogV2CreateReqBO reqBO) {
  49 + reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
  50 + reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
  51 + }
  52 +
  53 + public static void fillModuleFields(OperateLogV2CreateReqBO reqBO, LogRecord logRecord) {
  54 + reqBO.setModule(logRecord.getType()); // 大模块类型如 crm-客户
  55 + reqBO.setName(logRecord.getSubType());// 操作名称如 转移客户
  56 + reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
  57 + reqBO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  58 + reqBO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
  59 + }
  60 +
  61 + private static void fillRequestFields(OperateLogV2CreateReqBO reqBO) {
  62 + // 获得 Request 对象
  63 + HttpServletRequest request = ServletUtils.getRequest();
  64 + if (request == null) {
  65 + return;
  66 + }
  67 + // 补全请求信息
  68 + reqBO.setRequestMethod(request.getMethod());
  69 + reqBO.setRequestUrl(request.getRequestURI());
  70 + reqBO.setUserIp(ServletUtils.getClientIP(request));
  71 + reqBO.setUserAgent(ServletUtils.getUserAgent(request));
  72 + }
  73 +
  74 + @Override
  75 + public List<LogRecord> queryLog(String bizNo, String type) {
  76 + return Collections.emptyList();
  77 + }
  78 +
  79 + @Override
  80 + public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
  81 + return Collections.emptyList();
  82 + }
  83 +
  84 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogService.java
... ... @@ -4,6 +4,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
5 5 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
6 6 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
  8 +import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
  9 +
  10 +import java.util.List;
7 11  
8 12 /**
9 13 * 操作日志 Service 接口
... ... @@ -27,4 +31,22 @@ public interface OperateLogService {
27 31 */
28 32 PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO);
29 33  
  34 + //======================= LOG V2 =======================
  35 +
  36 + /**
  37 + * 记录操作日志 V2
  38 + *
  39 + * @param createReqBO 创建请求
  40 + */
  41 + void createOperateLogV2(OperateLogV2CreateReqBO createReqBO);
  42 +
  43 + /**
  44 + * 获取指定模块的指定数据的操作日志
  45 + *
  46 + * @param module 操作模块
  47 + * @param bizId 操作模块编号
  48 + * @return 操作日志
  49 + */
  50 + List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId);
  51 +
30 52 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogServiceImpl.java
... ... @@ -8,15 +8,19 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
8 8 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
9 9 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
11 12 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
12 13 import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
  14 +import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogV2Mapper;
  15 +import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
13 16 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  17 +import jakarta.annotation.Resource;
14 18 import lombok.extern.slf4j.Slf4j;
15 19 import org.springframework.stereotype.Service;
16 20 import org.springframework.validation.annotation.Validated;
17 21  
18   -import jakarta.annotation.Resource;
19 22 import java.util.Collection;
  23 +import java.util.List;
20 24  
21 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
22 26 import static cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO.JAVA_METHOD_ARGS_MAX_LENGTH;
... ... @@ -34,6 +38,8 @@ public class OperateLogServiceImpl implements OperateLogService {
34 38  
35 39 @Resource
36 40 private OperateLogMapper operateLogMapper;
  41 + @Resource
  42 + private OperateLogV2Mapper operateLogV2Mapper;
37 43  
38 44 @Resource
39 45 private AdminUserService userService;
... ... @@ -60,4 +66,17 @@ public class OperateLogServiceImpl implements OperateLogService {
60 66 return operateLogMapper.selectPage(pageReqVO, userIds);
61 67 }
62 68  
  69 + //======================= LOG V2 =======================
  70 +
  71 + @Override
  72 + public void createOperateLogV2(OperateLogV2CreateReqBO createReqBO) {
  73 + OperateLogV2DO log = BeanUtils.toBean(createReqBO, OperateLogV2DO.class);
  74 + operateLogV2Mapper.insert(log);
  75 + }
  76 +
  77 + @Override
  78 + public List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId) {
  79 + return operateLogV2Mapper.selectListByModuleAndBizId(module, bizId);
  80 + }
  81 +
63 82 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/bo/OperateLogV2CreateReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.logger.bo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 系统操作日志 Create Req BO
  9 + *
  10 + * @author HUIHUI
  11 + */
  12 +@Data
  13 +public class OperateLogV2CreateReqBO {
  14 +
  15 + /**
  16 + * 链路追踪编号
  17 + *
  18 + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
  19 + */
  20 + private String traceId;
  21 + /**
  22 + * 用户编号
  23 + *
  24 + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
  25 + */
  26 + @NotEmpty(message = "用户编号不能为空")
  27 + private Long userId;
  28 + /**
  29 + * 用户类型
  30 + *
  31 + * 关联 {@link UserTypeEnum}
  32 + */
  33 + @NotEmpty(message = "用户类型不能为空")
  34 + private Integer userType;
  35 + /**
  36 + * 操作模块
  37 + */
  38 + @NotEmpty(message = "操作模块不能为空")
  39 + private String module;
  40 + /**
  41 + * 操作名
  42 + */
  43 + @NotEmpty(message = "操作名不能为空")
  44 + private String name;
  45 + /**
  46 + * 操作模块业务编号
  47 + */
  48 + @NotEmpty(message = "操作模块业务编号不能为空")
  49 + private Long bizId;
  50 + /**
  51 + * 操作内容,记录整个操作的明细
  52 + * 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  53 + */
  54 + @NotEmpty(message = "操作内容不能为空")
  55 + private String content;
  56 + /**
  57 + * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
  58 + * 例如说,记录订单编号,{ orderId: "1"}
  59 + */
  60 + private String extra;
  61 +
  62 + /**
  63 + * 请求方法名
  64 + */
  65 + @NotEmpty(message = "请求方法名不能为空")
  66 + private String requestMethod;
  67 + /**
  68 + * 请求地址
  69 + */
  70 + @NotEmpty(message = "请求地址不能为空")
  71 + private String requestUrl;
  72 + /**
  73 + * 用户 IP
  74 + */
  75 + @NotEmpty(message = "用户 IP 不能为空")
  76 + private String userIp;
  77 + /**
  78 + * 浏览器 UA
  79 + */
  80 + @NotEmpty(message = "浏览器 UA 不能为空")
  81 + private String userAgent;
  82 +
  83 +}
... ...