Commit edea17e781fbca173c650f647e9b121052e2bc40

Authored by dhb52
2 parents 677a6d34 82789780

Merge remote-tracking branch 'upstream/master'

Showing 320 changed files with 14699 additions and 140 deletions

Too many changes.

To preserve performance only 100 of 320 files are displayed.

@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
21 <!-- <module>yudao-module-mp</module>--> 21 <!-- <module>yudao-module-mp</module>-->
22 <!-- <module>yudao-module-pay</module>--> 22 <!-- <module>yudao-module-pay</module>-->
23 <!-- <module>yudao-module-mall</module>--> 23 <!-- <module>yudao-module-mall</module>-->
  24 +<!-- <module>yudao-module-crm</module>-->
24 <!-- 示例项目 --> 25 <!-- 示例项目 -->
25 <!-- <module>yudao-example</module>--> 26 <!-- <module>yudao-example</module>-->
26 </modules> 27 </modules>
sql/mysql/crm.sql 0 → 100644
  1 +SET NAMES utf8mb4;
sql/mysql/crm_data.sql 0 → 100644
  1 +
  2 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (184, '回款管理审批状态', 'crm_receivable_check_status', 0, '回款管理审批状态(0 未审核 1 审核通过 2 审核拒绝 3 审核中 4 已撤回)', '1', '2023-10-18 21:44:24', '1', '2023-10-18 21:44:24', b'0', '1970-01-01 00:00:00');
  3 +
  4 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (185, '回款管理-回款方式', 'crm_return_type', 0, '回款管理-回款方式', '1', '2023-10-18 21:54:10', '1', '2023-10-18 21:54:10', b'0', '1970-01-01 00:00:00');
  5 +
  6 +
  7 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1389, 0, '未审核', '0', 'crm_receivable_check_status', 0, 'default', '', '0 未审核 ', '1', '2023-10-18 21:46:00', '1', '2023-10-18 21:47:16', b'0');
  8 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1390, 1, '审核通过', '1', 'crm_receivable_check_status', 0, 'default', '', '1 审核通过', '1', '2023-10-18 21:46:18', '1', '2023-10-18 21:47:08', b'0');
  9 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1391, 2, '审核拒绝', '2', 'crm_receivable_check_status', 0, 'default', '', ' 2 审核拒绝', '1', '2023-10-18 21:46:58', '1', '2023-10-18 21:47:21', b'0');
  10 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1392, 3, '审核中', '3', 'crm_receivable_check_status', 0, 'default', '', ' 3 审核中', '1', '2023-10-18 21:47:35', '1', '2023-10-18 21:47:35', b'0');
  11 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1393, 4, '已撤回', '4', 'crm_receivable_check_status', 0, 'default', '', ' 4 已撤回', '1', '2023-10-18 21:47:46', '1', '2023-10-18 21:47:46', b'0');
  12 +
  13 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1394, 1, '支票', '1', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:29', '1', '2023-10-18 21:54:29', b'0');
  14 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1395, 2, '现金', '2', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:41', '1', '2023-10-18 21:54:41', b'0');
  15 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1396, 3, '邮政汇款', '3', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:53', '1', '2023-10-18 21:54:53', b'0');
  16 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1397, 4, '电汇', '4', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:07', '1', '2023-10-18 21:55:07', b'0');
  17 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1398, 5, '网上转账', '5', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:24', '1', '2023-10-18 21:55:24', b'0');
  18 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1399, 6, '支付宝', '6', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:38', '1', '2023-10-18 21:55:38', b'0');
  19 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1400, 7, '微信支付', '7', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:53', '1', '2023-10-18 21:55:53', b'0');
  20 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1401, 8, '其他', '8', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:56:06', '1', '2023-10-18 21:56:06', b'0');
sql/mysql/crm_menu.sql 0 → 100644
  1 +-- ----------------------------
  2 +-- 客户公海配置
  3 +-- ----------------------------
  4 +-- 菜单 SQL
  5 +INSERT INTO system_menu(
  6 + name, permission, type, sort, parent_id,
  7 + path, icon, component, status, component_name
  8 +)
  9 +VALUES (
  10 + '客户公海配置', '', 2, 0, 2397,
  11 + 'customer-pool-config', 'ep:data-analysis', 'crm/customerPoolConf/index', 0, 'CustomerPoolConf'
  12 +);
  13 +
  14 +-- 按钮父菜单ID
  15 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  16 +SELECT @parentId := LAST_INSERT_ID();
  17 +
  18 +-- 按钮 SQL
  19 +INSERT INTO system_menu(
  20 + name, permission, type, sort, parent_id,
  21 + path, icon, component, status
  22 +)
  23 +VALUES (
  24 + '客户公海配置保存', 'crm:customer-pool-config:update', 3, 1, @parentId,
  25 + '', '', '', 0
  26 +);
  27 +
  28 +
  29 +
  30 +
  31 +-- ----------------------------
  32 +-- 客户限制配置管理
  33 +-- ----------------------------
  34 +-- 菜单 SQL
  35 +INSERT INTO system_menu(
  36 + name, permission, type, sort, parent_id,
  37 + path, icon, component, status, component_name
  38 +)
  39 +VALUES (
  40 + '客户限制配置管理', '', 2, 0, 2397,
  41 + 'customer-limit-config', '', 'crm/customerLimitConfig/index', 0, 'CrmCustomerLimitConfig'
  42 +);
  43 +
  44 +-- 按钮父菜单ID
  45 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  46 +SELECT @parentId := LAST_INSERT_ID();
  47 +
  48 +-- 按钮 SQL
  49 +INSERT INTO system_menu(
  50 + name, permission, type, sort, parent_id,
  51 + path, icon, component, status
  52 +)
  53 +VALUES (
  54 + '客户限制配置查询', 'crm:customer-limit-config:query', 3, 1, @parentId,
  55 + '', '', '', 0
  56 +);
  57 +INSERT INTO system_menu(
  58 + name, permission, type, sort, parent_id,
  59 + path, icon, component, status
  60 +)
  61 +VALUES (
  62 + '客户限制配置创建', 'crm:customer-limit-config:create', 3, 2, @parentId,
  63 + '', '', '', 0
  64 +);
  65 +INSERT INTO system_menu(
  66 + name, permission, type, sort, parent_id,
  67 + path, icon, component, status
  68 +)
  69 +VALUES (
  70 + '客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, @parentId,
  71 + '', '', '', 0
  72 +);
  73 +INSERT INTO system_menu(
  74 + name, permission, type, sort, parent_id,
  75 + path, icon, component, status
  76 +)
  77 +VALUES (
  78 + '客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, @parentId,
  79 + '', '', '', 0
  80 +);
  81 +INSERT INTO system_menu(
  82 + name, permission, type, sort, parent_id,
  83 + path, icon, component, status
  84 +)
  85 +VALUES (
  86 + '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, @parentId,
  87 + '', '', '', 0
  88 +);
sql/mysql/pay_wallet.sql
@@ -246,3 +246,11 @@ VALUES ( @@ -246,3 +246,11 @@ VALUES (
246 '转账订单', '', 2, 3, 1117, 246 '转账订单', '', 2, 3, 1117,
247 'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer' 247 'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer'
248 ); 248 );
  249 +
  250 +-- 转账通知脚本
  251 +
  252 +ALTER TABLE `pay_app`
  253 + ADD COLUMN `transfer_notify_url` varchar(1024) NOT NULL COMMENT '转账结果的回调地址' AFTER `refund_notify_url`;
  254 +ALTER TABLE `pay_notify_task`
  255 + MODIFY COLUMN `merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '商户订单编号' AFTER `status`,
  256 + ADD COLUMN `merchant_transfer_id` varchar(64) COMMENT '商户转账单编号' AFTER `merchant_order_id`;
249 \ No newline at end of file 257 \ No newline at end of file
sql/mysql/ruoyi-vue-pro.sql
@@ -535,8 +535,8 @@ CREATE TABLE `infra_demo01_contact` ( @@ -535,8 +535,8 @@ CREATE TABLE `infra_demo01_contact` (
535 `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', 535 `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
536 `sex` tinyint(1) NOT NULL COMMENT '性别', 536 `sex` tinyint(1) NOT NULL COMMENT '性别',
537 `birthday` datetime NOT NULL COMMENT '出生年', 537 `birthday` datetime NOT NULL COMMENT '出生年',
538 - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介',  
539 - `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', 538 + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介',
  539 + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像',
540 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', 540 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
541 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 541 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
542 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', 542 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -651,7 +651,7 @@ CREATE TABLE `infra_demo03_student` ( @@ -651,7 +651,7 @@ CREATE TABLE `infra_demo03_student` (
651 `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', 651 `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
652 `sex` tinyint NOT NULL COMMENT '性别', 652 `sex` tinyint NOT NULL COMMENT '性别',
653 `birthday` datetime NOT NULL COMMENT '出生日期', 653 `birthday` datetime NOT NULL COMMENT '出生日期',
654 - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介', 654 + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介',
655 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', 655 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
656 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 656 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
657 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', 657 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java
@@ -35,9 +35,12 @@ public class ServiceErrorCodeRange { @@ -35,9 +35,12 @@ public class ServiceErrorCodeRange {
35 // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000) 35 // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
36 // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000) 36 // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
37 // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000) 37 // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
38 - // 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)  
39 // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000) 38 // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
  39 +
  40 + // 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
40 // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000) 41 // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
41 // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000) 42 // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
42 43
  44 + // 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)
  45 +
43 } 46 }
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -280,6 +280,15 @@ public class CollectionUtils { @@ -280,6 +280,15 @@ public class CollectionUtils {
280 return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); 280 return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
281 } 281 }
282 282
  283 + public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
  284 + Function<? super T, ? extends U> mapper,
  285 + Function<U, ? extends Stream<? extends R>> func) {
  286 + if (CollUtil.isEmpty(from)) {
  287 + return new ArrayList<>();
  288 + }
  289 + return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
  290 + }
  291 +
283 public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from, 292 public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
284 Function<T, ? extends Stream<? extends U>> func) { 293 Function<T, ? extends Stream<? extends U>> func) {
285 if (CollUtil.isEmpty(from)) { 294 if (CollUtil.isEmpty(from)) {
@@ -288,4 +297,13 @@ public class CollectionUtils { @@ -288,4 +297,13 @@ public class CollectionUtils {
288 return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); 297 return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
289 } 298 }
290 299
  300 + public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
  301 + Function<? super T, ? extends U> mapper,
  302 + Function<U, ? extends Stream<? extends R>> func) {
  303 + if (CollUtil.isEmpty(from)) {
  304 + return new HashSet<>();
  305 + }
  306 + return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
  307 + }
  308 +
291 } 309 }
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-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; 6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
7 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; 7 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
8 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; 8 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
  9 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
9 10
10 import java.util.Map; 11 import java.util.Map;
11 12
@@ -86,4 +87,12 @@ public interface PayClient { @@ -86,4 +87,12 @@ public interface PayClient {
86 */ 87 */
87 PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); 88 PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO);
88 89
  90 + /**
  91 + * 获得转账订单信息
  92 + *
  93 + * @param outTradeNo 外部订单号
  94 + * @param type 转账类型
  95 + * @return 转账信息
  96 + */
  97 + PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
89 } 98 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java
@@ -53,11 +53,24 @@ public class PayTransferRespDTO { @@ -53,11 +53,24 @@ public class PayTransferRespDTO {
53 /** 53 /**
54 * 创建【WAITING】状态的转账返回 54 * 创建【WAITING】状态的转账返回
55 */ 55 */
56 - public static PayTransferRespDTO waitingOf(String channelOrderNo, 56 + public static PayTransferRespDTO waitingOf(String channelTransferNo,
57 String outTransferNo, Object rawData) { 57 String outTransferNo, Object rawData) {
58 PayTransferRespDTO respDTO = new PayTransferRespDTO(); 58 PayTransferRespDTO respDTO = new PayTransferRespDTO();
59 respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); 59 respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
60 - respDTO.channelTransferNo = channelOrderNo; 60 + respDTO.channelTransferNo = channelTransferNo;
  61 + respDTO.outTransferNo = outTransferNo;
  62 + respDTO.rawData = rawData;
  63 + return respDTO;
  64 + }
  65 +
  66 + /**
  67 + * 创建【IN_PROGRESS】状态的转账返回
  68 + */
  69 + public static PayTransferRespDTO dealingOf(String channelTransferNo,
  70 + String outTransferNo, Object rawData) {
  71 + PayTransferRespDTO respDTO = new PayTransferRespDTO();
  72 + respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus();
  73 + respDTO.channelTransferNo = channelTransferNo;
61 respDTO.outTransferNo = outTransferNo; 74 respDTO.outTransferNo = outTransferNo;
62 respDTO.rawData = rawData; 75 respDTO.rawData = rawData;
63 return respDTO; 76 return respDTO;
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
@@ -188,11 +188,11 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen @@ -188,11 +188,11 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
188 188
189 @Override 189 @Override
190 public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { 190 public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
  191 + validatePayTransferReqDTO(reqDTO);
191 PayTransferRespDTO resp; 192 PayTransferRespDTO resp;
192 - try{  
193 - validatePayTransferReqDTO(reqDTO); 193 + try {
194 resp = doUnifiedTransfer(reqDTO); 194 resp = doUnifiedTransfer(reqDTO);
195 - }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 195 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
196 throw ex; 196 throw ex;
197 } catch (Throwable ex) { 197 } catch (Throwable ex) {
198 // 系统异常,则包装成 PayException 异常抛出 198 // 系统异常,则包装成 PayException 异常抛出
@@ -219,9 +219,25 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen @@ -219,9 +219,25 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
219 } 219 }
220 } 220 }
221 221
  222 + @Override
  223 + public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
  224 + try {
  225 + return doGetTransfer(outTradeNo, type);
  226 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
  227 + throw ex;
  228 + } catch (Throwable ex) {
  229 + log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]",
  230 + getId(), outTradeNo, type, ex);
  231 + throw buildPayException(ex);
  232 + }
  233 + }
  234 +
222 protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) 235 protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
223 throws Throwable; 236 throws Throwable;
224 237
  238 + protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type)
  239 + throws Throwable;
  240 +
225 // ========== 各种工具方法 ========== 241 // ========== 各种工具方法 ==========
226 242
227 private PayException buildPayException(Throwable ex) { 243 private PayException buildPayException(Throwable ex) {
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
@@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse; @@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse;
23 import com.alipay.api.DefaultAlipayClient; 23 import com.alipay.api.DefaultAlipayClient;
24 import com.alipay.api.domain.*; 24 import com.alipay.api.domain.*;
25 import com.alipay.api.internal.util.AlipaySignature; 25 import com.alipay.api.internal.util.AlipaySignature;
26 -import com.alipay.api.request.AlipayFundTransUniTransferRequest;  
27 -import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;  
28 -import com.alipay.api.request.AlipayTradeQueryRequest;  
29 -import com.alipay.api.request.AlipayTradeRefundRequest;  
30 -import com.alipay.api.response.AlipayFundTransUniTransferResponse;  
31 -import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;  
32 -import com.alipay.api.response.AlipayTradeQueryResponse;  
33 -import com.alipay.api.response.AlipayTradeRefundResponse; 26 +import com.alipay.api.request.*;
  27 +import com.alipay.api.response.*;
34 import lombok.Getter; 28 import lombok.Getter;
35 import lombok.SneakyThrows; 29 import lombok.SneakyThrows;
36 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
@@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa @@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
126 } 120 }
127 // 2.2 解析订单的状态 121 // 2.2 解析订单的状态
128 Integer status = parseStatus(response.getTradeStatus()); 122 Integer status = parseStatus(response.getTradeStatus());
129 - Assert.notNull(status, () -> { 123 + Assert.notNull(status, () -> {
130 throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); 124 throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody()));
131 }); 125 });
132 return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), 126 return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()),
@@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa @@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
228 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { 222 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
229 // 1.1 校验公钥类型 必须使用公钥证书模式 223 // 1.1 校验公钥类型 必须使用公钥证书模式
230 if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) { 224 if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
231 - throw exception0(ERROR_CONFIGURATION.getCode(),"支付宝单笔转账必须使用公钥证书模式"); 225 + throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式");
232 } 226 }
233 // 1.2 构建 AlipayFundTransUniTransferModel 227 // 1.2 构建 AlipayFundTransUniTransferModel
234 AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); 228 AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
@@ -238,44 +232,96 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa @@ -238,44 +232,96 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
238 model.setOutBizNo(reqDTO.getOutTransferNo()); 232 model.setOutBizNo(reqDTO.getOutTransferNo());
239 model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD 233 model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
240 model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER 234 model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER
241 - model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); 235 + if (reqDTO.getChannelExtras() != null) {
  236 + model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
  237 + }
  238 + // ② 个性化的参数
  239 + Participant payeeInfo = new Participant();
