diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index 824a2f862..7cb931689 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -6,16 +6,15 @@ import cn.iocoder.yudao.framework.common.util.io.IoUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - import java.io.IOException; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -44,6 +43,15 @@ public class BpmModelController { return success(model); } + @GetMapping("/get-by-key") + @Operation(summary = "获得模型") + @Parameter(name = "key", description = "流程标识", required = true, example = "oa_leave") + @PreAuthorize("@ss.hasPermission('bpm:model:query')") + public CommonResult getModelByKey(@RequestParam("key") String key) { + BpmModelRespVO model = modelService.getBpmnModelByKey(key); + return success(model); + } + @PostMapping("/create") @Operation(summary = "新建模型") @PreAuthorize("@ss.hasPermission('bpm:model:create')") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index d7cdd89cb..4c3b0ba10 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.bpm.service.definition; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; -import org.flowable.bpmn.model.BpmnModel; - import jakarta.validation.Valid; +import org.flowable.bpmn.model.BpmnModel; /** * Flowable流程模型接口 @@ -38,6 +37,14 @@ public interface BpmModelService { */ BpmModelRespVO getModel(String id); + /** + * 获得流程模块 + * + * @param key 流程标识 + * @return 流程模型 + */ + BpmModelRespVO getBpmnModelByKey(String key); + /** * 修改流程模型 * @@ -62,7 +69,7 @@ public interface BpmModelService { /** * 修改模型的状态,实际更新的部署的流程定义的状态 * - * @param id 编号 + * @param id 编号 * @param state 状态 */ void updateModelState(String id, Integer state); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index fecf2e1ff..e23ac90e5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.BpmnModel; @@ -29,8 +31,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -134,6 +134,19 @@ public class BpmModelServiceImpl implements BpmModelService { return modelRespVO; } + @Override + public BpmModelRespVO getBpmnModelByKey(String key) { + Model model = getModelByKey(key); + if (model == null) { + return null; + } + BpmModelRespVO modelRespVO = BpmModelConvert.INSTANCE.convert(model); + // 拼接 bpmn XML + byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId()); + modelRespVO.setBpmnXml(StrUtil.utf8Str(bpmnBytes)); + return modelRespVO; + } + @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) { diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index 35c8f98e8..9190880fa 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -11,6 +11,7 @@ public interface ErrorCodeConstants { // ========== 合同管理 1-020-000-000 ========== ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); + ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑"); // ========== 线索管理 1-020-001-000 ========== ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); @@ -48,6 +49,9 @@ public interface ErrorCodeConstants { ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限"); ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限"); ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}"); + ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!"); + ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!"); + ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!"); // ========== 权限管理 1_020_007_000 ========== ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml index 234cf268e..b905bf6da 100644 --- a/yudao-module-crm/yudao-module-crm-biz/pom.xml +++ b/yudao-module-crm/yudao-module-crm-biz/pom.xml @@ -27,6 +27,11 @@ yudao-module-crm-api ${revision} + + cn.iocoder.boot + yudao-module-bpm-api + ${revision} + @@ -37,6 +42,10 @@ cn.iocoder.boot yudao-spring-boot-starter-biz-ip + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + @@ -70,11 +79,5 @@ cn.iocoder.boot yudao-spring-boot-starter-test - - - - cn.iocoder.boot - yudao-spring-boot-starter-biz-tenant - diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java index c2935184f..7f2541ae7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java @@ -140,4 +140,12 @@ public class CrmContractController { return success(true); } + @PutMapping("/approve") + @Operation(summary = "发起合同审批流程") + @PreAuthorize("@ss.hasPermission('crm:contract:update')") + public CommonResult transfer(@RequestParam("id") Long id) { + contractService.handleApprove(id, getLoginUserId()); + return success(true); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java index e495bbfa5..ef90f2f2f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java @@ -7,10 +7,13 @@ import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFu import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; +import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @@ -35,10 +38,6 @@ public class CrmContractSaveReqVO { @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME) private Long businessId; - @Schema(description = "工作流编号", example = "1043") - @DiffLogField(name = "工作流编号") - private Long processInstanceId; - @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) @DiffLogField(name = "下单日期") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @@ -86,16 +85,33 @@ public class CrmContractSaveReqVO { @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME) private Long signUserId; - @Schema(description = "最后跟进时间") - @DiffLogField(name = "最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime contactLastTime; - @Schema(description = "备注", example = "你猜") @DiffLogField(name = "备注") private String remark; - // TODO @dhb52:增加一个 status 字段:具体有哪些值,你来枚举下;主要页面上有个【草稿】【提交审核】的流程,可以看看。然后要对接工作流,这块也可以看看,不确定的地方问我。 + @Schema(description = "审批状态", example = "1") + private Integer auditStatus; + @Schema(description = "产品列表") + private List productItems; + + @Schema(description = "商品属性") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CrmContractProductItem { + + @Schema(description = "产品编号", example = "20529") + @NotNull(message = "产品编号不能为空") + private Long id; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @NotNull(message = "产品数量不能为空") + private Integer count; + + @Schema(description = "产品折扣") + private Integer discountPercent; + + } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java index 1eab89073..3bbd4dfd7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; @@ -29,9 +30,11 @@ import org.mapstruct.ap.internal.util.Collections; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -198,6 +201,36 @@ public class CrmCustomerController { BeanUtils.toBean(list, CrmCustomerRespVO.class)); } + @GetMapping("/get-import-template") + @Operation(summary = "获得导入客户模板") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Arrays.asList( + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("") + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") + .areaId(null).detailAddress("").build(), + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("") + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") + .areaId(null).detailAddress("").build() + ); + // 输出 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入客户") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + @PreAuthorize("@ss.hasPermission('system:customer:import')") + public CommonResult importExcel(@RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + List list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class); + return success(customerService.importCustomerList(list, updateSupport, getLoginUserId())); + } + + @PutMapping("/transfer") @Operation(summary = "转移客户") @PreAuthorize("@ss.hasPermission('crm:customer:update')") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java new file mode 100644 index 000000000..378abb6dd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; + +/** + * 客户 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题 +public class CrmCustomerImportExcelVO { + + @ExcelProperty("客户名称") + private String name; + + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_LEVEL) + private Integer level; + + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_SOURCE) + private Integer source; + + @ExcelProperty("手机") + private String mobile; + + @ExcelProperty("电话") + private String telephone; + + @ExcelProperty("网址") + private String website; + + @Size(max = 20, message = "QQ长度不能超过 20 个字符") + @ExcelProperty("QQ") + private String qq; + + @Size(max = 255, message = "微信长度不能超过 255 个字符") + @ExcelProperty("微信") + private String wechat; + + @Size(max = 255, message = "邮箱长度不能超过 255 个字符") + @ExcelProperty("邮箱") + private String email; + + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") + @ExcelProperty("客户描述") + private String description; + + @ExcelProperty("备注") + private String remark; + + @ExcelProperty("地区编号") + private Integer areaId; + + @ExcelProperty("详细地址") + private String detailAddress; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java new file mode 100644 index 000000000..de35b7b92 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 客户导入 Response VO") +@Data +@Builder +public class CrmCustomerImportRespVO { + + @Schema(description = "创建成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createCustomerNames; + + @Schema(description = "更新成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateCustomerNames; + + @Schema(description = "导入失败的客户集合,key 为客户名,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureCustomerNames; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java index b1275f4c5..83121dbdd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java @@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; -import java.math.BigDecimal; - /** * 商机产品关联表 DO * @@ -29,14 +28,12 @@ public class CrmBusinessProductDO extends BaseDO { */ @TableId private Long id; - /** * 商机编号 * * 关联 {@link CrmBusinessDO#getId()} */ private Long businessId; - /** * 产品编号 * @@ -50,29 +47,27 @@ public class CrmBusinessProductDO extends BaseDO { private Integer price; /** - * 销售价格 + * 销售价格, 单位:分 */ - private BigDecimal salesPrice; - + private Integer salesPrice; /** * 数量 */ - private BigDecimal count; - - // TODO @lzxhqs:改成 discountPercent + private Integer count; /** * 折扣 */ - private BigDecimal discountPercent; - - // TODO @lzxhqs:改成 totalPrice;总计价格,和现有项目风格一致; + private Integer discountPercent; /** - * 小计(折扣后价格) + * 总计价格(折扣后价格) */ - private BigDecimal totalPrice; + private Integer totalPrice; /** * 单位 + * + * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT} */ - private String unit; + private Integer unit; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java index bdaff7271..e70e7a3cb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java @@ -45,7 +45,7 @@ public class CrmContractDO extends BaseDO { /** * 工作流编号 */ - private Long processInstanceId; + private String processInstanceId; /** * 下单日期 */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java index a4ac3d3d7..6523782dd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.product; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -38,7 +39,7 @@ public class CrmProductDO extends BaseDO { /** * 单位 * - * 字典 {@link cn.iocoder.yudao.module.crm.enums.DictTypeConstants#CRM_PRODUCT_UNIT} + * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT} */ private Integer unit; /** diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java index f4e33dfa0..a3e631201 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java @@ -41,7 +41,7 @@ public interface CrmContractMapper extends BaseMapperX { default PageResult selectPage(CrmContractPageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX mpjLambdaWrapperX = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTRACT.getType(), CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 mpjLambdaWrapperX.selectAll(CrmContractDO.class) @@ -56,7 +56,7 @@ public interface CrmContractMapper extends BaseMapperX { default List selectBatchIds(Collection ids, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 构建数据权限连表条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId); + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId); // 拼接自身的查询条件 query.selectAll(CrmContractDO.class).in(CrmContractDO::getId, ids).orderByDesc(CrmContractDO::getId); return selectJoinList(CrmContractDO.class, query); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java index 09ff70a79..14732641d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java @@ -101,11 +101,15 @@ public interface CrmCustomerMapper extends BaseMapperX { return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); } - default List selectListByLockStatusAndOwnerUserIdNotNull(Boolean lockStatus) { + default List selectListByLockAndDealStatusAndNotPool(Boolean lockStatus, Boolean dealStatus) { return selectList(new LambdaQueryWrapper() .eq(CrmCustomerDO::getLockStatus, lockStatus) - // TODO @puhui999:not null 可以转化成大于 0 - .isNotNull(CrmCustomerDO::getOwnerUserId)); + .eq(CrmCustomerDO::getDealStatus, dealStatus) + .gt(CrmCustomerDO::getOwnerUserId, 0)); + } + + default CrmCustomerDO selectByCustomerName(String name) { + return selectOne(CrmCustomerDO::getName, name); } default PageResult selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java index 4e5898455..43bb729b6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java @@ -1,17 +1,9 @@ package cn.iocoder.yudao.module.crm.framework.permission.core.util; -import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; -import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum; -import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.system.api.permission.PermissionApi; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; /** @@ -30,22 +22,6 @@ public class CrmPermissionUtils { return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode()); } - // TODO @puhui999:这个貌似直接放到 CrmPermissionService 会更好? - /** - * 校验权限 - * - * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} - * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() - * @param userId 用户编号 - * @param levelEnum 权限级别 - * @return boolean - */ - public static boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) { - List permissionList = SingletonManager.getCrmPermissionService().getPermissionListByBiz(bizType, bizId); - return anyMatch(permissionList, permission -> - ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel())); - } - /** * 静态内部类实现单例获取 * @@ -54,16 +30,11 @@ public class CrmPermissionUtils { private static class SingletonManager { private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class); - private static final CrmPermissionService CRM_PERMISSION_SERVICE = SpringUtil.getBean(CrmPermissionService.class); public static PermissionApi getPermissionApi() { return PERMISSION_API; } - public static CrmPermissionService getCrmPermissionService() { - return CRM_PERMISSION_SERVICE; - } - } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java index 3d71df4f4..6f5817015 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java @@ -20,7 +20,7 @@ public class CrmCustomerAutoPutPoolJob implements JobHandler { @Override @TenantJob public String execute(String param) { - int count = customerService.customerAutoPutPoolBySystem(); + int count = customerService.autoPutCustomerPool(); return String.format("掉入公海客户 %s 个", count); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java index 6695e4f5d..9847af328 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java @@ -57,6 +57,14 @@ public interface CrmContractService { */ void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO); + /** + * 发起合同审批流程 + * + * @param id 合同编号 + * @param userId 用户编号 + */ + void handleApprove(Long id, Long userId); + /** * 获得合同 * @@ -111,9 +119,11 @@ public interface CrmContractService { Long getContractCountByCustomerId(Long customerId); /** - * 根据商机ID获取关联客户的合同数量 TODO @lzxhqs:1)方法注释,和参数注释之间要有空行;2)中英文之间有空格,更清晰,例如说 商机 ID - * @param businessId 商机ID + * 根据商机ID获取关联客户的合同数量 + * + * @param businessId 商机编号 * @return 数量 */ Long selectCountByBusinessId(Long businessId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java index 1f1561f9b..59274b522 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java @@ -2,20 +2,33 @@ 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.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; 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.CrmBusinessProductService; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +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; @@ -26,10 +39,14 @@ import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTRACT_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; /** * CRM 合同 Service 实现类 @@ -40,21 +57,36 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; @Validated public class CrmContractServiceImpl implements CrmContractService { + public static final String CONTRACT_APPROVE = "contract-approve"; // 合同审批流程标识 + @Resource private CrmContractMapper contractMapper; @Resource private CrmPermissionService crmPermissionService; + @Resource + private CrmBusinessProductService businessProductService; + @Resource + private CrmProductService productService; + @Resource + private BpmProcessInstanceApi bpmProcessInstanceApi; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmContactService contactService; + @Resource + private CrmBusinessService businessService; + @Resource + private AdminUserApi adminUserApi; @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}", success = CRM_CONTRACT_CREATE_SUCCESS) public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) { - createReqVO.setId(null); - // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO + validateRelationDataExists(createReqVO); // 插入合同 - CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class); + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null); contractMapper.insert(contract); // 创建数据权限 @@ -62,6 +94,9 @@ public class CrmContractServiceImpl implements CrmContractService { .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + // 插入商机关联商品 + List businessProduct = convertBusinessProductList(createReqVO); + businessProductService.insertBatch(businessProduct); // 4. 记录操作日志上下文 LogRecordContext.putVariable("contract", contract); return contract.getId(); @@ -74,12 +109,21 @@ public class CrmContractServiceImpl implements CrmContractService { @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateContract(CrmContractSaveReqVO updateReqVO) { // TODO @合同待定:只有草稿、审批中,可以编辑; + if (ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus()) || + ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { + throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED); + } + validateRelationDataExists(updateReqVO); // 校验存在 CrmContractDO oldContract = validateContractExists(updateReqVO.getId()); // 更新合同 CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class); contractMapper.updateById(updateObj); - // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO + + // TODO puhui999: @芋艿:合同变更关联的商机后商品怎么处理? + //List businessProduct = convertBusinessProductList(updateReqVO); + //businessProductService.selectListByBusinessId() + //diffList() // 3. 记录操作日志上下文 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class)); @@ -88,8 +132,55 @@ public class CrmContractServiceImpl implements CrmContractService { // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum - // TODO @合同待定:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + private List convertBusinessProductList(CrmContractSaveReqVO reqVO) { + // 校验商品存在 + Set productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId); + List productList = productService.getProductList(productIds); + if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) { + throw exception(PRODUCT_NOT_EXISTS); + } + Map productMap = convertMap(productList, CrmProductDO::getId); + return convertList(reqVO.getProductItems(), productItem -> { + CrmBusinessProductDO businessProduct = BeanUtils.toBean(productMap.get(productItem.getId()), CrmBusinessProductDO.class); + businessProduct.setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()) + .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()).setTotalPrice(calculator(businessProduct)); + return businessProduct; + }); + } + /** + * 计算商品总价 + * + * @param businessProduct 关联商品 + * @return 商品总价 + */ + private Integer calculator(CrmBusinessProductDO businessProduct) { + int price = businessProduct.getPrice() * businessProduct.getCount(); + if (businessProduct.getDiscountPercent() == null) { + return price; + } + return MoneyUtils.calculateRatePriceFloor(price, (double) (businessProduct.getDiscountPercent() / 100)); + } + + /** + * 校验关联数据是否存在 + * + * @param reqVO 请求 + */ + private void validateRelationDataExists(CrmContractSaveReqVO reqVO) { + // 1. 校验客户 + if (reqVO.getCustomerId() != null && customerService.getCustomer(reqVO.getCustomerId()) == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 2. 校验负责人 + if (reqVO.getOwnerUserId() != null && adminUserApi.getUser(reqVO.getOwnerUserId()) == null) { + throw exception(USER_NOT_EXISTS); + } + // 4. 如果有关联商机,则需要校验存在 + if (reqVO.getBusinessId() != null && businessService.getBusiness(reqVO.getBusinessId()) == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + } @Override @Transactional(rollbackFor = Exception.class) @@ -141,6 +232,18 @@ public class CrmContractServiceImpl implements CrmContractService { contractMapper.updateById(BeanUtils.toBean(contractUpdateFollowUpReqBO, CrmContractDO.class).setId(contractUpdateFollowUpReqBO.getBizId())); } + @Override + @Transactional(rollbackFor = Exception.class) + public void handleApprove(Long id, Long userId) { + // 创建合同审批流程实例 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() + .setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id))); + + // 更新合同工作流编号 + contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId) + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); + } + //======================= 查询相关 ======================= @Override @@ -182,6 +285,5 @@ public class CrmContractServiceImpl implements CrmContractService { public Long selectCountByBusinessId(Long businessId) { return contractMapper.selectCountByBusinessId(businessId); } - // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java new file mode 100644 index 000000000..e393c2664 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java @@ -0,0 +1,5 @@ +package cn.iocoder.yudao.module.crm.service.contract.listener; + +public class CrmContractResultListener { + // TODO puhui999: @芋艿: 艿艿写一下这个,没研究明白哈哈 +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java index 589fcbf20..259812e4b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java @@ -1,10 +1,7 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; @@ -109,6 +106,16 @@ public interface CrmCustomerService { */ Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId); + /** + * 批量导入客户 + * + * @param importCustomers 导入客户列表 + * @param isUpdateSupport 是否支持更新 + * @param userId 用户编号 + * @return 导入结果 + */ + CrmCustomerImportRespVO importCustomerList(List importCustomers, Boolean isUpdateSupport, Long userId); + // ==================== 公海相关操作 ==================== /** @@ -127,13 +134,12 @@ public interface CrmCustomerService { */ void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive); - // TODO @puhui999:autoPutCustomerPool,注释说明是系统就好哈; /** * 【系统】客户自动掉入公海 * * @return 掉入公海数量 */ - int customerAutoPutPoolBySystem(); + int autoPutCustomerPool(); PageResult getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, CrmCustomerPoolConfigDO poolConfigDO, diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java index b84cc4414..26e0e2346 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java @@ -3,15 +3,14 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; @@ -41,12 +40,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; @@ -96,7 +93,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1); // 2. 插入客户 - CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class) + CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class).setOwnerUserId(userId) .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); customerMapper.insert(customer); @@ -235,7 +232,55 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { return customer.getId(); } -// ==================== 公海相关操作 ==================== + @Override + public CrmCustomerImportRespVO importCustomerList(List importCustomers, Boolean isUpdateSupport, Long userId) { + if (CollUtil.isEmpty(importCustomers)) { + throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); + } + CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>()) + .updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build(); + importCustomers.forEach(importCustomer -> { + // 校验,判断是否有不符合的原因 + try { + validateCustomerForCreate(importCustomer); + } catch (ServiceException ex) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), ex.getMessage()); + return; + } + // 判断如果不存在,在进行插入 + CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName()); + if (existCustomer == null) { + CrmCustomerDO customer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class).setOwnerUserId(userId) + .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); + customerMapper.insert(customer); + respVO.getCreateCustomerNames().add(importCustomer.getName()); + // 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + return; + } + // 如果存在,判断是否允许更新 + if (!isUpdateSupport) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), + StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName())); + return; + } + CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class); + updateCustomer.setId(existCustomer.getId()); + customerMapper.updateById(updateCustomer); + respVO.getUpdateCustomerNames().add(importCustomer.getName()); + }); + return respVO; + } + + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { + // 校验客户名称不能为空 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) { + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL); + } + } + + // ==================== 公海相关操作 ==================== @Override @Transactional(rollbackFor = Exception.class) @@ -313,23 +358,22 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { } @Override - public int customerAutoPutPoolBySystem() { + public int autoPutCustomerPool() { CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); if (poolConfig == null || !poolConfig.getEnabled()) { return 0; } - // 1. 获取没有锁定的不在公海的客户 - List customerList = customerMapper.selectListByLockStatusAndOwnerUserIdNotNull(Boolean.FALSE); - List poolCustomerList = CollectionUtils.filterList(customerList, customer -> { - // TODO @puhui999:建议这里作为一个查询条件哈,不放内存里过滤; - // 1.1 未成交放入公海 - if (!customer.getDealStatus()) { - return (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0; - } - // 1.2 未跟进放入公海 + // 1.1 获取没有锁定的不在公海的客户且没有成交的 + List notDealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.FALSE); + // 1.2 获取没有锁定的不在公海的客户且成交的 + List dealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.TRUE); + List poolCustomerList = new ArrayList<>(); + poolCustomerList.addAll(filterList(notDealCustomerList, customer -> + (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0)); + poolCustomerList.addAll(filterList(dealCustomerList, customer -> { LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; - }); + })); // 2. 逐个放入公海 int count = 0; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java index 9d6f87faa..88f0b887c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java @@ -33,7 +33,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; -import static cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils.hasPermission; /** * 跟进记录 Service 实现类 @@ -119,7 +118,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { // 校验存在 CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id); // 校验权限 - if (!hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) { + if (!permissionService.hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) { throw exception(FOLLOW_UP_RECORD_DELETE_DENIED); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java index e822ac2ff..3385899fe 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java @@ -108,4 +108,15 @@ public interface CrmPermissionService { */ List getPermissionListByBizTypeAndUserId(Integer bizType, Long userId); + /** + * 校验权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @param userId 用户编号 + * @param levelEnum 权限级别 + * @return boolean + */ + boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java index 07818bd07..36a616938 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner; @@ -211,4 +212,11 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { return permissionMapper.selectListByBizTypeAndUserId(bizType, userId); } + @Override + public boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) { + List permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId); + return anyMatch(permissionList, permission -> + ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel())); + } + }