Quellcode durchsuchen

!817 crm联系人新增操作日志
Merge pull request !817 from zyna/develop

芋道源码 vor 1 Jahr
Ursprung
Commit
450cd1baf1
19 geänderte Dateien mit 457 neuen und 112 gelöschten Zeilen
  1. 1 1
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  2. 7 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  3. 22 10
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
  4. 0 14
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactCreateReqVO.java
  5. 87 15
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java
  6. 29 22
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java
  7. 0 20
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactUpdateReqVO.java
  8. 1 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
  9. 37 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBooleanParseFunction.java
  10. 44 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java
  11. 45 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java
  12. 39 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmSexParseFunction.java
  13. 44 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmSysUserParseFunction.java
  14. 3 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java
  15. 5 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
  16. 9 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
  17. 71 20
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
  18. 7 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
  19. 6 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java

+ 1 - 1
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java

@@ -24,7 +24,7 @@ public interface ErrorCodeConstants {
     // ========== 联系人管理 1-020-003-000 ==========
     ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
     ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode( 1_020_003_001, "联系人商机关联不存在");
-
+    ErrorCode CONTACT_CONTRACT_LINK_EXISTS = new ErrorCode( 1_020_003_002, "联系人已关联合同,不能删除");
     // ========== 回款 1-020-004-000 ==========
     ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
 

+ 7 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.business;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
@@ -28,12 +29,15 @@ import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Optional;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
 
 @Tag(name = "管理后台 - 商机")
 @RestController
@@ -95,7 +99,9 @@ public class CrmBusinessController {
     @GetMapping("/page-by-customer")
     @Operation(summary = "获得商机分页,基于指定客户")
     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
-        Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
+        if(pageReqVO.getCustomerId() == null){
+            throw exception(CUSTOMER_NOT_EXISTS);
+        }
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
         return success(buildBusinessDetailPageResult(pageResult));
     }

+ 22 - 10
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java

@@ -17,6 +17,9 @@ import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import com.google.common.collect.Lists;
@@ -44,6 +47,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CONTACT;
 
 @Tag(name = "管理后台 - CRM 联系人")
 @RestController
@@ -58,22 +62,25 @@ public class CrmContactController {
     private CrmCustomerService customerService;
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private OperateLogApi operateLogApi;
 
     @Resource
     private CrmContactBusinessService contactBusinessLinkService;
 
-    // TODO @zyna:CrmContactCreateReqVO、CrmContactUpdateReqVO、CrmContactRespVO 按照新的 VO 规范搞哈;可以参考 dept 模块
+
     @PostMapping("/create")
     @Operation(summary = "创建联系人")
     @PreAuthorize("@ss.hasPermission('crm:contact:create')")
-    public CommonResult<Long> createContact(@Valid @RequestBody CrmContactCreateReqVO createReqVO) {
+    public CommonResult<Long> createContact(@Valid @RequestBody CrmContactSaveReqVO createReqVO) {
         return success(contactService.createContact(createReqVO, getLoginUserId()));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新联系人")
+    @OperateLog(enable = false)
     @PreAuthorize("@ss.hasPermission('crm:contact:update')")
-    public CommonResult<Boolean> updateContact(@Valid @RequestBody CrmContactUpdateReqVO updateReqVO) {
+    public CommonResult<Boolean> updateContact(@Valid @RequestBody CrmContactSaveReqVO updateReqVO) {
         contactService.updateContact(updateReqVO);
         return success(true);
     }
@@ -112,11 +119,7 @@ public class CrmContactController {
     @Operation(summary = "获得联系人列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
     public CommonResult<List<CrmContactSimpleRespVO>> getSimpleContactList() {
-        // TODO @zyna:建议 contactService 单独搞个 list 接口哈
-        CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
-        pageReqVO.setPageSize(PAGE_SIZE_NONE);
-        List<CrmContactDO> list = contactService.getContactPage(pageReqVO, getLoginUserId()).getList();
-        return success(BeanUtils.toBean(list, CrmContactSimpleRespVO.class));
+        return success(contactService.simpleContactList());
     }
 
     @GetMapping("/page")
@@ -146,7 +149,16 @@ public class CrmContactController {
         ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class,
                 buildContactDetailPage(pageResult).getList());
     }
-
+    @GetMapping("/operate-log-page")
+    @Operation(summary = "获得客户操作日志")
+    @PreAuthorize("@ss.hasPermission('crm:customer:query')")
+    public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@RequestParam("bizId")Long bizId) {
+        OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO();
+        reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
+        reqVO.setBizType(CRM_CONTACT);
+        reqVO.setBizId(bizId);
+        return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
+    }
     /**
      * 构建详细的联系人分页结果
      *
@@ -181,7 +193,7 @@ public class CrmContactController {
     // ================== 关联/取关联系人  ===================
 
     @PostMapping("/create-business-list")
-    @Operation(summary = "创建联系人与联系人的关联")
+    @Operation(summary = "创建联系人与商机的关联")
     @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
     public CommonResult<Boolean> createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) {
         contactBusinessLinkService.createContactBusinessList(createReqVO);

+ 0 - 14
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactCreateReqVO.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - CRM 联系人创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmContactCreateReqVO extends CrmContactBaseVO {
-
-}

+ 87 - 15
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java

@@ -1,34 +1,107 @@
 package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.framework.common.validation.Telephone;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotNull;
 import lombok.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
 import java.time.LocalDateTime;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
 @Schema(description = "管理后台 - CRM 联系人 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @ExcelIgnoreUnannotated
-public class CrmContactRespVO extends CrmContactBaseVO {
+public class CrmContactRespVO {
 
     @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
     private Long id;
 
-    @Schema(description = "创建时间")
-    @ExcelProperty(value = "创建时间", order = 8)
-    private LocalDateTime createTime;
+    @ExcelProperty(value = "姓名",order = 1)
+    @Schema(description = "姓名", example = "芋艿")
+    private String name;
+
+    @Schema(description = "客户编号", example = "10795")
+    private Long customerId;
+
+    @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3)
+    @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX)
+    @Schema(description = "性别")
+    private Integer sex;
+
+    @Schema(description = "职位")
+    @ExcelProperty(value = "职位", order = 3)
+    private String post;
+
+    @Schema(description = "是否关键决策人")
+    @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3)
+    @DictFormat(DictTypeConstants.BOOLEAN_STRING)
+    private Boolean master;
+
+    @Schema(description = "直属上级", example = "23457")
+    private Long parentId;
+
+    @Schema(description = "手机号",example = "1387171766")
+    @ExcelProperty(value = "手机号",order = 4)
+    private String mobile;
+
+    @Schema(description = "电话",example = "021-0029922")
+    @ExcelProperty(value = "电话",order = 4)
+    private String telephone;
 
-    @Schema(description = "更新时间")
-    @ExcelProperty(value = "更新时间", order = 8)
-    private LocalDateTime updateTime;
+    @ExcelProperty(value = "QQ",order = 4)
+    @Schema(description = "QQ",example = "197272662")
+    private Long qq;
+
+    @ExcelProperty(value = "微信",order = 4)
+    @Schema(description = "微信",example = "zzz3883")
+    private String wechat;
+
+    @Schema(description = "电子邮箱",example = "1111@22.com")
+    @ExcelProperty(value = "邮箱",order = 4)
+    private String email;
+
+    @Schema(description = "地区编号", example = "20158")
+    private Integer areaId;
+
+    @ExcelProperty(value = "地址",order = 5)
+    @Schema(description = "地址")
+    private String detailAddress;
+
+    @Schema(description = "备注", example = "你说的对")
+    @ExcelProperty(value = "备注",order = 6)
+    private String remark;
+
+    @Schema(description = "负责人用户编号", example = "14334")
+    @NotNull(message = "负责人不能为空")
+    private Long ownerUserId;
+
+    @Schema(description = "最后跟进时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ExcelProperty(value = "最后跟进时间",order = 6)
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "下次联系时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    @ExcelProperty(value = "下次联系时间",order = 6)
+    private LocalDateTime contactNextTime;
 
     @Schema(description = "创建人", example = "25682")
     private String creator;
 
     @Schema(description = "创建人名字", example = "test")
-    @ExcelProperty(value = "创建人", order = 8)
+    @ExcelProperty(value = "创建人",order = 8)
     private String creatorName;
 
     @ExcelProperty(value = "客户名称",order = 2)
@@ -36,15 +109,14 @@ public class CrmContactRespVO extends CrmContactBaseVO {
     private String customerName;
 
     @Schema(description = "负责人", example = "test")
-    @ExcelProperty(value = "负责人", order = 7)
+    @ExcelProperty(value = "负责人",order = 7)
     private String ownerUserName;
 
-    @Schema(description = "直属上级名", example = "芋头")
-    @ExcelProperty(value = "直属上级", order = 4)
+    @Schema(description = "直属上级名",example = "芋头")
+    @ExcelProperty(value = "直属上级",order = 4)
     private String parentName;
 
-    @Schema(description = "地区名", example = "上海上海市浦东新区")
-    @ExcelProperty(value = "地区", order = 5)
+    @Schema(description = "地区名",example = "上海上海市浦东新区")
+    @ExcelProperty(value = "地区",order = 5)
     private String areaName;
-
 }

+ 29 - 22
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java

@@ -5,12 +5,14 @@ import cn.iocoder.yudao.framework.common.validation.Telephone;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.Email;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDateTime;
@@ -18,86 +20,91 @@ import java.time.LocalDateTime;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
-// TODO zyna:要不按照新的,干掉这个 basevo,都放子类里
-/**
- * CRM 联系人 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
+@Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO")
 @Data
-@ExcelIgnoreUnannotated
-public class CrmContactBaseVO {
+@ToString(callSuper = true)
+public class CrmContactSaveReqVO  {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
+    private Long id;
 
-    @ExcelProperty(value = "姓名",order = 1)
     @Schema(description = "姓名", example = "芋艿")
     @NotNull(message = "姓名不能为空")
+    @DiffLogField(name = "姓名")
     private String name;
 
     @Schema(description = "客户编号", example = "10795")
+    @DiffLogField(name = "姓名",function = "getCustomerById")
     private Long customerId;
 
-    @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3)
     @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX)
     @Schema(description = "性别")
+    @DiffLogField(name = "性别",function = "getSexById")
     private Integer sex;
 
     @Schema(description = "职位")
-    @ExcelProperty(value = "职位", order = 3)
+    @DiffLogField(name = "职位")
     private String post;
 
     @Schema(description = "是否关键决策人")
-    @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3)
     @DictFormat(DictTypeConstants.BOOLEAN_STRING)
+    @DiffLogField(name = "关键决策人", function = "getBooleanById")
     private Boolean master;
 
     @Schema(description = "直属上级", example = "23457")
+    @DiffLogField(name = "直属上级",function = "getContactById")
     private Long parentId;
 
     @Schema(description = "手机号",example = "1387171766")
     @Mobile
-    @ExcelProperty(value = "手机号",order = 4)
+    @DiffLogField(name = "手机号")
     private String mobile;
 
-    @Schema(description = "座机",example = "021-0029922")
+    @Schema(description = "电话",example = "021-0029922")
     @Telephone
-    @ExcelProperty(value = "座机",order = 4)
+    @DiffLogField(name = "电话")
     private String telephone;
 
-    @ExcelProperty(value = "QQ",order = 4)
     @Schema(description = "QQ",example = "197272662")
+    @DiffLogField(name = "QQ")
     private Long qq;
 
-    @ExcelProperty(value = "微信",order = 4)
     @Schema(description = "微信",example = "zzz3883")
+    @DiffLogField(name = "微信")
     private String wechat;
 
     @Schema(description = "电子邮箱",example = "1111@22.com")
+    @DiffLogField(name = "邮箱")
     @Email
-    @ExcelProperty(value = "邮箱",order = 4)
     private String email;
 
     @Schema(description = "地区编号", example = "20158")
+    @DiffLogField(name = "所在地", function = "getAreaById")
     private Integer areaId;
 
-    @ExcelProperty(value = "地址",order = 5)
     @Schema(description = "地址")
+    @DiffLogField(name = "地址")
     private String detailAddress;
 
     @Schema(description = "备注", example = "你说的对")
-    @ExcelProperty(value = "备注",order = 6)
+    @DiffLogField(name = "备注")
     private String remark;
 
     @Schema(description = "负责人用户编号", example = "14334")
     @NotNull(message = "负责人不能为空")
+    @DiffLogField(name = "负责人",function = "getUserById")
     private Long ownerUserId;
 
     @Schema(description = "最后跟进时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @ExcelProperty(value = "最后跟进时间",order = 6)
+    @DiffLogField(name = "最后跟进时间")
     private LocalDateTime contactLastTime;
 
     @Schema(description = "下次联系时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
-    @ExcelProperty(value = "下次联系时间",order = 6)
+    @DiffLogField(name = "下次联系时间")
     private LocalDateTime contactNextTime;
 
+    @Schema(description = "关联商机ID", example = "122233")
+    private Long businessId;
 }

+ 0 - 20
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactUpdateReqVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-import jakarta.validation.constraints.NotNull;
-
-@Schema(description = "管理后台 - CRM 联系人更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmContactUpdateReqVO extends CrmContactBaseVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
-    @NotNull(message = "主键不能为空")
-    private Long id;
-
-}

+ 1 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java

@@ -29,9 +29,7 @@ public interface CrmContactConvert {
 
     CrmContactConvert INSTANCE = Mappers.getMapper(CrmContactConvert.class);
 
-    CrmContactDO convert(CrmContactCreateReqVO bean);
-
-    CrmContactDO convert(CrmContactUpdateReqVO bean);
+    CrmContactDO convert(CrmContactSaveReqVO bean);
 
     CrmContactRespVO convert(CrmContactDO bean);
 

+ 37 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBooleanParseFunction.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class CrmBooleanParseFunction implements IParseFunction {
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return "getBooleanById";
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString());
+    }
+
+}

+ 44 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
+import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class CrmContactParseFunction implements IParseFunction {
+
+    @Resource
+    private CrmContactService contactService;
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return "getContactById";
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        CrmContactDO contactDO = contactService.getContact(Long.parseLong(value.toString()));
+        return contactDO == null ? "" : contactDO.getName();
+    }
+
+}

+ 45 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class CrmCustomerParseFunction implements IParseFunction {
+
+    @Resource
+    private CrmCustomerService customerService;
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return "getCustomerById";
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        CrmCustomerDO crmCustomerDO = customerService.getCustomer(Long.parseLong(value.toString()));
+        return crmCustomerDO == null ? "" : crmCustomerDO.getName();
+    }
+
+}

+ 39 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmSexParseFunction.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class CrmSexParseFunction implements IParseFunction {
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return "getSexById";
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString());
+    }
+
+}

+ 44 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmSysUserParseFunction.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class CrmSysUserParseFunction implements IParseFunction {
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return "getUserById";
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        AdminUserRespDTO adminUserRespDTO = adminUserApi.getUser(Long.parseLong(value.toString()));
+        return adminUserRespDTO == null ? "" : adminUserRespDTO.getNickname();
+    }
+
+}

+ 3 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java

@@ -35,4 +35,7 @@ public interface CrmContactBusinessService {
      */
     List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId);
 
+
+    void deleteContactBusinessByContactId(Long contactId);
+
 }

+ 5 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java

@@ -80,4 +80,9 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
         return contactBusinessMapper.selectListByContactId(contactId);
     }
 
+    @Override
+    public void deleteContactBusinessByContactId(Long contactId) {
+        contactBusinessMapper.delete(CrmContactBusinessDO::getContactId,contactId);
+    }
+
 }

+ 9 - 6
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java

@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
@@ -26,14 +23,14 @@ public interface CrmContactService {
      * @param userId      用户编号
      * @return 编号
      */
-    Long createContact(@Valid CrmContactCreateReqVO createReqVO, Long userId);
+    Long createContact(@Valid CrmContactSaveReqVO createReqVO, Long userId);
 
     /**
      * 更新联系人
      *
      * @param updateReqVO 更新信息
      */
-    void updateContact(@Valid CrmContactUpdateReqVO updateReqVO);
+    void updateContact(@Valid CrmContactSaveReqVO updateReqVO);
 
     /**
      * 删除联系人
@@ -88,4 +85,10 @@ public interface CrmContactService {
      */
     void transferContact(CrmContactTransferReqVO reqVO, Long userId);
 
+    /**
+     * 获取联系人简单列表
+     * @return 联系人
+     */
+    List<CrmContactSimpleRespVO> simpleContactList();
+
 }

+ 71 - 20
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java

@@ -2,29 +2,43 @@ package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.impl.DiffParseFunction;
+import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CONTACT;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.ERROR_CODE_DUPLICATE;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
 
 /**
@@ -38,45 +52,68 @@ public class CrmContactServiceImpl implements CrmContactService {
 
     @Resource
     private CrmContactMapper contactMapper;
-
     @Resource
     private CrmCustomerService customerService;
     @Resource
     private CrmPermissionService crmPermissionService;
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private CrmContractService crmContractService;
+    @Resource
+    private CrmContactBusinessService contactBusinessService;
+    @Resource
+    private CrmBusinessService businessService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    // TODO @zyna:增加操作日志,可以参考 CustomerService;内容是 新建了联系人【名字】
-    public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) {
+    @LogRecord(type = CRM_CONTACT, subType = "创建联系人[{{#contactName}}]", bizNo = "{{#contactId}}", success = "创建了联系人")
+    public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) {
         // 1. 校验
         validateRelationDataExists(createReqVO);
 
         // 2. 插入联系人
         CrmContactDO contact = CrmContactConvert.INSTANCE.convert(createReqVO);
-        contactMapper.insert(contact);
+        int contactId = contactMapper.insert(contact);
 
         // 3. 创建数据权限
         crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
                 .setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId())
                 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
 
-        // TODO @zyna:特殊逻辑:如果在【商机】详情那,点击【新增联系人】时,可以自动绑定商机
+        //4.若传businessId自动关联商机
+        Optional.ofNullable(createReqVO.getBusinessId()).ifPresent(businessId -> {
+            CrmBusinessDO crmBusinessDO = businessService.getBusiness(createReqVO.getBusinessId());
+            if(crmBusinessDO == null){
+                throw exception(BUSINESS_NOT_EXISTS);
+            }
+            CrmContactBusinessReqVO crmContactBusinessReqVO = new CrmContactBusinessReqVO();
+            crmContactBusinessReqVO.setContactId(contact.getId());
+            crmContactBusinessReqVO.setBusinessIds(List.of(businessId));
+            contactBusinessService.createContactBusinessList(crmContactBusinessReqVO);
+        });
+
+        // 5. 记录操作日志
+        LogRecordContext.putVariable("contactId", contact.getId());
+        LogRecordContext.putVariable("contactName", contact.getName());
         return contact.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = CRM_CONTACT, subType = "更新联系人", bizNo = "{{#updateReqVO.id}}", success = "更新了联系人{_DIFF{#updateReqVO}}")
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
-    // TODO @zyna:增加操作日志,可以参考 CustomerService;需要 diff 出字段
-    public void updateContact(CrmContactUpdateReqVO updateReqVO) {
+    public void updateContact(CrmContactSaveReqVO updateReqVO) {
         // 1. 校验存在
-        validateContactExists(updateReqVO.getId());
+        CrmContactDO contactDO = validateContactExists(updateReqVO.getId());
         validateRelationDataExists(updateReqVO);
 
         // 2. 更新联系人
         CrmContactDO updateObj = CrmContactConvert.INSTANCE.convert(updateReqVO);
         contactMapper.updateById(updateObj);
+
+        // 3. 记录操作日志
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contactDO, CrmContactSaveReqVO.class));
     }
 
     /**
@@ -84,7 +121,7 @@ public class CrmContactServiceImpl implements CrmContactService {
      *
      * @param saveReqVO 新增/修改请求 VO
      */
-    private void validateRelationDataExists(CrmContactBaseVO saveReqVO) {
+    private void validateRelationDataExists(CrmContactSaveReqVO saveReqVO) {
         // 1. 校验客户
         if (saveReqVO.getCustomerId() != null && customerService.getCustomer(saveReqVO.getCustomerId()) == null) {
             throw exception(CUSTOMER_NOT_EXISTS);
@@ -101,24 +138,30 @@ public class CrmContactServiceImpl implements CrmContactService {
 
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
+    @Transactional(rollbackFor = Exception.class)
     public void deleteContact(Long id) {
-        // 校验存在
+        //1. 校验存在
         validateContactExists(id);
-        // TODO @zyna:如果有关联的合同,不允许删除;Contract.contactId
-
-        // 删除
+        //2.校验是否关联合同
+        CrmContractDO crmContractDO = crmContractService.getContractByContactId(id);
+        if(crmContractDO != null){
+            throw exception(CONTACT_CONTRACT_LINK_EXISTS);
+        }
+        //3.删除联系人
         contactMapper.deleteById(id);
-        // 删除数据权限
+        //4.删除数据权限
         crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
-        // TODO @zyna:删除商机联系人关联
-
+        //5.删除商机关联
+        contactBusinessService.deleteContactBusinessByContactId(id);
         // TODO @puhui999:删除跟进记录
     }
 
-    private void validateContactExists(Long id) {
-        if (contactMapper.selectById(id) == null) {
+    private CrmContactDO validateContactExists(Long id) {
+        CrmContactDO contactDO = contactMapper.selectById(id);
+        if (contactDO == null) {
             throw exception(CONTACT_NOT_EXISTS);
         }
+        return contactDO;
     }
 
     @Override
@@ -162,4 +205,12 @@ public class CrmContactServiceImpl implements CrmContactService {
         // 3. TODO 记录转移日志
     }
 
+    @Override
+    public List<CrmContactSimpleRespVO> simpleContactList() {
+        CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
+        pageReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmContactDO> list =contactMapper.selectPage(pageReqVO, getLoginUserId()).getList();
+        return BeanUtils.toBean(list, CrmContactSimpleRespVO.class);
+    }
+
 }

+ 7 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java

@@ -87,4 +87,11 @@ public interface CrmContractService {
      */
     void transferContract(CrmContractTransferReqVO reqVO, Long userId);
 
+    /**
+     * 查询合同,基于联系人
+     * @param contactId 联系人ID
+     * @return 合同
+     */
+    CrmContractDO getContractByContactId(Long contactId);
+
 }

+ 6 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.contract;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
@@ -135,5 +136,10 @@ public class CrmContractServiceImpl implements CrmContractService {
         contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
     }
 
+    @Override
+    public CrmContractDO getContractByContactId(Long contactId) {
+        return contractMapper.selectOne(CrmContractDO::getContactId, contactId);
+    }
+
     // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
 }