242 PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType()); 240 PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
243 switch (transferType) { 241 switch (transferType) {
244 // TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦? 242 // TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦?
245 // @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试?? 243 // @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试??
246 case ALIPAY_BALANCE: { 244 case ALIPAY_BALANCE: {
247 - // ② 个性化的参数  
248 - Participant payeeInfo = new Participant();  
249 payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); 245 payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
250 payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号 246 payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
251 payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 247 payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
252 model.setPayeeInfo(payeeInfo); 248 model.setPayeeInfo(payeeInfo);
253 - // 1.3 构建 AlipayFundTransUniTransferRequest  
254 - AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();  
255 - request.setBizModel(model);  
256 - // 执行请求  
257 - AlipayFundTransUniTransferResponse response = client.certificateExecute(request);  
258 - // 处理结果  
259 - if (!response.isSuccess()) {  
260 - // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询  
261 - if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {  
262 - return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);  
263 - }  
264 - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),  
265 - reqDTO.getOutTransferNo(), response);  
266 - }  
267 - return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),  
268 - response.getOutBizNo(), response); 249 + break;
269 } 250 }
270 case BANK_CARD: { 251 case BANK_CARD: {
271 - Participant payeeInfo = new Participant();  
272 payeeInfo.setIdentityType("BANKCARD_ACCOUNT"); 252 payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
273 // TODO 待实现 253 // TODO 待实现
274 throw exception(NOT_IMPLEMENTED); 254 throw exception(NOT_IMPLEMENTED);
275 } 255 }
276 default: { 256 default: {
277 - throw exception0(BAD_REQUEST.getCode(),"不正确的转账类型: {}",transferType); 257 + throw exception0(BAD_REQUEST.getCode(), "不正确的转账类型: {}", transferType);
  258 + }
  259 + }
  260 + // 1.3 构建 AlipayFundTransUniTransferRequest
  261 + AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
  262 + request.setBizModel(model);
  263 + // 执行请求
  264 + AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
  265 + // 处理结果
  266 + if (!response.isSuccess()) {
  267 + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账
  268 + // 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询
  269 + if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
  270 + return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
  271 + }
  272 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  273 + reqDTO.getOutTransferNo(), response);
  274 + } else {
  275 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
  276 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  277 + reqDTO.getOutTransferNo(), response);
  278 + }
  279 + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
  280 + return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
  281 + }
  282 + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
  283 + response.getOutBizNo(), response);
  284 + }
  285 +
  286 + }
  287 +
  288 + @Override
  289 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
  290 + // 1.1 构建 AlipayFundTransCommonQueryModel
  291 + AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel();
  292 + model.setProductCode(type == PayTransferTypeEnum.BANK_CARD ? "TRANS_BANKCARD_NO_PWD" : "TRANS_ACCOUNT_NO_PWD");
  293 + model.setBizScene("DIRECT_TRANSFER"); //业务场景
  294 + model.setOutBizNo(outTradeNo);
  295 + // 1.2 构建 AlipayFundTransCommonQueryRequest
  296 + AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest();
  297 + request.setBizModel(model);
  298 +
  299 + // 2.1 执行请求
  300 + AlipayFundTransCommonQueryResponse response;
  301 + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
  302 + response = client.certificateExecute(request);
  303 + } else {
  304 + response = client.execute(request);
  305 + }
  306 + // 2.2 处理返回结果
  307 + if (response.isSuccess()) {
  308 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
  309 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  310 + outTradeNo, response);
  311 + }
  312 + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
  313 + return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response);
278 } 314 }
  315 + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
  316 + response.getOutBizNo(), response);
  317 + } else {
  318 + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
  319 + // 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
  320 + if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
  321 + return PayTransferRespDTO.waitingOf(null, outTradeNo, response);
  322 + }
  323 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  324 + outTradeNo, response);
279 } 325 }
280 } 326 }
281 327
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie
9 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 9 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
10 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; 10 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  12 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
12 13
13 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
14 import java.util.Map; 15 import java.util.Map;
@@ -71,4 +72,9 @@ public class MockPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; { @@ -71,4 +72,9 @@ public class MockPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
71 throw new UnsupportedOperationException("待实现"); 72 throw new UnsupportedOperationException("待实现");
72 } 73 }
73 74
  75 + @Override
  76 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
  77 + throw new UnsupportedOperationException("待实现");
  78 + }
  79 +
74 } 80 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
@@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT @@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT
16 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; 16 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
17 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 17 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
18 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; 18 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
  19 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
19 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; 20 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
20 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; 21 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
21 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; 22 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
@@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC @@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
431 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { 432 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
432 throw new UnsupportedOperationException("待实现"); 433 throw new UnsupportedOperationException("待实现");
433 } 434 }
  435 +
  436 + @Override
  437 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
  438 + throw new UnsupportedOperationException("待实现");
  439 + }
  440 +
434 // ========== 各种工具方法 ========== 441 // ========== 各种工具方法 ==========
435 442
436 static String formatDateV2(LocalDateTime time) { 443 static String formatDateV2(LocalDateTime time) {
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java
@@ -38,4 +38,8 @@ public enum PayTransferStatusRespEnum { @@ -38,4 +38,8 @@ public enum PayTransferStatusRespEnum {
38 public static boolean isClosed(Integer status) { 38 public static boolean isClosed(Integer status) {
39 return Objects.equals(status, CLOSED.getStatus()); 39 return Objects.equals(status, CLOSED.getStatus());
40 } 40 }
  41 +
  42 + public static boolean isInProgress(Integer status) {
  43 + return Objects.equals(status, IN_PROGRESS.getStatus());
  44 + }
41 } 45 }
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.flowable.core.context;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import com.alibaba.ttl.TransmittableThreadLocal;
  5 +
  6 +import java.util.Collections;
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * 工作流--用户用到的上下文相关信息
  12 + */
  13 +public class FlowableContextHolder {
  14 +
  15 + private static final ThreadLocal<Map<String, List<Long>>> ASSIGNEE = new TransmittableThreadLocal<>();
  16 +
  17 + /**
  18 + * 通过流程任务的定义 key ,拿到提前选好的审批人
  19 + * 此方法目的:首次创建流程实例时,数据库中还查询不到 assignee 字段,所以存入上下文中获取
  20 + *
  21 + * @param taskDefinitionKey 流程任务 key
  22 + * @return 审批人 ID 集合
  23 + */
  24 + public static List<Long> getAssigneeByTaskDefinitionKey(String taskDefinitionKey) {
  25 + if (CollUtil.isNotEmpty(ASSIGNEE.get())) {
  26 + return ASSIGNEE.get().get(taskDefinitionKey);
  27 + }
  28 + return Collections.emptyList();
  29 + }
  30 +
  31 + /**
  32 + * 存入提前选好的审批人到上下文线程变量中
  33 + *
  34 + * @param assignee 流程任务 key -> 审批人 ID 炅和
  35 + */
  36 + public static void setAssignee(Map<String, List<Long>> assignee) {
  37 + ASSIGNEE.set(assignee);
  38 + }
  39 +
  40 +}
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; @@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
12 import com.baomidou.mybatisplus.core.toolkit.support.SFunction; 12 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
13 import com.baomidou.mybatisplus.extension.toolkit.Db; 13 import com.baomidou.mybatisplus.extension.toolkit.Db;
14 import com.github.yulichang.base.MPJBaseMapper; 14 import com.github.yulichang.base.MPJBaseMapper;
  15 +import com.github.yulichang.interfaces.MPJBaseJoin;
15 import org.apache.ibatis.annotations.Param; 16 import org.apache.ibatis.annotations.Param;
16 17
17 import java.util.Collection; 18 import java.util.Collection;
@@ -39,6 +40,13 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; { @@ -39,6 +40,13 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
39 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); 40 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
40 } 41 }
41 42
  43 + default <DTO> PageResult<DTO> selectJoinPage(PageParam pageParam, Class<DTO> resultTypeClass, MPJBaseJoin<T> joinQueryWrapper) {
  44 + IPage<DTO> mpPage = MyBatisUtils.buildPage(pageParam);
  45 + selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper);
  46 + // 转换返回
  47 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
  48 + }
  49 +
42 default T selectOne(String field, Object value) { 50 default T selectOne(String field, Object value) {
43 return selectOne(new QueryWrapper<T>().eq(field, value)); 51 return selectOne(new QueryWrapper<T>().eq(field, value));
44 } 52 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.api.task.dto; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.api.task.dto;
3 import lombok.Data; 3 import lombok.Data;
4 4
5 import javax.validation.constraints.NotEmpty; 5 import javax.validation.constraints.NotEmpty;
  6 +import java.util.List;
6 import java.util.Map; 7 import java.util.Map;
7 8
8 /** 9 /**
@@ -30,4 +31,15 @@ public class BpmProcessInstanceCreateReqDTO { @@ -30,4 +31,15 @@ public class BpmProcessInstanceCreateReqDTO {
30 */ 31 */
31 @NotEmpty(message = "业务的唯一标识") 32 @NotEmpty(message = "业务的唯一标识")
32 private String businessKey; 33 private String businessKey;
  34 +
  35 + // TODO @hai:assignees 复数
  36 + /**
  37 + * 提前指派的审批人
  38 + *
  39 + * key:taskKey 任务编码
  40 + * value:审批人的数组
  41 + * 例如: { taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
  42 + */
  43 + private Map<String, List<Long>> assignee;
  44 +
33 } 45 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import javax.validation.constraints.NotEmpty; 6 import javax.validation.constraints.NotEmpty;
  7 +import java.util.List;
7 import java.util.Map; 8 import java.util.Map;
8 9
9 @Schema(description = "管理后台 - 流程实例的创建 Request VO") 10 @Schema(description = "管理后台 - 流程实例的创建 Request VO")
@@ -17,4 +18,8 @@ public class BpmProcessInstanceCreateReqVO { @@ -17,4 +18,8 @@ public class BpmProcessInstanceCreateReqVO {
17 @Schema(description = "变量实例") 18 @Schema(description = "变量实例")
18 private Map<String, Object> variables; 19 private Map<String, Object> variables;
19 20
  21 + // TODO @hai:assignees 复数
  22 + @Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1: [1, 2]}")
  23 + private Map<String, List<Long>> assignee;
  24 +
20 } 25 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java
@@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode; @@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
12 import lombok.ToString; 12 import lombok.ToString;
13 13
14 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
  15 +import java.util.List;
15 import java.util.Map; 16 import java.util.Map;
16 17
17 /** 18 /**
@@ -87,4 +88,11 @@ public class BpmProcessInstanceExtDO extends BaseDO { @@ -87,4 +88,11 @@ public class BpmProcessInstanceExtDO extends BaseDO {
87 @TableField(typeHandler = JacksonTypeHandler.class) 88 @TableField(typeHandler = JacksonTypeHandler.class)
88 private Map<String, Object> formVariables; 89 private Map<String, Object> formVariables;
89 90
  91 + // TODO @hai:assignees 复数
  92 + /**
  93 + * 提前设定好的审批人
  94 + */
  95 + @TableField(typeHandler = JacksonTypeHandler.class, exist = false) // TODO 芋艿:临时 exist = false,避免 db 报错;
  96 + private Map<String, List<Long>> assignee;
  97 +
90 } 98 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java
@@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
18 import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; 18 import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
19 import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; 19 import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
  21 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
21 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 22 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
22 import cn.iocoder.yudao.module.system.api.dept.PostApi; 23 import cn.iocoder.yudao.module.system.api.dept.PostApi;
23 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 24 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -39,6 +40,7 @@ import org.springframework.validation.annotation.Validated; @@ -39,6 +40,7 @@ import org.springframework.validation.annotation.Validated;
39 import javax.annotation.Resource; 40 import javax.annotation.Resource;
40 import javax.validation.Valid; 41 import javax.validation.Valid;
41 import java.util.*; 42 import java.util.*;
  43 +import java.util.function.Function;
42 44
43 import static cn.hutool.core.text.CharSequenceUtil.format; 45 import static cn.hutool.core.text.CharSequenceUtil.format;
44 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 46 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -77,6 +79,9 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { @@ -77,6 +79,9 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
77 private DictDataApi dictDataApi; 79 private DictDataApi dictDataApi;
78 @Resource 80 @Resource
79 private PermissionApi permissionApi; 81 private PermissionApi permissionApi;
  82 + @Resource
  83 + @Lazy // 解决循环依赖
  84 + private BpmProcessInstanceService processInstanceService;
80 /** 85 /**
81 * 任务分配脚本 86 * 任务分配脚本
82 */ 87 */
@@ -234,6 +239,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { @@ -234,6 +239,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
234 @Override 239 @Override
235 @DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题 240 @DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题
236 public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) { 241 public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
  242 + // 1. 先从提前选好的审批人中获取
  243 + List<Long> assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey(
  244 + execution.getProcessInstanceId(), execution.getCurrentActivityId());
  245 + if (CollUtil.isNotEmpty(assignee)) {
  246 + // TODO @hai:new HashSet 即可
  247 + return convertSet(assignee, Function.identity());
  248 + }
  249 + // 2. 通过分配规则,计算审批人
237 BpmTaskAssignRuleDO rule = getTaskRule(execution); 250 BpmTaskAssignRuleDO rule = getTaskRule(execution);
238 return calculateTaskCandidateUsers(execution, rule); 251 return calculateTaskCandidateUsers(execution, rule);
239 } 252 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
@@ -49,16 +49,17 @@ public interface BpmProcessInstanceService { @@ -49,16 +49,17 @@ public interface BpmProcessInstanceService {
49 /** 49 /**
50 * 获得流程实例的分页 50 * 获得流程实例的分页
51 * 51 *
52 - * @param userId 用户编号 52 + * @param userId 用户编号
53 * @param pageReqVO 分页请求 53 * @param pageReqVO 分页请求
54 * @return 流程实例的分页 54 * @return 流程实例的分页
55 */ 55 */
56 PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, 56 PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
57 @Valid BpmProcessInstanceMyPageReqVO pageReqVO); 57 @Valid BpmProcessInstanceMyPageReqVO pageReqVO);
  58 +
58 /** 59 /**
59 * 创建流程实例(提供给前端) 60 * 创建流程实例(提供给前端)
60 * 61 *
61 - * @param userId 用户编号 62 + * @param userId 用户编号
62 * @param createReqVO 创建信息 63 * @param createReqVO 创建信息
63 * @return 实例的编号 64 * @return 实例的编号
64 */ 65 */
@@ -67,7 +68,7 @@ public interface BpmProcessInstanceService { @@ -67,7 +68,7 @@ public interface BpmProcessInstanceService {
67 /** 68 /**
68 * 创建流程实例(提供给内部) 69 * 创建流程实例(提供给内部)
69 * 70 *
70 - * @param userId 用户编号 71 + * @param userId 用户编号
71 * @param createReqDTO 创建信息 72 * @param createReqDTO 创建信息
72 * @return 实例的编号 73 * @return 实例的编号
73 */ 74 */
@@ -84,7 +85,7 @@ public interface BpmProcessInstanceService { @@ -84,7 +85,7 @@ public interface BpmProcessInstanceService {
84 /** 85 /**
85 * 取消流程实例 86 * 取消流程实例
86 * 87 *
87 - * @param userId 用户编号 88 + * @param userId 用户编号
88 * @param cancelReqVO 取消信息 89 * @param cancelReqVO 取消信息
89 */ 90 */
90 void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); 91 void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
@@ -139,9 +140,19 @@ public interface BpmProcessInstanceService { @@ -139,9 +140,19 @@ public interface BpmProcessInstanceService {
139 /** 140 /**
140 * 更新 ProcessInstance 拓展记录为不通过 141 * 更新 ProcessInstance 拓展记录为不通过
141 * 142 *
142 - * @param id 流程编号 143 + * @param id 流程编号
143 * @param reason 理由。例如说,审批不通过时,需要传递该值 144 * @param reason 理由。例如说,审批不通过时,需要传递该值
144 */ 145 */
145 void updateProcessInstanceExtReject(String id, String reason); 146 void updateProcessInstanceExtReject(String id, String reason);
146 147
  148 + // TODO @hai:改成 getProcessInstanceAssigneesByTaskDefinitionKey(String id, String taskDefinitionKey)
  149 + /**
  150 + * 获取流程实例中,取出指定流程任务提前指定的审批人
  151 + *
  152 + * @param processInstanceId 流程实例的编号
  153 + * @param taskDefinitionKey 流程任务定义的 key
  154 + * @return 审批人集合
  155 + */
  156 + List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey);
  157 +
147 } 158 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1 -package cn.iocoder.yudao.module.bpm.service.task; 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.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables)); return instance.getId(); } }  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; 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.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List<Long> result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if (CollUtil.isNotEmpty(instance.getAssignee())) { return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } }
3 \ No newline at end of file 3 \ No newline at end of file
yudao-module-crm/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modules>
  11 + <module>yudao-module-crm-api</module>
  12 + <module>yudao-module-crm-biz</module>
  13 + </modules>
  14 + <modelVersion>4.0.0</modelVersion>
  15 + <artifactId>yudao-module-crm</artifactId>
  16 + <packaging>pom</packaging>
  17 +
  18 + <name>${project.artifactId}</name>
  19 +
  20 + <description>
  21 + crm 包下,客户关系管理(Customer Relationship Management)。
  22 + 例如说:客户、联系人、商机、合同、回款等等
  23 + </description>
  24 +
  25 +</project>
