Commit 9cf4cfca86a9506eb17ade02f2a55ce6a0e06c0a
Merge branch 'feature/crm' of https://gitee.com/jiangwanwan/ruoyi-vue-pro into feature/crm
# Conflicts: # sql/mysql/crm.sql
Showing
9 changed files
with
96 additions
and
89 deletions
sql/mysql/crm.sql
| ... | ... | @@ -41,26 +41,26 @@ CREATE TABLE `crm_contract` |
| 41 | 41 | |
| 42 | 42 | DROP TABLE IF EXISTS `crm_clue`; |
| 43 | 43 | CREATE TABLE `crm_clue` ( |
| 44 | - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号,主键自增', | |
| 45 | - `transform_status` tinyint NOT NULL COMMENT '转化状态', | |
| 46 | - `follow_up_status` tinyint NOT NULL COMMENT '跟进状态', | |
| 47 | - `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '线索名称', | |
| 48 | - `customer_id` bigint NOT NULL COMMENT '客户id', | |
| 49 | - `contact_next_time` datetime NULL DEFAULT NULL COMMENT '下次联系时间', | |
| 50 | - `telephone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '电话', | |
| 51 | - `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号', | |
| 52 | - `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '地址', | |
| 53 | - `owner_user_id` bigint NULL DEFAULT NULL COMMENT '负责人的用户编号', | |
| 54 | - `contact_last_time` datetime NULL DEFAULT NULL COMMENT '最后跟进时间', | |
| 55 | - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', | |
| 56 | - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', | |
| 57 | - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |
| 58 | - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', | |
| 59 | - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', | |
| 60 | - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', | |
| 61 | - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', | |
| 62 | - PRIMARY KEY (`id`) USING BTREE | |
| 63 | -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '线索表' ROW_FORMAT = Dynamic; | |
| 44 | + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号,主键自增', | |
| 45 | + `transform_status` tinyint DEFAULT NULL COMMENT '转化状态', | |
| 46 | + `follow_up_status` tinyint DEFAULT NULL COMMENT '跟进状态', | |
| 47 | + `name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '线索名称', | |
| 48 | + `customer_id` bigint NOT NULL COMMENT '客户id', | |
| 49 | + `contact_next_time` datetime DEFAULT NULL COMMENT '下次联系时间', | |
| 50 | + `telephone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话', | |
| 51 | + `mobile` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号', | |
| 52 | + `address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址', | |
| 53 | + `owner_user_id` bigint NOT NULL COMMENT '负责人的用户编号', | |
| 54 | + `contact_last_time` datetime DEFAULT NULL COMMENT '最后跟进时间', | |
| 55 | + `remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', | |
| 56 | + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', | |
| 57 | + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |
| 58 | + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', | |
| 59 | + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', | |
| 60 | + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', | |
| 61 | + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', | |
| 62 | + PRIMARY KEY (`id`) USING BTREE | |
| 63 | +) ENGINE = InnoDB COMMENT = '线索表' ; | |
| 64 | 64 | |
| 65 | 65 | DROP TABLE IF EXISTS `crm_business`; |
| 66 | 66 | CREATE TABLE `crm_business` ( | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/Telephone.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.common.validation; | |
| 2 | + | |
| 3 | +import javax.validation.Constraint; | |
| 4 | +import javax.validation.Payload; | |
| 5 | +import java.lang.annotation.*; | |
| 6 | + | |
| 7 | +@Target({ | |
| 8 | + ElementType.METHOD, | |
| 9 | + ElementType.FIELD, | |
| 10 | + ElementType.ANNOTATION_TYPE, | |
| 11 | + ElementType.CONSTRUCTOR, | |
| 12 | + ElementType.PARAMETER, | |
| 13 | + ElementType.TYPE_USE | |
| 14 | +}) | |
| 15 | +@Retention(RetentionPolicy.RUNTIME) | |
| 16 | +@Documented | |
| 17 | +@Constraint( | |
| 18 | + validatedBy = TelephoneValidator.class | |
| 19 | +) | |
| 20 | +public @interface Telephone { | |
| 21 | + | |
| 22 | + String message() default "电话格式不正确"; | |
| 23 | + | |
| 24 | + Class<?>[] groups() default {}; | |
| 25 | + | |
| 26 | + Class<? extends Payload>[] payload() default {}; | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/TelephoneValidator.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.common.validation; | |
| 2 | + | |
| 3 | +import cn.hutool.core.text.CharSequenceUtil; | |
| 4 | +import cn.hutool.core.util.PhoneUtil; | |
| 5 | + | |
| 6 | +import javax.validation.ConstraintValidator; | |
| 7 | +import javax.validation.ConstraintValidatorContext; | |
| 8 | + | |
| 9 | +public class TelephoneValidator implements ConstraintValidator<Telephone, String> { | |
| 10 | + | |
| 11 | + @Override | |
| 12 | + public void initialize(Telephone annotation) { | |
| 13 | + } | |
| 14 | + | |
| 15 | + @Override | |
| 16 | + public boolean isValid(String value, ConstraintValidatorContext context) { | |
| 17 | + // 如果手机号为空,默认不校验,即校验通过 | |
| 18 | + if (CharSequenceUtil.isEmpty(value)) { | |
| 19 | + return true; | |
| 20 | + } | |
| 21 | + // 校验手机 | |
| 22 | + return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value); | |
| 23 | + } | |
| 24 | + | |
| 25 | +} | ... | ... |
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
| ... | ... | @@ -12,8 +12,8 @@ public interface ErrorCodeConstants { |
| 12 | 12 | // ========== 合同管理 1-020-000-000 ========== |
| 13 | 13 | ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); |
| 14 | 14 | |
| 15 | - // TODO @wanwan:要单独一个分段噢 | |
| 16 | - ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_000_001, "线索不存在"); | |
| 15 | + // ========== 线索管理 1-020-001-000 ========== | |
| 16 | + ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); | |
| 17 | 17 | |
| 18 | 18 | // ========== 商机管理 1-020-001-000 ========== |
| 19 | 19 | ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_001_000, "商机不存在"); | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.Mobile; |
| 4 | +import cn.iocoder.yudao.framework.common.validation.Telephone; | |
| 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 6 | import lombok.Data; |
| 6 | 7 | import org.springframework.format.annotation.DateTimeFormat; |
| 7 | 8 | |
| 9 | +import javax.validation.constraints.NotEmpty; | |
| 8 | 10 | import javax.validation.constraints.NotNull; |
| 9 | 11 | import java.time.LocalDateTime; |
| 10 | 12 | |
| ... | ... | @@ -17,44 +19,31 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ |
| 17 | 19 | @Data |
| 18 | 20 | public class CrmClueBaseVO { |
| 19 | 21 | |
| 20 | - // TODO @wanwan:转化状态,新增和修改的时候,应该不传递的哈;而是在未来的时候,才会更新到 | |
| 21 | - @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 22 | - @NotNull(message = "转化状态不能为空") | |
| 23 | - private Boolean transformStatus; | |
| 24 | - | |
| 25 | - // TODO @wanwan:同 transformStatus | |
| 26 | - @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 27 | - @NotNull(message = "跟进状态不能为空") | |
| 28 | - private Boolean followUpStatus; | |
| 29 | - | |
| 30 | 22 | @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") |
| 31 | - @NotNull(message = "线索名称不能为空") // TODO @wanwan:应该是 NotEmpty 噢。空串都无法接受 | |
| 23 | + @NotEmpty(message = "线索名称不能为空") | |
| 32 | 24 | private String name; |
| 33 | 25 | |
| 34 | - // TODO @wanwan:中英文之间,要有个空格;例如说,客户 id 不能为空 | |
| 35 | - @Schema(description = "客户id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") | |
| 36 | - @NotNull(message = "客户id不能为空") | |
| 26 | + @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") | |
| 27 | + @NotNull(message = "客户不能为空") | |
| 37 | 28 | private Long customerId; |
| 38 | 29 | |
| 39 | 30 | @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") |
| 40 | 31 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| 41 | 32 | private LocalDateTime contactNextTime; |
| 42 | 33 | |
| 43 | - // TODO @wanwan:@Schema 在 @Mobile 之前,要保持统一的顺序;2)可以加个 @Telephone 的校验格式;应该不是手机的格式哈 | |
| 44 | - @Mobile(message = "电话格式不正确") | |
| 45 | 34 | @Schema(description = "电话", example = "18000000000") |
| 35 | + @Telephone | |
| 46 | 36 | private String telephone; |
| 47 | 37 | |
| 48 | - // TODO @wanwan:@Schema 在 @Mobile 之前,要保持统一的顺序;2)类似 @Mobile 这个提示如果是默认的,就可以不写 message | |
| 49 | - @Mobile(message = "手机号格式不正确") | |
| 50 | 38 | @Schema(description = "手机号", example = "18000000000") |
| 39 | + @Mobile | |
| 51 | 40 | private String mobile; |
| 52 | 41 | |
| 53 | 42 | @Schema(description = "地址", example = "北京市海淀区") |
| 54 | 43 | private String address; |
| 55 | 44 | |
| 56 | 45 | @Schema(description = "负责人的用户编号", example = "27199") |
| 57 | - // TODO @wanwan:这个是必填字段哈; | |
| 46 | + @NotNull(message = "负责人不能为空") | |
| 58 | 47 | private Long ownerUserId; |
| 59 | 48 | |
| 60 | 49 | @Schema(description = "最后跟进时间") | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; | |
| 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | import lombok.*; |
| 5 | 6 | import java.util.*; |
| ... | ... | @@ -12,7 +13,6 @@ import com.alibaba.excel.annotation.ExcelProperty; |
| 12 | 13 | import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; |
| 13 | 14 | import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; |
| 14 | 15 | |
| 15 | - | |
| 16 | 16 | /** |
| 17 | 17 | * 线索 Excel VO |
| 18 | 18 | * |
| ... | ... | @@ -21,20 +21,21 @@ import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; |
| 21 | 21 | @Data |
| 22 | 22 | public class CrmClueExcelVO { |
| 23 | 23 | |
| 24 | - @ExcelProperty("编号,主键自增") | |
| 24 | + @ExcelProperty("编号") | |
| 25 | 25 | private Long id; |
| 26 | 26 | |
| 27 | 27 | @ExcelProperty(value = "转化状态", converter = DictConvert.class) |
| 28 | - @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中 | |
| 28 | + @DictFormat(DictTypeConstants.BOOLEAN_STRING) | |
| 29 | 29 | private Boolean transformStatus; |
| 30 | 30 | |
| 31 | 31 | @ExcelProperty(value = "跟进状态", converter = DictConvert.class) |
| 32 | - @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中 | |
| 32 | + @DictFormat(DictTypeConstants.BOOLEAN_STRING) | |
| 33 | 33 | private Boolean followUpStatus; |
| 34 | 34 | |
| 35 | 35 | @ExcelProperty("线索名称") |
| 36 | 36 | private String name; |
| 37 | 37 | |
| 38 | + // TODO 这里需要导出成客户名称 | |
| 38 | 39 | @ExcelProperty("客户id") |
| 39 | 40 | private Long customerId; |
| 40 | 41 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
| ... | ... | @@ -5,54 +5,18 @@ import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | import lombok.EqualsAndHashCode; |
| 7 | 7 | import lombok.ToString; |
| 8 | -import org.springframework.format.annotation.DateTimeFormat; | |
| 9 | - | |
| 10 | -import java.time.LocalDateTime; | |
| 11 | - | |
| 12 | -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 13 | 8 | |
| 14 | 9 | @Schema(description = "管理后台 - 线索分页 Request VO") |
| 15 | 10 | @Data |
| 16 | 11 | @EqualsAndHashCode(callSuper = true) |
| 17 | 12 | @ToString(callSuper = true) |
| 18 | 13 | public class CrmCluePageReqVO extends PageParam { |
| 19 | - | |
| 20 | - // TODO @wanwan:目前只要支持 name、mobile、telephone 的搜索即可;其它字段应该暂时不需要哈; | |
| 21 | - | |
| 22 | - @Schema(description = "转化状态", example = "true") | |
| 23 | - private Boolean transformStatus; | |
| 24 | - | |
| 25 | - @Schema(description = "跟进状态", example = "true") | |
| 26 | - private Boolean followUpStatus; | |
| 27 | - | |
| 28 | 14 | @Schema(description = "线索名称", example = "线索xxx") |
| 29 | 15 | private String name; |
| 30 | 16 | |
| 31 | - @Schema(description = "客户id", example = "520") | |
| 32 | - private Long customerId; | |
| 33 | - | |
| 34 | - @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") | |
| 35 | - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 36 | - private LocalDateTime[] contactNextTime; | |
| 37 | - | |
| 38 | 17 | @Schema(description = "电话", example = "18000000000") |
| 39 | 18 | private String telephone; |
| 40 | 19 | |
| 41 | 20 | @Schema(description = "手机号", example = "18000000000") |
| 42 | 21 | private String mobile; |
| 43 | - | |
| 44 | - @Schema(description = "地址", example = "北京市海淀区") | |
| 45 | - private String address; | |
| 46 | - | |
| 47 | - @Schema(description = "负责人的用户编号", example = "27199") | |
| 48 | - private Long ownerUserId; | |
| 49 | - | |
| 50 | - @Schema(description = "最后跟进时间") | |
| 51 | - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 52 | - private LocalDateTime[] contactLastTime; | |
| 53 | - | |
| 54 | - @Schema(description = "创建时间") | |
| 55 | - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 56 | - private LocalDateTime[] createTime; | |
| 57 | - | |
| 58 | 22 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java
| ... | ... | @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; |
| 2 | 2 | |
| 3 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import lombok.*; |
| 5 | + | |
| 6 | +import javax.validation.constraints.NotNull; | |
| 5 | 7 | import java.time.LocalDateTime; |
| 6 | 8 | |
| 7 | 9 | @Schema(description = "管理后台 - 线索 Response VO") |
| ... | ... | @@ -16,4 +18,10 @@ public class CrmClueRespVO extends CrmClueBaseVO { |
| 16 | 18 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 17 | 19 | private LocalDateTime createTime; |
| 18 | 20 | |
| 21 | + @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 22 | + private Boolean transformStatus; | |
| 23 | + | |
| 24 | + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 25 | + private Boolean followUpStatus; | |
| 26 | + | |
| 19 | 27 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
| ... | ... | @@ -19,17 +19,9 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> { |
| 19 | 19 | |
| 20 | 20 | default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO reqVO) { |
| 21 | 21 | return selectPage(reqVO, new LambdaQueryWrapperX<CrmClueDO>() |
| 22 | - .eqIfPresent(CrmClueDO::getTransformStatus, reqVO.getTransformStatus()) | |
| 23 | - .eqIfPresent(CrmClueDO::getFollowUpStatus, reqVO.getFollowUpStatus()) | |
| 24 | 22 | .likeIfPresent(CrmClueDO::getName, reqVO.getName()) |
| 25 | - .eqIfPresent(CrmClueDO::getCustomerId, reqVO.getCustomerId()) | |
| 26 | - .betweenIfPresent(CrmClueDO::getContactNextTime, reqVO.getContactNextTime()) | |
| 27 | 23 | .likeIfPresent(CrmClueDO::getTelephone, reqVO.getTelephone()) |
| 28 | 24 | .likeIfPresent(CrmClueDO::getMobile, reqVO.getMobile()) |
| 29 | - .likeIfPresent(CrmClueDO::getAddress, reqVO.getAddress()) | |
| 30 | - .eqIfPresent(CrmClueDO::getOwnerUserId, reqVO.getOwnerUserId()) | |
| 31 | - .betweenIfPresent(CrmClueDO::getContactLastTime, reqVO.getContactLastTime()) | |
| 32 | - .betweenIfPresent(CrmClueDO::getCreateTime, reqVO.getCreateTime()) | |
| 33 | 25 | .orderByDesc(CrmClueDO::getId)); |
| 34 | 26 | } |
| 35 | 27 | ... | ... |