Merge remote-tracking branch 'upstream/master'

This commit is contained in:
dhb52 2023-11-21 23:19:49 +08:00
commit edea17e781
320 changed files with 14701 additions and 142 deletions

View File

@ -21,6 +21,7 @@
<!-- <module>yudao-module-mp</module>--> <!-- <module>yudao-module-mp</module>-->
<!-- <module>yudao-module-pay</module>--> <!-- <module>yudao-module-pay</module>-->
<!-- <module>yudao-module-mall</module>--> <!-- <module>yudao-module-mall</module>-->
<!-- <module>yudao-module-crm</module>-->
<!-- 示例项目 --> <!-- 示例项目 -->
<!-- <module>yudao-example</module>--> <!-- <module>yudao-example</module>-->
</modules> </modules>

1
sql/mysql/crm.sql Normal file
View File

@ -0,0 +1 @@
SET NAMES utf8mb4;

20
sql/mysql/crm_data.sql Normal file
View File

@ -0,0 +1,20 @@
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');
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');

88
sql/mysql/crm_menu.sql Normal file
View File

@ -0,0 +1,88 @@
-- ----------------------------
-- 客户公海配置
-- ----------------------------
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'客户公海配置', '', 2, 0, 2397,
'customer-pool-config', 'ep:data-analysis', 'crm/customerPoolConf/index', 0, 'CustomerPoolConf'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户公海配置保存', 'crm:customer-pool-config:update', 3, 1, @parentId,
'', '', '', 0
);
-- ----------------------------
-- 客户限制配置管理
-- ----------------------------
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'客户限制配置管理', '', 2, 0, 2397,
'customer-limit-config', '', 'crm/customerLimitConfig/index', 0, 'CrmCustomerLimitConfig'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL如果你是 OraclePostgreSQLSQLServer 的话需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户限制配置查询', 'crm:customer-limit-config:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户限制配置创建', 'crm:customer-limit-config:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, @parentId,
'', '', '', 0
);

View File

@ -246,3 +246,11 @@ VALUES (
'转账订单', '', 2, 3, 1117, '转账订单', '', 2, 3, 1117,
'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer' 'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer'
); );
-- 转账通知脚本
ALTER TABLE `pay_app`
ADD COLUMN `transfer_notify_url` varchar(1024) NOT NULL COMMENT '转账结果的回调地址' AFTER `refund_notify_url`;
ALTER TABLE `pay_notify_task`
MODIFY COLUMN `merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '商户订单编号' AFTER `status`,
ADD COLUMN `merchant_transfer_id` varchar(64) COMMENT '商户转账单编号' AFTER `merchant_order_id`;

View File

@ -535,8 +535,8 @@ CREATE TABLE `infra_demo01_contact` (
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
`sex` tinyint(1) NOT NULL COMMENT '性别', `sex` tinyint(1) NOT NULL COMMENT '性别',
`birthday` datetime NOT NULL COMMENT '出生年', `birthday` datetime NOT NULL COMMENT '出生年',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介', `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介',
`avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -651,7 +651,7 @@ CREATE TABLE `infra_demo03_student` (
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
`sex` tinyint NOT NULL COMMENT '性别', `sex` tinyint NOT NULL COMMENT '性别',
`birthday` datetime NOT NULL COMMENT '出生日期', `birthday` datetime NOT NULL COMMENT '出生日期',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介', `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',

View File

@ -35,9 +35,12 @@ public class ServiceErrorCodeRange {
// 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000) // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
// 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000) // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
// 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000) // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
// 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
// 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000) // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
// 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
// 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000) // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
// 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000) // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
// 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)
} }

View File

@ -280,6 +280,15 @@ public class CollectionUtils {
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
} }
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from, public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) { Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) { if (CollUtil.isEmpty(from)) {
@ -288,4 +297,13 @@ public class CollectionUtils {
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
} }
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
} }

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.common.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = TelephoneValidator.class
)
public @interface Telephone {
String message() default "电话格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.common.validation;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.PhoneUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
@Override
public void initialize(Telephone annotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果手机号为空默认不校验即校验通过
if (CharSequenceUtil.isEmpty(value)) {
return true;
}
// 校验手机
return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);
}
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import java.util.Map; import java.util.Map;
@ -86,4 +87,12 @@ public interface PayClient {
*/ */
PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO);
/**
* 获得转账订单信息
*
* @param outTradeNo 外部订单号
* @param type 转账类型
* @return 转账信息
*/
PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
} }

View File

@ -53,11 +53,24 @@ public class PayTransferRespDTO {
/** /**
* 创建WAITING状态的转账返回 * 创建WAITING状态的转账返回
*/ */
public static PayTransferRespDTO waitingOf(String channelOrderNo, public static PayTransferRespDTO waitingOf(String channelTransferNo,
String outTransferNo, Object rawData) { String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO(); PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
respDTO.channelTransferNo = channelOrderNo; respDTO.channelTransferNo = channelTransferNo;
respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData;
return respDTO;
}
/**
* 创建IN_PROGRESS状态的转账返回
*/
public static PayTransferRespDTO dealingOf(String channelTransferNo,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus();
respDTO.channelTransferNo = channelTransferNo;
respDTO.outTransferNo = outTransferNo; respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData; respDTO.rawData = rawData;
return respDTO; return respDTO;

View File

@ -188,11 +188,11 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
@Override @Override
public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
validatePayTransferReqDTO(reqDTO);
PayTransferRespDTO resp; PayTransferRespDTO resp;
try{ try {
validatePayTransferReqDTO(reqDTO);
resp = doUnifiedTransfer(reqDTO); resp = doUnifiedTransfer(reqDTO);
}catch (ServiceException ex) { // 业务异常都是实现类已经翻译所以直接抛出即可 } catch (ServiceException ex) { // 业务异常都是实现类已经翻译所以直接抛出即可
throw ex; throw ex;
} catch (Throwable ex) { } catch (Throwable ex) {
// 系统异常则包装成 PayException 异常抛出 // 系统异常则包装成 PayException 异常抛出
@ -219,9 +219,25 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
} }
} }
@Override
public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
try {
return doGetTransfer(outTradeNo, type);
} catch (ServiceException ex) { // 业务异常都是实现类已经翻译所以直接抛出即可
throw ex;
} catch (Throwable ex) {
log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]",
getId(), outTradeNo, type, ex);
throw buildPayException(ex);
}
}
protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
throws Throwable; throws Throwable;
protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type)
throws Throwable;
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========
private PayException buildPayException(Throwable ex) { private PayException buildPayException(Throwable ex) {

View File

@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse;
import com.alipay.api.DefaultAlipayClient; import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.*; import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayFundTransUniTransferRequest; import com.alipay.api.request.*;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest; import com.alipay.api.response.*;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
} }
// 2.2 解析订单的状态 // 2.2 解析订单的状态
Integer status = parseStatus(response.getTradeStatus()); Integer status = parseStatus(response.getTradeStatus());
Assert.notNull(status, () -> { Assert.notNull(status, () -> {
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody()));
}); });
return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()),
@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 校验公钥类型 必须使用公钥证书模式 // 1.1 校验公钥类型 必须使用公钥证书模式
if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) { if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
throw exception0(ERROR_CONFIGURATION.getCode(),"支付宝单笔转账必须使用公钥证书模式"); throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式");
} }
// 1.2 构建 AlipayFundTransUniTransferModel // 1.2 构建 AlipayFundTransUniTransferModel
AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
@ -238,45 +232,97 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
model.setOutBizNo(reqDTO.getOutTransferNo()); model.setOutBizNo(reqDTO.getOutTransferNo());
model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER
model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); if (reqDTO.getChannelExtras() != null) {
model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
}
// 个性化的参数
Participant payeeInfo = new Participant();
PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType()); PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
switch (transferType) { switch (transferType) {
// TODO @jason是不是不用传递 transferType 参数哈因为应该已经明确是支付宝啦 // TODO @jason是不是不用传递 transferType 参数哈因为应该已经明确是支付宝啦
// @芋艿 是不是还要考虑转账到银行卡所以传 transferType 但是转账到银行卡不知道要如何测试?? // @芋艿 是不是还要考虑转账到银行卡所以传 transferType 但是转账到银行卡不知道要如何测试??
case ALIPAY_BALANCE: { case ALIPAY_BALANCE: {
// 个性化的参数
Participant payeeInfo = new Participant();
payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号 payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
model.setPayeeInfo(payeeInfo); model.setPayeeInfo(payeeInfo);
// 1.3 构建 AlipayFundTransUniTransferRequest break;
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
request.setBizModel(model);
// 执行请求
AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
// 处理结果
if (!response.isSuccess()) {
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败 返回 WAIT 状态. 后续 job 会轮询
if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
response.getOutBizNo(), response);
} }
case BANK_CARD: { case BANK_CARD: {
Participant payeeInfo = new Participant();
payeeInfo.setIdentityType("BANKCARD_ACCOUNT"); payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
// TODO 待实现 // TODO 待实现
throw exception(NOT_IMPLEMENTED); throw exception(NOT_IMPLEMENTED);
} }
default: { default: {
throw exception0(BAD_REQUEST.getCode(),"不正确的转账类型: {}",transferType); throw exception0(BAD_REQUEST.getCode(), "不正确的转账类型: {}", transferType);
} }
} }
// 1.3 构建 AlipayFundTransUniTransferRequest
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
request.setBizModel(model);
// 执行请求
AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
// 处理结果
if (!response.isSuccess()) {
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败 返回 WAIT 状态. 后续 job 会轮询或相同 outBizNo 重新发起转账
// 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询
if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTransferNo(), response);
} else {
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTransferNo(), response);
}
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
response.getOutBizNo(), response);
}
}
@Override
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
// 1.1 构建 AlipayFundTransCommonQueryModel
AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel();
model.setProductCode(type == PayTransferTypeEnum.BANK_CARD ? "TRANS_BANKCARD_NO_PWD" : "TRANS_ACCOUNT_NO_PWD");
model.setBizScene("DIRECT_TRANSFER"); //业务场景
model.setOutBizNo(outTradeNo);
// 1.2 构建 AlipayFundTransCommonQueryRequest
AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest();
request.setBizModel(model);
// 2.1 执行请求
AlipayFundTransCommonQueryResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
// 2.2 处理返回结果
if (response.isSuccess()) {
if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
outTradeNo, response);
}
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
response.getOutBizNo(), response);
} else {
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
// 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
return PayTransferRespDTO.waitingOf(null, outTradeNo, response);
}
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
outTradeNo, response);
}
} }
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.Map;
@ -71,4 +72,9 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
throw new UnsupportedOperationException("待实现"); throw new UnsupportedOperationException("待实现");
} }
@Override
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
throw new UnsupportedOperationException("待实现");
}
} }