yudao-module-crm/yudao-module-crm-api/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao-module-crm</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 + <artifactId>yudao-module-crm-api</artifactId>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>
  16 + crm 模块 API,暴露给其它模块调用
  17 + </description>
  18 +
  19 + <dependencies>
  20 + <dependency>
  21 + <groupId>cn.iocoder.boot</groupId>
  22 + <artifactId>yudao-common</artifactId>
  23 + </dependency>
  24 +
  25 + <!-- 参数校验 -->
  26 + <dependency>
  27 + <groupId>org.springframework.boot</groupId>
  28 + <artifactId>spring-boot-starter-validation</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
  31 + </dependencies>
  32 +
  33 +</project>
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java 0 → 100644
  1 +/**
  2 + * crm API 包,定义暴露给其它模块的 API
  3 + */
  4 +package cn.iocoder.yudao.module.crm.api;
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/AuditStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +
  5 +import java.util.Arrays;
  6 +
  7 +// TODO @liuhongfeng:这个状态,还是搞成专属 CrmReceivableDO 专属的 status;
  8 +/**
  9 + * 流程审批状态枚举类
  10 + * 0 未审核 1 审核通过 2 审核拒绝 3 审核中 4 已撤回 TODO @liuhongfeng:这一行可以删除,因为已经有枚举属性了哈;
  11 + * @author 赤焰
  12 + */
  13 +// TODO @liuhongfeng:可以使用 @Getter、@AllArgsConstructor 简化 get、构造方法
  14 +public enum AuditStatusEnum implements IntArrayValuable {
  15 +
  16 + // TODO @liuhongfeng:草稿 0;10 审核中;20 审核通过;30 审核拒绝;40 已撤回;主要是留好间隙,万一每个地方要做点拓展; 然后,枚举字段的顺序调整下,审批中,一定要放两个审批通过、拒绝前面哈;
  17 + /**
  18 + * 未审批
  19 + */
  20 + AUDIT_NEW(0, "未审批"),
  21 + /**
  22 + * 审核通过
  23 + */
  24 + AUDIT_FINISH(1, "审核通过"),
  25 + /**
  26 + * 审核拒绝
  27 + */
  28 + AUDIT_REJECT(2, "审核拒绝"),
  29 + /**
  30 + * 审核中
  31 + */
  32 + AUDIT_DOING(3, "审核中"),
  33 + /**
  34 + * 已撤回
  35 + */
  36 + AUDIT_RETURN(4, "已撤回");
  37 +
  38 + // TODO liuhongfeng:value 改成 status;desc 改成 name;
  39 + private final Integer value;
  40 + private final String desc;
  41 +
  42 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AuditStatusEnum::getValue).toArray();
  43 +
  44 + AuditStatusEnum(Integer value, String desc) {
  45 + this.value = value;
  46 + this.desc = desc;
  47 + }
  48 +
  49 + public Integer getValue() {
  50 + return value;
  51 + }
  52 +
  53 + public String getDesc() {
  54 + return desc;
  55 + }
  56 +
  57 + @Override
  58 + public int[] array() {
  59 + return ARRAYS;
  60 + }
  61 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums;
  2 +
  3 +/**
  4 + * CRM 字典类型的枚举类
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface DictTypeConstants {
  9 +
  10 + // ========== CRM 模块 ==========
  11 + String CRM_CUSTOMER_INDUSTRY = "crm_customer_industry"; // CRM 客户所属行业
  12 + String CRM_CUSTOMER_LEVEL = "crm_customer_level"; // CRM 客户等级
  13 + String CRM_CUSTOMER_SOURCE = "crm_customer_source"; // CRM 客户来源
  14 + String CRM_RECEIVABLE_CHECK_STATUS = "crm_receivable_check_status"; // CRM 审批状态
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  4 +
  5 +/**
  6 + * CRM 错误码枚举类
  7 + * <p>
  8 + * crm 系统,使用 1-020-000-000 段
  9 + */
  10 +public interface ErrorCodeConstants {
  11 +
  12 + // ========== 合同管理 1-020-000-000 ==========
  13 + ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
  14 +
  15 + // ========== 线索管理 1-020-001-000 ==========
  16 + ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
  17 +
  18 + // ========== 商机管理 1-020-002-000 ==========
  19 + ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
  20 +
  21 + // TODO @lilleo:商机状态、商机类型,都单独错误码段
  22 +
  23 + ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_002_001, "商机状态类型不存在");
  24 + ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_002_002, "商机状态不存在");
  25 +
  26 + // ========== 联系人管理 1-020-003-000 ==========
  27 + ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
  28 +
  29 + // ========== 回款管理 1-020-004-000 ==========
  30 + ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款管理不存在");
  31 +
  32 + // ========== 合同管理 1-020-005-000 ==========
  33 + ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
  34 +
  35 + // ========== 客户管理 1_020_006_000 ==========
  36 + ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
  37 + ErrorCode CUSTOMER_OWNER_EXISTS = new ErrorCode(1_020_006_001, "客户已存在所属负责人");
  38 + ErrorCode CUSTOMER_LOCKED = new ErrorCode(1_020_006_002, "客户状态已锁定");
  39 + ErrorCode CUSTOMER_ALREADY_DEAL = new ErrorCode(1_020_006_003, "客户已交易");
  40 + // TODO @wanwan:这 2 个单独配置段噢
  41 + ErrorCode CUSTOMER_POOL_CONFIG_ERROR = new ErrorCode(1_020_006_001, "客户公海规则设置不正确");
  42 + ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_006_002, "客户限制配置不存在");
  43 +
  44 + // ========== 权限管理 1_020_007_000 ==========
  45 + ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
  46 + ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
  47 + ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在");
  48 + ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
  49 +
  50 + // ========== 产品 1_020_008_000 ==========
  51 + ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
  52 + ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在");
  53 +
  54 + // ========== 产品分类 1_020_009_000 ==========
  55 + ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
  56 +
  57 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ReturnTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums;
  2 +
  3 +// TODO @liuhongfeng:这个的作用是?
  4 +/**
  5 + * @author 赤焰
  6 + */
  7 +public enum ReturnTypeEnum {
  8 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLevelEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums.customer;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * CRM 客户等级
  11 + *
  12 + * @author Wanwan
  13 + */
  14 +@Getter
  15 +@AllArgsConstructor
  16 +public enum CrmCustomerLevelEnum implements IntArrayValuable {
  17 +
  18 + IMPORTANT(1, "A(重点客户)"),
  19 + GENERAL(2, "B(普通客户)"),
  20 + LOW_PRIORITY(3, "C(非优先客户)");
  21 +
  22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLevelEnum::getLevel).toArray();
  23 +
  24 + /**
  25 + * 状态
  26 + */
  27 + private final Integer level;
  28 + /**
  29 + * 状态名
  30 + */
  31 + private final String name;
  32 +
  33 + @Override
  34 + public int[] array() {
  35 + return ARRAYS;
  36 + }
  37 +
  38 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerSceneEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums.customer;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +// TODO @puhui999:这个应该是 crm 全局的,不仅仅属于 customer 客户哈;
  11 +/**
  12 + * CRM 客户等级
  13 + *
  14 + * @author Wanwan
  15 + */
  16 +@Getter
  17 +@AllArgsConstructor
  18 +public enum CrmCustomerSceneEnum implements IntArrayValuable {
  19 +
  20 + OWNER(1, "我负责的客户"),
  21 + FOLLOW(2, "我关注的客户");
  22 +
  23 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerSceneEnum::getType).toArray();
  24 +
  25 + /**
  26 + * 场景类型
  27 + */
  28 + private final Integer type;
  29 + /**
  30 + * 场景名称
  31 + */
  32 + private final String name;
  33 +
  34 + public static boolean isOwner(Integer type) {
  35 + return ObjUtil.equal(OWNER.getType(), type);
  36 + }
  37 +
  38 + public static boolean isFollow(Integer type) {
  39 + return ObjUtil.equal(FOLLOW.getType(), type);
  40 + }
  41 +
  42 + @Override
  43 + public int[] array() {
  44 + return ARRAYS;
  45 + }
  46 +
  47 +}