View File

@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现"); throw new UnsupportedOperationException("待实现");
} }
@Override
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
throw new UnsupportedOperationException("待实现");
}
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========
static String formatDateV2(LocalDateTime time) { static String formatDateV2(LocalDateTime time) {

View File

@ -38,4 +38,8 @@ public enum PayTransferStatusRespEnum {
public static boolean isClosed(Integer status) { public static boolean isClosed(Integer status) {
return Objects.equals(status, CLOSED.getStatus()); return Objects.equals(status, CLOSED.getStatus());
} }
public static boolean isInProgress(Integer status) {
return Objects.equals(status, IN_PROGRESS.getStatus());
}
} }

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.flowable.core.context;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 工作流--用户用到的上下文相关信息
*/
public class FlowableContextHolder {
private static final ThreadLocal<Map<String, List<Long>>> ASSIGNEE = new TransmittableThreadLocal<>();
/**
* 通过流程任务的定义 key 拿到提前选好的审批人
* 此方法目的首次创建流程实例时数据库中还查询不到 assignee 字段所以存入上下文中获取
*
* @param taskDefinitionKey 流程任务 key
* @return 审批人 ID 集合
*/
public static List<Long> getAssigneeByTaskDefinitionKey(String taskDefinitionKey) {
if (CollUtil.isNotEmpty(ASSIGNEE.get())) {
return ASSIGNEE.get().get(taskDefinitionKey);
}
return Collections.emptyList();
}
/**
* 存入提前选好的审批人到上下文线程变量中
*
* @param assignee 流程任务 key -> 审批人 ID 炅和
*/
public static void setAssignee(Map<String, List<Long>> assignee) {
ASSIGNEE.set(assignee);
}
}

View File

@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.Db; import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseMapper; import com.github.yulichang.base.MPJBaseMapper;
import com.github.yulichang.interfaces.MPJBaseJoin;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.Collection; import java.util.Collection;
@ -39,6 +40,13 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
} }
default <DTO> PageResult<DTO> selectJoinPage(PageParam pageParam, Class<DTO> resultTypeClass, MPJBaseJoin<T> joinQueryWrapper) {
IPage<DTO> mpPage = MyBatisUtils.buildPage(pageParam);
selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper);
// 转换返回
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
default T selectOne(String field, Object value) { default T selectOne(String field, Object value) {
return selectOne(new QueryWrapper<T>().eq(field, value)); return selectOne(new QueryWrapper<T>().eq(field, value));
} }

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.api.task.dto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -30,4 +31,15 @@ public class BpmProcessInstanceCreateReqDTO {
*/ */
@NotEmpty(message = "业务的唯一标识") @NotEmpty(message = "业务的唯一标识")
private String businessKey; private String businessKey;
// TODO @haiassignees 复数
/**
* 提前指派的审批人
*
* keytaskKey 任务编码
* value审批人的数组
* 例如 { taskKey1 :[1, 2] }则表示 taskKey1 这个任务提前设定了 userId 1,2 的用户进行审批
*/
private Map<String, List<Long>> assignee;
} }

View File

@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map; import java.util.Map;
@Schema(description = "管理后台 - 流程实例的创建 Request VO") @Schema(description = "管理后台 - 流程实例的创建 Request VO")
@ -17,4 +18,8 @@ public class BpmProcessInstanceCreateReqVO {
@Schema(description = "变量实例") @Schema(description = "变量实例")
private Map<String, Object> variables; private Map<String, Object> variables;
// TODO @haiassignees 复数
@Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1: [1, 2]}")
private Map<String, List<Long>> assignee;
} }

View File

@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -87,4 +88,11 @@ public class BpmProcessInstanceExtDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> formVariables; private Map<String, Object> formVariables;
// TODO @haiassignees 复数
/**
* 提前设定好的审批人
*/
@TableField(typeHandler = JacksonTypeHandler.class, exist = false) // TODO 芋艿临时 exist = false避免 db 报错
private Map<String, List<Long>> assignee;
} }

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi; import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -39,6 +40,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.*; import java.util.*;
import java.util.function.Function;
import static cn.hutool.core.text.CharSequenceUtil.format; import static cn.hutool.core.text.CharSequenceUtil.format;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -77,6 +79,9 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
private DictDataApi dictDataApi; private DictDataApi dictDataApi;
@Resource @Resource
private PermissionApi permissionApi; private PermissionApi permissionApi;
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService processInstanceService;
/** /**
* 任务分配脚本 * 任务分配脚本
*/ */
@ -234,6 +239,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
@Override @Override
@DataPermission(enable = false) // 忽略数据权限不然分配会存在问题 @DataPermission(enable = false) // 忽略数据权限不然分配会存在问题
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) { public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
// 1. 先从提前选好的审批人中获取
List<Long> assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey(
execution.getProcessInstanceId(), execution.getCurrentActivityId());
if (CollUtil.isNotEmpty(assignee)) {
// TODO @hainew HashSet 即可
return convertSet(assignee, Function.identity());
}
// 2. 通过分配规则计算审批人
BpmTaskAssignRuleDO rule = getTaskRule(execution); BpmTaskAssignRuleDO rule = getTaskRule(execution);
return calculateTaskCandidateUsers(execution, rule); return calculateTaskCandidateUsers(execution, rule);
} }

View File

@ -49,16 +49,17 @@ public interface BpmProcessInstanceService {
/** /**
* 获得流程实例的分页 * 获得流程实例的分页
* *
* @param userId 用户编号 * @param userId 用户编号
* @param pageReqVO 分页请求 * @param pageReqVO 分页请求
* @return 流程实例的分页 * @return 流程实例的分页
*/ */
PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
@Valid BpmProcessInstanceMyPageReqVO pageReqVO); @Valid BpmProcessInstanceMyPageReqVO pageReqVO);
/** /**
* 创建流程实例提供给前端 * 创建流程实例提供给前端
* *
* @param userId 用户编号 * @param userId 用户编号
* @param createReqVO 创建信息 * @param createReqVO 创建信息
* @return 实例的编号 * @return 实例的编号
*/ */
@ -67,7 +68,7 @@ public interface BpmProcessInstanceService {
/** /**
* 创建流程实例提供给内部 * 创建流程实例提供给内部
* *
* @param userId 用户编号 * @param userId 用户编号
* @param createReqDTO 创建信息 * @param createReqDTO 创建信息
* @return 实例的编号 * @return 实例的编号
*/ */
@ -84,7 +85,7 @@ public interface BpmProcessInstanceService {
/** /**
* 取消流程实例 * 取消流程实例
* *
* @param userId 用户编号 * @param userId 用户编号
* @param cancelReqVO 取消信息 * @param cancelReqVO 取消信息
*/ */
void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
@ -139,9 +140,19 @@ public interface BpmProcessInstanceService {
/** /**
* 更新 ProcessInstance 拓展记录为不通过 * 更新 ProcessInstance 拓展记录为不通过
* *
* @param id 流程编号 * @param id 流程编号
* @param reason 理由例如说审批不通过时需要传递该值 * @param reason 理由例如说审批不通过时需要传递该值
*/ */
void updateProcessInstanceExtReject(String id, String reason); void updateProcessInstanceExtReject(String id, String reason);
// TODO @hai改成 getProcessInstanceAssigneesByTaskDefinitionKey(String id, String taskDefinitionKey)
/**
* 获取流程实例中取出指定流程任务提前指定的审批人
*
* @param processInstanceId 流程实例的编号
* @param taskDefinitionKey 流程任务定义的 key
* @return 审批人集合
*/
List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey);
} }

25
yudao-module-crm/pom.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modules>
<module>yudao-module-crm-api</module>
<module>yudao-module-crm-biz</module>
</modules>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-crm</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
crm 包下客户关系管理Customer Relationship Management
例如说:客户、联系人、商机、合同、回款等等
</description>
</project>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-crm-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
crm 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
/**
* crm API 定义暴露给其它模块的 API
*/
package cn.iocoder.yudao.module.crm.api;

View File

@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.crm.enums;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import java.util.Arrays;
// TODO @liuhongfeng这个状态还是搞成专属 CrmReceivableDO 专属的 status
/**
* 流程审批状态枚举类
* 0 未审核 1 审核通过 2 审核拒绝 3 审核中 4 已撤回 TODO @liuhongfeng这一行可以删除因为已经有枚举属性了哈
* @author 赤焰
*/
// TODO @liuhongfeng可以使用 @Getter@AllArgsConstructor 简化 get构造方法
public enum AuditStatusEnum implements IntArrayValuable {
// TODO @liuhongfeng草稿 010 审核中20 审核通过30 审核拒绝40 已撤回主要是留好间隙万一每个地方要做点拓展 然后枚举字段的顺序调整下审批中一定要放两个审批通过拒绝前面哈
/**
* 未审批
*/
AUDIT_NEW(0, "未审批"),
/**
* 审核通过
*/
AUDIT_FINISH(1, "审核通过"),
/**
* 审核拒绝
*/
AUDIT_REJECT(2, "审核拒绝"),
/**
* 审核中
*/
AUDIT_DOING(3, "审核中"),
/**
* 已撤回
*/
AUDIT_RETURN(4, "已撤回");
// TODO liuhongfengvalue 改成 statusdesc 改成 name
private final Integer value;
private final String desc;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AuditStatusEnum::getValue).toArray();
AuditStatusEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public Integer getValue() {
return value;
}
public String getDesc() {
return desc;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.enums;
/**
* CRM 字典类型的枚举类
*
* @author 芋道源码
*/
public interface DictTypeConstants {
// ========== CRM 模块 ==========
String CRM_CUSTOMER_INDUSTRY = "crm_customer_industry"; // CRM 客户所属行业
String CRM_CUSTOMER_LEVEL = "crm_customer_level"; // CRM 客户等级
String CRM_CUSTOMER_SOURCE = "crm_customer_source"; // CRM 客户来源
String CRM_RECEIVABLE_CHECK_STATUS = "crm_receivable_check_status"; // CRM 审批状态
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.crm.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* CRM 错误码枚举类
* <p>
* crm 系统使用 1-020-000-000
*/
public interface ErrorCodeConstants {
// ========== 合同管理 1-020-000-000 ==========
ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
// ========== 线索管理 1-020-001-000 ==========
ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
// ========== 商机管理 1-020-002-000 ==========
ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
// TODO @lilleo商机状态商机类型都单独错误码段
ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_002_001, "商机状态类型不存在");
ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_002_002, "商机状态不存在");
// ========== 联系人管理 1-020-003-000 ==========
ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
// ========== 回款管理 1-020-004-000 ==========
ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款管理不存在");
// ========== 合同管理 1-020-005-000 ==========
ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
// ========== 客户管理 1_020_006_000 ==========
ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
ErrorCode CUSTOMER_OWNER_EXISTS = new ErrorCode(1_020_006_001, "客户已存在所属负责人");
ErrorCode CUSTOMER_LOCKED = new ErrorCode(1_020_006_002, "客户状态已锁定");
ErrorCode CUSTOMER_ALREADY_DEAL = new ErrorCode(1_020_006_003, "客户已交易");
// TODO @wanwan 2 个单独配置段噢
ErrorCode CUSTOMER_POOL_CONFIG_ERROR = new ErrorCode(1_020_006_001, "客户公海规则设置不正确");
ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_006_002, "客户限制配置不存在");
// ========== 权限管理 1_020_007_000 ==========
ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在");
ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
// ========== 产品 1_020_008_000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在");
// ========== 产品分类 1_020_009_000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
}

View File