yudao-module-crm/yudao-module-crm-biz/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 + xmlns="http://maven.apache.org/POM/4.0.0"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao-module-crm</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 + <artifactId>yudao-module-crm-biz</artifactId>
  12 +
  13 + <name>${project.artifactId}</name>
  14 + <description>
  15 + crm 包下,客户关系管理(Customer Relationship Management)。
  16 + 例如说:客户、联系人、商机、合同、回款等等
  17 + </description>
  18 +
  19 + <dependencies>
  20 + <dependency>
  21 + <groupId>cn.iocoder.boot</groupId>
  22 + <artifactId>yudao-module-system-api</artifactId>
  23 + <version>${revision}</version>
  24 + </dependency>
  25 + <dependency>
  26 + <groupId>cn.iocoder.boot</groupId>
  27 + <artifactId>yudao-module-crm-api</artifactId>
  28 + <version>${revision}</version>
  29 + </dependency>
  30 +
  31 + <!-- 业务组件 -->
  32 + <dependency>
  33 + <groupId>cn.iocoder.boot</groupId>
  34 + <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
  35 + </dependency>
  36 + <dependency>
  37 + <groupId>cn.iocoder.boot</groupId>
  38 + <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
  39 + </dependency>
  40 +
  41 + <!-- Web 相关 -->
  42 + <dependency>
  43 + <groupId>cn.iocoder.boot</groupId>
  44 + <artifactId>yudao-spring-boot-starter-web</artifactId>
  45 + </dependency>
  46 +
  47 + <dependency>
  48 + <groupId>cn.iocoder.boot</groupId>
  49 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  50 + </dependency>
  51 +
  52 + <!-- DB 相关 -->
  53 + <dependency>
  54 + <groupId>cn.iocoder.boot</groupId>
  55 + <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
  56 + </dependency>
  57 +
  58 + <!-- 工具类相关 -->
  59 + <dependency>
  60 + <groupId>cn.iocoder.boot</groupId>
  61 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  62 + </dependency>
  63 +
  64 + <!-- Test 测试相关 -->
  65 + <dependency>
  66 + <groupId>cn.iocoder.boot</groupId>
  67 + <artifactId>yudao-spring-boot-starter-test</artifactId>
  68 + </dependency>
  69 + </dependencies>
  70 +</project>
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java 0 → 100644
  1 +/**
  2 + * crm API 实现类,定义暴露给其它模块的 API
  3 + */
  4 +package cn.iocoder.yudao.module.crm.api;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http 0 → 100644
  1 +### 请求 /transfer
  2 +PUT {{baseUrl}}/crm/business/transfer
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenentId}}
  6 +
  7 +{
  8 + "id": 1,
  9 + "ownerUserId": 2,
  10 + "transferType": 2,
  11 + "permissionType": 2
  12 +}
  13 +
  14 +### 请求 /update
  15 +PUT {{baseUrl}}/crm/business/update
  16 +Content-Type: application/json
  17 +Authorization: Bearer {{token}}
  18 +tenant-id: {{adminTenentId}}
  19 +
  20 +{
  21 + "id": 1,
  22 + "name": "2",
  23 + "statusTypeId": 2,
  24 + "statusId": 2,
  25 + "customerId": 1
  26 +}
  27 +
  28 +### 请求 /get
  29 +GET {{baseUrl}}/crm/business/get?id=1024
  30 +Content-Type: application/json
  31 +Authorization: Bearer {{token}}
  32 +tenant-id: {{adminTenentId}}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.*;
  8 +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  10 +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
  11 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import javax.annotation.Resource;
  20 +import javax.servlet.http.HttpServletResponse;
  21 +import javax.validation.Valid;
  22 +import java.io.IOException;
  23 +import java.util.List;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  27 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  28 +
  29 +@Tag(name = "管理后台 - 商机")
  30 +@RestController
  31 +@RequestMapping("/crm/business")
  32 +@Validated
  33 +public class CrmBusinessController {
  34 +
  35 + @Resource
  36 + private CrmBusinessService businessService;
  37 +
  38 + @PostMapping("/create")
  39 + @Operation(summary = "创建商机")
  40 + @PreAuthorize("@ss.hasPermission('crm:business:create')")
  41 + public CommonResult<Long> createBusiness(@Valid @RequestBody CrmBusinessCreateReqVO createReqVO) {
  42 + return success(businessService.createBusiness(createReqVO, getLoginUserId()));
  43 + }
  44 +
  45 + @PutMapping("/update")
  46 + @Operation(summary = "更新商机")
  47 + @PreAuthorize("@ss.hasPermission('crm:business:update')")
  48 + public CommonResult<Boolean> updateBusiness(@Valid @RequestBody CrmBusinessUpdateReqVO updateReqVO) {
  49 + businessService.updateBusiness(updateReqVO);
  50 + return success(true);
  51 + }
  52 +
  53 + @DeleteMapping("/delete")
  54 + @Operation(summary = "删除商机")
  55 + @Parameter(name = "id", description = "编号", required = true)
  56 + @PreAuthorize("@ss.hasPermission('crm:business:delete')")
  57 + public CommonResult<Boolean> deleteBusiness(@RequestParam("id") Long id) {
  58 + businessService.deleteBusiness(id);
  59 + return success(true);
  60 + }
  61 +
  62 + @GetMapping("/get")
  63 + @Operation(summary = "获得商机")
  64 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  65 + @PreAuthorize("@ss.hasPermission('crm:business:query')")
  66 + public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
  67 + CrmBusinessDO business = businessService.getBusiness(id);
  68 + return success(CrmBusinessConvert.INSTANCE.convert(business));
  69 + }
  70 +
  71 + @GetMapping("/page")
  72 + @Operation(summary = "获得商机分页")
  73 + @PreAuthorize("@ss.hasPermission('crm:business:query')")
  74 + public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
  75 + PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
  76 + return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult));
  77 + }
  78 +
  79 + @GetMapping("/pool-page")
  80 + @Operation(summary = "获得商机公海分页")
  81 + @PreAuthorize("@ss.hasPermission('crm:business:query')")
  82 + public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPoolPage(@Valid CrmBusinessPageReqVO pageVO) {
  83 + PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, CrmPermissionDO.POOL_USER_ID);
  84 + return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult));
  85 + }
  86 +
  87 + @GetMapping("/export-excel")
  88 + @Operation(summary = "导出商机 Excel")
  89 + @PreAuthorize("@ss.hasPermission('crm:business:export')")
  90 + @OperateLog(type = EXPORT)
  91 + public void exportBusinessExcel(@Valid CrmBusinessExportReqVO exportReqVO,
  92 + HttpServletResponse response) throws IOException {
  93 + List<CrmBusinessDO> list = businessService.getBusinessList(exportReqVO);
  94 + // 导出 Excel
  95 + List<CrmBusinessExcelVO> datas = CrmBusinessConvert.INSTANCE.convertList02(list);
  96 + ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class, datas);
  97 + }
  98 +
  99 + @PutMapping("/transfer")
  100 + @Operation(summary = "商机转移")
  101 + @PreAuthorize("@ss.hasPermission('crm:business:update')")
  102 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) {
  103 + businessService.transferBusiness(reqVO, getLoginUserId());
  104 + return success(true);
  105 + }
  106 +
  107 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/package-info.java 0 → 100644
  1 +/**
  2 + * 商机(销售机会)
  3 + */
  4 +package cn.iocoder.yudao.module.crm.controller.admin.business;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.math.BigDecimal;
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +/**
  14 + * 商机 Base VO,提供给添加、修改、详细的子 VO 使用
  15 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  16 + */
  17 +@Data
  18 +public class CrmBusinessBaseVO {
  19 +
  20 + @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  21 + @NotNull(message = "商机名称不能为空")
  22 + private String name;
  23 +
  24 + @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
  25 + @NotNull(message = "商机状态类型不能为空")
  26 + private Long statusTypeId;
  27 +
  28 + @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
  29 + @NotNull(message = "商机状态不能为空")
  30 + private Long statusId;
  31 +
  32 + @Schema(description = "下次联系时间")
  33 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  34 + private LocalDateTime contactNextTime;
  35 +
  36 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
  37 + @NotNull(message = "客户不能为空")
  38 + private Long customerId;
  39 +
  40 + @Schema(description = "预计成交日期")
  41 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  42 + private LocalDateTime dealTime;
  43 +
  44 + @Schema(description = "商机金额", example = "12371")
  45 + private Integer price;
  46 +
  47 + // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
  48 + @Schema(description = "整单折扣")
  49 + private Integer discountPercent;
  50 +
  51 + @Schema(description = "产品总金额", example = "12025")
  52 + private BigDecimal productPrice;
  53 +
  54 + @Schema(description = "备注", example = "随便")
  55 + private String remark;
  56 +
  57 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - 商机创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class CrmBusinessCreateReqVO extends CrmBusinessBaseVO {
  13 +
  14 + // TODO @ljileo:新建的时候,应该可以传递添加的产品;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +import java.time.LocalDateTime;
  8 +import java.util.Set;
  9 +
  10 +/**
  11 + * 商机 Excel VO
  12 + *
  13 + * @author ljlleo
  14 + */
  15 +@Data
  16 +public class CrmBusinessExcelVO {
  17 +
  18 + @ExcelProperty("主键")
  19 + private Long id;
  20 +
  21 + @ExcelProperty("商机名称")
  22 + private String name;
  23 +
  24 + @ExcelProperty("商机状态类型编号")
  25 + private Long statusTypeId;
  26 +
  27 + @ExcelProperty("商机状态编号")
  28 + private Long statusId;
  29 +
  30 + @ExcelProperty("下次联系时间")
  31 + private LocalDateTime contactNextTime;
  32 +
  33 + @ExcelProperty("客户编号")
  34 + private Long customerId;
  35 +
  36 + @ExcelProperty("预计成交日期")
  37 + private LocalDateTime dealTime;
  38 +
  39 + @ExcelProperty("商机金额")
  40 + private BigDecimal price;
  41 +
  42 + @ExcelProperty("整单折扣")
  43 + private BigDecimal discountPercent;
  44 +
  45 + @ExcelProperty("产品总金额")
  46 + private BigDecimal productPrice;
  47 +
  48 + @ExcelProperty("备注")
  49 + private String remark;
  50 +
  51 + @ExcelProperty("负责人的用户编号")
  52 + private Long ownerUserId;
  53 +
  54 + @ExcelProperty("创建时间")
  55 + private LocalDateTime createTime;
  56 +
  57 + @ExcelProperty("只读权限的用户编号数组")
  58 + private Set<Long> roUserIds;
  59 +
  60 + @ExcelProperty("读写权限的用户编号数组")
  61 + private Set<Long> rwUserIds;
  62 +
  63 + @ExcelProperty("1赢单2输单3无效")
  64 + private Integer endStatus;
  65 +
  66 + @ExcelProperty("结束时的备注")
  67 + private String endRemark;
  68 +
  69 + @ExcelProperty("最后跟进时间")
  70 + private LocalDateTime contactLastTime;
  71 +
  72 + @ExcelProperty("跟进状态")
  73 + private Integer followUpStatus;
  74 +
  75 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - 商机 Excel 导出 Request VO,参数和 CrmBusinessPageReqVO 是一致的")
  13 +@Data
  14 +public class CrmBusinessExportReqVO {
  15 +
  16 + @Schema(description = "商机名称", example = "李四")
  17 + private String name;
  18 +
  19 + @Schema(description = "商机状态类型编号", example = "25714")
  20 + private Long statusTypeId;
  21 +
  22 + @Schema(description = "商机状态编号", example = "30320")
  23 + private Long statusId;
  24 +
  25 + @Schema(description = "下次联系时间")
  26 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  27 + private LocalDateTime[] contactNextTime;
  28 +
  29 + @Schema(description = "客户编号", example = "10299")
  30 + private Long customerId;
  31 +
  32 + @Schema(description = "预计成交日期")
  33 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  34 + private LocalDateTime[] dealTime;
  35 +
  36 + @Schema(description = "商机金额", example = "12371")
  37 + private BigDecimal price;
  38 +
  39 + @Schema(description = "整单折扣")
  40 + private BigDecimal discountPercent;
  41 +
  42 + @Schema(description = "产品总金额", example = "12025")
  43 + private BigDecimal productPrice;
  44 +
  45 + @Schema(description = "备注", example = "随便")
  46 + private String remark;
  47 +
  48 + @Schema(description = "负责人的用户编号", example = "25562")
  49 + private Long ownerUserId;
  50 +
  51 + @Schema(description = "创建时间")
  52 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  53 + private LocalDateTime[] createTime;
  54 +
  55 + @Schema(description = "只读权限的用户编号数组")
  56 + private String roUserIds;
  57 +
  58 + @Schema(description = "读写权限的用户编号数组")
  59 + private String rwUserIds;
  60 +
  61 + @Schema(description = "1赢单2输单3无效", example = "1")
  62 + private Integer endStatus;
  63 +
  64 + @Schema(description = "结束时的备注", example = "你说的对")
  65 + private String endRemark;
  66 +
  67 + @Schema(description = "最后跟进时间")
  68 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  69 + private LocalDateTime[] contactLastTime;
  70 +
  71 + @Schema(description = "跟进状态", example = "1")
  72 + private Integer followUpStatus;
  73 +
  74 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - 商机分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmBusinessPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "商机名称", example = "李四")
  16 + private String name;
  17 +
  18 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.time.LocalDateTime;
  6 +
  7 +@Schema(description = "管理后台 - 商机 Response VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class CrmBusinessRespVO extends CrmBusinessBaseVO {
  12 +
  13 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private LocalDateTime createTime;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 商机转移 Request VO")
  10 +@Data
  11 +public class CrmBusinessTransferReqVO {
  12 +
  13 + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  14 + @NotNull(message = "联系人编号不能为空")
  15 + private Long id;
  16 +
  17 + /**
  18 + * 新负责人的用户编号
  19 + */
  20 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  21 + @NotNull(message = "新负责人的用户编号不能为空")
  22 + private Long newOwnerUserId;
  23 +
  24 + /**
  25 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  26 + *
  27 + * 关联 {@link CrmPermissionLevelEnum}
  28 + */
  29 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  30 + private Integer oldOwnerPermissionLevel;
  31 +
  32 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 商机更新 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class CrmBusinessUpdateReqVO extends CrmBusinessBaseVO {
  15 +
  16 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
  17 + @NotNull(message = "主键不能为空")
  18 + private Long id;
  19 +
  20 + // TODO @ljileo:修改的时候,应该可以传递添加的产品;
  21 +
  22 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/CrmBusinessStatusController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo.*;
  8 +import cn.iocoder.yudao.module.crm.convert.businessstatus.CrmBusinessStatusConvert;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.businessstatus.CrmBusinessStatusDO;
  10 +import cn.iocoder.yudao.module.crm.service.businessstatus.CrmBusinessStatusService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.servlet.http.HttpServletResponse;
  20 +import javax.validation.Valid;
  21 +import java.io.IOException;
  22 +import java.util.Collection;
  23 +import java.util.List;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  27 +
  28 +// TODO @lilleo:这个模块,可以挪到 business 下;这样我打开 business 包下,就知道,噢~原来里面有 business 商机、有 type 状态类型、status 具体状态;
  29 +@Tag(name = "管理后台 - 商机状态")
  30 +@RestController
  31 +@RequestMapping("/crm/business-status")
  32 +@Validated
  33 +public class CrmBusinessStatusController {
  34 +
  35 + @Resource
  36 + private CrmBusinessStatusService businessStatusService;
  37 +
  38 + @PostMapping("/create")
  39 + @Operation(summary = "创建商机状态")
  40 + @PreAuthorize("@ss.hasPermission('crm:business-status:create')")
  41 + public CommonResult<Long> createBusinessStatus(@Valid @RequestBody CrmBusinessStatusCreateReqVO createReqVO) {
  42 + return success(businessStatusService.createBusinessStatus(createReqVO));
  43 + }
  44 +
  45 + @PutMapping("/update")
  46 + @Operation(summary = "更新商机状态")
  47 + @PreAuthorize("@ss.hasPermission('crm:business-status:update')")
  48 + public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessStatusUpdateReqVO updateReqVO) {
  49 + businessStatusService.updateBusinessStatus(updateReqVO);
  50 + return success(true);
  51 + }
  52 +
  53 + @DeleteMapping("/delete")
  54 + @Operation(summary = "删除商机状态")
  55 + @Parameter(name = "id", description = "编号", required = true)
  56 + @PreAuthorize("@ss.hasPermission('crm:business-status:delete')")
  57 + public CommonResult<Boolean> deleteBusinessStatus(@RequestParam("id") Long id) {
  58 + businessStatusService.deleteBusinessStatus(id);
  59 + return success(true);
  60 + }
  61 +
  62 + @GetMapping("/get")
  63 + @Operation(summary = "获得商机状态")
  64 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  65 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  66 + public CommonResult<CrmBusinessStatusRespVO> getBusinessStatus(@RequestParam("id") Long id) {
  67 + CrmBusinessStatusDO businessStatus = businessStatusService.getBusinessStatus(id);
  68 + return success(CrmBusinessStatusConvert.INSTANCE.convert(businessStatus));
  69 + }
  70 +
  71 + // TODO @lilleo:这个接口,暂时用不到,可以考虑先删除掉
  72 + @GetMapping("/list")
  73 + @Operation(summary = "获得商机状态列表")
  74 + @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
  75 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  76 + public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusList(@RequestParam("ids") Collection<Long> ids) {
  77 + List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList(ids);
  78 + return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
  79 + }
  80 +
  81 + @GetMapping("/page")
  82 + @Operation(summary = "获得商机状态分页")
  83 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  84 + public CommonResult<PageResult<CrmBusinessStatusRespVO>> getBusinessStatusPage(@Valid CrmBusinessStatusPageReqVO pageVO) {
  85 + PageResult<CrmBusinessStatusDO> pageResult = businessStatusService.getBusinessStatusPage(pageVO);
  86 + return success(CrmBusinessStatusConvert.INSTANCE.convertPage(pageResult));
  87 + }
  88 +
  89 + @GetMapping("/export-excel")
  90 + @Operation(summary = "导出商机状态 Excel")
  91 + @PreAuthorize("@ss.hasPermission('crm:business-status:export')")
  92 + @OperateLog(type = EXPORT)
  93 + public void exportBusinessStatusExcel(@Valid CrmBusinessStatusExportReqVO exportReqVO,
  94 + HttpServletResponse response) throws IOException {
  95 + List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList(exportReqVO);
  96 + // 导出 Excel
  97 + List<CrmBusinessStatusExcelVO> datas = CrmBusinessStatusConvert.INSTANCE.convertList02(list);
  98 + ExcelUtils.write(response, "商机状态.xls", "数据", CrmBusinessStatusExcelVO.class, datas);
  99 + }
  100 +
  101 + // TODO 芋艿:后续再看看
  102 + @GetMapping("/get-simple-list")
  103 + @Operation(summary = "获得商机状态列表")
  104 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  105 + public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusListByTypeId(@RequestParam("typeId") Integer typeId) {
  106 + List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusListByTypeId(typeId);
  107 + return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
  108 + }
  109 +
  110 + // TODO 芋艿:后续再看看
  111 + @GetMapping("/get-all-list")
  112 + @Operation(summary = "获得商机状态列表")
  113 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  114 + public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusList() {
  115 + List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList();
  116 + return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
  117 + }
  118 +
  119 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +/**
  9 + * 商机状态 Base VO,提供给添加、修改、详细的子 VO 使用
  10 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 + */
  12 +@Data
  13 +public class CrmBusinessStatusBaseVO {
  14 +
  15 + // TODO @lilleo:example 要写下
  16 +
  17 + @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22882")
  18 + @NotNull(message = "状态类型编号不能为空")
  19 + private Long typeId;
  20 +
  21 + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  22 + @NotNull(message = "状态名不能为空")
  23 + private String name;
  24 +
  25 + // TODO @lilleo:percent 应该是 Integer;
  26 + @Schema(description = "赢单率")
  27 + private String percent;
  28 +
  29 + // TODO @lilleo:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了;
  30 + @Schema(description = "排序")
  31 + private Integer sort;
  32 +
  33 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import lombok.*;
  4 +import java.util.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import javax.validation.constraints.*;
  7 +
  8 +@Schema(description = "管理后台 - 商机状态创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class CrmBusinessStatusCreateReqVO extends CrmBusinessStatusBaseVO {
  13 +
  14 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import lombok.Data;
  5 +
  6 +// TODO @lilleo:这个暂时不需要;嘿嘿~不是每个模块都需要导出哈
  7 +/**
  8 + * 商机状态 Excel VO
  9 + *
  10 + * @author ljlleo
  11 + */
  12 +@Data
  13 +public class CrmBusinessStatusExcelVO {
  14 +
  15 + @ExcelProperty("主键")
  16 + private Long id;
  17 +
  18 + @ExcelProperty("状态类型编号")
  19 + private Long typeId;
  20 +
  21 + @ExcelProperty("状态名")
  22 + private String name;
  23 +
  24 + @ExcelProperty("赢单率")
  25 + private String percent;
  26 +
  27 + @ExcelProperty("排序")
  28 + private Integer sort;
  29 +
  30 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +// TODO @lilleo:这个暂时不需要;嘿嘿~不是每个模块都需要导出哈
  7 +@Schema(description = "管理后台 - 商机状态 Excel 导出 Request VO,参数和 CrmBusinessStatusPageReqVO 是一致的")
  8 +@Data
  9 +public class CrmBusinessStatusExportReqVO {
  10 +
  11 + @Schema(description = "状态类型编号", example = "22882")
  12 + private Long typeId;
  13 +
  14 + @Schema(description = "状态名", example = "李四")
  15 + private String name;
  16 +
  17 + @Schema(description = "赢单率")
  18 + private String percent;
  19 +
  20 + @Schema(description = "排序")
  21 + private Integer sort;
  22 +
  23 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - 商机状态分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmBusinessStatusPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "状态类型编号", example = "22882")
  16 + private Long typeId;
  17 +
  18 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +
  6 +@Schema(description = "管理后台 - 商机状态 Response VO")
  7 +@Data
  8 +@EqualsAndHashCode(callSuper = true)
  9 +@ToString(callSuper = true)
  10 +public class CrmBusinessStatusRespVO extends CrmBusinessStatusBaseVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6802")
  13 + private Long id;
  14 +
  15 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatus/vo/CrmBusinessStatusUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 商机状态更新 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class CrmBusinessStatusUpdateReqVO extends CrmBusinessStatusBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6802")
  17 + @NotNull(message = "编号不能为空")
  18 + private Long id;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/CrmBusinessStatusTypeController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  7 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo.*;
  9 +import cn.iocoder.yudao.module.crm.convert.businessstatustype.CrmBusinessStatusTypeConvert;
  10 +import cn.iocoder.yudao.module.crm.dal.dataobject.businessstatustype.CrmBusinessStatusTypeDO;
  11 +import cn.iocoder.yudao.module.crm.service.businessstatustype.CrmBusinessStatusTypeService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import javax.annotation.Resource;
  20 +import javax.servlet.http.HttpServletResponse;
  21 +import javax.validation.Valid;
  22 +import java.io.IOException;
  23 +import java.util.Collection;
  24 +import java.util.List;
  25 +
  26 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  28 +
  29 +// TODO @lilleo:这个模块,可以挪到 business 下;这样我打开 business 包下,就知道,噢~原来里面有 business 商机、有 type 状态类型、status 具体状态;
  30 +@Tag(name = "管理后台 - 商机状态类型")
  31 +@RestController
  32 +@RequestMapping("/crm/business-status-type")
  33 +@Validated
  34 +public class CrmBusinessStatusTypeController {
  35 +
  36 + @Resource
  37 + private CrmBusinessStatusTypeService businessStatusTypeService;
  38 +
  39 + @PostMapping("/create")
  40 + @Operation(summary = "创建商机状态类型")
  41 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:create')")
  42 + public CommonResult<Long> createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeCreateReqVO createReqVO) {
  43 + return success(businessStatusTypeService.createBusinessStatusType(createReqVO));
  44 + }
  45 +
  46 + @PutMapping("/update")
  47 + @Operation(summary = "更新商机状态类型")
  48 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:update')")
  49 + public CommonResult<Boolean> updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeUpdateReqVO updateReqVO) {
  50 + businessStatusTypeService.updateBusinessStatusType(updateReqVO);
  51 + return success(true);
  52 + }
  53 +
  54 + @DeleteMapping("/delete")
  55 + @Operation(summary = "删除商机状态类型")
  56 + @Parameter(name = "id", description = "编号", required = true)
  57 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')")
  58 + public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {
  59 + businessStatusTypeService.deleteBusinessStatusType(id);
  60 + return success(true);
  61 + }
  62 +
  63 + @GetMapping("/get")
  64 + @Operation(summary = "获得商机状态类型")
  65 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  66 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
  67 + public CommonResult<CrmBusinessStatusTypeRespVO> getBusinessStatusType(@RequestParam("id") Long id) {
  68 + CrmBusinessStatusTypeDO businessStatusType = businessStatusTypeService.getBusinessStatusType(id);
  69 + return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(businessStatusType));
  70 + }
  71 +
  72 + // TODO @lilleo:这个接口,暂时用不到,可以考虑先删除掉
  73 + @GetMapping("/list")
  74 + @Operation(summary = "获得商机状态类型列表")
  75 + @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
  76 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
  77 + public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList(@RequestParam("ids") Collection<Long> ids) {
  78 + List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList(ids);
  79 + return success(CrmBusinessStatusTypeConvert.INSTANCE.convertList(list));
  80 + }
  81 +
  82 + @GetMapping("/page")
  83 + @Operation(summary = "获得商机状态类型分页")
  84 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
  85 + public CommonResult<PageResult<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageVO) {
  86 + PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageVO);
  87 + return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult));
  88 + }
  89 +
  90 + @GetMapping("/export-excel")
  91 + @Operation(summary = "导出商机状态类型 Excel")
  92 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:export')")
  93 + @OperateLog(type = EXPORT)
  94 + public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypeExportReqVO exportReqVO,
  95 + HttpServletResponse response) throws IOException {
  96 + List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList(exportReqVO);
  97 + // 导出 Excel
  98 + List<CrmBusinessStatusTypeExcelVO> datas = CrmBusinessStatusTypeConvert.INSTANCE.convertList02(list);
  99 + ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeExcelVO.class, datas);
  100 + }
  101 +
  102 + @GetMapping("/get-simple-list")
  103 + @Operation(summary = "获得商机状态类型列表")
  104 + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
  105 + public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList() {
  106 + List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeListByStatus(CommonStatusEnum.ENABLE.getStatus());
  107 + return success(CrmBusinessStatusTypeConvert.INSTANCE.convertList(list));
  108 + }
  109 +
  110 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +/**
  9 + * 商机状态类型 Base VO,提供给添加、修改、详细的子 VO 使用
  10 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 + */
  12 +@Data
  13 +public class CrmBusinessStatusTypeBaseVO {
  14 +
  15 + @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  16 + @NotNull(message = "状态类型名不能为空")
  17 + private String name;
  18 +
  19 + @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + @NotNull(message = "使用的部门编号不能为空")
  21 + private String deptIds;
  22 +
  23 + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @NotNull(message = "开启状态不能为空")
  25 + private Boolean status;
  26 +
  27 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +// TODO 状态类型和状态添加,是在一个请求里,所以需要把 CrmBusinessStatusCreateReqVO 融合进来;
  9 +@Schema(description = "管理后台 - 商机状态类型创建 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmBusinessStatusTypeCreateReqVO extends CrmBusinessStatusTypeBaseVO {
  14 +
  15 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +// TODO @lilleo:这个暂时不需要;嘿嘿~不是每个模块都需要导出哈
  9 +/**
  10 + * 商机状态类型 Excel VO
  11 + *
  12 + * @author ljlleo
  13 + */
  14 +@Data
  15 +public class CrmBusinessStatusTypeExcelVO {
  16 +
  17 + @ExcelProperty("主键")
  18 + private Long id;
  19 +
  20 + @ExcelProperty("状态类型名")
  21 + private String name;
  22 +
  23 + @ExcelProperty("使用的部门编号")
  24 + private String deptIds;
  25 +
  26 + @ExcelProperty("开启状态")
  27 + private Boolean status;
  28 +
  29 + @ExcelProperty("创建时间")
  30 + private LocalDateTime createTime;
  31 +
  32 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +
  11 +// TODO @lilleo:这个暂时不需要;嘿嘿~不是每个模块都需要导出哈
  12 +@Schema(description = "管理后台 - 商机状态类型 Excel 导出 Request VO,参数和 CrmBusinessStatusTypePageReqVO 是一致的")
  13 +@Data
  14 +public class CrmBusinessStatusTypeExportReqVO {
  15 +
  16 + @Schema(description = "状态类型名", example = "芋艿")
  17 + private String name;
  18 +
  19 + @Schema(description = "使用的部门编号")
  20 + private String deptIds;
  21 +
  22 + @Schema(description = "开启状态", example = "1")
  23 + private Boolean status;
  24 +
  25 + @Schema(description = "创建时间")
  26 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  27 + private LocalDateTime[] createTime;
  28 +
  29 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - 商机状态类型分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmBusinessStatusTypePageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "状态类型名", example = "芋艿")
  16 + private String name;
  17 +
  18 + @Schema(description = "开启状态", example = "1")
  19 + private Boolean status;
  20 +
  21 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.time.LocalDateTime;
  6 +
  7 +@Schema(description = "管理后台 - 商机状态类型 Response VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class CrmBusinessStatusTypeRespVO extends CrmBusinessStatusTypeBaseVO {
  12 +
  13 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24019")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private LocalDateTime createTime;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/businessstatustype/vo/CrmBusinessStatusTypeUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +// TODO 状态类型和状态添加,是在一个请求里,所以需要把 CrmBusinessStatusUpdateReqVO 融合进来;
  11 +@Schema(description = "管理后台 - 商机状态类型更新 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class CrmBusinessStatusTypeUpdateReqVO extends CrmBusinessStatusTypeBaseVO {
  16 +
  17 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24019")
  18 + @NotNull(message = "主键不能为空")
  19 + private Long id;
  20 +
  21 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
  8 +import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
  10 +import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.servlet.http.HttpServletResponse;
  20 +import javax.validation.Valid;
  21 +import java.io.IOException;
  22 +import java.util.List;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  25 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  26 +
  27 +@Tag(name = "管理后台 - 线索")
  28 +@RestController
  29 +@RequestMapping("/crm/clue")
  30 +@Validated
  31 +public class CrmClueController {
  32 +
  33 + @Resource
  34 + private CrmClueService clueService;
  35 +
  36 + @PostMapping("/create")
  37 + @Operation(summary = "创建线索")
  38 + @PreAuthorize("@ss.hasPermission('crm:clue:create')")
  39 + public CommonResult<Long> createClue(@Valid @RequestBody CrmClueCreateReqVO createReqVO) {
  40 + return success(clueService.createClue(createReqVO));
  41 + }
  42 +
  43 + @PutMapping("/update")
  44 + @Operation(summary = "更新线索")
  45 + @PreAuthorize("@ss.hasPermission('crm:clue:update')")
  46 + public CommonResult<Boolean> updateClue(@Valid @RequestBody CrmClueUpdateReqVO updateReqVO) {
  47 + clueService.updateClue(updateReqVO);
  48 + return success(true);
  49 + }
  50 +
  51 + @DeleteMapping("/delete")
  52 + @Operation(summary = "删除线索")
  53 + @Parameter(name = "id", description = "编号", required = true)
  54 + @PreAuthorize("@ss.hasPermission('crm:clue:delete')")
  55 + public CommonResult<Boolean> deleteClue(@RequestParam("id") Long id) {
  56 + clueService.deleteClue(id);
  57 + return success(true);
  58 + }
  59 +
  60 + @GetMapping("/get")
  61 + @Operation(summary = "获得线索")
  62 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  63 + @PreAuthorize("@ss.hasPermission('crm:clue:query')")
  64 + public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
  65 + CrmClueDO clue = clueService.getClue(id);
  66 + return success(CrmClueConvert.INSTANCE.convert(clue));
  67 + }
  68 +
  69 + @GetMapping("/page")
  70 + @Operation(summary = "获得线索分页")
  71 + @PreAuthorize("@ss.hasPermission('crm:clue:query')")
  72 + public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
  73 + PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO);
  74 + return success(CrmClueConvert.INSTANCE.convertPage(pageResult));
  75 + }
  76 +
  77 + @GetMapping("/export-excel")
  78 + @Operation(summary = "导出线索 Excel")
  79 + @PreAuthorize("@ss.hasPermission('crm:clue:export')")
  80 + @OperateLog(type = EXPORT)
  81 + public void exportClueExcel(@Valid CrmClueExportReqVO exportReqVO,
  82 + HttpServletResponse response) throws IOException {
  83 + List<CrmClueDO> list = clueService.getClueList(exportReqVO);
  84 + // 导出 Excel
  85 + List<CrmClueExcelVO> datas = CrmClueConvert.INSTANCE.convertList02(list);
  86 + ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas);
  87 + }
  88 +
  89 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/package-info.java 0 → 100644
  1 +/**
  2 + * 线索
  3 + */
  4 +package cn.iocoder.yudao.module.crm.controller.admin.clue;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.Mobile;
  4 +import cn.iocoder.yudao.framework.common.validation.Telephone;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import javax.validation.constraints.NotEmpty;
  10 +import javax.validation.constraints.NotNull;
  11 +import java.time.LocalDateTime;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +/**
  16 + * 线索 Base VO,提供给添加、修改、详细的子 VO 使用
  17 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  18 + */
  19 +@Data
  20 +public class CrmClueBaseVO {
  21 +
  22 + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
  23 + @NotEmpty(message = "线索名称不能为空")
  24 + private String name;
  25 +
  26 + @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")
  27 + @NotNull(message = "客户不能为空")
  28 + private Long customerId;
  29 +
  30 + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime contactNextTime;
  33 +
  34 + @Schema(description = "电话", example = "18000000000")
  35 + @Telephone
  36 + private String telephone;
  37 +
  38 + @Schema(description = "手机号", example = "18000000000")
  39 + @Mobile
  40 + private String mobile;
  41 +
  42 + @Schema(description = "地址", example = "北京市海淀区")
  43 + private String address;
  44 +
  45 + @Schema(description = "最后跟进时间")
  46 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  47 + private LocalDateTime contactLastTime;
  48 +
  49 + @Schema(description = "备注", example = "随便")
  50 + private String remark;
  51 +
  52 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import lombok.*;
  4 +import java.util.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import javax.validation.constraints.*;
  7 +
  8 +@Schema(description = "管理后台 - 线索创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class CrmClueCreateReqVO extends CrmClueBaseVO {
  13 +
  14 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.*;
  6 +import java.util.*;
  7 +import java.time.LocalDateTime;
  8 +import java.time.LocalDateTime;
  9 +import java.time.LocalDateTime;
  10 +import java.time.LocalDateTime;
  11 +
  12 +import com.alibaba.excel.annotation.ExcelProperty;
  13 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  14 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  15 +
  16 +/**
  17 + * 线索 Excel VO
  18 + *
  19 + * @author Wanwan
  20 + */
  21 +@Data
  22 +public class CrmClueExcelVO {
  23 +
  24 + @ExcelProperty("编号")
  25 + private Long id;
  26 +
  27 + @ExcelProperty(value = "转化状态", converter = DictConvert.class)
  28 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  29 + private Boolean transformStatus;
  30 +
  31 + @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
  32 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  33 + private Boolean followUpStatus;
  34 +
  35 + @ExcelProperty("线索名称")
  36 + private String name;
  37 +
  38 + // TODO 这里需要导出成客户名称
  39 + @ExcelProperty("客户id")
  40 + private Long customerId;
  41 +
  42 + @ExcelProperty("下次联系时间")
  43 + private LocalDateTime contactNextTime;
  44 +
  45 + @ExcelProperty("电话")
  46 + private String telephone;
  47 +
  48 + @ExcelProperty("手机号")
  49 + private String mobile;
  50 +
  51 + @ExcelProperty("地址")
  52 + private String address;
  53 +
  54 + @ExcelProperty("负责人的用户编号")
  55 + private Long ownerUserId;
  56 +
  57 + @ExcelProperty("最后跟进时间")
  58 + private LocalDateTime contactLastTime;
  59 +
  60 + @ExcelProperty("备注")
  61 + private String remark;
  62 +
  63 + @ExcelProperty("创建时间")
  64 + private LocalDateTime createTime;
  65 +
  66 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import lombok.*;
  4 +import java.util.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  7 +import java.time.LocalDateTime;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - 线索 Excel 导出 Request VO,参数和 CrmCluePageReqVO 是一致的")
  13 +@Data
  14 +public class CrmClueExportReqVO {
  15 +
  16 + @Schema(description = "转化状态", example = "true")
  17 + private Boolean transformStatus;
  18 +
  19 + @Schema(description = "跟进状态", example = "true")
  20 + private Boolean followUpStatus;
  21 +
  22 + @Schema(description = "线索名称", example = "线索xxx")
  23 + private String name;
  24 +
  25 + @Schema(description = "客户id", example = "520")
  26 + private Long customerId;
  27 +
  28 + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
  29 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  30 + private LocalDateTime[] contactNextTime;
  31 +
  32 + @Schema(description = "电话", example = "18000000000")
  33 + private String telephone;
  34 +
  35 + @Schema(description = "手机号", example = "18000000000")
  36 + private String mobile;
  37 +
  38 + @Schema(description = "地址", example = "北京市海淀区")
  39 + private String address;
  40 +
  41 + @Schema(description = "负责人的用户编号", example = "27199")
  42 + private Long ownerUserId;
  43 +
  44 + @Schema(description = "最后跟进时间")
  45 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  46 + private LocalDateTime[] contactLastTime;
  47 +
  48 + @Schema(description = "创建时间")
  49 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  50 + private LocalDateTime[] createTime;
  51 +
  52 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - 线索分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmCluePageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "线索名称", example = "线索xxx")
  16 + private String name;
  17 +
  18 + @Schema(description = "电话", example = "18000000000")
  19 + private String telephone;
  20 +
  21 + @Schema(description = "手机号", example = "18000000000")
  22 + private String mobile;
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +import java.time.LocalDateTime;
  8 +
  9 +@Schema(description = "管理后台 - 线索 Response VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CrmClueRespVO extends CrmClueBaseVO {
  14 +
  15 + @Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969")
  16 + private Long id;
  17 +
  18 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  19 + private LocalDateTime createTime;
  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 +
  27 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 线索更新 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class CrmClueUpdateReqVO extends CrmClueBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969")
  17 + @NotNull(message = "编号不能为空")
  18 + private Long id;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/ContactController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.NumberUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  8 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerExportReqVO;
  11 +import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
  12 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.ContactDO;
  13 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  14 +import cn.iocoder.yudao.module.crm.service.contact.ContactService;
  15 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  16 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  17 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  18 +import com.google.common.collect.Lists;
  19 +import io.swagger.v3.oas.annotations.Operation;
  20 +import io.swagger.v3.oas.annotations.Parameter;
  21 +import io.swagger.v3.oas.annotations.tags.Tag;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.*;
  25 +
  26 +import javax.annotation.Resource;
  27 +import javax.servlet.http.HttpServletResponse;
  28 +import javax.validation.Valid;
  29 +import java.io.IOException;
  30 +import java.util.List;
  31 +import java.util.Map;
  32 +import java.util.Optional;
  33 +import java.util.stream.Collectors;
  34 +
  35 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  36 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  37 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  38 +
  39 +// TODO @zya:crm 所有的类,dou带 Crm 前缀,因为它的名字太通用了,可能和后续的 erp 之类的冲突
  40 +@Tag(name = "管理后台 - CRM 联系人")
  41 +@RestController
  42 +@RequestMapping("/crm/contact")
  43 +@Validated
  44 +public class ContactController {
  45 +
  46 + @Resource
  47 + private ContactService contactService;
  48 + // TODO @zyna:模块内,注入的变量,不用带 crm 前缀哈
  49 + @Resource
  50 + private CrmCustomerService crmCustomerService;
  51 +
  52 + @Resource
  53 + private AdminUserApi adminUserApi;
  54 +
  55 + @PostMapping("/create")
  56 + @Operation(summary = "创建联系人")
  57 + @PreAuthorize("@ss.hasPermission('crm:contact:create')")
  58 + public CommonResult<Long> createContact(@Valid @RequestBody ContactCreateReqVO createReqVO) {
  59 + return success(contactService.createContact(createReqVO, getLoginUserId()));
  60 + }
  61 +
  62 + @PutMapping("/update")
  63 + @Operation(summary = "更新联系人")
  64 + @PreAuthorize("@ss.hasPermission('crm:contact:update')")
  65 + public CommonResult<Boolean> updateContact(@Valid @RequestBody ContactUpdateReqVO updateReqVO) {
  66 + contactService.updateContact(updateReqVO);
  67 + return success(true);
  68 + }
  69 +
  70 + @DeleteMapping("/delete")
  71 + @Operation(summary = "删除联系人")
  72 + @Parameter(name = "id", description = "编号", required = true)
  73 + @PreAuthorize("@ss.hasPermission('crm:contact:delete')")
  74 + public CommonResult<Boolean> deleteContact(@RequestParam("id") Long id) {
  75 + contactService.deleteContact(id);
  76 + return success(true);
  77 + }
  78 +
  79 + @GetMapping("/get")
  80 + @Operation(summary = "获得联系人")
  81 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  82 + @PreAuthorize("@ss.hasPermission('crm:contact:query')")
  83 + public CommonResult<ContactRespVO> getContact(@RequestParam("id") Long id) {
  84 + ContactDO contact = contactService.getContact(id);
  85 + // TODO @zyna:需要考虑 null 的情况;
  86 + ContactRespVO contactRespVO = ContactConvert.INSTANCE.convert(contact);
  87 + // TODO @zyna:可以把数据读完后,convert 统一交给 ContactConvert,让 controller 更简洁;而 convert 专门去做一些转换逻辑
  88 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList(
  89 + NumberUtil.parseLong(contact.getCreator()))));
  90 + contactRespVO.setCreatorName(Optional.ofNullable(userMap.get(NumberUtil.parseLong(contact.getCreator()))).map(AdminUserRespDTO::getNickname).orElse(null));
  91 + contactRespVO.setCustomerName(Optional.ofNullable(crmCustomerService.getCustomer(contact.getCustomerId())).map(CrmCustomerDO::getName).orElse(null));
  92 + return success(contactRespVO);
  93 + }
  94 +
  95 + // TODO @zyna:url 使用中划线噢;然后,单词的拼写也要注意呀,AllList 是不是更好呀;
  96 + @GetMapping("/simpleAlllist")
  97 + @Operation(summary = "获得联系人列表")
  98 + @PreAuthorize("@ss.hasPermission('crm:contact:query')")
  99 + public CommonResult<List<ContactSimpleRespVO>> simpleAlllist() {
  100 + // TODO @zyna:方法名改成,getContactList;方法命名,要动名词,get 动词;all 可以去掉,因为没条件,自然是全部
  101 + List<ContactDO> list = contactService.allContactList();
  102 + return success(ContactConvert.INSTANCE.convertAllList(list));
  103 + }
  104 +
  105 + @GetMapping("/page")
  106 + @Operation(summary = "获得联系人分页")
  107 + @PreAuthorize("@ss.hasPermission('crm:contact:query')")
  108 + public CommonResult<PageResult<ContactRespVO>> getContactPage(@Valid ContactPageReqVO pageVO) {
  109 + PageResult<ContactDO> pageData = contactService.getContactPage(pageVO);
  110 + PageResult<ContactRespVO> pageResult =ContactConvert.INSTANCE.convertPage(pageData);
  111 + // TODO @zyna:需要考虑 null 的情况;
  112 + // TODO @zyna:可以把数据读完后,convert 统一交给 ContactConvert,让 controller 更简洁;而 convert 专门去做一些转换逻辑
  113 + //待接口实现后修改
  114 + List<CrmCustomerDO> crmCustomerDOList = crmCustomerService.getCustomerList(new CrmCustomerExportReqVO());
  115 + Map<Long,CrmCustomerDO> crmCustomerDOMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId,v->v));
  116 + pageResult.getList().forEach(item -> {
  117 + item.setCustomerName(Optional.ofNullable(crmCustomerDOMap.get(item.getCustomerId())).map(CrmCustomerDO::getName).orElse(null));
  118 + });
  119 + return success(pageResult);
  120 + }
  121 +
  122 + // TODO @zyna:可以看下新的导出写法,这里调整下
  123 + @GetMapping("/export-excel")
  124 + @Operation(summary = "导出联系人 Excel")
  125 + @PreAuthorize("@ss.hasPermission('crm:contact:export')")
  126 + @OperateLog(type = EXPORT)
  127 + public void exportContactExcel(@Valid ContactExportReqVO exportReqVO,
  128 + HttpServletResponse response) throws IOException {
  129 + List<ContactDO> list = contactService.getContactList(exportReqVO);
  130 + // 导出 Excel
  131 + List<ContactExcelVO> datas = ContactConvert.INSTANCE.convertList02(list);
  132 + ExcelUtils.write(response, "crm联系人.xls", "数据", ContactExcelVO.class, datas);
  133 + }
  134 +
  135 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +// TODO zyna:参考新的 vo,重新拆分下 VO
  13 +/**
  14 + * CRM 联系人 Base VO,提供给添加、修改、详细的子 VO 使用
  15 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  16 + */
  17 +@Data
  18 +public class ContactBaseVO {
  19 +
  20 + // TODO @zyna:example 最好都写下
  21 + // TODO @zyna:必要的字段校验,例如说 @Mobile,@Emal 等等
  22 +
  23 + @Schema(description = "下次联系时间")
  24 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
  25 + private LocalDateTime nextTime;
  26 +
  27 + @Schema(description = "手机号")
  28 + private String mobile;
  29 +
  30 + @Schema(description = "电话")
  31 + private String telephone;
  32 +
  33 + @Schema(description = "电子邮箱")
  34 + private String email;
  35 +
  36 + @Schema(description = "客户编号", example = "10795")
  37 + private Long customerId;
  38 +
  39 + @Schema(description = "地址")
  40 + private String address;
  41 +
  42 + @Schema(description = "备注", example = "你说的对")
  43 + private String remark;
  44 +
  45 + @Schema(description = "最后跟进时间")
  46 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  47 + private LocalDateTime lastTime;
  48 +
  49 + @Schema(description = "直属上级", example = "23457")
  50 + private Long parentId;
  51 +
  52 + @Schema(description = "姓名", example = "芋艿")
  53 + private String name;
  54 +
  55 + @Schema(description = "职位")
  56 + private String post;
  57 +
  58 + @Schema(description = "QQ")
  59 + private Long qq;
  60 +
  61 + @Schema(description = "微信")
  62 + private String webchat;
  63 +
  64 + @Schema(description = "性别")
  65 + private Integer sex;
  66 +
  67 + @Schema(description = "是否关键决策人")
  68 + private Boolean policyMakers;
  69 +
  70 + @Schema(description = "负责人用户编号", example = "14334")
  71 + private String ownerUserId;
  72 +
  73 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - CRM 联系人创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class ContactCreateReqVO extends ContactBaseVO {
  13 +
  14 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +// TODO @zyna:参考新的 VO 结构,把 ContactExcelVO 融合到 ContactRespVO 中
  9 +/**
  10 + * crm联系人 Excel VO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +@Deprecated
  16 +public class ContactExcelVO {
  17 +
  18 + @ExcelProperty("下次联系时间")
  19 + private LocalDateTime nextTime;
  20 +
  21 + @ExcelProperty("手机号")
  22 + private String mobile;
  23 +
  24 + @ExcelProperty("电话")
  25 + private String telephone;
  26 +
  27 + @ExcelProperty("电子邮箱")
  28 + private String email;
  29 +
  30 + @ExcelProperty("客户编号")
  31 + private Long customerId;
  32 +
  33 + @ExcelProperty("地址")
  34 + private String address;
  35 +
  36 + @ExcelProperty("备注")
  37 + private String remark;
  38 +
  39 + @ExcelProperty("创建时间")
  40 + private LocalDateTime createTime;
  41 +
  42 + @ExcelProperty("最后跟进时间")
  43 + private LocalDateTime lastTime;
  44 +
  45 + @ExcelProperty("主键")
  46 + private Long id;
  47 +
  48 + @ExcelProperty("直属上级")
  49 + private Long parentId;
  50 +
  51 + @ExcelProperty("姓名")
  52 + private String name;
  53 +
  54 + @ExcelProperty("职位")
  55 + private String post;
  56 +
  57 + @ExcelProperty("QQ")
  58 + private Long qq;
  59 +
  60 + @ExcelProperty("微信")
  61 + private String webchat;
  62 +
  63 + @ExcelProperty("性别")
  64 + private Integer sex;
  65 +
  66 + @ExcelProperty("是否关键决策人")
  67 + private Boolean policyMakers;
  68 +
  69 + @ExcelProperty("负责人用户编号")
  70 + private String ownerUserId;
  71 +
  72 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +
  11 +// TODO @zyna:参考新的 VO 结构,使用 ContactPageReqVO 查询导出的数据
  12 +@Schema(description = "管理后台 - crm联系人 Excel 导出 Request VO,参数和 ContactPageReqVO 是一致的")
  13 +@Data
  14 +@Deprecated
  15 +public class ContactExportReqVO {
  16 +
  17 + @Schema(description = "下次联系时间")
  18 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  19 + private LocalDateTime[] nextTime;
  20 +
  21 + @Schema(description = "手机号")
  22 + private String mobile;
  23 +
  24 + @Schema(description = "电话")
  25 + private String telephone;
  26 +
  27 + @Schema(description = "电子邮箱")
  28 + private String email;
  29 +
  30 + @Schema(description = "客户编号", example = "10795")
  31 + private Long customerId;
  32 +
  33 + @Schema(description = "地址")
  34 + private String address;
  35 +
  36 + @Schema(description = "备注", example = "你说的对")
  37 + private String remark;
  38 +
  39 + @Schema(description = "创建时间")
  40 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  41 + private LocalDateTime[] createTime;
  42 +
  43 + @Schema(description = "最后跟进时间")
  44 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  45 + private LocalDateTime[] lastTime;
  46 +
  47 + @Schema(description = "直属上级", example = "23457")
  48 + private Long parentId;
  49 +
  50 + @Schema(description = "姓名", example = "芋艿")
  51 + private String name;
  52 +
  53 + @Schema(description = "职位")
  54 + private String post;
  55 +
  56 + @Schema(description = "QQ")
  57 + private Long qq;
  58 +
  59 + @Schema(description = "微信")
  60 + private String webchat;
  61 +
  62 + @Schema(description = "性别")
  63 + private Integer sex;
  64 +
  65 + @Schema(description = "是否关键决策人")
  66 + private Boolean policyMakers;
  67 +
  68 + @Schema(description = "负责人用户编号", example = "14334")
  69 + private String ownerUserId;
  70 +
  71 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  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 +
  14 +@Schema(description = "管理后台 - crm联系人分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class ContactPageReqVO extends PageParam {
  19 +
  20 + // TODO @zyna:筛选条件
  21 + // ●客户:
  22 + // ●姓名:
  23 + // ●手机、电话、座机、QQ、微信、邮箱
  24 +
  25 + @Schema(description = "下次联系时间")
  26 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  27 + private LocalDateTime[] nextTime;
  28 +
  29 + @Schema(description = "手机号")
  30 + private String mobile;
  31 +
  32 + @Schema(description = "电话")
  33 + private String telephone;
  34 +
  35 + @Schema(description = "电子邮箱")
  36 + private String email;
  37 +
  38 + @Schema(description = "客户编号", example = "10795")
  39 + private Long customerId;
  40 +
  41 + @Schema(description = "地址")
  42 + private String address;
  43 +
  44 + @Schema(description = "备注", example = "你说的对")
  45 + private String remark;
  46 +
  47 + @Schema(description = "创建时间")
  48 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  49 + private LocalDateTime[] createTime;
  50 +
  51 + @Schema(description = "最后跟进时间")
  52 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  53 + private LocalDateTime[] lastTime;
  54 +
  55 + @Schema(description = "直属上级", example = "23457")
  56 + private Long parentId;
  57 +
  58 + @Schema(description = "姓名", example = "芋艿")
  59 + private String name;
  60 +
  61 + @Schema(description = "职位")
  62 + private String post;
  63 +
  64 + @Schema(description = "QQ")
  65 + private Long qq;
  66 +
  67 + @Schema(description = "微信")
  68 + private String webchat;
  69 +
  70 + @Schema(description = "性别")
  71 + private Integer sex;
  72 +
  73 + @Schema(description = "是否关键决策人")
  74 + private Boolean policyMakers;
  75 +
  76 + @Schema(description = "负责人用户编号", example = "14334")
  77 + private String ownerUserId;
  78 +
  79 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.time.LocalDateTime;
  6 +
  7 +@Schema(description = "管理后台 - CRM 联系人 Response VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class ContactRespVO extends ContactBaseVO {
  12 +
  13 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建时间")
  17 + private LocalDateTime createTime;
  18 +
  19 + // TODO @zyna:example 最好写下;
  20 +
  21 + @Schema(description = "创建人")
  22 + private String creatorName;
  23 +
  24 + @Schema(description = "客户名字")
  25 + private String customerName;
  26 +
  27 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactSimpleRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +@Schema(description = "管理后台 - CRM 联系人 Response VO")
  8 +@Data
  9 +@ToString(callSuper = true)
  10 +public class ContactSimpleRespVO {
  11 + @Schema(description = "姓名", example = "芋艿") // TODO @zyna:requiredMode = Schema.RequiredMode.REQUIRED;需要空一行;字段的顺序改下,id 在 name 前面,会更干净
  12 + private String name;
  13 +
  14 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
  15 + private Long id;
  16 +
  17 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/ContactUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - CRM 联系人更新 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class ContactUpdateReqVO extends ContactBaseVO {
  15 +
  16 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
  17 + @NotNull(message = "主键不能为空")
  18 + private Long id;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - CRM 联系人转移 Request VO")
  10 +@Data
  11 +public class CrmContactTransferReqVO {
  12 +
  13 + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  14 + @NotNull(message = "联系人编号不能为空")
  15 + private Long id;
  16 +
  17 + /**
  18 + * 新负责人的用户编号
  19 + */
  20 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  21 + @NotNull(message = "新负责人的用户编号不能为空")
  22 + private Long newOwnerUserId;
  23 +
  24 + /**
  25 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  26 + *
  27 + * 关联 {@link CrmPermissionLevelEnum}
  28 + */
  29 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  30 + private Integer oldOwnerPermissionLevel;
  31 +
  32 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/ContractController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
  8 +import cn.iocoder.yudao.module.crm.convert.contract.ContractConvert;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.ContractDO;
  10 +import cn.iocoder.yudao.module.crm.service.contract.ContractService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.servlet.http.HttpServletResponse;
  20 +import javax.validation.Valid;
  21 +import java.io.IOException;
  22 +import java.util.List;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  25 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  26 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  27 +
  28 +@Tag(name = "管理后台 - CRM 合同")
  29 +@RestController
  30 +@RequestMapping("/crm/contract")
  31 +@Validated
  32 +public class ContractController {
  33 +
  34 + @Resource
  35 + private ContractService contractService;
  36 +
  37 + @PostMapping("/create")
  38 + @Operation(summary = "创建合同")
  39 + @PreAuthorize("@ss.hasPermission('crm:contract:create')")
  40 + public CommonResult<Long> createContract(@Valid @RequestBody ContractCreateReqVO createReqVO) {
  41 + return success(contractService.createContract(createReqVO, getLoginUserId()));
  42 + }
  43 +
  44 + @PutMapping("/update")
  45 + @Operation(summary = "更新合同")
  46 + @PreAuthorize("@ss.hasPermission('crm:contract:update')")
  47 + public CommonResult<Boolean> updateContract(@Valid @RequestBody ContractUpdateReqVO updateReqVO) {
  48 + contractService.updateContract(updateReqVO);
  49 + return success(true);
  50 + }
  51 +
  52 + @DeleteMapping("/delete")
  53 + @Operation(summary = "删除合同")
  54 + @Parameter(name = "id", description = "编号", required = true)
  55 + @PreAuthorize("@ss.hasPermission('crm:contract:delete')")
  56 + public CommonResult<Boolean> deleteContract(@RequestParam("id") Long id) {
  57 + contractService.deleteContract(id);
  58 + return success(true);
  59 + }
  60 +
  61 + @GetMapping("/get")
  62 + @Operation(summary = "获得合同")
  63 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  64 + @PreAuthorize("@ss.hasPermission('crm:contract:query')")
  65 + public CommonResult<ContractRespVO> getContract(@RequestParam("id") Long id) {
  66 + ContractDO contract = contractService.getContract(id);
  67 + return success(ContractConvert.INSTANCE.convert(contract));
  68 + }
  69 +
  70 + @GetMapping("/page")
  71 + @Operation(summary = "获得合同分页")
  72 + @PreAuthorize("@ss.hasPermission('crm:contract:query')")
  73 + public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid ContractPageReqVO pageVO) {
  74 + PageResult<ContractDO> pageResult = contractService.getContractPage(pageVO);
  75 + return success(ContractConvert.INSTANCE.convertPage(pageResult));
  76 + }
  77 +
  78 + @GetMapping("/export-excel")
  79 + @Operation(summary = "导出合同 Excel")
  80 + @PreAuthorize("@ss.hasPermission('crm:contract:export')")
  81 + @OperateLog(type = EXPORT)
  82 + public void exportContractExcel(@Valid ContractExportReqVO exportReqVO,
  83 + HttpServletResponse response) throws IOException {
  84 + List<ContractDO> list = contractService.getContractList(exportReqVO);
  85 + // 导出 Excel
  86 + List<ContractExcelVO> datas = ContractConvert.INSTANCE.convertList02(list);
  87 + ExcelUtils.write(response, "合同.xls", "数据", ContractExcelVO.class, datas);
  88 + }
  89 +
  90 + @PutMapping("/transfer")
  91 + @Operation(summary = "合同转移")
  92 + @PreAuthorize("@ss.hasPermission('crm:contract:update')")
  93 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
  94 + contractService.transferContract(reqVO, getLoginUserId());
  95 + return success(true);
  96 + }
  97 +
  98 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +// TODO @dhb52:所有类,带下 Crm 前缀,避免和别的模块重复
  13 +/**
  14 + * 合同 Base VO,提供给添加、修改、详细的子 VO 使用
  15 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  16 + */
  17 +@Data
  18 +public class ContractBaseVO {
  19 +
  20 + // TODO @dhb52:类似 no 字段的 example 要写xia 哈;
  21 +
  22 + @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  23 + @NotNull(message = "合同名称不能为空")
  24 + private String name;
  25 +
  26 + // TODO @dhb52:这个必须传递
  27 + @Schema(description = "客户编号", example = "18336")
  28 + private Long customerId;
  29 +
  30 + @Schema(description = "商机编号", example = "10864")
  31 + private Long businessId;
  32 +
  33 + @Schema(description = "工作流编号", example = "1043")
  34 + private Long processInstanceId;
  35 +
  36 + // TODO @dhb52:这个必须传递
  37 + @Schema(description = "下单日期")
  38 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  39 + private LocalDateTime orderDate;
  40 +
  41 + // TODO @dhb52:这个必须传递
  42 + @Schema(description = "负责人的用户编号", example = "17144")
  43 + private Long ownerUserId;
  44 +
  45 + // TODO @芋艿:未来应该支持自动生成;
  46 + // TODO @dhb52:这个必须传递;
  47 + @Schema(description = "合同编号")
  48 + private String no;
  49 +
  50 + @Schema(description = "开始时间")
  51 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  52 + private LocalDateTime startTime;
  53 +
  54 + @Schema(description = "结束时间")
  55 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  56 + private LocalDateTime endTime;
  57 +
  58 + @Schema(description = "合同金额", example = "5617")
  59 + private Integer price;
  60 +
  61 + @Schema(description = "整单折扣")
  62 + private Integer discountPercent;
  63 +
  64 + @Schema(description = "产品总金额", example = "19510")
  65 + private Integer productPrice;
  66 +
  67 + @Schema(description = "联系人编号", example = "18546")
  68 + private Long contactId;
  69 +
  70 + @Schema(description = "公司签约人", example = "14036")
  71 + private Long signUserId;
  72 +
  73 + @Schema(description = "最后跟进时间")
  74 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  75 + private LocalDateTime contactLastTime;
  76 +
  77 + @Schema(description = "备注", example = "你猜")
  78 + private String remark;
  79 +
  80 + // TODO @dhb52:增加一个 status 字段:具体有哪些值,你来枚举下;主要页面上有个【草稿】【提交审核】的流程,可以看看。然后要对接工作流,这块也可以看看,不确定的地方问我。
  81 +
  82 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - CRM 合同创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class ContractCreateReqVO extends ContractBaseVO {
  13 +
  14 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * CRM 合同 Excel VO
  10 + *
  11 + * @author dhb52
  12 + */
  13 +@Data
  14 +public class ContractExcelVO {
  15 +
  16 + @ExcelProperty("合同编号")
  17 + private Long id;
  18 +
  19 + @ExcelProperty("合同名称")
  20 + private String name;
  21 +
  22 + @ExcelProperty("客户编号")
  23 + private Long customerId;
  24 +
  25 + @ExcelProperty("商机编号")
  26 + private Long businessId;
  27 +
  28 + @ExcelProperty("工作流编号")
  29 + private Long processInstanceId;
  30 +
  31 + @ExcelProperty("下单日期")
  32 + private LocalDateTime orderDate;
  33 +
  34 + @ExcelProperty("负责人的用户编号")
  35 + private Long ownerUserId;
  36 +
  37 + @ExcelProperty("合同编号")
  38 + private String no;
  39 +
  40 + @ExcelProperty("开始时间")
  41 + private LocalDateTime startTime;
  42 +
  43 + @ExcelProperty("结束时间")
  44 + private LocalDateTime endTime;
  45 +
  46 + @ExcelProperty("合同金额")
  47 + private Integer price;
  48 +
  49 + @ExcelProperty("整单折扣")
  50 + private Integer discountPercent;
  51 +
  52 + @ExcelProperty("产品总金额")
  53 + private Integer productPrice;
  54 +
  55 + @ExcelProperty("联系人编号")
  56 + private Long contactId;
  57 +
  58 + @ExcelProperty("公司签约人")
  59 + private Long signUserId;
  60 +
  61 + @ExcelProperty("最后跟进时间")
  62 + private LocalDateTime contactLastTime;
  63 +
  64 + @ExcelProperty("备注")
  65 + private String remark;
  66 +
  67 + @ExcelProperty("创建时间")
  68 + private LocalDateTime createTime;
  69 +
  70 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +
  11 +@Schema(description = "管理后台 - CRM 合同 Excel 导出 Request VO,参数和 ContractPageReqVO 是一致的")
  12 +@Data
  13 +public class ContractExportReqVO {
  14 +
  15 + @Schema(description = "合同名称", example = "王五")
  16 + private String name;
  17 +
  18 + @Schema(description = "客户编号", example = "18336")
  19 + private Long customerId;
  20 +
  21 + @Schema(description = "商机编号", example = "10864")
  22 + private Long businessId;
  23 +
  24 + @Schema(description = "下单日期")
  25 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  26 + private LocalDateTime[] orderDate;
  27 +
  28 + @Schema(description = "合同编号")
  29 + private String no;
  30 +
  31 + @Schema(description = "整单折扣")
  32 + private Integer discountPercent;
  33 +
  34 + @Schema(description = "产品总金额", example = "19510")
  35 + private Integer productPrice;
  36 +
  37 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  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 +
  14 +@Schema(description = "管理后台 - CRM 合同分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class ContractPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "合同名称", example = "王五")
  21 + private String name;
  22 +
  23 + @Schema(description = "客户编号", example = "18336")
  24 + private Long customerId;
  25 +
  26 + @Schema(description = "商机编号", example = "10864")
  27 + private Long businessId;
  28 +
  29 + @Schema(description = "下单日期")
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + private LocalDateTime[] orderDate;
  32 +
  33 + @Schema(description = "合同编号")
  34 + private String no;
  35 +
  36 + @Schema(description = "整单折扣")
  37 + private Integer discountPercent;
  38 +
  39 + @Schema(description = "产品总金额", example = "19510")
  40 + private Integer productPrice;
  41 +
  42 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - CRM 合同 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class ContractRespVO extends ContractBaseVO {
  15 +
  16 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  17 + private Long id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - CRM 合同更新 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class ContractUpdateReqVO extends ContractBaseVO {
  15 +
  16 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  17 + @NotNull(message = "合同编号不能为空")
  18 + private Long id;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
  2 +
  3 +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - CRM 合同转移 Request VO")
  10 +@Data
  11 +public class CrmContractTransferReqVO {
  12 +
  13 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  14 + @NotNull(message = "联系人编号不能为空")
  15 + private Long id;
  16 +
  17 + /**
  18 + * 新负责人的用户编号
  19 + */
  20 + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  21 + @NotNull(message = "新负责人的用户编号不能为空")
  22 + private Long newOwnerUserId;
  23 +
  24 + /**
  25 + * 老负责人加入团队后的权限级别。如果 null 说明移除
  26 + *
  27 + * 关联 {@link CrmPermissionLevelEnum}
  28 + */
  29 + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  30 + private Integer oldOwnerPermissionLevel;
  31 +
  32 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  8 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
  10 +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  12 +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
  13 +import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum;
  14 +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
  15 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  16 +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  17 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  18 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  19 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  20 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  21 +import io.swagger.v3.oas.annotations.Operation;
  22 +import io.swagger.v3.oas.annotations.Parameter;
  23 +import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import org.springframework.security.access.prepost.PreAuthorize;
  25 +import org.springframework.util.CollectionUtils;
  26 +import org.springframework.validation.annotation.Validated;
  27 +import org.springframework.web.bind.annotation.*;
  28 +
  29 +import javax.annotation.Resource;
  30 +import javax.servlet.http.HttpServletResponse;
  31 +import javax.validation.Valid;
  32 +import java.io.IOException;
  33 +import java.util.Collections;
  34 +import java.util.List;
  35 +import java.util.Map;
  36 +import java.util.Set;
  37 +
  38 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
  39 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  42 +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  43 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  44 +
  45 +@Tag(name = "管理后台 - CRM 客户")
  46 +@RestController
  47 +@RequestMapping("/crm/customer")
  48 +@Validated
  49 +public class CrmCustomerController {
  50 +
  51 + @Resource
  52 + private CrmCustomerService customerService;
  53 +
  54 + @Resource
  55 + private DeptApi deptApi;
  56 + @Resource
  57 + private AdminUserApi adminUserApi;
  58 + @Resource
  59 + private CrmPermissionService permissionService;
  60 +
  61 + @PostMapping("/create")
  62 + @Operation(summary = "创建客户")
  63 + @PreAuthorize("@ss.hasPermission('crm:customer:create')")
  64 + public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) {
  65 + return success(customerService.createCustomer(createReqVO, getLoginUserId()));
  66 + }
  67 +
  68 + @PutMapping("/update")
  69 + @Operation(summary = "更新客户")
  70 + @PreAuthorize("@ss.hasPermission('crm:customer:update')")
  71 + public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
  72 + customerService.updateCustomer(updateReqVO);
  73 + return success(true);
  74 + }
  75 +
  76 + @DeleteMapping("/delete")
  77 + @Operation(summary = "删除客户")
  78 + @Parameter(name = "id", description = "编号", required = true)
  79 + @PreAuthorize("@ss.hasPermission('crm:customer:delete')")
  80 + public CommonResult<Boolean> deleteCustomer(@RequestParam("id") Long id) {
  81 + customerService.deleteCustomer(id);
  82 + return success(true);
  83 + }
  84 +
  85 + @GetMapping("/get")
  86 + @Operation(summary = "获得客户")
  87 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  88 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  89 + public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
  90 + // 1. 获取客户
  91 + CrmCustomerDO customer = customerService.getCustomer(id);
  92 + if (customer == null) {
  93 + return success(null);
  94 + }
  95 +
  96 + // 2. 拼接数据
  97 + // 2.1 获取负责人
  98 + List<CrmPermissionDO> ownerList = permissionService.getPermissionByBizTypeAndBizIdsAndLevel(
  99 + CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singletonList(customer.getId()),
  100 + CrmPermissionLevelEnum.OWNER.getLevel());
  101 + Map<Long, CrmPermissionDO> ownerMap = convertMap(ownerList, CrmPermissionDO::getBizId);
  102 + // 2.2 获取负责人详情
  103 + Set<Long> userIds = convertSet(ownerList, CrmPermissionDO::getUserId);
  104 + userIds.add(Long.parseLong(customer.getCreator())); // 加入创建者
  105 + List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIds);
  106 + Map<Long, AdminUserRespDTO> userMap = convertMap(userList, AdminUserRespDTO::getId);
  107 + // 2.3 获取部门详情
  108 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
  109 + return success(CrmCustomerConvert.INSTANCE.convert(customer, ownerMap, userMap, deptMap));
  110 + }
  111 +
  112 + // TODO @puhui999:可以在 CrmCustomerPageReqVO 里面加个 pool 参数,为 true 时,代表来自公海客户的分页
  113 + @GetMapping("/page")
  114 + @Operation(summary = "获得客户分页")
  115 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  116 + public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
  117 + PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
  118 + if (CollUtil.isEmpty(pageResult.getList())) {
  119 + return success(PageResult.empty(pageResult.getTotal()));
  120 + }
  121 + // 拼接数据
  122 + // TODO @puhui999:这块的拼接逻辑,可以和 convertPage 合并下;
  123 +// Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  124 +// convertSetByFlatMap(pageResult.getList(), user -> Stream.of(NumberUtil.parseLong(user.getCreator()), user.getOwnerUserId())));
  125 +// Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  126 +// convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  127 + return convertPage(customerService.getCustomerPage(pageVO, getLoginUserId()));
  128 + }
  129 +
  130 + // TODO @puhui999:
  131 + @GetMapping("/pool-page")
  132 + @Operation(summary = "获得公海客户分页")
  133 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  134 + public CommonResult<PageResult<CrmCustomerRespVO>> getPoolCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
  135 + return convertPage(customerService.getCustomerPage(pageVO, CrmPermissionDO.POOL_USER_ID));
  136 + }
  137 +
  138 + private CommonResult<PageResult<CrmCustomerRespVO>> convertPage(PageResult<CrmCustomerDO> pageResult) {
  139 + // 2. 拼接数据
  140 + Set<Long> ids = convertSet(pageResult.getList(), CrmCustomerDO::getId);
  141 + // 2.1 获取负责人
  142 + List<CrmPermissionDO> ownerList = permissionService.getPermissionByBizTypeAndBizIdsAndLevel(
  143 + CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, CrmPermissionLevelEnum.OWNER.getLevel());
  144 + Map<Long, CrmPermissionDO> ownerMap = convertMap(ownerList, CrmPermissionDO::getBizId);
  145 + // 2.2 获取负责人详情
  146 + Set<Long> userIds = convertSet(ownerList, CrmPermissionDO::getUserId);
  147 + userIds.addAll(convertSet(pageResult.getList(), item -> Long.parseLong(item.getCreator()))); // 加入创建者
  148 + List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIds);
  149 + Map<Long, AdminUserRespDTO> userMap = convertMap(userList, AdminUserRespDTO::getId);
  150 + // 2.3 获取部门详情
  151 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
  152 + return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, ownerMap, userMap, deptMap));
  153 + }
  154 +
  155 + @GetMapping("/export-excel")
  156 + @Operation(summary = "导出客户 Excel")
  157 + @PreAuthorize("@ss.hasPermission('crm:customer:export')")
  158 + @OperateLog(type = EXPORT)
  159 + public void exportCustomerExcel(@Valid CrmCustomerExportReqVO exportReqVO,
  160 + HttpServletResponse response) throws IOException {
  161 + List<CrmCustomerDO> list = customerService.getCustomerList(exportReqVO);
  162 + // 导出 Excel
  163 + List<CrmCustomerExcelVO> datas = CrmCustomerConvert.INSTANCE.convertList02(list);
  164 + ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerExcelVO.class, datas);
  165 + }
  166 +
  167 + @PutMapping("/transfer")
  168 + @Operation(summary = "客户转移")
  169 + @PreAuthorize("@ss.hasPermission('crm:customer:update')")
  170 + public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
  171 + customerService.transferCustomer(reqVO, getLoginUserId());
  172 + return success(true);
  173 + }
  174 +
  175 + // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;
  176 + @PutMapping("/lock")
  177 + @Operation(summary = "锁定/解锁客户")
  178 + @PreAuthorize("@ss.hasPermission('crm:customer:update')")
  179 + public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
  180 + customerService.lockCustomer(updateReqVO);
  181 + return success(true);
  182 + }
  183 +
  184 + @PutMapping("/receive")
  185 + @Operation(summary = "领取公海客户")
  186 + // TODO @xiaqing:1)receiveCustomer 方法名字;2)cIds 改成 ids,要加下 @RequestParam,还有 swagger 注解;3)参数非空,使用 validator 校验;4)返回 true 即可;
  187 + @PreAuthorize("@ss.hasPermission('crm:customer:receive')")
  188 + public CommonResult<String> receiveByIds(List<Long> cIds){
  189 + // 判断是否为空
  190 + if(CollectionUtils.isEmpty(cIds))
  191 + return error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),GlobalErrorCodeConstants.BAD_REQUEST.getMsg());
  192 + // 领取公海任务
  193 + // TODO @xiaqing:userid,通过 controller 传递给 service,不要在 service 里面获取,无状态
  194 + customerService.receive(cIds);
  195 + return success("领取成功");
  196 + }
  197 +
  198 + // TODO @xiaqing:1)distributeCustomer 方法名;2)cIds 同上;3)参数校验,同上;4)ownerId 改成 ownerUserId,和别的模块统一;5)返回 true 即可;
  199 + @PutMapping("/distributeByIds")
  200 + @Operation(summary = "分配公海给对应负责人")
  201 + @PreAuthorize("@ss.hasPermission('crm:customer:distributeByIds')")
  202 + public CommonResult<String> distributeByIds(Long ownerId,List<Long>cIds){
  203 + //判断参数不能为空
  204 + if(ownerId==null || CollectionUtils.isEmpty(cIds))
  205 + return error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),GlobalErrorCodeConstants.BAD_REQUEST.getMsg());
  206 + customerService.distributeByIds(cIds,ownerId);
  207 + return success("分配成功");
  208 + }
  209 +
  210 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigCreateReqVO;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigPageReqVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigRespVO;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigUpdateReqVO;
  10 +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.customerlimitconfig.CrmCustomerLimitConfigDO;
  12 +import cn.iocoder.yudao.module.crm.service.customerlimitconfig.CrmCustomerLimitConfigService;
  13 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  14 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  15 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  16 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  17 +import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.*;
  23 +
  24 +import javax.annotation.Resource;
  25 +import javax.validation.Valid;
  26 +import java.util.Collection;
  27 +import java.util.Map;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  31 +
  32 +@Tag(name = "管理后台 - 客户限制配置")
  33 +@RestController
  34 +@RequestMapping("/crm/customer-limit-config")
  35 +@Validated
  36 +public class CrmCustomerLimitConfigController {
  37 +
  38 + @Resource
  39 + private CrmCustomerLimitConfigService customerLimitConfigService;
  40 +
  41 + @Resource
  42 + private DeptApi deptApi;
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 +
  46 + @PostMapping("/create")
  47 + @Operation(summary = "创建客户限制配置")
  48 + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')")
  49 + public CommonResult<Long> createCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigCreateReqVO createReqVO) {
  50 + return success(customerLimitConfigService.createCustomerLimitConfig(createReqVO));
  51 + }
  52 +
  53 + @PutMapping("/update")
  54 + @Operation(summary = "更新客户限制配置")
  55 + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:update')")
  56 + public CommonResult<Boolean> updateCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigUpdateReqVO updateReqVO) {
  57 + customerLimitConfigService.updateCustomerLimitConfig(updateReqVO);
  58 + return success(true);
  59 + }
  60 +
  61 + @DeleteMapping("/delete")
  62 + @Operation(summary = "删除客户限制配置")
  63 + @Parameter(name = "id", description = "编号", required = true)
  64 + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:delete')")
  65 + public CommonResult<Boolean> deleteCustomerLimitConfig(@RequestParam("id") Long id) {
  66 + customerLimitConfigService.deleteCustomerLimitConfig(id);
  67 + return success(true);
  68 + }
  69 +
  70 + @GetMapping("/get")
  71 + @Operation(summary = "获得客户限制配置")
  72 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  73 + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
  74 + public CommonResult<CrmCustomerLimitConfigRespVO> getCustomerLimitConfig(@RequestParam("id") Long id) {
  75 + CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigService.getCustomerLimitConfig(id);
  76 + // 拼接数据
  77 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(customerLimitConfig.getUserIds());
  78 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(customerLimitConfig.getDeptIds());
  79 + return success(CrmCustomerLimitConfigConvert.INSTANCE.convert(customerLimitConfig, userMap, deptMap));
  80 + }
  81 +
  82 + @GetMapping("/page")
  83 + @Operation(summary = "获得客户限制配置分页")
  84 + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
  85 + public CommonResult<PageResult<CrmCustomerLimitConfigRespVO>> getCustomerLimitConfigPage(@Valid CrmCustomerLimitConfigPageReqVO pageVO) {
  86 + PageResult<CrmCustomerLimitConfigDO> pageResult = customerLimitConfigService.getCustomerLimitConfigPage(pageVO);
  87 + if (CollUtil.isEmpty(pageResult.getList())) {
  88 + return success(PageResult.empty(pageResult.getTotal()));
  89 + }
  90 + // 拼接数据
  91 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  92 + convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream));
  93 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  94 + convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream));
  95 + return success(CrmCustomerLimitConfigConvert.INSTANCE.convertPage(pageResult, userMap, deptMap));
  96 + }
  97 +
  98 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPoolConfigRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPoolConfigUpdateReqVO;
  6 +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
  7 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
  8 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import org.springframework.security.access.prepost.PreAuthorize;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.*;
  14 +
  15 +import javax.annotation.Resource;
  16 +import javax.validation.Valid;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - CRM 客户公海配置")
  21 +@RestController
  22 +@RequestMapping("/crm/customer-pool-config")
  23 +@Validated
  24 +public class CrmCustomerPoolConfigController {
  25 +
  26 + @Resource
  27 + private CrmCustomerPoolConfigService customerPoolConfigService;
  28 +
  29 + @GetMapping("/get")
  30 + @Operation(summary = "获取客户公海规则设置")
  31 + @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")
  32 + public CommonResult<CrmCustomerPoolConfigRespVO> getCustomerPoolConfig() {
  33 + CrmCustomerPoolConfigDO customerPoolConfig = customerPoolConfigService.getCustomerPoolConfig();
  34 + return success(CrmCustomerConvert.INSTANCE.convert(customerPoolConfig));
  35 + }
  36 +
  37 + // TODO @wanwan:这个请求,搞成 save 哈;
  38 + @PutMapping("/update")
  39 + @Operation(summary = "更新客户公海规则设置")
  40 + @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:update')")
  41 + public CommonResult<Boolean> updateCustomerPoolConfig(@Valid @RequestBody CrmCustomerPoolConfigUpdateReqVO updateReqVO) {
  42 + customerPoolConfigService.updateCustomerPoolConfig(updateReqVO);
  43 + return success(true);
  44 + }
  45 +
  46 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.Mobile;
  5 +import cn.iocoder.yudao.framework.common.validation.Telephone;
  6 +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import javax.validation.constraints.Email;
  12 +import javax.validation.constraints.NotEmpty;
  13 +import javax.validation.constraints.Size;
  14 +import java.time.LocalDateTime;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  17 +
  18 +/**
  19 + * 客户 Base VO,提供给添加、修改、详细的子 VO 使用
  20 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  21 + */
  22 +@Data
  23 +public class CrmCustomerBaseVO {
  24 +
  25 + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
  26 + @NotEmpty(message = "客户名称不能为空")
  27 + private String name;
  28 +
  29 + @Schema(description = "所属行业", example = "1")
  30 + private Integer industryId;
  31 +
  32 + @Schema(description = "客户等级", example = "2")
  33 + @InEnum(CrmCustomerLevelEnum.class)
  34 + private Integer level;
  35 +
  36 + @Schema(description = "客户来源", example = "3")
  37 + private Integer source;
  38 +
  39 + @Schema(description = "手机", example = "18000000000")
  40 + @Mobile
  41 + private String mobile;
  42 +
  43 + @Schema(description = "电话", example = "18000000000")
  44 + @Telephone
  45 + private String telephone;
  46 +
  47 + @Schema(description = "网址", example = "https://www.baidu.com")
  48 + private String website;
  49 +
  50 + @Schema(description = "QQ", example = "123456789")
  51 + @Size(max = 20, message = "QQ长度不能超过 20 个字符")
  52 + private String qq;
  53 +
  54 + @Schema(description = "wechat", example = "123456789")
  55 + @Size(max = 255, message = "微信长度不能超过 255 个字符")
  56 + private String wechat;
  57 +
  58 + @Schema(description = "email", example = "123456789@qq.com")
  59 + @Email(message = "邮箱格式不正确")
  60 + @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
  61 + private String email;
  62 +
  63 + @Schema(description = "客户描述", example = "任意文字")
  64 + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
  65 + private String description;
  66 +
  67 + @Schema(description = "备注", example = "随便")
  68 + private String remark;
  69 +
  70 + @Schema(description = "地区编号", example = "20158")
  71 + private Integer areaId;
  72 +
  73 + @Schema(description = "详细地址", example = "北京市海淀区")
  74 + private String detailAddress;
  75 +
  76 + @Schema(description = "下次联系时间")
  77 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  78 + private LocalDateTime contactNextTime;
  79 +
  80 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - CRM 客户创建 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class CrmCustomerCreateReqVO extends CrmCustomerBaseVO {
  15 +
  16 + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  17 + @NotNull(message = "负责人不能为空")
  18 + private Long ownerUserId;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  5 +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
  6 +import com.alibaba.excel.annotation.ExcelProperty;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +// TODO 芋艿:导出最后做,等基本确认的差不多之后;
  13 +/**
  14 + * CRM 客户 Excel VO
  15 + *
  16 + * @author Wanwan
  17 + */
  18 +@Data
  19 +public class CrmCustomerExcelVO {
  20 +
  21 + @ExcelProperty("编号")
  22 + private Long id;
  23 +
  24 + @ExcelProperty("客户名称")
  25 + private String name;
  26 +
  27 + @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
  28 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  29 + private Boolean followUpStatus;
  30 +
  31 + @ExcelProperty(value = "锁定状态", converter = DictConvert.class)
  32 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  33 + private Boolean lockStatus;
  34 +
  35 + @ExcelProperty(value = "成交状态", converter = DictConvert.class)
  36 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  37 + private Boolean dealStatus;
  38 +
  39 + @ExcelProperty(value = "所属行业", converter = DictConvert.class)
  40 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
  41 + private Integer industryId;
  42 +
  43 + @ExcelProperty(value = "客户等级", converter = DictConvert.class)
  44 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
  45 + private Integer level;
  46 +
  47 + @ExcelProperty(value = "客户来源", converter = DictConvert.class)
  48 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
  49 + private Integer source;
  50 +
  51 + @ExcelProperty("手机")
  52 + private String mobile;
  53 +
  54 + @ExcelProperty("电话")
  55 + private String telephone;
  56 +
  57 + @ExcelProperty("网址")
  58 + private String website;
  59 +
  60 + @ExcelProperty("QQ")
  61 + private String qq;
  62 +
  63 + @ExcelProperty("wechat")
  64 + private String wechat;
  65 +
  66 + @ExcelProperty("email")
  67 + private String email;
  68 +
  69 + @ExcelProperty("客户描述")
  70 + private String description;
  71 +
  72 + @ExcelProperty("备注")
  73 + private String remark;
  74 +
  75 + @ExcelProperty("负责人的用户编号")
  76 + private Long ownerUserId;
  77 +
  78 + @ExcelProperty("地区编号")
  79 + private Integer areaId;
  80 +
  81 + @ExcelProperty("详细地址")
  82 + private String detailAddress;
  83 +
  84 + @ExcelProperty("最后跟进时间")
  85 + private LocalDateTime contactLastTime;
  86 +
  87 + @ExcelProperty("下次联系时间")
  88 + private LocalDateTime contactNextTime;
  89 +
  90 + @ExcelProperty("创建时间")
  91 + private LocalDateTime createTime;
  92 +
  93 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerExportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +// TODO 芋艿:导出最后做,等基本确认的差不多之后;
  7 +@Schema(description = "管理后台 - CRM 客户 Excel 导出 Request VO,参数和 CrmCustomerPageReqVO 是一致的")
  8 +@Data
  9 +public class CrmCustomerExportReqVO {
  10 +
  11 + @Schema(description = "客户名称", example = "赵六")
  12 + private String name;
  13 +
  14 + @Schema(description = "手机", example = "18000000000")
  15 + private String mobile;
  16 +
  17 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLimitConfigBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +import java.util.List;
  8 +
  9 +// TODO @wanwan:vo 下,可以新建一个 limitconfig,放它的 vo;
  10 +/**
  11 + * 客户限制配置 Base VO,提供给添加、修改、详细的子 VO 使用
  12 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  13 + */
  14 +@Data
  15 +public class CrmCustomerLimitConfigBaseVO {
  16 +
  17 + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  18 + @NotNull(message = "规则类型不能为空")
  19 + private Integer type;
  20 +
  21 + @Schema(description = "规则适用人群")
  22 + private List<Long> userIds;
  23 +
  24 + @Schema(description = "规则适用部门")
  25 + private List<Long> deptIds;
  26 +
  27 + @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384")
  28 + @NotNull(message = "数量上限不能为空")
  29 + private Integer maxCount;
  30 +
  31 + @Schema(description = "成交客户是否占有拥有客户数(当 type = 1 时)")
  32 + private Boolean dealCountEnabled;
  33 +
  34 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLimitConfigCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - 客户限制配置创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class CrmCustomerLimitConfigCreateReqVO extends CrmCustomerLimitConfigBaseVO {
  13 +
  14 +}