@ -0,0 +1,8 @@
package cn.iocoder.yudao.module.crm.enums;
// TODO @liuhongfeng这个的作用是
/**
* @author 赤焰
*/
public enum ReturnTypeEnum {
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.crm.enums.customer;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* CRM 客户等级
*
* @author Wanwan
*/
@Getter
@AllArgsConstructor
public enum CrmCustomerLevelEnum implements IntArrayValuable {
IMPORTANT(1, "A重点客户"),
GENERAL(2, "B普通客户"),
LOW_PRIORITY(3, "C非优先客户");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLevelEnum::getLevel).toArray();
/**
* 状态
*/
private final Integer level;
/**
* 状态名
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.crm.enums.customer;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
// TODO @puhui999这个应该是 crm 全局的不仅仅属于 customer 客户哈
/**
* CRM 客户等级
*
* @author Wanwan
*/
@Getter
@AllArgsConstructor
public enum CrmCustomerSceneEnum implements IntArrayValuable {
OWNER(1, "我负责的客户"),
FOLLOW(2, "我关注的客户");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerSceneEnum::getType).toArray();
/**
* 场景类型
*/
private final Integer type;
/**
* 场景名称
*/
private final String name;
public static boolean isOwner(Integer type) {
return ObjUtil.equal(OWNER.getType(), type);
}
public static boolean isFollow(Integer type) {
return ObjUtil.equal(FOLLOW.getType(), type);
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-crm-biz</artifactId>
<name>${project.artifactId}</name>
<description>
crm 包下客户关系管理Customer Relationship Management
例如说:客户、联系人、商机、合同、回款等等
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
/**
* crm API 实现类定义暴露给其它模块的 API
*/
package cn.iocoder.yudao.module.crm.api;

View File

@ -0,0 +1,32 @@
### 请求 /transfer
PUT {{baseUrl}}/crm/business/transfer
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": 1,
"ownerUserId": 2,
"transferType": 2,
"permissionType": 2
}
### 请求 /update
PUT {{baseUrl}}/crm/business/update
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": 1,
"name": "2",
"statusTypeId": 2,
"statusId": 2,
"customerId": 1
}
### 请求 /get
GET {{baseUrl}}/crm/business/get?id=1024
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.crm.controller.admin.business;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.*;
import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 商机")
@RestController
@RequestMapping("/crm/business")
@Validated
public class CrmBusinessController {
@Resource
private CrmBusinessService businessService;
@PostMapping("/create")
@Operation(summary = "创建商机")
@PreAuthorize("@ss.hasPermission('crm:business:create')")
public CommonResult<Long> createBusiness(@Valid @RequestBody CrmBusinessCreateReqVO createReqVO) {
return success(businessService.createBusiness(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新商机")
@PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> updateBusiness(@Valid @RequestBody CrmBusinessUpdateReqVO updateReqVO) {
businessService.updateBusiness(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除商机")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business:delete')")
public CommonResult<Boolean> deleteBusiness(@RequestParam("id") Long id) {
businessService.deleteBusiness(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得商机")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
CrmBusinessDO business = businessService.getBusiness(id);
return success(CrmBusinessConvert.INSTANCE.convert(business));
}
@GetMapping("/page")
@Operation(summary = "获得商机分页")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/pool-page")
@Operation(summary = "获得商机公海分页")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPoolPage(@Valid CrmBusinessPageReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, CrmPermissionDO.POOL_USER_ID);
return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出商机 Excel")
@PreAuthorize("@ss.hasPermission('crm:business:export')")
@OperateLog(type = EXPORT)
public void exportBusinessExcel(@Valid CrmBusinessExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CrmBusinessDO> list = businessService.getBusinessList(exportReqVO);
// 导出 Excel
List<CrmBusinessExcelVO> datas = CrmBusinessConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class, datas);
}
@PutMapping("/transfer")
@Operation(summary = "商机转移")
@PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) {
businessService.transferBusiness(reqVO, getLoginUserId());
return success(true);
}
}

View File

@ -0,0 +1,4 @@
/**
* 商机销售机会
*/
package cn.iocoder.yudao.module.crm.controller.admin.business;

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 商机 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmBusinessBaseVO {
@Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@NotNull(message = "商机名称不能为空")
private String name;
@Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
@NotNull(message = "商机状态类型不能为空")
private Long statusTypeId;
@Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
@NotNull(message = "商机状态不能为空")
private Long statusId;
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactNextTime;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
@NotNull(message = "客户不能为空")
private Long customerId;
@Schema(description = "预计成交日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime dealTime;
@Schema(description = "商机金额", example = "12371")
private Integer price;
// TODO @ljileo折扣使用 Integer 类型存储时默认 * 100展示的时候前端需要 / 100避免精度丢失问题
@Schema(description = "整单折扣")
private Integer discountPercent;
@Schema(description = "产品总金额", example = "12025")
private BigDecimal productPrice;
@Schema(description = "备注", example = "随便")
private String remark;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 商机创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessCreateReqVO extends CrmBusinessBaseVO {
// TODO @ljileo新建的时候应该可以传递添加的产品
}

View File

@ -0,0 +1,75 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Set;
/**
* 商机 Excel VO
*
* @author ljlleo
*/
@Data
public class CrmBusinessExcelVO {
@ExcelProperty("主键")
private Long id;
@ExcelProperty("商机名称")
private String name;
@ExcelProperty("商机状态类型编号")
private Long statusTypeId;
@ExcelProperty("商机状态编号")
private Long statusId;
@ExcelProperty("下次联系时间")
private LocalDateTime contactNextTime;
@ExcelProperty("客户编号")
private Long customerId;
@ExcelProperty("预计成交日期")
private LocalDateTime dealTime;
@ExcelProperty("商机金额")
private BigDecimal price;
@ExcelProperty("整单折扣")
private BigDecimal discountPercent;
@ExcelProperty("产品总金额")
private BigDecimal productPrice;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("负责人的用户编号")
private Long ownerUserId;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@ExcelProperty("只读权限的用户编号数组")
private Set<Long> roUserIds;
@ExcelProperty("读写权限的用户编号数组")
private Set<Long> rwUserIds;
@ExcelProperty("1赢单2输单3无效")
private Integer endStatus;
@ExcelProperty("结束时的备注")
private String endRemark;
@ExcelProperty("最后跟进时间")
private LocalDateTime contactLastTime;
@ExcelProperty("跟进状态")
private Integer followUpStatus;
}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 商机 Excel 导出 Request VO参数和 CrmBusinessPageReqVO 是一致的")
@Data
public class CrmBusinessExportReqVO {
@Schema(description = "商机名称", example = "李四")
private String name;
@Schema(description = "商机状态类型编号", example = "25714")
private Long statusTypeId;
@Schema(description = "商机状态编号", example = "30320")
private Long statusId;
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] contactNextTime;
@Schema(description = "客户编号", example = "10299")
private Long customerId;
@Schema(description = "预计成交日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] dealTime;
@Schema(description = "商机金额", example = "12371")
private BigDecimal price;
@Schema(description = "整单折扣")
private BigDecimal discountPercent;
@Schema(description = "产品总金额", example = "12025")
private BigDecimal productPrice;
@Schema(description = "备注", example = "随便")
private String remark;
@Schema(description = "负责人的用户编号", example = "25562")
private Long ownerUserId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "只读权限的用户编号数组")
private String roUserIds;
@Schema(description = "读写权限的用户编号数组")
private String rwUserIds;
@Schema(description = "1赢单2输单3无效", example = "1")
private Integer endStatus;
@Schema(description = "结束时的备注", example = "你说的对")
private String endRemark;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] contactLastTime;
@Schema(description = "跟进状态", example = "1")
private Integer followUpStatus;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 商机分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessPageReqVO extends PageParam {
@Schema(description = "商机名称", example = "李四")
private String name;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 商机 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessRespVO extends CrmBusinessBaseVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 商机转移 Request VO")
@Data
public class CrmBusinessTransferReqVO {
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "联系人编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 商机更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessUpdateReqVO extends CrmBusinessBaseVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
@NotNull(message = "主键不能为空")
private Long id;
// TODO @ljileo修改的时候应该可以传递添加的产品
}

View File

@ -0,0 +1,119 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo.*;
import cn.iocoder.yudao.module.crm.convert.businessstatus.CrmBusinessStatusConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.businessstatus.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.service.businessstatus.CrmBusinessStatusService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
// TODO @lilleo这个模块可以挪到 business 这样我打开 business 包下就知道~原来里面有 business 商机 type 状态类型status 具体状态
@Tag(name = "管理后台 - 商机状态")
@RestController
@RequestMapping("/crm/business-status")
@Validated
public class CrmBusinessStatusController {
@Resource
private CrmBusinessStatusService businessStatusService;
@PostMapping("/create")
@Operation(summary = "创建商机状态")
@PreAuthorize("@ss.hasPermission('crm:business-status:create')")
public CommonResult<Long> createBusinessStatus(@Valid @RequestBody CrmBusinessStatusCreateReqVO createReqVO) {
return success(businessStatusService.createBusinessStatus(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新商机状态")
@PreAuthorize("@ss.hasPermission('crm:business-status:update')")
public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessStatusUpdateReqVO updateReqVO) {
businessStatusService.updateBusinessStatus(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除商机状态")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business-status:delete')")
public CommonResult<Boolean> deleteBusinessStatus(@RequestParam("id") Long id) {
businessStatusService.deleteBusinessStatus(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得商机状态")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:business-status:query')")
public CommonResult<CrmBusinessStatusRespVO> getBusinessStatus(@RequestParam("id") Long id) {
CrmBusinessStatusDO businessStatus = businessStatusService.getBusinessStatus(id);
return success(CrmBusinessStatusConvert.INSTANCE.convert(businessStatus));
}
// TODO @lilleo这个接口暂时用不到可以考虑先删除掉
@GetMapping("/list")
@Operation(summary = "获得商机状态列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('crm:business-status:query')")
public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusList(@RequestParam("ids") Collection<Long> ids) {
List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList(ids);
return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得商机状态分页")
@PreAuthorize("@ss.hasPermission('crm:business-status:query')")
public CommonResult<PageResult<CrmBusinessStatusRespVO>> getBusinessStatusPage(@Valid CrmBusinessStatusPageReqVO pageVO) {
PageResult<CrmBusinessStatusDO> pageResult = businessStatusService.getBusinessStatusPage(pageVO);
return success(CrmBusinessStatusConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出商机状态 Excel")
@PreAuthorize("@ss.hasPermission('crm:business-status:export')")
@OperateLog(type = EXPORT)
public void exportBusinessStatusExcel(@Valid CrmBusinessStatusExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList(exportReqVO);
// 导出 Excel
List<CrmBusinessStatusExcelVO> datas = CrmBusinessStatusConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "商机状态.xls", "数据", CrmBusinessStatusExcelVO.class, datas);
}
// TODO 芋艿后续再看看
@GetMapping("/get-simple-list")
@Operation(summary = "获得商机状态列表")
@PreAuthorize("@ss.hasPermission('crm:business-status:query')")
public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusListByTypeId(@RequestParam("typeId") Integer typeId) {
List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusListByTypeId(typeId);
return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
}
// TODO 芋艿后续再看看
@GetMapping("/get-all-list")
@Operation(summary = "获得商机状态列表")
@PreAuthorize("@ss.hasPermission('crm:business-status:query')")
public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusList() {
List<CrmBusinessStatusDO> list = businessStatusService.getBusinessStatusList();
return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 商机状态 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmBusinessStatusBaseVO {
// TODO @lilleoexample 要写下
@Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22882")
@NotNull(message = "状态类型编号不能为空")
private Long typeId;
@Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@NotNull(message = "状态名不能为空")
private String name;
// TODO @lilleopercent 应该是 Integer
@Schema(description = "赢单率")
private String percent;
// TODO @lilleo这个是不是不用前端新增和修改的时候传递交给顺序计算出来存储起来就好了
@Schema(description = "排序")
private Integer sort;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 商机状态创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusCreateReqVO extends CrmBusinessStatusBaseVO {
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
// TODO @lilleo这个暂时不需要嘿嘿~不是每个模块都需要导出哈
/**
* 商机状态 Excel VO
*
* @author ljlleo
*/
@Data
public class CrmBusinessStatusExcelVO {
@ExcelProperty("主键")
private Long id;
@ExcelProperty("状态类型编号")
private Long typeId;
@ExcelProperty("状态名")
private String name;
@ExcelProperty("赢单率")
private String percent;
@ExcelProperty("排序")
private Integer sort;
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
// TODO @lilleo这个暂时不需要嘿嘿~不是每个模块都需要导出哈
@Schema(description = "管理后台 - 商机状态 Excel 导出 Request VO参数和 CrmBusinessStatusPageReqVO 是一致的")
@Data
public class CrmBusinessStatusExportReqVO {
@Schema(description = "状态类型编号", example = "22882")
private Long typeId;
@Schema(description = "状态名", example = "李四")
private String name;
@Schema(description = "赢单率")
private String percent;
@Schema(description = "排序")
private Integer sort;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 商机状态分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusPageReqVO extends PageParam {
@Schema(description = "状态类型编号", example = "22882")
private Long typeId;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Schema(description = "管理后台 - 商机状态 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusRespVO extends CrmBusinessStatusBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6802")
private Long id;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatus.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 商机状态更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusUpdateReqVO extends CrmBusinessStatusBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6802")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo.*;
import cn.iocoder.yudao.module.crm.convert.businessstatustype.CrmBusinessStatusTypeConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.businessstatustype.CrmBusinessStatusTypeDO;
import cn.iocoder.yudao.module.crm.service.businessstatustype.CrmBusinessStatusTypeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
// TODO @lilleo这个模块可以挪到 business 这样我打开 business 包下就知道~原来里面有 business 商机 type 状态类型status 具体状态
@Tag(name = "管理后台 - 商机状态类型")
@RestController
@RequestMapping("/crm/business-status-type")
@Validated
public class CrmBusinessStatusTypeController {
@Resource
private CrmBusinessStatusTypeService businessStatusTypeService;
@PostMapping("/create")
@Operation(summary = "创建商机状态类型")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:create')")
public CommonResult<Long> createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeCreateReqVO createReqVO) {
return success(businessStatusTypeService.createBusinessStatusType(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新商机状态类型")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:update')")
public CommonResult<Boolean> updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeUpdateReqVO updateReqVO) {
businessStatusTypeService.updateBusinessStatusType(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除商机状态类型")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')")
public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {
businessStatusTypeService.deleteBusinessStatusType(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得商机状态类型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
public CommonResult<CrmBusinessStatusTypeRespVO> getBusinessStatusType(@RequestParam("id") Long id) {
CrmBusinessStatusTypeDO businessStatusType = businessStatusTypeService.getBusinessStatusType(id);
return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(businessStatusType));
}
// TODO @lilleo这个接口暂时用不到可以考虑先删除掉
@GetMapping("/list")
@Operation(summary = "获得商机状态类型列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList(@RequestParam("ids") Collection<Long> ids) {
List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList(ids);
return success(CrmBusinessStatusTypeConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得商机状态类型分页")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
public CommonResult<PageResult<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageVO) {
PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageVO);
return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出商机状态类型 Excel")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:export')")
@OperateLog(type = EXPORT)
public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypeExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList(exportReqVO);
// 导出 Excel
List<CrmBusinessStatusTypeExcelVO> datas = CrmBusinessStatusTypeConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeExcelVO.class, datas);
}
@GetMapping("/get-simple-list")
@Operation(summary = "获得商机状态类型列表")
@PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList() {
List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(CrmBusinessStatusTypeConvert.INSTANCE.convertList(list));
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 商机状态类型 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmBusinessStatusTypeBaseVO {
@Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@NotNull(message = "状态类型名不能为空")
private String name;
@Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "使用的部门编号不能为空")
private String deptIds;
@Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "开启状态不能为空")
private Boolean status;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
// TODO 状态类型和状态添加是在一个请求里所以需要把 CrmBusinessStatusCreateReqVO 融合进来
@Schema(description = "管理后台 - 商机状态类型创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusTypeCreateReqVO extends CrmBusinessStatusTypeBaseVO {
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @lilleo这个暂时不需要嘿嘿~不是每个模块都需要导出哈
/**
* 商机状态类型 Excel VO
*
* @author ljlleo
*/
@Data
public class CrmBusinessStatusTypeExcelVO {
@ExcelProperty("主键")
private Long id;
@ExcelProperty("状态类型名")
private String name;
@ExcelProperty("使用的部门编号")
private String deptIds;
@ExcelProperty("开启状态")
private Boolean status;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @lilleo这个暂时不需要嘿嘿~不是每个模块都需要导出哈
@Schema(description = "管理后台 - 商机状态类型 Excel 导出 Request VO参数和 CrmBusinessStatusTypePageReqVO 是一致的")
@Data
public class CrmBusinessStatusTypeExportReqVO {
@Schema(description = "状态类型名", example = "芋艿")
private String name;
@Schema(description = "使用的部门编号")
private String deptIds;
@Schema(description = "开启状态", example = "1")
private Boolean status;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 商机状态类型分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusTypePageReqVO extends PageParam {
@Schema(description = "状态类型名", example = "芋艿")
private String name;
@Schema(description = "开启状态", example = "1")
private Boolean status;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 商机状态类型 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusTypeRespVO extends CrmBusinessStatusTypeBaseVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24019")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.crm.controller.admin.businessstatustype.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
// TODO 状态类型和状态添加是在一个请求里所以需要把 CrmBusinessStatusUpdateReqVO 融合进来
@Schema(description = "管理后台 - 商机状态类型更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmBusinessStatusTypeUpdateReqVO extends CrmBusinessStatusTypeBaseVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24019")
@NotNull(message = "主键不能为空")
private Long id;
}

View File

@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - 线索")
@RestController
@RequestMapping("/crm/clue")
@Validated
public class CrmClueController {
@Resource
private CrmClueService clueService;
@PostMapping("/create")
@Operation(summary = "创建线索")
@PreAuthorize("@ss.hasPermission('crm:clue:create')")
public CommonResult<Long> createClue(@Valid @RequestBody CrmClueCreateReqVO createReqVO) {
return success(clueService.createClue(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新线索")
@PreAuthorize("@ss.hasPermission('crm:clue:update')")
public CommonResult<Boolean> updateClue(@Valid @RequestBody CrmClueUpdateReqVO updateReqVO) {
clueService.updateClue(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除线索")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:clue:delete')")
public CommonResult<Boolean> deleteClue(@RequestParam("id") Long id) {
clueService.deleteClue(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得线索")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:clue:query')")
public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
CrmClueDO clue = clueService.getClue(id);
return success(CrmClueConvert.INSTANCE.convert(clue));
}
@GetMapping("/page")
@Operation(summary = "获得线索分页")
@PreAuthorize("@ss.hasPermission('crm:clue:query')")
public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO);
return success(CrmClueConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出线索 Excel")
@PreAuthorize("@ss.hasPermission('crm:clue:export')")
@OperateLog(type = EXPORT)
public void exportClueExcel(@Valid CrmClueExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CrmClueDO> list = clueService.getClueList(exportReqVO);
// 导出 Excel
List<CrmClueExcelVO> datas = CrmClueConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas);
}
}

View File

@ -0,0 +1,4 @@
/**
* 线索
*/
package cn.iocoder.yudao.module.crm.controller.admin.clue;

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.framework.common.validation.Telephone;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 线索 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmClueBaseVO {
@Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
@NotEmpty(message = "线索名称不能为空")
private String name;
@Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")
@NotNull(message = "客户不能为空")
private Long customerId;
@Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactNextTime;
@Schema(description = "电话", example = "18000000000")
@Telephone
private String telephone;
@Schema(description = "手机号", example = "18000000000")
@Mobile
private String mobile;
@Schema(description = "地址", example = "北京市海淀区")
private String address;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactLastTime;
@Schema(description = "备注", example = "随便")
private String remark;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 线索创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmClueCreateReqVO extends CrmClueBaseVO {
}

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
/**
* 线索 Excel VO
*
* @author Wanwan
*/
@Data
public class CrmClueExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty(value = "转化状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean transformStatus;
@ExcelProperty(value = "跟进状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean followUpStatus;
@ExcelProperty("线索名称")
private String name;
// TODO 这里需要导出成客户名称
@ExcelProperty("客户id")
private Long customerId;
@ExcelProperty("下次联系时间")
private LocalDateTime contactNextTime;
@ExcelProperty("电话")
private String telephone;
@ExcelProperty("手机号")
private String mobile;
@ExcelProperty("地址")
private String address;
@ExcelProperty("负责人的用户编号")
private Long ownerUserId;
@ExcelProperty("最后跟进时间")
private LocalDateTime contactLastTime;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 线索 Excel 导出 Request VO参数和 CrmCluePageReqVO 是一致的")
@Data
public class CrmClueExportReqVO {
@Schema(description = "转化状态", example = "true")
private Boolean transformStatus;
@Schema(description = "跟进状态", example = "true")
private Boolean followUpStatus;
@Schema(description = "线索名称", example = "线索xxx")
private String name;
@Schema(description = "客户id", example = "520")
private Long customerId;
@Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] contactNextTime;
@Schema(description = "电话", example = "18000000000")
private String telephone;
@Schema(description = "手机号", example = "18000000000")
private String mobile;
@Schema(description = "地址", example = "北京市海淀区")
private String address;
@Schema(description = "负责人的用户编号", example = "27199")
private Long ownerUserId;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] contactLastTime;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 线索分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmCluePageReqVO extends PageParam {
@Schema(description = "线索名称", example = "线索xxx")
private String name;
@Schema(description = "电话", example = "18000000000")
private String telephone;
@Schema(description = "手机号", example = "18000000000")
private String mobile;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 线索 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmClueRespVO extends CrmClueBaseVO {
@Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean transformStatus;
@Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean followUpStatus;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 线索更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmClueUpdateReqVO extends CrmClueBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@ -0,0 +1,135 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerExportReqVO;
import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.ContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.contact.ContactService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
// TODO @zyacrm 所有的类dou带 Crm 前缀因为它的名字太通用了可能和后续的 erp 之类的冲突
@Tag(name = "管理后台 - CRM 联系人")
@RestController
@RequestMapping("/crm/contact")
@Validated
public class ContactController {
@Resource
private ContactService contactService;
// TODO @zyna模块内注入的变量不用带 crm 前缀哈
@Resource
private CrmCustomerService crmCustomerService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建联系人")
@PreAuthorize("@ss.hasPermission('crm:contact:create')")
public CommonResult<Long> createContact(@Valid @RequestBody ContactCreateReqVO createReqVO) {
return success(contactService.createContact(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新联系人")
@PreAuthorize("@ss.hasPermission('crm:contact:update')")
public CommonResult<Boolean> updateContact(@Valid @RequestBody ContactUpdateReqVO updateReqVO) {
contactService.updateContact(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除联系人")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contact:delete')")
public CommonResult<Boolean> deleteContact(@RequestParam("id") Long id) {
contactService.deleteContact(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得联系人")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<ContactRespVO> getContact(@RequestParam("id") Long id) {
ContactDO contact = contactService.getContact(id);
// TODO @zyna需要考虑 null 的情况
ContactRespVO contactRespVO = ContactConvert.INSTANCE.convert(contact);
// TODO @zyna可以把数据读完后convert 统一交给 ContactConvert controller 更简洁 convert 专门去做一些转换逻辑
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList(
NumberUtil.parseLong(contact.getCreator()))));
contactRespVO.setCreatorName(Optional.ofNullable(userMap.get(NumberUtil.parseLong(contact.getCreator()))).map(AdminUserRespDTO::getNickname).orElse(null));
contactRespVO.setCustomerName(Optional.ofNullable(crmCustomerService.getCustomer(contact.getCustomerId())).map(CrmCustomerDO::getName).orElse(null));
return success(contactRespVO);
}
// TODO @zynaurl 使用中划线噢然后单词的拼写也要注意呀AllList 是不是更好呀
@GetMapping("/simpleAlllist")
@Operation(summary = "获得联系人列表")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<ContactSimpleRespVO>> simpleAlllist() {
// TODO @zyna方法名改成getContactList方法命名要动名词get 动词all 可以去掉因为没条件自然是全部
List<ContactDO> list = contactService.allContactList();
return success(ContactConvert.INSTANCE.convertAllList(list));
}
@GetMapping("/page")
@Operation(summary = "获得联系人分页")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<PageResult<ContactRespVO>> getContactPage(@Valid ContactPageReqVO pageVO) {
PageResult<ContactDO> pageData = contactService.getContactPage(pageVO);
PageResult<ContactRespVO> pageResult =ContactConvert.INSTANCE.convertPage(pageData);
// TODO @zyna需要考虑 null 的情况
// TODO @zyna可以把数据读完后convert 统一交给 ContactConvert controller 更简洁 convert 专门去做一些转换逻辑
//待接口实现后修改
List<CrmCustomerDO> crmCustomerDOList = crmCustomerService.getCustomerList(new CrmCustomerExportReqVO());
Map<Long,CrmCustomerDO> crmCustomerDOMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId,v->v));
pageResult.getList().forEach(item -> {
item.setCustomerName(Optional.ofNullable(crmCustomerDOMap.get(item.getCustomerId())).map(CrmCustomerDO::getName).orElse(null));
});
return success(pageResult);
}
// TODO @zyna可以看下新的导出写法这里调整下
@GetMapping("/export-excel")
@Operation(summary = "导出联系人 Excel")
@PreAuthorize("@ss.hasPermission('crm:contact:export')")
@OperateLog(type = EXPORT)
public void exportContactExcel(@Valid ContactExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<ContactDO> list = contactService.getContactList(exportReqVO);
// 导出 Excel
List<ContactExcelVO> datas = ContactConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "crm联系人.xls", "数据", ContactExcelVO.class, datas);
}
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO zyna参考新的 vo重新拆分下 VO
/**
* CRM 联系人 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class ContactBaseVO {
// TODO @zynaexample 最好都写下
// TODO @zyna必要的字段校验例如说 @Mobile@Emal 等等
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDateTime nextTime;
@Schema(description = "手机号")
private String mobile;
@Schema(description = "电话")
private String telephone;
@Schema(description = "电子邮箱")
private String email;
@Schema(description = "客户编号", example = "10795")
private Long customerId;
@Schema(description = "地址")
private String address;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime lastTime;
@Schema(description = "直属上级", example = "23457")
private Long parentId;
@Schema(description = "姓名", example = "芋艿")
private String name;
@Schema(description = "职位")
private String post;
@Schema(description = "QQ")
private Long qq;
@Schema(description = "微信")
private String webchat;
@Schema(description = "性别")
private Integer sex;
@Schema(description = "是否关键决策人")
private Boolean policyMakers;
@Schema(description = "负责人用户编号", example = "14334")
private String ownerUserId;
}

View File

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

View File

@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @zyna参考新的 VO 结构 ContactExcelVO 融合到 ContactRespVO
/**
* crm联系人 Excel VO
*
* @author 芋道源码
*/
@Data
@Deprecated
public class ContactExcelVO {
@ExcelProperty("下次联系时间")
private LocalDateTime nextTime;
@ExcelProperty("手机号")
private String mobile;
@ExcelProperty("电话")
private String telephone;
@ExcelProperty("电子邮箱")
private String email;
@ExcelProperty("客户编号")
private Long customerId;
@ExcelProperty("地址")
private String address;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@ExcelProperty("最后跟进时间")
private LocalDateTime lastTime;
@ExcelProperty("主键")
private Long id;
@ExcelProperty("直属上级")
private Long parentId;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("职位")
private String post;
@ExcelProperty("QQ")
private Long qq;
@ExcelProperty("微信")
private String webchat;
@ExcelProperty("性别")
private Integer sex;
@ExcelProperty("是否关键决策人")
private Boolean policyMakers;
@ExcelProperty("负责人用户编号")
private String ownerUserId;
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @zyna参考新的 VO 结构使用 ContactPageReqVO 查询导出的数据
@Schema(description = "管理后台 - crm联系人 Excel 导出 Request VO参数和 ContactPageReqVO 是一致的")
@Data
@Deprecated
public class ContactExportReqVO {
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] nextTime;
@Schema(description = "手机号")
private String mobile;
@Schema(description = "电话")
private String telephone;
@Schema(description = "电子邮箱")
private String email;
@Schema(description = "客户编号", example = "10795")
private Long customerId;
@Schema(description = "地址")
private String address;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastTime;
@Schema(description = "直属上级", example = "23457")
private Long parentId;
@Schema(description = "姓名", example = "芋艿")
private String name;
@Schema(description = "职位")
private String post;
@Schema(description = "QQ")
private Long qq;
@Schema(description = "微信")
private String webchat;
@Schema(description = "性别")
private Integer sex;
@Schema(description = "是否关键决策人")
private Boolean policyMakers;
@Schema(description = "负责人用户编号", example = "14334")
private String ownerUserId;
}

View File

@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - crm联系人分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ContactPageReqVO extends PageParam {
// TODO @zyna筛选条件
// 客户
// 姓名
// 手机电话座机QQ微信邮箱
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] nextTime;
@Schema(description = "手机号")
private String mobile;
@Schema(description = "电话")
private String telephone;
@Schema(description = "电子邮箱")
private String email;
@Schema(description = "客户编号", example = "10795")
private Long customerId;
@Schema(description = "地址")
private String address;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastTime;
@Schema(description = "直属上级", example = "23457")
private Long parentId;
@Schema(description = "姓名", example = "芋艿")
private String name;
@Schema(description = "职位")
private String post;
@Schema(description = "QQ")
private Long qq;
@Schema(description = "微信")
private String webchat;
@Schema(description = "性别")
private Integer sex;
@Schema(description = "是否关键决策人")
private Boolean policyMakers;
@Schema(description = "负责人用户编号", example = "14334")
private String ownerUserId;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 联系人 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ContactRespVO extends ContactBaseVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
private Long id;
@Schema(description = "创建时间")
private LocalDateTime createTime;
// TODO @zynaexample 最好写下
@Schema(description = "创建人")
private String creatorName;
@Schema(description = "客户名字")
private String customerName;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
@Schema(description = "管理后台 - CRM 联系人 Response VO")
@Data
@ToString(callSuper = true)
public class ContactSimpleRespVO {
@Schema(description = "姓名", example = "芋艿") // TODO @zynarequiredMode = Schema.RequiredMode.REQUIRED需要空一行字段的顺序改下id name 前面会更干净
private String name;
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
private Long id;
}

View File

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

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - CRM 联系人转移 Request VO")
@Data
public class CrmContactTransferReqVO {
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "联系人编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
}

View File

@ -0,0 +1,98 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
import cn.iocoder.yudao.module.crm.convert.contract.ContractConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.ContractDO;
import cn.iocoder.yudao.module.crm.service.contract.ContractService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM 合同")
@RestController
@RequestMapping("/crm/contract")
@Validated
public class ContractController {
@Resource
private ContractService contractService;
@PostMapping("/create")
@Operation(summary = "创建合同")
@PreAuthorize("@ss.hasPermission('crm:contract:create')")
public CommonResult<Long> createContract(@Valid @RequestBody ContractCreateReqVO createReqVO) {
return success(contractService.createContract(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新合同")
@PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> updateContract(@Valid @RequestBody ContractUpdateReqVO updateReqVO) {
contractService.updateContract(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除合同")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contract:delete')")
public CommonResult<Boolean> deleteContract(@RequestParam("id") Long id) {
contractService.deleteContract(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得合同")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<ContractRespVO> getContract(@RequestParam("id") Long id) {
ContractDO contract = contractService.getContract(id);
return success(ContractConvert.INSTANCE.convert(contract));
}
@GetMapping("/page")
@Operation(summary = "获得合同分页")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid ContractPageReqVO pageVO) {
PageResult<ContractDO> pageResult = contractService.getContractPage(pageVO);
return success(ContractConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出合同 Excel")
@PreAuthorize("@ss.hasPermission('crm:contract:export')")
@OperateLog(type = EXPORT)
public void exportContractExcel(@Valid ContractExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<ContractDO> list = contractService.getContractList(exportReqVO);
// 导出 Excel
List<ContractExcelVO> datas = ContractConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "合同.xls", "数据", ContractExcelVO.class, datas);
}
@PutMapping("/transfer")
@Operation(summary = "合同转移")
@PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
contractService.transferContract(reqVO, getLoginUserId());
return success(true);
}
}

View File

@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @dhb52所有类带下 Crm 前缀避免和别的模块重复
/**
* 合同 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class ContractBaseVO {
// TODO @dhb52类似 no 字段的 example 要写xia
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@NotNull(message = "合同名称不能为空")
private String name;
// TODO @dhb52这个必须传递
@Schema(description = "客户编号", example = "18336")
private Long customerId;
@Schema(description = "商机编号", example = "10864")
private Long businessId;
@Schema(description = "工作流编号", example = "1043")
private Long processInstanceId;
// TODO @dhb52这个必须传递
@Schema(description = "下单日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime orderDate;
// TODO @dhb52这个必须传递
@Schema(description = "负责人的用户编号", example = "17144")
private Long ownerUserId;
// TODO @芋艿未来应该支持自动生成
// TODO @dhb52这个必须传递
@Schema(description = "合同编号")
private String no;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime endTime;
@Schema(description = "合同金额", example = "5617")
private Integer price;
@Schema(description = "整单折扣")
private Integer discountPercent;
@Schema(description = "产品总金额", example = "19510")
private Integer productPrice;
@Schema(description = "联系人编号", example = "18546")
private Long contactId;
@Schema(description = "公司签约人", example = "14036")
private Long signUserId;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactLastTime;
@Schema(description = "备注", example = "你猜")
private String remark;
// TODO @dhb52增加一个 status 字段具体有哪些值你来枚举下主要页面上有个草稿提交审核的流程可以看看然后要对接工作流这块也可以看看不确定的地方问我
}

View File

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

View File

@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* CRM 合同 Excel VO
*
* @author dhb52
*/
@Data
public class ContractExcelVO {
@ExcelProperty("合同编号")
private Long id;
@ExcelProperty("合同名称")
private String name;
@ExcelProperty("客户编号")
private Long customerId;
@ExcelProperty("商机编号")
private Long businessId;
@ExcelProperty("工作流编号")
private Long processInstanceId;
@ExcelProperty("下单日期")
private LocalDateTime orderDate;
@ExcelProperty("负责人的用户编号")
private Long ownerUserId;
@ExcelProperty("合同编号")
private String no;
@ExcelProperty("开始时间")
private LocalDateTime startTime;
@ExcelProperty("结束时间")
private LocalDateTime endTime;
@ExcelProperty("合同金额")
private Integer price;
@ExcelProperty("整单折扣")
private Integer discountPercent;
@ExcelProperty("产品总金额")
private Integer productPrice;
@ExcelProperty("联系人编号")
private Long contactId;
@ExcelProperty("公司签约人")
private Long signUserId;
@ExcelProperty("最后跟进时间")
private LocalDateTime contactLastTime;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 合同 Excel 导出 Request VO参数和 ContractPageReqVO 是一致的")
@Data
public class ContractExportReqVO {
@Schema(description = "合同名称", example = "王五")
private String name;
@Schema(description = "客户编号", example = "18336")
private Long customerId;
@Schema(description = "商机编号", example = "10864")
private Long businessId;
@Schema(description = "下单日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] orderDate;
@Schema(description = "合同编号")
private String no;
@Schema(description = "整单折扣")
private Integer discountPercent;
@Schema(description = "产品总金额", example = "19510")
private Integer productPrice;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 合同分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ContractPageReqVO extends PageParam {
@Schema(description = "合同名称", example = "王五")
private String name;
@Schema(description = "客户编号", example = "18336")
private Long customerId;
@Schema(description = "商机编号", example = "10864")
private Long businessId;
@Schema(description = "下单日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] orderDate;
@Schema(description = "合同编号")
private String no;
@Schema(description = "整单折扣")
private Integer discountPercent;
@Schema(description = "产品总金额", example = "19510")
private Integer productPrice;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 合同 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ContractRespVO extends ContractBaseVO {
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - CRM 合同更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ContractUpdateReqVO extends ContractBaseVO {
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "合同编号不能为空")
private Long id;
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - CRM 合同转移 Request VO")
@Data
public class CrmContractTransferReqVO {
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "联系人编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
}

View File

@ -0,0 +1,210 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM 客户")
@RestController
@RequestMapping("/crm/customer")
@Validated
public class CrmCustomerController {
@Resource
private CrmCustomerService customerService;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Resource
private CrmPermissionService permissionService;
@PostMapping("/create")
@Operation(summary = "创建客户")
@PreAuthorize("@ss.hasPermission('crm:customer:create')")
public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) {
return success(customerService.createCustomer(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
customerService.updateCustomer(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除客户")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer:delete')")
public CommonResult<Boolean> deleteCustomer(@RequestParam("id") Long id) {
customerService.deleteCustomer(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得客户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
// 1. 获取客户
CrmCustomerDO customer = customerService.getCustomer(id);
if (customer == null) {
return success(null);
}
// 2. 拼接数据
// 2.1 获取负责人
List<CrmPermissionDO> ownerList = permissionService.getPermissionByBizTypeAndBizIdsAndLevel(
CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singletonList(customer.getId()),
CrmPermissionLevelEnum.OWNER.getLevel());
Map<Long, CrmPermissionDO> ownerMap = convertMap(ownerList, CrmPermissionDO::getBizId);
// 2.2 获取负责人详情
Set<Long> userIds = convertSet(ownerList, CrmPermissionDO::getUserId);
userIds.add(Long.parseLong(customer.getCreator())); // 加入创建者
List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIds);
Map<Long, AdminUserRespDTO> userMap = convertMap(userList, AdminUserRespDTO::getId);
// 2.3 获取部门详情
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
return success(CrmCustomerConvert.INSTANCE.convert(customer, ownerMap, userMap, deptMap));
}
// TODO @puhui999可以在 CrmCustomerPageReqVO 里面加个 pool 参数 true 代表来自公海客户的分页
@GetMapping("/page")
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 拼接数据
// TODO @puhui999这块的拼接逻辑可以和 convertPage 合并下
// Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
// convertSetByFlatMap(pageResult.getList(), user -> Stream.of(NumberUtil.parseLong(user.getCreator()), user.getOwnerUserId())));
// Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
// convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
return convertPage(customerService.getCustomerPage(pageVO, getLoginUserId()));
}
// TODO @puhui999
@GetMapping("/pool-page")
@Operation(summary = "获得公海客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getPoolCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
return convertPage(customerService.getCustomerPage(pageVO, CrmPermissionDO.POOL_USER_ID));
}
private CommonResult<PageResult<CrmCustomerRespVO>> convertPage(PageResult<CrmCustomerDO> pageResult) {
// 2. 拼接数据
Set<Long> ids = convertSet(pageResult.getList(), CrmCustomerDO::getId);
// 2.1 获取负责人
List<CrmPermissionDO> ownerList = permissionService.getPermissionByBizTypeAndBizIdsAndLevel(
CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, CrmPermissionLevelEnum.OWNER.getLevel());
Map<Long, CrmPermissionDO> ownerMap = convertMap(ownerList, CrmPermissionDO::getBizId);
// 2.2 获取负责人详情
Set<Long> userIds = convertSet(ownerList, CrmPermissionDO::getUserId);
userIds.addAll(convertSet(pageResult.getList(), item -> Long.parseLong(item.getCreator()))); // 加入创建者
List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIds);
Map<Long, AdminUserRespDTO> userMap = convertMap(userList, AdminUserRespDTO::getId);
// 2.3 获取部门详情
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, ownerMap, userMap, deptMap));
}
@GetMapping("/export-excel")
@Operation(summary = "导出客户 Excel")
@PreAuthorize("@ss.hasPermission('crm:customer:export')")
@OperateLog(type = EXPORT)
public void exportCustomerExcel(@Valid CrmCustomerExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CrmCustomerDO> list = customerService.getCustomerList(exportReqVO);
// 导出 Excel
List<CrmCustomerExcelVO> datas = CrmCustomerConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerExcelVO.class, datas);
}
@PutMapping("/transfer")
@Operation(summary = "客户转移")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
customerService.transferCustomer(reqVO, getLoginUserId());
return success(true);
}
// TODO @Joey单独建一个属于自己业务的 ReqVO因为前端如果模拟请求是不是可以更新其它字段了
@PutMapping("/lock")
@Operation(summary = "锁定/解锁客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
customerService.lockCustomer(updateReqVO);
return success(true);
}
@PutMapping("/receive")
@Operation(summary = "领取公海客户")
// TODO @xiaqing1receiveCustomer 方法名字2cIds 改成 ids要加下 @RequestParam还有 swagger 注解3参数非空使用 validator 校验4返回 true 即可
@PreAuthorize("@ss.hasPermission('crm:customer:receive')")
public CommonResult<String> receiveByIds(List<Long> cIds){
// 判断是否为空
if(CollectionUtils.isEmpty(cIds))
return error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),GlobalErrorCodeConstants.BAD_REQUEST.getMsg());
// 领取公海任务
// TODO @xiaqinguserid通过 controller 传递给 service不要在 service 里面获取无状态
customerService.receive(cIds);
return success("领取成功");
}
// TODO @xiaqing1distributeCustomer 方法名2cIds 同上3参数校验同上4ownerId 改成 ownerUserId和别的模块统一5返回 true 即可
@PutMapping("/distributeByIds")
@Operation(summary = "分配公海给对应负责人")
@PreAuthorize("@ss.hasPermission('crm:customer:distributeByIds')")
public CommonResult<String> distributeByIds(Long ownerId,List<Long>cIds){
//判断参数不能为空
if(ownerId==null || CollectionUtils.isEmpty(cIds))
return error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),GlobalErrorCodeConstants.BAD_REQUEST.getMsg());
customerService.distributeByIds(cIds,ownerId);
return success("分配成功");
}
}

View File

@ -0,0 +1,98 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLimitConfigUpdateReqVO;
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customerlimitconfig.CrmCustomerLimitConfigDO;
import cn.iocoder.yudao.module.crm.service.customerlimitconfig.CrmCustomerLimitConfigService;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collection;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
@Tag(name = "管理后台 - 客户限制配置")
@RestController
@RequestMapping("/crm/customer-limit-config")
@Validated
public class CrmCustomerLimitConfigController {
@Resource
private CrmCustomerLimitConfigService customerLimitConfigService;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建客户限制配置")
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')")
public CommonResult<Long> createCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigCreateReqVO createReqVO) {
return success(customerLimitConfigService.createCustomerLimitConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新客户限制配置")
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:update')")
public CommonResult<Boolean> updateCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigUpdateReqVO updateReqVO) {
customerLimitConfigService.updateCustomerLimitConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除客户限制配置")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:delete')")
public CommonResult<Boolean> deleteCustomerLimitConfig(@RequestParam("id") Long id) {
customerLimitConfigService.deleteCustomerLimitConfig(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得客户限制配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
public CommonResult<CrmCustomerLimitConfigRespVO> getCustomerLimitConfig(@RequestParam("id") Long id) {
CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigService.getCustomerLimitConfig(id);
// 拼接数据
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(customerLimitConfig.getUserIds());
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(customerLimitConfig.getDeptIds());
return success(CrmCustomerLimitConfigConvert.INSTANCE.convert(customerLimitConfig, userMap, deptMap));
}
@GetMapping("/page")
@Operation(summary = "获得客户限制配置分页")
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
public CommonResult<PageResult<CrmCustomerLimitConfigRespVO>> getCustomerLimitConfigPage(@Valid CrmCustomerLimitConfigPageReqVO pageVO) {
PageResult<CrmCustomerLimitConfigDO> pageResult = customerLimitConfigService.getCustomerLimitConfigPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 拼接数据
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream));
return success(CrmCustomerLimitConfigConvert.INSTANCE.convertPage(pageResult, userMap, deptMap));
}
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPoolConfigRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPoolConfigUpdateReqVO;
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 客户公海配置")
@RestController
@RequestMapping("/crm/customer-pool-config")
@Validated
public class CrmCustomerPoolConfigController {
@Resource
private CrmCustomerPoolConfigService customerPoolConfigService;
@GetMapping("/get")
@Operation(summary = "获取客户公海规则设置")
@PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")
public CommonResult<CrmCustomerPoolConfigRespVO> getCustomerPoolConfig() {
CrmCustomerPoolConfigDO customerPoolConfig = customerPoolConfigService.getCustomerPoolConfig();
return success(CrmCustomerConvert.INSTANCE.convert(customerPoolConfig));
}
// TODO @wanwan这个请求搞成 save
@PutMapping("/update")
@Operation(summary = "更新客户公海规则设置")
@PreAuthorize("@ss.hasPermission('crm:customer-pool-config:update')")
public CommonResult<Boolean> updateCustomerPoolConfig(@Valid @RequestBody CrmCustomerPoolConfigUpdateReqVO updateReqVO) {
customerPoolConfigService.updateCustomerPoolConfig(updateReqVO);
return success(true);
}
}

View File

@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.framework.common.validation.Telephone;
import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 客户 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmCustomerBaseVO {
@Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotEmpty(message = "客户名称不能为空")
private String name;
@Schema(description = "所属行业", example = "1")
private Integer industryId;
@Schema(description = "客户等级", example = "2")
@InEnum(CrmCustomerLevelEnum.class)
private Integer level;
@Schema(description = "客户来源", example = "3")
private Integer source;
@Schema(description = "手机", example = "18000000000")
@Mobile
private String mobile;
@Schema(description = "电话", example = "18000000000")
@Telephone
private String telephone;
@Schema(description = "网址", example = "https://www.baidu.com")
private String website;
@Schema(description = "QQ", example = "123456789")
@Size(max = 20, message = "QQ长度不能超过 20 个字符")
private String qq;
@Schema(description = "wechat", example = "123456789")
@Size(max = 255, message = "微信长度不能超过 255 个字符")
private String wechat;
@Schema(description = "email", example = "123456789@qq.com")
@Email(message = "邮箱格式不正确")
@Size(max = 255, message = "邮箱长度不能超过 255 个字符")
private String email;
@Schema(description = "客户描述", example = "任意文字")
@Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
private String description;
@Schema(description = "备注", example = "随便")
private String remark;
@Schema(description = "地区编号", example = "20158")
private Integer areaId;
@Schema(description = "详细地址", example = "北京市海淀区")
private String detailAddress;
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactNextTime;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - CRM 客户创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmCustomerCreateReqVO extends CrmCustomerBaseVO {
@Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
@NotNull(message = "负责人不能为空")
private Long ownerUserId;
}

View File

@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
// TODO 芋艿导出最后做等基本确认的差不多之后
/**
* CRM 客户 Excel VO
*
* @author Wanwan
*/
@Data
public class CrmCustomerExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("客户名称")
private String name;
@ExcelProperty(value = "跟进状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean followUpStatus;
@ExcelProperty(value = "锁定状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean lockStatus;
@ExcelProperty(value = "成交状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean dealStatus;
@ExcelProperty(value = "所属行业", converter = DictConvert.class)
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
private Integer industryId;
@ExcelProperty(value = "客户等级", converter = DictConvert.class)
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
private Integer level;
@ExcelProperty(value = "客户来源", converter = DictConvert.class)
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
private Integer source;
@ExcelProperty("手机")
private String mobile;
@ExcelProperty("电话")
private String telephone;
@ExcelProperty("网址")
private String website;
@ExcelProperty("QQ")
private String qq;
@ExcelProperty("wechat")
private String wechat;
@ExcelProperty("email")
private String email;
@ExcelProperty("客户描述")
private String description;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("负责人的用户编号")
private Long ownerUserId;
@ExcelProperty("地区编号")
private Integer areaId;
@ExcelProperty("详细地址")
private String detailAddress;
@ExcelProperty("最后跟进时间")
private LocalDateTime contactLastTime;
@ExcelProperty("下次联系时间")
private LocalDateTime contactNextTime;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
// TODO 芋艿导出最后做等基本确认的差不多之后
@Schema(description = "管理后台 - CRM 客户 Excel 导出 Request VO参数和 CrmCustomerPageReqVO 是一致的")
@Data
public class CrmCustomerExportReqVO {
@Schema(description = "客户名称", example = "赵六")
private String name;
@Schema(description = "手机", example = "18000000000")
private String mobile;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
// TODO @wanwanvo 可以新建一个 limitconfig放它的 vo
/**
* 客户限制配置 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CrmCustomerLimitConfigBaseVO {
@Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "规则类型不能为空")
private Integer type;
@Schema(description = "规则适用人群")
private List<Long> userIds;
@Schema(description = "规则适用部门")
private List<Long> deptIds;
@Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384")
@NotNull(message = "数量上限不能为空")
private Integer maxCount;
@Schema(description = "成交客户是否占有拥有客户数(当 type = 1 时)")
private Boolean dealCountEnabled;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 客户限制配置创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmCustomerLimitConfigCreateReqVO extends CrmCustomerLimitConfigBaseVO {
}

Some files were not shown because too many files have changed in this diff Show More