diff --git a/README.md b/README.md index d91e03a1f..99aa50650 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ 如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。 +如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。 + ## 😎 开源协议 **为什么推荐使用本项目?** diff --git a/pom.xml b/pom.xml index df8fc2c6f..86f894f4a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,13 +15,13 @@ yudao-module-system yudao-module-infra - + yudao-module-member - - - + yudao-module-pay + yudao-module-mall + yudao-module-crm diff --git a/sql/mysql/Ureport.sql b/sql/mysql/optinal/Ureport.sql similarity index 100% rename from sql/mysql/Ureport.sql rename to sql/mysql/optinal/Ureport.sql diff --git a/sql/mysql/optinal/crm.sql b/sql/mysql/optinal/crm.sql deleted file mode 100644 index 1cb1371ae..000000000 --- a/sql/mysql/optinal/crm.sql +++ /dev/null @@ -1,15 +0,0 @@ --- `ruoyi-vue-pro`.crm_contact_business_link definition - -CREATE TABLE `crm_contact_business_link` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', - `contact_id` int(11) DEFAULT NULL COMMENT '联系人id', - `business_id` int(11) DEFAULT NULL COMMENT '商机id', - `creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号', - PRIMARY KEY (`id`), - UNIQUE KEY `crm_contact_business_link_un` (`contact_id`,`business_id`,`deleted`,`tenant_id`) -) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='联系人商机关联表'; \ No newline at end of file diff --git a/sql/mysql/optinal/mall.sql b/sql/mysql/optinal/mall.sql deleted file mode 100644 index e69de29bb..000000000 diff --git a/sql/mysql/optinal/pay_wallet.sql b/sql/mysql/optinal/pay_wallet.sql deleted file mode 100644 index 1e9f1d255..000000000 --- a/sql/mysql/optinal/pay_wallet.sql +++ /dev/null @@ -1,256 +0,0 @@ --- ---------------------------- --- 转账单表 --- ---------------------------- -DROP TABLE IF EXISTS `pay_transfer`; -CREATE TABLE `pay_transfer` -( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', - `no` varchar(64) NOT NULL COMMENT '转账单号', - `app_id` bigint NOT NULL COMMENT '应用编号', - `channel_id` bigint NOT NULL COMMENT '转账渠道编号', - `channel_code` varchar(32) NOT NULL COMMENT '转账渠道编码', - `merchant_transfer_id` varchar(64) NOT NULL COMMENT '商户转账单编号', - `type` int NOT NULL COMMENT '类型', - `status` tinyint NOT NULL COMMENT '转账状态', - `success_time` datetime NULL COMMENT '转账成功时间', - `price` int NOT NULL COMMENT '转账金额,单位:分', - `subject` varchar(512) NOT NULL COMMENT '转账标题', - `user_name` varchar(64) NULL COMMENT '收款人姓名', - `alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号', - `openid` varchar(64) NULL COMMENT '微信 openId', - `notify_url` varchar(1024) NOT NULL COMMENT '异步通知商户地址', - `user_ip` varchar(50) NOT NULL COMMENT '用户 IP', - `channel_extras` varchar(512) NULL DEFAULT NULL COMMENT '渠道的额外参数', - `channel_transfer_no` varchar(64) NULL DEFAULT NULL COMMENT '渠道转账单号', - `channel_error_code` varchar(128) NULL DEFAULT NULL COMMENT '调用渠道的错误码', - `channel_error_msg` varchar(256) NULL DEFAULT NULL COMMENT '调用渠道的错误提示', - `channel_notify_data` varchar(4096) NULL DEFAULT NULL COMMENT '渠道的同步/异步通知的内容', - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB COMMENT='转账单表'; - --- ---------------------------- --- Table structure for pay_demo_transfer --- ---------------------------- -DROP TABLE IF EXISTS `pay_demo_transfer`; -CREATE TABLE `pay_demo_transfer` ( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单编号', - `app_id` bigint NOT NULL COMMENT '应用编号', - `type` int NOT NULL COMMENT '转账类型', - `price` int NOT NULL COMMENT '转账金额,单位:分', - `user_name` varchar(64) NULL COMMENT '收款人姓名', - `alipay_logon_id` varchar(64) NULL COMMENT '支付宝登录号', - `openid` varchar(64) NULL COMMENT '微信 openId', - `transfer_status` tinyint NOT NULL DEFAULT 0 COMMENT '转账状态', - `pay_transfer_id` bigint NULL DEFAULT NULL COMMENT '转账订单编号', - `pay_channel_code` varchar(16) NULL DEFAULT NULL COMMENT '转账支付成功渠道', - `transfer_time` datetime NULL DEFAULT NULL COMMENT '转账支付时间', - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB COMMENT = '示例业务转账订单'; - - --- ALTER TABLE `pay_channel` --- MODIFY COLUMN `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付渠道配置' AFTER `app_id`; - --- ---------------------------- --- 充值套餐表 --- ---------------------------- -DROP TABLE IF EXISTS `pay_wallet_recharge_package`; -CREATE TABLE `pay_wallet_recharge_package` -( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', - `name` varchar(64) NOT NULL COMMENT '套餐名', - `pay_price` int NOT NULL COMMENT '支付金额', - `bonus_price` int NOT NULL COMMENT '赠送金额', - `status` tinyint NOT NULL COMMENT '状态', - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB COMMENT='充值套餐表'; - --- ---------------------------- --- Table structure for pay_wallet_recharge --- ---------------------------- -DROP TABLE IF EXISTS `pay_wallet_recharge`; -CREATE TABLE `pay_wallet_recharge` ( - `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '编号', - `wallet_id` bigint(0) NOT NULL COMMENT '会员钱包 id', - `total_price` int(0) NOT NULL COMMENT '用户实际到账余额,例如充 100 送 20,则该值是 120', - `pay_price` int(0) NOT NULL COMMENT '实际支付金额', - `bonus_price` int(0) NOT NULL COMMENT '钱包赠送金额', - `package_id` bigint(0) DEFAULT NULL COMMENT '充值套餐编号', - `pay_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]', - `pay_order_id` bigint(0) DEFAULT NULL COMMENT '支付订单编号', - `pay_channel_code` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付成功的支付渠道', - `pay_time` datetime(0) DEFAULT NULL COMMENT '订单支付时间', - `pay_refund_id` bigint(0) DEFAULT NULL COMMENT '支付退款单编号', - `refund_total_price` int(0) NOT NULL DEFAULT 0 COMMENT '退款金额,包含赠送金额', - `refund_pay_price` int(0) NOT NULL DEFAULT 0 COMMENT '退款支付金额', - `refund_bonus_price` int(0) NOT NULL DEFAULT 0 COMMENT '退款钱包赠送金额', - `refund_time` datetime(0) DEFAULT NULL COMMENT '退款时间', - `refund_status` int(0) NOT NULL DEFAULT 0 COMMENT '退款状态', - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员钱包充值' ROW_FORMAT = Dynamic; - --- 钱包充值套餐,钱包余额菜单脚本 - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '钱包管理', '', 1, 5, 1117, - 'wallet', 'ep:caret-right', '', 0, '' - ); -SELECT @parentId1 := LAST_INSERT_ID(); - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '充值套餐', '', 2, 2, @parentId1, - 'wallet-recharge-package', 'fa:leaf', 'pay/wallet/rechargePackage/index', 0, 'WalletRechargePackage' - ); -SELECT @parentId := LAST_INSERT_ID(); - --- 按钮 SQL -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '钱包充值套餐查询', 'pay:wallet-recharge-package:query', 3, 1, @parentId, - '', '', '', 0 - ); -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '钱包充值套餐创建', 'pay:wallet-recharge-package:create', 3, 2, @parentId, - '', '', '', 0 - ); -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '钱包充值套餐更新', 'pay:wallet-recharge-package:update', 3, 3, @parentId, - '', '', '', 0 - ); -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '钱包充值套餐删除', 'pay:wallet-recharge-package:delete', 3, 4, @parentId, - '', '', '', 0 - ); - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '钱包余额', '', 2, 1, @parentId1, - 'wallet-balance', 'fa:leaf', 'pay/wallet/balance/index', 0, 'WalletBalance' - ); - -SELECT @parentId := LAST_INSERT_ID(); - --- 按钮 SQL -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '钱包余额查询', 'pay:wallet:query', 3, 1, @parentId, - '', '', '', 0 - ); - --- 支付实战和转账实战数据库脚本 - -update system_menu set deleted = 1 where id = 2161; - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '接入示例', '', 1, 99, 1117, - 'demo', 'ep:caret-right', '', 0, '' - ); - -SELECT @parentId1 := LAST_INSERT_ID(); - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '支付实战', '', 2, 1, @parentId1, - 'demo-order', 'fa:leaf', 'pay/demo/order/index', 0, NULL - ); - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '转账实战', '', 2, 1, @parentId1, - 'demo-transfer', 'fa:leaf', 'pay/demo/transfer/index', 0, NULL - ); - --- 转账状态和转账类型数据字典 -INSERT INTO `system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES ('支付转账类型', 'pay_transfer_type', 0, '', '1', '2023-10-28 16:27:18', '1', '2023-10-28 16:27:18', b'0', '1970-01-01 00:00:00'); -INSERT INTO `system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES ('转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', b'0', '1970-01-01 00:00:00'); - -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '钱包余额', '4', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:37', '1', '2023-10-28 16:28:37', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, '银行卡', '3', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:28:21', '1', '2023-10-28 16:28:21', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '微信余额', '2', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:07', '1', '2023-10-28 16:28:07', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '支付宝余额', '1', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:27:44', '1', '2023-10-28 16:27:44', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '转账失败', '30', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', '2023-10-28 16:24:16', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, '转账成功', '20', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2023-10-28 16:23:50', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '转账进行中', '10', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2023-10-28 16:23:12', b'0'); -INSERT INTO `system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', '2023-10-28 16:23:22', b'0'); - --- 转账订单菜单脚本 - -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '转账订单', '', 2, 3, 1117, - '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`; \ No newline at end of file diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 98acf8be3..f96d5e10b 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -2370,6 +2370,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2369, '拼团记录', 'promotion:combination-record:query', 2, 2, 2303, 'record', 'ep:avatar', 'mall/promotion/combination/record/index.vue', 'PromotionCombinationRecord', 0, b'1', b'1', b'1', '1', '2023-10-08 07:10:22', '1', '2023-10-08 07:34:11', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2374, '会员统计', '', 2, 2, 2358, 'member', 'ep:avatar', 'statistics/member/index', 'MemberStatistics', 0, b'1', b'1', b'1', '', '2023-10-11 04:39:24', '1', '2023-10-11 12:50:22', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2375, '会员统计查询', 'statistics:member:query', 3, 1, 2374, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-11 04:39:24', '', '2023-10-11 04:39:24', b'0'); +--此处缺少trade:order:query 和 trade:order:update权限,会导致订单的发货和更多按钮无法显示。 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2376, '订单核销', 'trade:order:pick-up', 3, 10, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-10-14 17:11:58', '1', '2023-10-14 17:11:58', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2377, '文章分类', '', 2, 0, 2387, 'article/category', 'fa:certificate', 'mall/promotion/article/category/index', 'ArticleCategory', 0, b'1', b'1', b'1', '', '2023-10-16 01:26:18', '1', '2023-10-16 09:38:26', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2378, '分类查询', 'promotion:article-category:query', 3, 1, 2377, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); @@ -2478,6 +2479,15 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2523, '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2524, '系统配置', '', 1, 99, 2397, 'config', 'ep:connection', '', '', 0, b'1', b'1', b'1', '1', '2023-11-18 21:58:00', '1', '2023-11-18 21:58:00', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2525, 'WebSocket 测试', '', 2, 7, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, b'1', b'1', b'1', '1', '2023-11-23 19:41:55', '1', '2023-11-24 19:22:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2526, '抄送流程', '', 2, 21, 1200, 'processInstanceCC', 'eye', '/bpm/task/cc/index', + 'BpmCCProcessInstance', 0, true, true, true, 1, '2023-01-13 16:14:00', '1', '2023-01-13 16:14:00', false), + (2527, '抄送流程查询', 'bpm:process-instance-cc:query', 3, 1, 2526, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 16:14:00', '1', '2023-01-13 16:14:00', b'0'), + (2528, '抄送流程创建', 'bpm:process-instance-cc:create', 3, 2, 2526, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 16:14:00', '1', '2023-01-13 16:14:00', b'0'); COMMIT; -- ---------------------------- diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 76da6b6c4..7d9faa8a8 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -26,7 +26,7 @@ 3.5.4.1 3.5.4.1 4.2.0 - 1.4.7.2 + 1.4.8.1 3.25.0 8.1.3.62 @@ -62,6 +62,7 @@ 0.1.55 2.9.1 2.7.0 + 3.0.6 3.5.0 4.11.0 @@ -99,6 +100,17 @@ yudao-spring-boot-starter-biz-operatelog ${revision} + + io.github.mouzt + bizlog-sdk + ${bizlog-sdk.version} + + + org.springframework.boot + spring-boot-starter + + + cn.iocoder.boot yudao-spring-boot-starter-biz-dict diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java index 632675203..f256712b3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java @@ -15,6 +15,7 @@ import java.util.Arrays; @Getter public enum TerminalEnum implements IntArrayValuable { + UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它 WECHAT_MINI_PROGRAM(10, "微信小程序"), WECHAT_WAP(11, "微信公众号"), H5(20, "H5 网页"), diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortablePageParam.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortablePageParam.java new file mode 100644 index 000000000..2365c41c4 --- /dev/null +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortablePageParam.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.framework.common.pojo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +@Schema(description = "可排序的分页参数") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SortablePageParam extends PageParam { + + @Schema(description = "排序字段") + private List sortingFields; + +} \ No newline at end of file diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java index c7e50a487..53b5574f9 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java @@ -177,4 +177,14 @@ public class DateUtils { return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now()); } + /** + * 是否昨天 + * + * @param date 日期 + * @return 是否 + */ + public static boolean isYesterday(LocalDateTime date) { + return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1)); + } + } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java index 2674a110e..59656cbdd 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java @@ -6,6 +6,7 @@ import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; /** @@ -121,4 +122,14 @@ public class LocalDateTimeUtils { return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX); } + /** + * 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负 + * + * @param dateTime 日期 + * @return 相差天数 + */ + public static Long between(LocalDateTime dateTime) { + return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS); + } + } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java index e14572a7f..720b56510 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import java.util.List; +import java.util.function.Consumer; /** * Bean 工具类 @@ -20,6 +21,14 @@ public class BeanUtils { return BeanUtil.toBean(source, targetClass); } + public static T toBean(Object source, Class targetClass, Consumer peek) { + T target = toBean(source, targetClass); + if (target != null) { + peek.accept(target); + } + return target; + } + public static List toBean(List source, Class targetType) { if (source == null) { return null; @@ -27,11 +36,27 @@ public class BeanUtils { return CollectionUtils.convertList(source, s -> toBean(s, targetType)); } - public static PageResult toBean(PageResult source, Class targetType) { + public static List toBean(List source, Class targetType, Consumer peek) { + List list = toBean(source, targetType); + if (list != null) { + list.forEach(peek); + } + return list; + } + + public static PageResult toBean(PageResult source, Class targetType) { + return toBean(source, targetType, null); + } + + public static PageResult toBean(PageResult source, Class targetType, Consumer peek) { if (source == null) { return null; } - return new PageResult<>(toBean(source.getList(), targetType), source.getTotal()); + List list = toBean(source.getList(), targetType); + if (peek != null) { + list.forEach(peek); + } + return new PageResult<>(list, source.getTotal()); } } \ No newline at end of file diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/PageUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/PageUtils.java index 72403a9bd..0abdf7be2 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/PageUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/PageUtils.java @@ -1,6 +1,15 @@ package cn.iocoder.yudao.framework.common.util.object; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.func.Func1; +import cn.hutool.core.lang.func.LambdaUtil; +import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.SortablePageParam; +import cn.iocoder.yudao.framework.common.pojo.SortingField; +import org.springframework.util.Assert; + +import java.util.List; /** * {@link cn.iocoder.yudao.framework.common.pojo.PageParam} 工具类 @@ -9,8 +18,50 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; */ public class PageUtils { + private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC}; + public static int getStart(PageParam pageParam) { return (pageParam.getPageNo() - 1) * pageParam.getPageSize(); } + /** + * 构建排序字段(默认倒序) + * + * @param func 排序字段的 Lambda 表达式 + * @param 排序字段所属的类型 + * @return 排序字段 + */ + public static SortingField buildSortingField(Func1 func) { + return buildSortingField(func, SortingField.ORDER_DESC); + } + + /** + * 构建排序字段 + * + * @param func 排序字段的 Lambda 表达式 + * @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC} + * @param 排序字段所属的类型 + * @return 排序字段 + */ + public static SortingField buildSortingField(Func1 func, String order) { + Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format("字段的排序类型只能是 %s/%s", ORDER_TYPES)); + + String fieldName = LambdaUtil.getFieldName(func); + return new SortingField(fieldName, order); + } + + /** + * 构建默认的排序字段 + * 如果排序字段为空,则设置排序字段;否则忽略 + * + * @param sortablePageParam 排序分页查询参数 + * @param func 排序字段的 Lambda 表达式 + * @param 排序字段所属的类型 + */ + public static void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1 func) { + if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) { + sortablePageParam.setSortingFields(List.of(buildSortingField(func))); + } + } + } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/servlet/ServletUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/servlet/ServletUtils.java index 672c1fa19..bc592d890 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/servlet/ServletUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/servlet/ServletUtils.java @@ -88,8 +88,6 @@ public class ServletUtils { return JakartaServletUtil.getClientIP(request); } - // TODO @疯狂:terminal 还是从 ServletUtils 里拿,更容易全局治理; - public static boolean isJsonRequest(ServletRequest request) { return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java index 98f64d11a..cbeedee4d 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java @@ -496,7 +496,8 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme Expression allExpression = null; for (DataPermissionRule rule : ContextHolder.getRules()) { // 判断表名是否匹配 - if (!rule.getTableNames().contains(table.getName())) { + String tableName = MyBatisUtils.getTableName(table); + if (!rule.getTableNames().contains(tableName)) { continue; } // 如果有匹配的规则,说明可重写。 @@ -505,7 +506,6 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme ContextHolder.setRewrite(true); // 单条规则的条件 - String tableName = MyBatisUtils.getTableName(table); Expression oneExpress = rule.getExpression(tableName, table.getAlias()); if (oneExpress == null){ continue; diff --git a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml index 5792b4cb4..c52d8fc10 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml @@ -46,6 +46,7 @@ com.google.guava guava + diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index ada1f42e6..a8c50cf1c 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -71,6 +71,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient extends MPJBaseMapper { + default PageResult selectPage(SortablePageParam pageParam, @Param("ew") Wrapper queryWrapper) { + return selectPage(pageParam, pageParam.getSortingFields(), queryWrapper); + } + default PageResult selectPage(PageParam pageParam, @Param("ew") Wrapper queryWrapper) { + return selectPage(pageParam, null, queryWrapper); + } + + default PageResult selectPage(PageParam pageParam, Collection sortingFields, @Param("ew") Wrapper queryWrapper) { // 特殊:不分页,直接查询全部 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { List list = selectList(queryWrapper); @@ -34,12 +45,26 @@ public interface BaseMapperX extends MPJBaseMapper { } // MyBatis Plus 查询 - IPage mpPage = MyBatisUtils.buildPage(pageParam); + IPage mpPage = MyBatisUtils.buildPage(pageParam, sortingFields); selectPage(mpPage, queryWrapper); // 转换返回 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); } + default PageResult selectJoinPage(PageParam pageParam, Class clazz, MPJLambdaWrapper lambdaWrapper) { + // 特殊:不分页,直接查询全部 + if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageNo())) { + List list = selectJoinList(clazz, lambdaWrapper); + return new PageResult<>(list, (long) list.size()); + } + + // MyBatis Plus Join 查询 + IPage mpPage = MyBatisUtils.buildPage(pageParam); + mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + default PageResult selectJoinPage(PageParam pageParam, Class resultTypeClass, MPJBaseJoin joinQueryWrapper) { IPage mpPage = MyBatisUtils.buildPage(pageParam); selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper); diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index 0b1b01b08..3da059a6c 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -45,8 +45,8 @@ public class MyBatisUtils { * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 * * @param interceptor 链 - * @param inner 拦截器 - * @param index 位置 + * @param inner 拦截器 + * @param index 位置 */ public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) { List inners = new ArrayList<>(interceptor.getInterceptors()); @@ -73,9 +73,9 @@ public class MyBatisUtils { /** * 构建 Column 对象 * - * @param tableName 表名 + * @param tableName 表名 * @param tableAlias 别名 - * @param column 字段名 + * @param column 字段名 * @return Column 对象 */ public static Column buildColumn(String tableName, Alias tableAlias, String column) { diff --git a/yudao-framework/yudao-spring-boot-starter-security/pom.xml b/yudao-framework/yudao-spring-boot-starter-security/pom.xml index 864f1f101..e76e57ef9 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-security/pom.xml @@ -12,7 +12,10 @@ jar ${project.artifactId} - 用户的认证、权限的校验 + + 1. security:用户的认证、权限的校验,实现「谁」可以做「什么事」 + 2. operatelog:操作日志,实现「谁」在「什么时间」对「什么」做了「什么事」 + https://github.com/YunaiV/ruoyi-vue-pro @@ -50,6 +53,13 @@ guava + + + + io.github.mouzt + bizlog-sdk + + cn.iocoder.boot diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogV2Configuration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogV2Configuration.java new file mode 100644 index 000000000..866f94f78 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogV2Configuration.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.framework.operatelog.config; + +import cn.iocoder.yudao.framework.operatelog.core.service.LogRecordServiceImpl; +import com.mzt.logapi.service.ILogRecordService; +import com.mzt.logapi.starter.annotation.EnableLogRecord; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +/** + * 操作日志配置类 + * + * @author HUIHUI + */ +@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦 +@AutoConfiguration +@Slf4j +public class YudaoOperateLogV2Configuration { + + @Bean + @Primary + public ILogRecordService iLogRecordServiceImpl() { + return new LogRecordServiceImpl(); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/package-info.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/package-info.java new file mode 100644 index 000000000..97ce4eaa7 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,无特殊作用 + */ +package cn.iocoder.yudao.framework.operatelog.core; \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/LogRecordServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/LogRecordServiceImpl.java new file mode 100644 index 000000000..5f0ba9b6d --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/LogRecordServiceImpl.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.framework.operatelog.core.service; + +import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO; +import com.mzt.logapi.beans.LogRecord; +import com.mzt.logapi.service.ILogRecordService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 操作日志 ILogRecordService 实现类 + * + * 基于 {@link OperateLogApi} 实现,记录操作日志 + * + * @author HUIHUI + */ +@Slf4j +public class LogRecordServiceImpl implements ILogRecordService { + + @Resource + private OperateLogApi operateLogApi; + + @Override + public void record(LogRecord logRecord) { + // 1. 补全通用字段 + OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO(); + reqDTO.setTraceId(TracerUtils.getTraceId()); + // 补充用户信息 + fillUserFields(reqDTO); + // 补全模块信息 + fillModuleFields(reqDTO, logRecord); + // 补全请求信息 + fillRequestFields(reqDTO); + + // 2. 异步记录日志 + operateLogApi.createOperateLogV2(reqDTO); + // TODO 测试结束删除或搞个开关 + log.info("操作日志 ===> {}", reqDTO); + } + + private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) { + // 使用 SecurityFrameworkUtils。因为要考虑,rpc、mq、job,它其实不是 web; + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + return; + } + reqDTO.setUserId(loginUser.getId()); + reqDTO.setUserType(loginUser.getUserType()); + } + + public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) { + reqDTO.setType(logRecord.getType()); // 大模块类型,例如:CRM 客户 + reqDTO.setSubType(logRecord.getSubType());// 操作名称,例如:转移客户 + reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号,例如:客户编号 + reqDTO.setAction(logRecord.getAction());// 操作内容,例如:修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。 + reqDTO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"} + } + + private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) { + // 获得 Request 对象 + HttpServletRequest request = ServletUtils.getRequest(); + if (request == null) { + return; + } + // 补全请求信息 + reqDTO.setRequestMethod(request.getMethod()); + reqDTO.setRequestUrl(request.getRequestURI()); + reqDTO.setUserIp(ServletUtils.getClientIP(request)); + reqDTO.setUserAgent(ServletUtils.getUserAgent(request)); + } + + @Override + public List queryLog(String bizNo, String type) { + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询"); + } + + @Override + public List queryLogByBizNo(String bizNo, String type, String subType) { + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询"); + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/package-info.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/package-info.java new file mode 100644 index 000000000..c90139b89 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/package-info.java @@ -0,0 +1,7 @@ +/** + * 基于 mzt-log 框架 + * 实现操作日志功能 + * + * @author HUIHUI + */ +package cn.iocoder.yudao.framework.operatelog; diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index f64277f01..c66f41985 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration -cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter \ No newline at end of file +cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter +cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index 70c59cb4e..ae18634c2 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.framework.web.core.util; import cn.hutool.core.util.NumberUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.iocoder.yudao.framework.common.enums.TerminalEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.web.config.WebProperties; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -25,6 +28,13 @@ public class WebFrameworkUtils { public static final String HEADER_TENANT_ID = "tenant-id"; + /** + * 终端的 Header + * + * @see cn.iocoder.yudao.framework.common.enums.TerminalEnum + */ + public static final String HEADER_TERMINAL = "terminal"; + private static WebProperties properties; public WebFrameworkUtils(WebProperties webProperties) { @@ -107,6 +117,15 @@ public class WebFrameworkUtils { return getLoginUserId(request); } + public static Integer getTerminal() { + HttpServletRequest request = getRequest(); + if (request == null) { + return TerminalEnum.UNKNOWN.getTerminal(); + } + String terminalValue = request.getHeader(HEADER_TERMINAL); + return NumberUtil.parseInt(terminalValue, TerminalEnum.UNKNOWN.getTerminal()); + } + public static void setCommonResult(ServletRequest request, CommonResult result) { request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateRuleVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateRuleVO.java new file mode 100644 index 000000000..25426f604 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateRuleVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo; + +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleBaseVO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Set; + +/** + * 流程任务分配规则 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @see BpmTaskAssignRuleBaseVO + */ +@Data +public class BpmTaskCandidateRuleVO { + + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "bpm_task_assign_rule_type") + @NotNull(message = "规则类型不能为空") + private Integer type; + + @Schema(description = "规则值数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + @NotNull(message = "规则值数组不能为空") + private Set options; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index af8e8edcd..0cba2b35f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -3,17 +3,17 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; +import cn.iocoder.yudao.module.bpm.service.cc.BpmProcessInstanceCopyService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -26,6 +26,9 @@ public class BpmProcessInstanceController { @Resource private BpmProcessInstanceService processInstanceService; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + @GetMapping("/my-page") @Operation(summary = "获得我的实例分页列表", description = "在【我的流程】菜单中,进行调用") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") @@ -56,4 +59,22 @@ public class BpmProcessInstanceController { processInstanceService.cancelProcessInstance(getLoginUserId(), cancelReqVO); return success(true); } + + // TODO @kyle:抄送要不单独 controller? + + @PostMapping("/cc/create") + @Operation(summary = "抄送流程") + @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:create')") + public CommonResult createProcessInstanceCC(@Valid @RequestBody BpmProcessInstanceCCReqVO createReqVO) { + return success(processInstanceCopyService.ccProcessInstance(getLoginUserId(), createReqVO)); + } + + @GetMapping("/cc/my-page") + @Operation(summary = "获得抄送流程分页列表") + @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:query')") + public CommonResult> getProcessInstanceCCPage( + @Valid BpmProcessInstanceCCMyPageReqVO pageReqVO) { + return success(processInstanceCopyService.getMyProcessInstanceCCPage(getLoginUserId(), pageReqVO)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCMyPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCMyPageReqVO.java new file mode 100644 index 000000000..928ea8d97 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCMyPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +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; + +// TODO @kyle:建议改成 BpmProcessInstanceCopyMyPageReqVO;cc 缩写不容易理解,所以改成 copy,虽然会长一点,但是可读性更重要; +@Schema(description = "管理后台 - 流程实例抄送的分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BpmProcessInstanceCCMyPageReqVO extends PageParam { + + @Schema(description = "流程名称", example = "芋道") + private String processInstanceName; + + @Schema(description = "流程编号", example = "123456768") + private String processInstanceId; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCPageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCPageItemRespVO.java new file mode 100644 index 000000000..176350c24 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCPageItemRespVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 流程实例抄送的分页 Item Response VO") +@Data +public class BpmProcessInstanceCCPageItemRespVO { + + // TODO @kyle:如果已经写了 swagger 注解,可以不用写 java 注释哈; + /** + * 编号 + */ + @Schema(description = "抄送主键") + private Long id; + + /** + * 发起人Id + */ + @Schema(description = "发起人Id") + private Long startUserId; + + @Schema(description = "发起人别名") + private String startUserNickname; + + /** + * 流程主键 + */ + @Schema(description = "流程实例的主键") + private String processInstanceId; + + @Schema(description = "流程实例的名称") + private String processInstanceName; + /** + * 任务主键 + */ + @Schema(description = "发起抄送的任务编号") + private String taskId; + + @Schema(description = "发起抄送的任务名称") + private String taskName; + + @Schema(description = "抄送原因") + private String reason; + + @Schema(description = "抄送人") + private String creator; + + @Schema(description = "抄送人别名") + private String creatorNickname; + + @Schema(description = "抄送时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCReqVO.java new file mode 100644 index 000000000..a07fd1514 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCCReqVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// TODO @kyle:这个 VO 可以改成 BpmProcessInstanceCopyCreateReqVO +@Schema(description = "管理后台 - 流程实例的抄送 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BpmProcessInstanceCCReqVO extends BpmTaskCandidateRuleVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String taskKey; + + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务名称不能为空") + private String taskName; + + @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceKey; + + @Schema(description = "发起流程的用户的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "发起流程的用户的编号不能为空") + private Long startUserId; + + @Schema(description = "任务实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务实例名称不能为空") + private String processInstanceName; + + @Schema(description = "抄送原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "请帮忙审查下!") + @NotBlank(message = "抄送原因不能为空") + private String reason; + + // TODO @kyle:看了下字段有点多,尽量不传递可推导的字段; + // 需要传递:taskId(任务编号)、reason、userIds(被抄送的人) + // 不需要传递:taskKey、taskName、processInstanceKey、startUserId、processInstanceName 因为这些可以后端查询到 + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java index 14dca13ea..1a79441ce 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/cc/BpmProcessInstanceCopyConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/cc/BpmProcessInstanceCopyConvert.java new file mode 100644 index 000000000..3be0c131f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/cc/BpmProcessInstanceCopyConvert.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.bpm.convert.cc; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCPageItemRespVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.cc.BpmProcessInstanceCopyDO; +import cn.iocoder.yudao.module.bpm.service.cc.BpmProcessInstanceCopyVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +// TODO kyle:类注释不太对 +/** + * 动态表单 Convert + * + * @author 芋艿 + */ +@Mapper +public interface BpmProcessInstanceCopyConvert { + + BpmProcessInstanceCopyConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceCopyConvert.class); + + // TODO @kyle:可以使用 BeanUtils copy 替代这些简单的哈; + BpmProcessInstanceCopyDO copy(BpmProcessInstanceCopyDO bean); + + BpmProcessInstanceCopyVO convert(BpmProcessInstanceCopyDO bean); + + List convertList(List list); + + // TODO @kyle:/* taskId */ 这种注释一般不用写,可以一眼看明白的;避免变量看着略微不清晰哈 + default PageResult convertPage(PageResult page + , Map taskMap + , Map processInstaneMap + , Map userMap + ) { + List list = convertList(page.getList()); + for (BpmProcessInstanceCCPageItemRespVO vo : list) { + MapUtils.findAndThen(userMap, Long.valueOf(vo.getCreator()), + vo::setCreatorNickname); + MapUtils.findAndThen(userMap, vo.getStartUserId(), + vo::setStartUserNickname); + MapUtils.findAndThen(taskMap, vo.getTaskId(), + vo::setTaskName); + MapUtils.findAndThen(processInstaneMap, vo.getProcessInstanceId(), + vo::setProcessInstanceName); + } + // TODO @kyle:可以精简成下面的哈; +// List list2 = BeanUtils.toBean(page.getList(), +// BpmProcessInstanceCCPageItemRespVO.class, +// copy -> { +// MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()), copy::setCreatorNickname); +// MapUtils.findAndThen(userMap, copy.getStartUserId(), copy::setStartUserNickname); +// MapUtils.findAndThen(taskMap, copy.getTaskId(), copy::setTaskName); +// MapUtils.findAndThen(processInstaneMap, copy.getProcessInstanceId(), copy::setProcessInstanceName); +// }); + return new PageResult<>(list, page.getTotal()); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/cc/BpmProcessInstanceCopyDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/cc/BpmProcessInstanceCopyDO.java new file mode 100644 index 000000000..3bd287409 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/cc/BpmProcessInstanceCopyDO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.bpm.dal.dataobject.cc; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 流程抄送 DO + * + * @author kyle + * @date 2022-05-19 TODO @kyle:@date 不是标准 java doc,可以使用 @since 替代,然后日期是不是不对 + */ +@TableName(value = "bpm_process_instance_copy", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmProcessInstanceCopyDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + // TODO @kyle:字段如果是关联或者冗余,要写下注释。以 processInstanceId 举例子。 + /** + * 发起人 Id + */ + private Long startUserId; + /** + * 流程名 + */ + private String processInstanceName; + /** + * 流程实例的编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + + /** + * 任务主键 + */ + private String taskId; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 用户编号 + */ + private Long userId; + + /** + * 抄送原因 + */ + private String reason; + + // TODO @kyle:这个字段,可以用 category 简化点 + /** + * 流程分类 + */ + private String processDefinitionCategory; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/cc/BpmProcessInstanceCopyMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/cc/BpmProcessInstanceCopyMapper.java new file mode 100644 index 000000000..3b8848428 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/cc/BpmProcessInstanceCopyMapper.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.bpm.dal.mysql.cc; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCMyPageReqVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.cc.BpmProcessInstanceCopyDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface BpmProcessInstanceCopyMapper extends BaseMapperX { // TODO @kyle:方法和类之间要空行下; + default PageResult selectPage(Long loginUserId, BpmProcessInstanceCCMyPageReqVO reqVO){ + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(BpmProcessInstanceCopyDO::getUserId, loginUserId) + .eqIfPresent(BpmProcessInstanceCopyDO::getProcessInstanceId, reqVO.getProcessInstanceId()) + .likeIfPresent(BpmProcessInstanceCopyDO::getProcessInstanceName, reqVO.getProcessInstanceName()) + .betweenIfPresent(BpmProcessInstanceCopyDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(BpmProcessInstanceCopyDO::getId)); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java new file mode 100644 index 000000000..cff03488b --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.bpm.framework.bpm.config; + +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; +import cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor.*; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +// TODO @芋艿:Candidate 相关还在完善中,用户可以暂时忽略,仅 yudao 开发的同学需要关注~计划是把 Candidate 和 Assign 融合成一套 +/** + * BPM 通用的 Configuration 配置类,提供给 Activiti 和 Flowable + * @author kyle + */ +@Configuration(proxyBeanMethods = false) +public class BpmCandidateProcessorConfiguration { + @Bean + public BpmCandidateAdminUserApiSourceInfoProcessor bpmCandidateAdminUserApiSourceInfoProcessor() { + return new BpmCandidateAdminUserApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateDeptApiSourceInfoProcessor bpmCandidateDeptApiSourceInfoProcessor() { + return new BpmCandidateDeptApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidatePostApiSourceInfoProcessor bpmCandidatePostApiSourceInfoProcessor() { + return new BpmCandidatePostApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateRoleApiSourceInfoProcessor bpmCandidateRoleApiSourceInfoProcessor() { + return new BpmCandidateRoleApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateUserGroupApiSourceInfoProcessor bpmCandidateUserGroupApiSourceInfoProcessor() { + return new BpmCandidateUserGroupApiSourceInfoProcessor(); + } + + /** + * 可以自己定制脚本,然后通过这里设置到处理器里面去 + * @param scriptsOp 脚本包装对象 + * @return + */ + @Bean + public BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor(ObjectProvider scriptsOp) { + BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor = new BpmCandidateScriptApiSourceInfoProcessor(); + bpmCandidateScriptApiSourceInfoProcessor.setScripts(scriptsOp); + return bpmCandidateScriptApiSourceInfoProcessor; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java new file mode 100644 index 000000000..8582ad9a0 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.HashSet; +import java.util.Set; + +/** + * 获取候选人信息 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class BpmCandidateSourceInfo { + + @Schema(description = "流程id") + @NotNull + private String processInstanceId; + + @Schema(description = "当前任务ID") + @NotNull + private String taskId; + /** + * 通过这些规则,生成最终需要生成的用户 + */ + @Schema(description = "当前任务预选规则") + @NotEmpty(message = "不允许空规则") + private Set rules; + + @Schema(description = "发起抄送的用户") + private String creator; + + @Schema(description = "抄送原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "请帮忙审查下!") + @NotEmpty(message = "抄送原因不能为空") + private String reason; + + public void addRule(BpmTaskCandidateRuleVO vo) { + assert vo != null; + if (rules == null) { + rules = new HashSet<>(); + } + rules.add(vo); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java new file mode 100644 index 000000000..0fe741c20 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import org.flowable.engine.delegate.DelegateExecution; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public interface BpmCandidateSourceInfoProcessor { + /** + * 获取该处理器支持的类型 + * 来自 {@link BpmTaskAssignRuleTypeEnum} + * + * @return + */ + Set getSupportedTypes(); + + /** + * 对规则和人员做校验 + * + * @param type 规则 + * @param options 人员id + */ + void validRuleOptions(Integer type, Set options); + + /** + * 默认的处理 + * 如果想去操作所有的规则,则可以覆盖此方法 + * + * @param request 原始请求 + * @param delegateExecution 审批过程中的对象 + * @return 必须包含的是用户ID,而不是其他的ID + * @throws Exception + */ + default Set process(BpmCandidateSourceInfo request, DelegateExecution delegateExecution) throws Exception { + Set rules = request.getRules(); + Set results = new HashSet<>(); + for (BpmTaskCandidateRuleVO rule : rules) { + // 每个处理器都有机会处理自己支持的事件 + if (CollUtil.contains(getSupportedTypes(), rule.getType())) { + results.addAll(doProcess(request, rule, delegateExecution)); + } + } + return results; + } + + default Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + return Collections.emptySet(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java new file mode 100644 index 000000000..dafc0835a --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.*; + +@Service +public class BpmCandidateSourceInfoProcessorChain { + + // 保存处理节点 + + private List processorList; + @Resource + private AdminUserApi adminUserApi; + + /** + * 可添加其他处理器 + * + * @param processorOp + * @return + */ + @Resource + // 动态扩展处理节点 + public BpmCandidateSourceInfoProcessorChain addProcessor(ObjectProvider processorOp) { + List processor = ListUtil.toList(processorOp.iterator()); + if (null == processorList) { + processorList = new ArrayList<>(processor.size()); + } + processorList.addAll(processor); + return this; + } + + // 获取处理器处理 + public Set process(BpmCandidateSourceInfo sourceInfo, DelegateExecution execution) throws Exception { + // Verify our parameters + if (sourceInfo == null) { + throw new IllegalArgumentException(); + } + for (BpmCandidateSourceInfoProcessor processor : processorList) { + try { + for (BpmTaskCandidateRuleVO vo : sourceInfo.getRules()) { + if (CollUtil.contains(processor.getSupportedTypes(), vo.getType())) { + processor.validRuleOptions(vo.getType(), vo.getOptions()); + } + } + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + Set saveResult = Collections.emptySet(); + Exception saveException = null; + for (BpmCandidateSourceInfoProcessor processor : processorList) { + try { + saveResult = processor.process(sourceInfo, execution); + if (CollUtil.isNotEmpty(saveResult)) { + removeDisableUsers(saveResult); + break; + } + } catch (Exception e) { + saveException = e; + break; + } + } + // Return the exception or result state from the last execute() + if ((saveException != null)) { + throw saveException; + } else { + return (saveResult); + } + } + + public Set calculateTaskCandidateUsers(DelegateExecution execution, BpmCandidateSourceInfo sourceInfo) { + Set results = Collections.emptySet(); + try { + results = process(sourceInfo, execution); + } catch (Exception e) { + e.printStackTrace(); + } + return results; + } + + /** + * 移除禁用用户 + * + * @param assigneeUserIds + */ + public void removeDisableUsers(Set assigneeUserIds) { + if (CollUtil.isEmpty(assigneeUserIds)) { + return; + } + Map userMap = adminUserApi.getUserMap(assigneeUserIds); + assigneeUserIds.removeIf(id -> { + AdminUserRespDTO user = userMap.get(id); + return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); + }); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java new file mode 100644 index 000000000..536b3eec2 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import org.flowable.engine.delegate.DelegateExecution; + +import jakarta.annotation.Resource; +import java.util.Set; + +public class BpmCandidateAdminUserApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private AdminUserApi api; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.USER.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validateUserList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + return rule.getOptions(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java new file mode 100644 index 000000000..6fcfa23fd --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +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 org.flowable.engine.delegate.DelegateExecution; + +import jakarta.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +public class BpmCandidateDeptApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private DeptApi api; + @Resource + private AdminUserApi adminUserApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), + BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validateDeptList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) { + List users = adminUserApi.getUserListByDeptIds(rule.getOptions()); + return convertSet(users, AdminUserRespDTO::getId); + } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) { + List depts = api.getDeptList(rule.getOptions()); + return convertSet(depts, DeptRespDTO::getLeaderUserId); + } + return Collections.emptySet(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java new file mode 100644 index 000000000..f924d87ef --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.dept.PostApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.delegate.DelegateExecution; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +public class BpmCandidatePostApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private PostApi api; + @Resource + private AdminUserApi adminUserApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.POST.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validPostList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + List users = adminUserApi.getUserListByPostIds(rule.getOptions()); + return convertSet(users, AdminUserRespDTO::getId); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java new file mode 100644 index 000000000..f3ba5b92b --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.permission.RoleApi; +import org.flowable.engine.delegate.DelegateExecution; + +import jakarta.annotation.Resource; +import java.util.Set; + +public class BpmCandidateRoleApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private RoleApi api; + + @Resource + private PermissionApi permissionApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.ROLE.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validRoleList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions()); + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java new file mode 100644 index 000000000..4bf25694d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; +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.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.beans.factory.ObjectProvider; + +import jakarta.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS; + +public class BpmCandidateScriptApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private DictDataApi dictDataApi; + + /** + * 任务分配脚本 + */ + private Map scriptMap = Collections.emptyMap(); + + public void setScripts(ObjectProvider scriptsOp) { + List scripts = scriptsOp.orderedStream().collect(Collectors.toList()); + setScripts(scripts); + } + + public void setScripts(List scripts) { + this.scriptMap = convertMap(scripts, script -> script.getEnum().getId()); + } + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.SCRIPT.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + dictDataApi.validateDictDataList(DictTypeConstants.TASK_ASSIGN_SCRIPT, + CollectionUtils.convertSet(options, String::valueOf)); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + return calculateTaskCandidateUsersByScript(delegateExecution, rule.getOptions()); + } + + private Set calculateTaskCandidateUsersByScript(DelegateExecution execution, Set options) { + // 获得对应的脚本 + List scripts = new ArrayList<>(options.size()); + options.forEach(id -> { + BpmTaskAssignScript script = scriptMap.get(id); + if (script == null) { + throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id); + } + scripts.add(script); + }); + // 逐个计算任务 + Set userIds = new HashSet<>(); + scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution))); + return userIds; + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java new file mode 100644 index 000000000..b0720b0d5 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; +import org.flowable.engine.delegate.DelegateExecution; + +import jakarta.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BpmCandidateUserGroupApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private BpmUserGroupService api; + @Resource + private BpmUserGroupService userGroupService; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validUserGroups(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateRuleVO rule, DelegateExecution delegateExecution) { + List userGroups = userGroupService.getUserGroupList(rule.getOptions()); + Set userIds = new HashSet<>(); + userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds())); + return userIds; + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyService.java new file mode 100644 index 000000000..799656fde --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyService.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.bpm.service.cc; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCMyPageReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCPageItemRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCReqVO; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; + +// TODO @kyle:这个 Service 要不挪到 task 包下;保持统一,task 下有流程、任务、抄送等; +// TODO @kyle:中文和英文之间,有个空格,会更清晰点;例如说;流程抄送 Service 接口;中文写作习惯~ +/** + * 流程抄送Service接口 + * + * 现在是在审批的时候进行流程抄送 + */ +public interface BpmProcessInstanceCopyService { + + // TODO @kyle:无用的方法,可以去掉哈;另外,考虑到避免过多的 VO,这里就可以返回 BpmProcessInstanceCopyDO + /** + * 查询流程抄送 + * + * @param copyId 流程抄送主键 + * @return 流程抄送 + */ + BpmProcessInstanceCopyVO queryById(Long copyId); + + // TODO 芋艿:这块要 review 下;思考下~~ + /** + * 抄送 + * @param sourceInfo 抄送源信息,方便抄送处理 + * @return + */ + boolean makeCopy(BpmCandidateSourceInfo sourceInfo); + + // TODO @kyle:可以方法名改成 createProcessInstanceCopy;现在项目一般新增都用 create 为主; + /** + * 流程实例的抄送 + * + * @param userId 当前登录用户 + * @param createReqVO 创建的抄送请求 + * @return 是否抄送成功,抄送成功则返回true TODO @kyle:这里可以不用返回哈;目前一般是失败,就抛出业务异常; + */ + boolean ccProcessInstance(Long userId, BpmProcessInstanceCCReqVO createReqVO); + + /** + * 抄送的流程 + * @param loginUserId 登录用户id + * @param pageReqVO 分页请求 + * @return 抄送的分页结果 + */ + PageResult getMyProcessInstanceCCPage(Long loginUserId, + BpmProcessInstanceCCMyPageReqVO pageReqVO); +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceImpl.java new file mode 100644 index 000000000..edc5f7f0a --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceImpl.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.bpm.service.cc; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCMyPageReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCPageItemRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCCReqVO; +import cn.iocoder.yudao.module.bpm.convert.cc.BpmProcessInstanceCopyConvert; +import cn.iocoder.yudao.module.bpm.dal.dataobject.cc.BpmProcessInstanceCopyDO; +import cn.iocoder.yudao.module.bpm.dal.mysql.cc.BpmProcessInstanceCopyMapper; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessorChain; +import cn.iocoder.yudao.module.bpm.service.cc.dto.BpmDelegateExecutionDTO; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; +import cn.iocoder.yudao.module.bpm.util.FlowableUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +// TODO @kyle:类注释要写下 +@Slf4j // TODO @kyle:按照 @Service、@Validated、@Slf4j,从重要到不重要的顺序; +@Service +@Validated +public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopyService { + @Resource // TODO @kyle:第一个变量,和类之间要有空行; + private BpmProcessInstanceCopyMapper processInstanceCopyMapper; + + /** + * 和flowable有关的,查询流程名用的 TODO @kyle:可以不写哈注释; + */ + @Resource + private RuntimeService runtimeService; + + /** + * 找抄送人用的 TODO @kyle:可以不写哈注释; + */ + @Resource + private BpmCandidateSourceInfoProcessorChain processorChain; + + // TODO @kyle:多余的变量,可以去掉哈 + @Resource + @Lazy // 解决循环依赖 + private BpmTaskService bpmTaskService; + @Resource + @Lazy // 解决循环依赖 + private BpmProcessInstanceService bpmProcessInstanceService; + @Resource + private AdminUserApi adminUserApi; + + @Override + public BpmProcessInstanceCopyVO queryById(Long copyId) { + BpmProcessInstanceCopyDO bpmProcessInstanceCopyDO = processInstanceCopyMapper.selectById(copyId); + return BpmProcessInstanceCopyConvert.INSTANCE.convert(bpmProcessInstanceCopyDO); + } + + // TODO @kyle:makeCopy 和 ccProcessInstance 的调用关系,感受上反了; + // makeCopy 有点像基于规则,查找抄送人,然后创建; + // ccProcessInstance 是已经有了抄送人,然后创建; + // 建议的改造:独立基于 processInstanceCopyMapper 做 insert + @Override + public boolean makeCopy(BpmCandidateSourceInfo sourceInfo) { + if (null == sourceInfo) { + return false; + } + + DelegateExecution executionEntity = new BpmDelegateExecutionDTO(sourceInfo.getProcessInstanceId()); + Set ccCandidates = processorChain.calculateTaskCandidateUsers(executionEntity, sourceInfo); + if (CollUtil.isEmpty(ccCandidates)) { + log.warn("相关抄送人不存在 {}", sourceInfo.getTaskId()); + return false; + } else { + BpmProcessInstanceCopyDO copyDO = new BpmProcessInstanceCopyDO(); + // 调用 + // 设置任务id + copyDO.setTaskId(sourceInfo.getTaskId()); + copyDO.setTaskName(FlowableUtils.getTaskNameByTaskId(sourceInfo.getTaskId())); + copyDO.setProcessInstanceId(sourceInfo.getProcessInstanceId()); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(sourceInfo.getProcessInstanceId()) + .singleResult(); + if (null == processInstance) { + log.warn("相关流程实例不存在 {}", sourceInfo.getTaskId()); + return false; + } + copyDO.setStartUserId(FlowableUtils.getStartUserIdFromProcessInstance(processInstance)); + copyDO.setProcessInstanceName(processInstance.getName()); + ProcessDefinition processDefinition = FlowableUtils.getProcessDefinition(processInstance.getProcessDefinitionId()); + copyDO.setProcessDefinitionCategory(processDefinition.getCategory()); + copyDO.setReason(sourceInfo.getReason()); + copyDO.setCreator(sourceInfo.getCreator()); + copyDO.setCreateTime(LocalDateTime.now()); + List copyList = new ArrayList<>(ccCandidates.size()); + for (Long userId : ccCandidates) { + BpmProcessInstanceCopyDO copy = BpmProcessInstanceCopyConvert.INSTANCE.copy(copyDO); + copy.setUserId(userId); + copyList.add(copy); + } + return processInstanceCopyMapper.insertBatch(copyList); + } + } + + @Override + public boolean ccProcessInstance(Long userId, BpmProcessInstanceCCReqVO reqVO) { + // 在能正常审批的情况下抄送流程 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.setTaskId(reqVO.getTaskKey()); + sourceInfo.setProcessInstanceId(reqVO.getProcessInstanceKey()); + sourceInfo.addRule(reqVO); + sourceInfo.setCreator(String.valueOf(userId)); + sourceInfo.setReason(reqVO.getReason()); + if (!makeCopy(sourceInfo)) { + throw new RuntimeException("抄送任务失败"); + } + return false; + } + + //获取流程抄送分页 TODO @kyle:接口已经注释,这里不用注释了哈; + @Override + public PageResult getMyProcessInstanceCCPage(Long loginUserId, BpmProcessInstanceCCMyPageReqVO pageReqVO) { + // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 + // TODO @kyle:一般读逻辑,Service 返回 PageResult 即可。关联数据的查询和拼接,交给 Controller;目的是:保证 Service 聚焦写逻辑,清晰简洁; + PageResult pageResult = processInstanceCopyMapper.selectPage(loginUserId, pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return new PageResult<>(pageResult.getTotal()); + } + + // TODO @kyle:这种可以简洁点;参考如下 +// Map processInstanceMap = bpmProcessInstanceService.getProcessInstanceMap( +// convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); + + Set taskIds = new HashSet<>(); + Set processInstaneIds = new HashSet<>(); + Set userIds = new HashSet<>(); + for (BpmProcessInstanceCopyDO doItem : pageResult.getList()) { + taskIds.add(doItem.getTaskId()); + processInstaneIds.add(doItem.getProcessInstanceId()); + userIds.add(doItem.getStartUserId()); + Long userId = Long.valueOf(doItem.getCreator()); + userIds.add(userId); + } + + Map taskNameByTaskIds = FlowableUtils.getTaskNameByTaskIds(taskIds); + Map processInstanceNameByTaskIds = FlowableUtils.getProcessInstanceNameByTaskIds(processInstaneIds); + + Map userMap = adminUserApi.getUserList(userIds).stream().collect(Collectors.toMap( + AdminUserRespDTO::getId, AdminUserRespDTO::getNickname)); + + // 转换返回 + return BpmProcessInstanceCopyConvert.INSTANCE.convertPage(pageResult, taskNameByTaskIds, processInstanceNameByTaskIds, userMap); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyVO.java new file mode 100644 index 000000000..881ec6fd3 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyVO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.bpm.service.cc; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @kyle:看看是不是要删除 +/** + * 流程抄送视图对象 wf_copy + * + * @author ruoyi + * @date 2022-05-19 + */ +@Data +public class BpmProcessInstanceCopyVO { + + /** + * 编号 + */ + @Schema(description = "抄送主键") + private Long id; + + /** + * 发起人Id + */ + @Schema(description = "发起人Id") + private Long startUserId; + + @Schema(description = "发起人别名") + private String startUserNickname; + + /** + * 流程主键 + */ + @Schema(description = "流程实例的主键") + private String processInstanceId; + + @Schema(description = "流程实例的名字") + private String processInstanceName; + + /** + * 任务主键 + */ + @Schema(description = "发起抄送的任务编号") + private String taskId; + + @Schema(description = "发起抄送的任务名称") + private String taskName; + /** + * 用户主键 + */ + @Schema(description = "用户编号") + private Long userId; + + @Schema(description = "用户别名") + private Long userNickname; + + @Schema(description = "抄送原因") + private String reason; + + @Schema(description = "抄送人") + private String creator; + + @Schema(description = "抄送时间") + private LocalDateTime createTime; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/dto/BpmDelegateExecutionDTO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/dto/BpmDelegateExecutionDTO.java new file mode 100644 index 000000000..47213ae52 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/cc/dto/BpmDelegateExecutionDTO.java @@ -0,0 +1,439 @@ +package cn.iocoder.yudao.module.bpm.service.cc.dto; + +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowableListener; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ReadOnlyDelegateExecution; +import org.flowable.variable.api.persistence.entity.VariableInstance; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 仅为了传输processInstanceId + */ +public class BpmDelegateExecutionDTO implements DelegateExecution { + + public BpmDelegateExecutionDTO(String getProcessInstanceId) { + this.getProcessInstanceId = getProcessInstanceId; + } + + private final String getProcessInstanceId; + + @Override + public String getId() { + return null; + } + + @Override + public String getProcessInstanceId() { + return null; + } + + @Override + public String getRootProcessInstanceId() { + return null; + } + + @Override + public String getEventName() { + return null; + } + + @Override + public void setEventName(String eventName) { + + } + + @Override + public String getProcessInstanceBusinessKey() { + return null; + } + + @Override + public String getProcessInstanceBusinessStatus() { + return null; + } + + @Override + public String getProcessDefinitionId() { + return null; + } + + @Override + public String getPropagatedStageInstanceId() { + return null; + } + + @Override + public String getParentId() { + return null; + } + + @Override + public String getSuperExecutionId() { + return null; + } + + @Override + public String getCurrentActivityId() { + return null; + } + + @Override + public String getTenantId() { + return null; + } + + @Override + public FlowElement getCurrentFlowElement() { + return null; + } + + @Override + public void setCurrentFlowElement(FlowElement flowElement) { + + } + + @Override + public FlowableListener getCurrentFlowableListener() { + return null; + } + + @Override + public void setCurrentFlowableListener(FlowableListener currentListener) { + + } + + @Override + public ReadOnlyDelegateExecution snapshotReadOnly() { + return null; + } + + @Override + public DelegateExecution getParent() { + return null; + } + + @Override + public List getExecutions() { + return null; + } + + @Override + public void setActive(boolean isActive) { + + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public boolean isEnded() { + return false; + } + + @Override + public void setConcurrent(boolean isConcurrent) { + + } + + @Override + public boolean isConcurrent() { + return false; + } + + @Override + public boolean isProcessInstanceType() { + return false; + } + + @Override + public void inactivate() { + + } + + @Override + public boolean isScope() { + return false; + } + + @Override + public void setScope(boolean isScope) { + + } + + @Override + public boolean isMultiInstanceRoot() { + return false; + } + + @Override + public void setMultiInstanceRoot(boolean isMultiInstanceRoot) { + + } + + @Override + public Map getVariables() { + return null; + } + + @Override + public Map getVariableInstances() { + return null; + } + + @Override + public Map getVariables(Collection collection) { + return null; + } + + @Override + public Map getVariableInstances(Collection collection) { + return null; + } + + @Override + public Map getVariables(Collection collection, boolean b) { + return null; + } + + @Override + public Map getVariableInstances(Collection collection, boolean b) { + return null; + } + + @Override + public Map getVariablesLocal() { + return null; + } + + @Override + public Map getVariableInstancesLocal() { + return null; + } + + @Override + public Map getVariablesLocal(Collection collection) { + return null; + } + + @Override + public Map getVariableInstancesLocal(Collection collection) { + return null; + } + + @Override + public Map getVariablesLocal(Collection collection, boolean b) { + return null; + } + + @Override + public Map getVariableInstancesLocal(Collection collection, boolean b) { + return null; + } + + @Override + public Object getVariable(String s) { + return null; + } + + @Override + public VariableInstance getVariableInstance(String s) { + return null; + } + + @Override + public Object getVariable(String s, boolean b) { + return null; + } + + @Override + public VariableInstance getVariableInstance(String s, boolean b) { + return null; + } + + @Override + public Object getVariableLocal(String s) { + return null; + } + + @Override + public VariableInstance getVariableInstanceLocal(String s) { + return null; + } + + @Override + public Object getVariableLocal(String s, boolean b) { + return null; + } + + @Override + public VariableInstance getVariableInstanceLocal(String s, boolean b) { + return null; + } + + @Override + public T getVariable(String s, Class aClass) { + return null; + } + + @Override + public T getVariableLocal(String s, Class aClass) { + return null; + } + + @Override + public Set getVariableNames() { + return null; + } + + @Override + public Set getVariableNamesLocal() { + return null; + } + + @Override + public void setVariable(String s, Object o) { + + } + + @Override + public void setVariable(String s, Object o, boolean b) { + + } + + @Override + public Object setVariableLocal(String s, Object o) { + return null; + } + + @Override + public Object setVariableLocal(String s, Object o, boolean b) { + return null; + } + + @Override + public void setVariables(Map map) { + + } + + @Override + public void setVariablesLocal(Map map) { + + } + + @Override + public boolean hasVariables() { + return false; + } + + @Override + public boolean hasVariablesLocal() { + return false; + } + + @Override + public boolean hasVariable(String s) { + return false; + } + + @Override + public boolean hasVariableLocal(String s) { + return false; + } + + @Override + public void removeVariable(String s) { + + } + + @Override + public void removeVariableLocal(String s) { + + } + + @Override + public void removeVariables(Collection collection) { + + } + + @Override + public void removeVariablesLocal(Collection collection) { + + } + + @Override + public void removeVariables() { + + } + + @Override + public void removeVariablesLocal() { + + } + + @Override + public void setTransientVariable(String s, Object o) { + + } + + @Override + public void setTransientVariableLocal(String s, Object o) { + + } + + @Override + public void setTransientVariables(Map map) { + + } + + @Override + public Object getTransientVariable(String s) { + return null; + } + + @Override + public Map getTransientVariables() { + return null; + } + + @Override + public void setTransientVariablesLocal(Map map) { + + } + + @Override + public Object getTransientVariableLocal(String s) { + return null; + } + + @Override + public Map getTransientVariablesLocal() { + return null; + } + + @Override + public void removeTransientVariableLocal(String s) { + + } + + @Override + public void removeTransientVariable(String s) { + + } + + @Override + public void removeTransientVariables() { + + } + + @Override + public void removeTransientVariablesLocal() { + + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index a0f938e67..27a4c7763 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -19,6 +19,8 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskAddSignTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.cc.BpmProcessInstanceCopyService; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -94,6 +96,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Resource private ManagementService managementService; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + @Override public PageResult getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { // 查询待办任务 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/util/FlowableUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/util/FlowableUtils.java new file mode 100644 index 000000000..0217ea382 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/util/FlowableUtils.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.bpm.util; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; + +import java.util.*; + +/** + * 流程引擎工具类封装 + * + * @author: linjinp + * @create: 2019-12-24 13:51 + **/ +public class FlowableUtils { + + /** + * 获取流程名称 + * + * @param processDefinitionId + * @return + */ + public static String getProcessDefinitionName(String processDefinitionId) { + RepositoryService repositoryService = SpringUtil.getBean(RepositoryService.class); + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId); + return processDefinition.getName(); + } + + public static ProcessDefinition getProcessDefinition(String processDefinitionId) { + RepositoryService repositoryService = SpringUtil.getBean(RepositoryService.class); + return repositoryService.getProcessDefinition(processDefinitionId); + } + + /** + * 获取节点数据 + * + * @param processInstanceId + * @param nodeId + * @return + */ + public static FlowNode getFlowNode(String processInstanceId, String nodeId) { + + RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class); + RepositoryService repositoryService = SpringUtil.getBean(RepositoryService.class); + + String definitionld = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId(); // 获取bpm(模型)对象 + BpmnModel bpmnModel = repositoryService.getBpmnModel(definitionld); + // 传节点定义key获取当前节点 + FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(nodeId); + return flowNode; + } + + public static ExtensionElement generateFlowNodeIdExtension(String nodeId) { + ExtensionElement extensionElement = new ExtensionElement(); + extensionElement.setElementText(nodeId); + extensionElement.setName("nodeId"); + extensionElement.setNamespacePrefix("flowable"); + extensionElement.setNamespace("nodeId"); + return extensionElement; + } + + public static String getNodeIdFromExtension(FlowElement flowElement) { + Map> extensionElements = flowElement.getExtensionElements(); + return extensionElements.get("nodeId").get(0).getElementText(); + } + + public static Long getStartUserIdFromProcessInstance(ProcessInstance instance) { + if (null == instance) { + return null; + } + return NumberUtils.parseLong(instance.getStartUserId()); + } + + public static String getTaskNameByTaskId(String taskId) { + TaskService taskService = SpringUtil.getBean(TaskService.class); + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + return task.getName(); + } + + // TODO @kyle:Utils 里不做查询;可以封装到 bpmTaskService 里 + public static Map getTaskNameByTaskIds(Collection taskIds) { + TaskService taskService = SpringUtil.getBean(TaskService.class); + List tasks = taskService.createTaskQuery().taskIds(taskIds).list(); + if (CollUtil.isNotEmpty(tasks)) { + Map taskMap = new HashMap<>(tasks.size()); + for (Task task : tasks) { + taskMap.putIfAbsent(task.getId(), task.getName()); + } + return taskMap; + } + return Collections.emptyMap(); + } + + public static String getProcessInstanceNameByTaskId(String processInstanceId) { + RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + return processInstance.getName(); + } + + // TODO @kyle:Utils 里不做查询;可以封装到 bpmTaskService 里 + public static Map getProcessInstanceNameByTaskIds(Set taskIds) { + RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class); + List processInstances = runtimeService.createProcessInstanceQuery().processInstanceIds(taskIds).list(); + if (CollUtil.isNotEmpty(processInstances)) { + Map processInstaneMap = new HashMap<>(processInstances.size()); + for (ProcessInstance processInstance : processInstances) { + processInstaneMap.putIfAbsent(processInstance.getId(), processInstance.getName()); + } + return processInstaneMap; + } + return Collections.emptyMap(); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java new file mode 100644 index 000000000..38eb7cbea --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java @@ -0,0 +1,242 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateRuleVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl.BpmTaskAssignLeaderX1Script; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl.BpmTaskAssignLeaderX2Script; +import cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor.BpmCandidateScriptApiSourceInfoProcessor; +import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; +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.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.permission.RoleApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@Import({BpmCandidateSourceInfoProcessorChain.class, + BpmCandidateScriptApiSourceInfoProcessor.class, BpmTaskAssignLeaderX1Script.class, + BpmTaskAssignLeaderX2Script.class}) +public class BpmCandidateSourceInfoProcessorChainTest extends BaseDbUnitTest { + @Resource + private BpmCandidateSourceInfoProcessorChain processorChain; + + @MockBean + private BpmUserGroupService userGroupService; + @MockBean + private DeptApi deptApi; + @MockBean + private AdminUserApi adminUserApi; + @MockBean + private PermissionApi permissionApi; + @MockBean + private RoleApi roleApi; + @MockBean + private PostApi postApi; + @MockBean + private DictDataApi dictDataApi; + @Resource + private BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor; + + @Test + public void testCalculateTaskCandidateUsers_Role() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.ROLE.getType()); + // mock 方法 + when(permissionApi.getUserRoleIdListByRoleIds(eq(rule.getOptions()))) + .thenReturn(asSet(11L, 22L)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_DeptMember() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType()); + // mock 方法 + List users = CollectionUtils.convertList(asSet(11L, 22L), + id -> new AdminUserRespDTO().setId(id)); + when(adminUserApi.getUserListByDeptIds(eq(rule.getOptions()))).thenReturn(users); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_DeptLeader() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType()); + // mock 方法 + DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L)); + DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L)); + when(deptApi.getDeptList(eq(rule.getOptions()))).thenReturn(Arrays.asList(dept1, dept2)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_Post() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.POST.getType()); + // mock 方法 + List users = CollectionUtils.convertList(asSet(11L, 22L), + id -> new AdminUserRespDTO().setId(id)); + when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(users); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_User() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.USER.getType()); + // mock 方法 + mockGetUserMap(asSet(1L, 2L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(1L, 2L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_UserGroup() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType()); + // mock 方法 + BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setMemberUserIds(asSet(11L, 12L))); + BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setMemberUserIds(asSet(21L, 22L))); + when(userGroupService.getUserGroupList(eq(rule.getOptions()))).thenReturn(Arrays.asList(userGroup1, userGroup2)); + mockGetUserMap(asSet(11L, 12L, 21L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 12L, 21L, 22L), results); + } + + private void mockGetUserMap(Set assigneeUserIds) { + Map userMap = CollectionUtils.convertMap(assigneeUserIds, id -> id, + id -> new AdminUserRespDTO().setId(id).setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap); + } + + @Test + public void testCalculateTaskCandidateUsers_Script() { + // 准备参数 + BpmTaskCandidateRuleVO rule = new BpmTaskCandidateRuleVO().setOptions(asSet(20L, 21L)) + .setType(BpmTaskAssignRuleTypeEnum.SCRIPT.getType()); + // mock 方法 + BpmTaskAssignScript script1 = new BpmTaskAssignScript() { + + @Override + public Set calculateTaskCandidateUsers(DelegateExecution task) { + return singleton(11L); + } + + @Override + public BpmTaskRuleScriptEnum getEnum() { + return BpmTaskRuleScriptEnum.LEADER_X1; + } + }; + BpmTaskAssignScript script2 = new BpmTaskAssignScript() { + + @Override + public Set calculateTaskCandidateUsers(DelegateExecution task) { + return singleton(22L); + } + + @Override + public BpmTaskRuleScriptEnum getEnum() { + return BpmTaskRuleScriptEnum.LEADER_X2; + } + }; + bpmCandidateScriptApiSourceInfoProcessor.setScripts(Arrays.asList(script1, script2)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testRemoveDisableUsers() { + // 准备参数. 1L 可以找到;2L 是禁用的;3L 找不到 + Set assigneeUserIds = asSet(1L, 2L, 3L); + // mock 方法 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap); + + // 调用 + processorChain.removeDisableUsers(assigneeUserIds); + // 断言 + assertEquals(asSet(1L), assigneeUserIds); + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceTest.java new file mode 100644 index 000000000..1f0ced103 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/cc/BpmProcessInstanceCopyServiceTest.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.bpm.service.cc; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +@Import({BpmProcessInstanceCopyServiceImpl.class}) +class BpmProcessInstanceCopyServiceTest extends BaseDbUnitTest { + @Resource + private BpmProcessInstanceCopyServiceImpl service; + + @Test + void queryById() { + } +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java index 76a4d872c..22bb0b426 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java @@ -13,5 +13,6 @@ public interface DictTypeConstants { String CRM_AUDIT_STATUS = "crm_audit_status"; // CRM 审批状态 String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态 + String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式 } diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index a7494dc8d..616c8685c 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -14,9 +14,12 @@ public interface ErrorCodeConstants { // ========== 线索管理 1-020-001-000 ========== ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); + ErrorCode CLUE_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在"); + ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化"); // ========== 商机管理 1-020-002-000 ========== ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在"); + ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除"); // TODO @lilleo:商机状态、商机类型,都单独错误码段 @@ -24,6 +27,7 @@ public interface ErrorCodeConstants { // ========== 联系人管理 1-020-003-000 ========== ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode( 1_020_003_001, "联系人商机关联不存在"); + ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode( 1_020_003_002, "联系人已关联合同,不能删除"); // ========== 回款 1-020-004-000 ========== ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); @@ -39,6 +43,11 @@ public interface ErrorCodeConstants { ErrorCode CUSTOMER_IN_POOL = new ErrorCode(1_020_006_004, "客户【{}】放入公海失败,原因:已经是公海客户"); ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定"); ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常"); + ErrorCode CUSTOMER_LOCK_FAIL_IS_LOCK = new ErrorCode(1_020_006_007, "锁定客户失败,它已经处于锁定状态"); + ErrorCode CUSTOMER_UNLOCK_FAIL_IS_UNLOCK = new ErrorCode(1_020_006_008, "解锁客户失败,它已经处于未锁定状态"); + ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限"); + ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限"); + ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}"); // ========== 权限管理 1_020_007_000 ========== ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); @@ -49,6 +58,7 @@ public interface ErrorCodeConstants { ErrorCode CRM_PERMISSION_DELETE_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_005, "删除数据权限失败,原因:存在负责人"); ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限"); ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); + ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); // ========== 产品 1_020_008_000 ========== ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); @@ -72,4 +82,8 @@ public interface ErrorCodeConstants { // ========== 客户公海规则设置 1_020_012_000 ========== ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_000, "客户限制配置不存在"); + // ========== 跟进记录 1_020_013_000 ========== + ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); + ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); + } diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java new file mode 100644 index 000000000..223994725 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java @@ -0,0 +1,125 @@ +package cn.iocoder.yudao.module.crm.enums; + +/** + * CRM 操作日志枚举 + * 目的:统一管理,也减少 Service 里各种“复杂”字符串 + * + * @author HUIHUI + */ +public interface LogRecordConstants { + + // ======================= CRM_LEADS 线索 ======================= + + String CRM_LEADS_TYPE = "CRM 线索"; + + // ======================= CRM_CUSTOMER 客户 ======================= + + String CRM_CUSTOMER_TYPE = "CRM 客户"; + String CRM_CUSTOMER_CREATE_SUB_TYPE = "创建客户"; + String CRM_CUSTOMER_CREATE_SUCCESS = "创建了客户{{#customer.name}}"; + String CRM_CUSTOMER_UPDATE_SUB_TYPE = "更新客户"; + String CRM_CUSTOMER_UPDATE_SUCCESS = "更新了客户【{{#customerName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CUSTOMER_DELETE_SUB_TYPE = "删除客户"; + String CRM_CUSTOMER_DELETE_SUCCESS = "删除了客户【{{#customerName}}】"; + String CRM_CUSTOMER_TRANSFER_SUB_TYPE = "转移客户"; + String CRM_CUSTOMER_TRANSFER_SUCCESS = "将客户【{{#customer.name}}】的负责人从【{getAdminUserById{#customer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_CUSTOMER_LOCK_SUB_TYPE = "{{#customer.lockStatus ? '解锁客户' : '锁定客户'}}"; + String CRM_CUSTOMER_LOCK_SUCCESS = "{{#customer.lockStatus ? '将客户【' + #customer.name + '】解锁' : '将客户【' + #customer.name + '】锁定'}}"; + String CRM_CUSTOMER_POOL_SUB_TYPE = "客户放入公海"; + String CRM_CUSTOMER_POOL_SUCCESS = "将客户【{{#customerName}}】放入了公海"; + String CRM_CUSTOMER_RECEIVE_SUB_TYPE = "{{#ownerUserName != null ? '分配客户' : '领取客户'}}"; + String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}"; + + // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= + + String CRM_CUSTOMER_LIMIT_CONFIG_TYPE = "CRM 客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUB_TYPE = "创建客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUCCESS = "创建了【{{#limitType}}】类型的客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUB_TYPE = "更新客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUCCESS = "更新了客户限制配置: {_DIFF{#updateReqVO}}"; + String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUB_TYPE = "删除客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS = "删除了【{{#limitType}}】类型的客户限制配置"; + + // ======================= CRM_CUSTOMER_POOL_CONFIG 客户公海规则 ======================= + + String CRM_CUSTOMER_POOL_CONFIG_TYPE = "CRM 客户公海规则"; + String CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新客户公海规则' : '创建客户公海规则'}}"; + String CRM_CUSTOMER_POOL_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了客户公海规则' : '创建了客户公海规则'}}"; + + // ======================= CRM_CONTACT 联系人 ======================= + + String CRM_CONTACT_TYPE = "CRM 联系人"; + String CRM_CONTACT_CREATE_SUB_TYPE = "创建联系人"; + String CRM_CONTACT_CREATE_SUCCESS = "创建了联系人{{#contact.name}}"; + String CRM_CONTACT_UPDATE_SUB_TYPE = "更新联系人"; + String CRM_CONTACT_UPDATE_SUCCESS = "更新了联系人【{{#contactName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CONTACT_DELETE_SUB_TYPE = "删除联系人"; + String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】"; + String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人"; + String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + + // ======================= CRM_BUSINESS 商机 ======================= + + String CRM_BUSINESS_TYPE = "CRM 商机"; + String CRM_BUSINESS_CREATE_SUB_TYPE = "创建商机"; + String CRM_BUSINESS_CREATE_SUCCESS = "创建了商机{{#business.name}}"; + String CRM_BUSINESS_UPDATE_SUB_TYPE = "更新商机"; + String CRM_BUSINESS_UPDATE_SUCCESS = "更新了商机【{{#businessName}}】: {_DIFF{#updateReqVO}}"; + String CRM_BUSINESS_DELETE_SUB_TYPE = "删除商机"; + String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】"; + String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机"; + String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + + // ======================= CRM_CONTRACT 合同 ======================= + + String CRM_CONTRACT_TYPE = "CRM 合同"; + String CRM_CONTRACT_CREATE_SUB_TYPE = "创建合同"; + String CRM_CONTRACT_CREATE_SUCCESS = "创建了合同{{#contract.name}}"; + String CRM_CONTRACT_UPDATE_SUB_TYPE = "更新合同"; + String CRM_CONTRACT_UPDATE_SUCCESS = "更新了合同【{{#contractName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CONTRACT_DELETE_SUB_TYPE = "删除合同"; + String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】"; + String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同"; + String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + + // ======================= CRM_PRODUCT 产品 ======================= + + String CRM_PRODUCT_TYPE = "CRM 产品"; + String CRM_PRODUCT_CREATE_SUB_TYPE = "创建产品"; + String CRM_PRODUCT_CREATE_SUCCESS = "创建了产品【{{#createReqVO.name}}】"; + String CRM_PRODUCT_UPDATE_SUB_TYPE = "更新产品"; + String CRM_PRODUCT_UPDATE_SUCCESS = "更新了产品【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}"; + String CRM_PRODUCT_DELETE_SUB_TYPE = "删除产品"; + String CRM_PRODUCT_DELETE_SUCCESS = "删除了产品【{{#product.name}}】"; + + // ======================= CRM_PRODUCT_CATEGORY 产品分类 ======================= + + String CRM_PRODUCT_CATEGORY_TYPE = "CRM 产品分类"; + String CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE = "创建产品分类"; + String CRM_PRODUCT_CATEGORY_CREATE_SUCCESS = "创建了产品分类【{{#createReqVO.name}}】"; + String CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE = "更新产品分类"; + String CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS = "更新了产品分类【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}"; + String CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE = "删除产品分类"; + String CRM_PRODUCT_CATEGORY_DELETE_SUCCESS = "删除了产品分类【{{#productCategory.name}}】"; + + // ======================= CRM_RECEIVABLE 回款 ======================= + + String CRM_RECEIVABLE_TYPE = "CRM 回款"; + String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款"; + String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款"; + String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; + String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; + String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + + // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= + + String CRM_RECEIVABLE_PLAN_TYPE = "CRM 回款计划"; + String CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE = "创建回款计划"; + String CRM_RECEIVABLE_PLAN_CREATE_SUCCESS = "创建了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划"; + String CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE = "更新回款计划"; + String CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划: {_DIFF{#updateReqVO}}"; + String CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE = "删除回款计划"; + String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "删除了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划"; + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java new file mode 100644 index 000000000..55548dbff --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.crm.enums.business; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +// TODO @lzxhqs:1)title、description、create 可以删除,非标准的 javadoc 注释哈,然后可以在类上加下这个类的注释;2)CrmBizEndStatus 改成 CrmBusinessEndStatus,非必要不缩写哈,可阅读比较重要 +/** + * @author lzxhqs + * @version 1.0 + * @title CrmBizEndStatus + * @description + * @create 2024/1/12 + */ +@RequiredArgsConstructor +@Getter +public enum CrmBizEndStatus implements IntArrayValuable { + + WIN(1, "赢单"), + LOSE(2, "输单"), + INVALID(3, "无效"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizEndStatus::getStatus).toArray(); + + // TODO @lzxhqs:这里的方法,建议放到 49 行之后;一般类里是,静态变量,普通变量;静态方法;普通方法 + public static boolean isWin(Integer status) { + return ObjectUtil.equal(WIN.getStatus(), status); + } + + public static boolean isLose(Integer status) { + return ObjectUtil.equal(LOSE.getStatus(), status); + } + + public static boolean isInvalid(Integer status) { + return ObjectUtil.equal(INVALID.getStatus(), status); + } + + /** + * 场景类型 + */ + private final Integer status; + /** + * 场景名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java index 0a441d229..f0784cab2 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java @@ -22,7 +22,9 @@ public enum CrmBizTypeEnum implements IntArrayValuable { CRM_CONTACT(3, "联系人"), CRM_BUSINESS(4, "商机"), CRM_CONTRACT(5, "合同"), - CRM_PRODUCT(6, "产品") + CRM_PRODUCT(6, "产品"), + CRM_RECEIVABLE(7, "回款"), + CRM_RECEIVABLE_PLAN(8, "回款计划") ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizTypeEnum::getType).toArray(); diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java similarity index 72% rename from yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneEnum.java rename to yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java index a8d892a90..945d7c6a3 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneEnum.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java @@ -7,7 +7,6 @@ import lombok.Getter; import java.util.Arrays; -// TODO @puhui999:这个枚举,要不改成 CrmSceneTypeEnum /** * CRM 列表检索场景 * @@ -15,14 +14,13 @@ import java.util.Arrays; */ @Getter @AllArgsConstructor -public enum CrmSceneEnum implements IntArrayValuable { +public enum CrmSceneTypeEnum implements IntArrayValuable { OWNER(1, "我负责的"), - FOLLOW(2, "我关注的"), - // TODO @puhui999:还有一个我参与的 + INVOLVED(2, "我参与的"), SUBORDINATE(3, "下属负责的"); - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmSceneEnum::getType).toArray(); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmSceneTypeEnum::getType).toArray(); /** * 场景类型 @@ -37,8 +35,8 @@ public enum CrmSceneEnum implements IntArrayValuable { return ObjUtil.equal(OWNER.getType(), type); } - public static boolean isFollow(Integer type) { - return ObjUtil.equal(FOLLOW.getType(), type); + public static boolean isInvolved(Integer type) { + return ObjUtil.equal(INVOLVED.getType(), type); } public static boolean isSubordinate(Integer type) { diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java index 6300dee0e..2cf8d7811 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.crm.enums.customer; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; @@ -18,24 +20,30 @@ public enum CrmCustomerLimitConfigTypeEnum implements IntArrayValuable { /** * 拥有客户数限制 */ - CUSTOMER_QUANTITY_LIMIT(1, "拥有客户数限制"), + CUSTOMER_OWNER_LIMIT(1, "拥有客户数限制"), /** * 锁定客户数限制 */ CUSTOMER_LOCK_LIMIT(2, "锁定客户数限制"), ; - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getCode).toArray(); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getType).toArray(); /** * 状态 */ - private final Integer code; + private final Integer type; /** * 状态名 */ private final String name; + public static String getNameByType(Integer type) { + CrmCustomerLimitConfigTypeEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmCustomerLimitConfigTypeEnum.values()), + item -> ObjUtil.equal(item.type, type)); + return typeEnum == null ? null : typeEnum.getName(); + } + @Override public int[] array() { return ARRAYS; diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/message/CrmContactStatusEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/message/CrmContactStatusEnum.java new file mode 100644 index 000000000..6ca5f52dc --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/message/CrmContactStatusEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.enums.message; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * CRM 联系状态 + * + * @author dhb52 + */ +@RequiredArgsConstructor +@Getter +public enum CrmContactStatusEnum implements IntArrayValuable { + + NEEDED_TODAY(1, "今日需联系"), + EXPIRED(2, "已逾期"), + ALREADY_CONTACT(3, "已联系"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmContactStatusEnum::getType).toArray(); + + /** + * 状态 + */ + private final Integer type; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java new file mode 100644 index 000000000..c9a51057b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Crm 数据权限角色枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmPermissionRoleCodeEnum { + + CRM_ADMIN("crm_admin", "CRM 管理员"); + + /** + * 角色标识 + */ + private String code; + /** + * 角色名称 + */ + private String name; + +} + diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml index 15bbc932d..9e1a9e152 100644 --- a/yudao-module-crm/yudao-module-crm-biz/pom.xml +++ b/yudao-module-crm/yudao-module-crm-biz/pom.xml @@ -60,6 +60,10 @@ cn.iocoder.boot yudao-spring-boot-starter-excel + + cn.iocoder.boot + yudao-spring-boot-starter-biz-dict + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java index 119440378..fd2154d7a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java @@ -1,15 +1,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.business; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.business.*; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; 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.business.CrmBusinessStatusDO; @@ -22,22 +22,24 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; @Tag(name = "管理后台 - 商机") @RestController @@ -47,27 +49,24 @@ public class CrmBusinessController { @Resource private CrmBusinessService businessService; - @Resource private CrmCustomerService customerService; - @Resource private CrmBusinessStatusTypeService businessStatusTypeService; - @Resource private CrmBusinessStatusService businessStatusService; @PostMapping("/create") @Operation(summary = "创建商机") @PreAuthorize("@ss.hasPermission('crm:business:create')") - public CommonResult createBusiness(@Valid @RequestBody CrmBusinessCreateReqVO createReqVO) { + public CommonResult createBusiness(@Valid @RequestBody CrmBusinessSaveReqVO createReqVO) { return success(businessService.createBusiness(createReqVO, getLoginUserId())); } @PutMapping("/update") @Operation(summary = "更新商机") @PreAuthorize("@ss.hasPermission('crm:business:update')") - public CommonResult updateBusiness(@Valid @RequestBody CrmBusinessUpdateReqVO updateReqVO) { + public CommonResult updateBusiness(@Valid @RequestBody CrmBusinessSaveReqVO updateReqVO) { businessService.updateBusiness(updateReqVO); return success(true); } @@ -87,7 +86,26 @@ public class CrmBusinessController { @PreAuthorize("@ss.hasPermission('crm:business:query')") public CommonResult getBusiness(@RequestParam("id") Long id) { CrmBusinessDO business = businessService.getBusiness(id); - return success(CrmBusinessConvert.INSTANCE.convert(business)); + return success(BeanUtils.toBean(business, CrmBusinessRespVO.class)); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得商机列表") + @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { + return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class)); + } + + @GetMapping("/simple-all-list") + @Operation(summary = "获得联系人的精简列表") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getSimpleContactList() { + CrmBusinessPageReqVO reqVO = new CrmBusinessPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + PageResult pageResult = businessService.getBusinessPage(reqVO, getLoginUserId()); + return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段 + new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))); } @GetMapping("/page") @@ -95,60 +113,25 @@ public class CrmBusinessController { @PreAuthorize("@ss.hasPermission('crm:business:query')") public CommonResult> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) { PageResult pageResult = businessService.getBusinessPage(pageVO, getLoginUserId()); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty(pageResult.getTotal())); - } - // 处理客户名称回显 - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈; - Set customerIds = pageResult.getList().stream() - .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet()); - List customerList = customerService.getCustomerList(customerIds); - // 处理商机状态类型名称回显 - Set statusTypeIds = pageResult.getList().stream() - .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet()); - CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO(); - queryStatusTypeVO.setIdList(statusTypeIds); - List statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO); - // 处理商机状态名称回显 - Set statusIds = pageResult.getList().stream() - .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet()); - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); - queryVO.setIdList(statusIds); - List statusList = businessStatusService.selectList(queryVO); - return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList)); + return success(buildBusinessDetailPageResult(pageResult)); } @GetMapping("/page-by-customer") @Operation(summary = "获得商机分页,基于指定客户") - public CommonResult> getBusinessPageByCustomer(@Valid CrmContractPageReqVO pageReqVO) { - Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); - PageResult pageResult = businessService.getBusinessPageByCustomer(pageReqVO); - // 处理客户名称回显 - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈; - Set customerIds = pageResult.getList().stream() - .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet()); - List customerList = customerService.getCustomerList(customerIds); - // 处理商机状态类型名称回显 - Set statusTypeIds = pageResult.getList().stream() - .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet()); - CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO(); - queryStatusTypeVO.setIdList(statusTypeIds); - List statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO); - // 处理商机状态名称回显 - Set statusIds = pageResult.getList().stream() - .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet()); - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); - queryVO.setIdList(statusIds); - List statusList = businessStatusService.selectList(queryVO); - return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList)); + public CommonResult> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) { + if (pageReqVO.getCustomerId() == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + PageResult pageResult = businessService.getBusinessPageByCustomerId(pageReqVO); + return success(buildBusinessDetailPageResult(pageResult)); } - @GetMapping("/pool-page") - @Operation(summary = "获得商机公海分页") + @GetMapping("/page-by-contact") + @Operation(summary = "获得联系人的商机分页") @PreAuthorize("@ss.hasPermission('crm:business:query')") - public CommonResult> getBusinessPoolPage(@Valid CrmBusinessPageReqVO pageVO) { - // TODO puhui999: 等数据权限完善后再实现 - return null; + public CommonResult> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) { + PageResult pageResult = businessService.getBusinessPageByContact(pageReqVO); + return success(buildBusinessDetailPageResult(pageResult)); } @GetMapping("/export-excel") @@ -157,10 +140,30 @@ public class CrmBusinessController { @OperateLog(type = EXPORT) public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO, HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); PageResult pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId()); // 导出 Excel - ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class, - CrmBusinessConvert.INSTANCE.convertList02(pageResult.getList())); + ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class, + buildBusinessDetailPageResult(pageResult).getList()); + } + + /** + * 构建详细的商机分页结果 + * + * @param pageResult 简单的商机分页结果 + * @return 详细的商机分页结果 + */ + private PageResult buildBusinessDetailPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + List statusTypeList = businessStatusTypeService.getBusinessStatusTypeList( + convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId)); + List statusList = businessStatusService.getBusinessStatusList( + convertSet(pageResult.getList(), CrmBusinessDO::getStatusId)); + List customerList = customerService.getCustomerList( + convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId)); + return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList); } @PutMapping("/transfer") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java index cd4a60e81..231f66683 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; @@ -24,19 +25,17 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.Set; -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; @@ -85,13 +84,13 @@ public class CrmBusinessStatusTypeController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") public CommonResult getBusinessStatusType(@RequestParam("id") Long id) { - CrmBusinessStatusTypeDO businessStatusType = businessStatusTypeService.getBusinessStatusType(id); + CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id); // 处理状态回显 - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈; + // TODO @lzxhqs:可以在 businessStatusService 加个 getBusinessStatusListByTypeId 方法,直接返回 List 哈,常用的,尽量封装个简单易懂的方法,不用追求绝对通用哈; CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); queryVO.setTypeId(id); List statusList = businessStatusService.selectList(queryVO); - return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(businessStatusType, statusList)); + return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(statusType, statusList)); } @GetMapping("/page") @@ -100,12 +99,7 @@ public class CrmBusinessStatusTypeController { public CommonResult> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageReqVO) { PageResult pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO); // 处理部门回显 - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈; - Set deptIds = pageResult.getList().stream() - .map(CrmBusinessStatusTypeDO::getDeptIds) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + Set deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream); List deptList = deptApi.getDeptList(deptIds); return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList)); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessCreateReqVO.java deleted file mode 100644 index 1f01e76eb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessCreateReqVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; - -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:新建的时候,应该可以传递添加的产品; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java index c756b79cb..0e47bf5be 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -18,4 +20,11 @@ public class CrmBusinessPageReqVO extends PageParam { @Schema(description = "客户编号", example = "10795") private Long customerId; + @Schema(description = "联系人编号", example = "10795") + private Long contactId; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java index 53c8f45da..d3b6ab2fb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java @@ -1,18 +1,59 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import jakarta.validation.constraints.NotNull; +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 = "管理后台 - 商机 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmBusinessRespVO extends CrmBusinessBaseVO { +public class CrmBusinessRespVO { @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") private Long id; + @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; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java similarity index 53% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java index db0a342a6..672450c4a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java @@ -1,57 +1,85 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO; +import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; -import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; -/** - * 商机 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ +@Schema(description = "管理后台 - CRM 商机创建/更新 Request VO") @Data -public class CrmBusinessBaseVO { +public class CrmBusinessSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + private Long id; @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @DiffLogField(name = "商机名称") @NotNull(message = "商机名称不能为空") private String name; @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") + @DiffLogField(name = "商机状态") @NotNull(message = "商机状态类型不能为空") private Long statusTypeId; @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @DiffLogField(name = "商机状态") @NotNull(message = "商机状态不能为空") private Long statusId; @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactNextTime; @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) @NotNull(message = "客户不能为空") private Long customerId; @Schema(description = "预计成交日期") + @DiffLogField(name = "预计成交日期") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime dealTime; @Schema(description = "商机金额", example = "12371") + @DiffLogField(name = "商机金额") private Integer price; - // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题 + // TODO @lzxhqs:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题 @Schema(description = "整单折扣") + @DiffLogField(name = "整单折扣") private Integer discountPercent; @Schema(description = "产品总金额", example = "12025") + @DiffLogField(name = "产品总金额") private BigDecimal productPrice; @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") private String remark; + @Schema(description = "结束状态", example = "1") + @InEnum(CrmBizEndStatus.class) + private Integer endStatus; + + // TODO @lzxhqs:不设置默认 new ArrayList<>();一般 pojo 不设置默认值哈 + @Schema(description = "商机产品列表") + private List products = new ArrayList<>(); + + @Schema(description = "联系人编号", example = "110") + private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java index c76c4873f..a76c48cae 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java @@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - import jakarta.validation.constraints.NotNull; +import lombok.Data; @Schema(description = "管理后台 - 商机转移 Request VO") @Data public class CrmBusinessTransferReqVO { @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") - @NotNull(message = "联系人编号不能为空") + @NotNull(message = "商机编号不能为空") private Long id; /** diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateReqVO.java deleted file mode 100644 index 94b82fa5a..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; - -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessBaseVO; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.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:修改的时候,应该可以传递添加的产品; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java new file mode 100644 index 000000000..4804768a5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product; + +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; + +// TODO @lzxhqs:这个类,如果没用到,可以考虑删除哈 +@Schema(description = "管理后台 - 商机产品分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmBusinessProductPageReqVO extends PageParam { +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java new file mode 100644 index 000000000..d4996816f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商机产品关联 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmBusinessProductRespVO { +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java new file mode 100644 index 000000000..286f1a256 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - CRM 商机产品关联表 创建/更新 Request VO") +@Data +public class CrmBusinessProductSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + private Long id; + + // TODO @lzxhqs:这个字段,应该是 Long 类型 + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "商机编号不能为空") + private Integer businessId; + + // TODO @lzxhqs:这个字段,应该是 Long 类型 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "产品编号不能为空") + private Integer productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "产品单价不能为空") + private BigDecimal price; + + @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "销售价格不能为空") + private BigDecimal salesPrice; + + @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "数量不能为空") + private BigDecimal num; + + @Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "折扣不能为空") + private BigDecimal discount; + + @Schema(description = "小计(折扣后价格)", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "小计(折扣后价格)不能为空") + private BigDecimal subtotal; + + // TODO @lzxhqs:字符串,用 @NotEmpty,因为要考虑 "" 前端搞了这个玩意 + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "单位不能为空") + private String unit; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java index c5ecf52d6..3327b09f7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java @@ -21,11 +21,11 @@ public class CrmBusinessStatusSaveReqVO { @NotEmpty(message = "状态名不能为空") private String name; - // TODO @lilleo:percent 应该是 Integer; + // TODO @lzxhqs::percent 应该是 Integer; @Schema(description = "赢单率") private String percent; - // TODO @lilleo:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了; + // TODO @lzxhqs:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了; @Schema(description = "排序") private Integer sort; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java index 096b42e1f..23dc7742d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java @@ -19,11 +19,11 @@ public class CrmBusinessStatusTypeSaveReqVO { @NotEmpty(message = "状态类型名不能为空") private String name; + // TODO @lzxhqs: VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢 @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) private List deptIds = Lists.newArrayList(); - // TODO @ljlleo VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢 @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED) - private List statusList = Lists.newArrayList(); + private List statusList; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java index 759e46d13..f6d32f3a4 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java @@ -2,27 +2,29 @@ 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.common.util.object.BeanUtils; 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 jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.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.common.pojo.PageParam.PAGE_SIZE_NONE; 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 @@ -36,14 +38,14 @@ public class CrmClueController { @PostMapping("/create") @Operation(summary = "创建线索") @PreAuthorize("@ss.hasPermission('crm:clue:create')") - public CommonResult createClue(@Valid @RequestBody CrmClueCreateReqVO createReqVO) { + public CommonResult createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) { return success(clueService.createClue(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新线索") @PreAuthorize("@ss.hasPermission('crm:clue:update')") - public CommonResult updateClue(@Valid @RequestBody CrmClueUpdateReqVO updateReqVO) { + public CommonResult updateClue(@Valid @RequestBody CrmClueSaveReqVO updateReqVO) { clueService.updateClue(updateReqVO); return success(true); } @@ -63,27 +65,43 @@ public class CrmClueController { @PreAuthorize("@ss.hasPermission('crm:clue:query')") public CommonResult getClue(@RequestParam("id") Long id) { CrmClueDO clue = clueService.getClue(id); - return success(CrmClueConvert.INSTANCE.convert(clue)); + return success(BeanUtils.toBean(clue, CrmClueRespVO.class)); } @GetMapping("/page") @Operation(summary = "获得线索分页") @PreAuthorize("@ss.hasPermission('crm:clue:query')") public CommonResult> getCluePage(@Valid CrmCluePageReqVO pageVO) { - PageResult pageResult = clueService.getCluePage(pageVO); - return success(CrmClueConvert.INSTANCE.convertPage(pageResult)); + PageResult pageResult = clueService.getCluePage(pageVO, getLoginUserId()); + return success(BeanUtils.toBean(pageResult, CrmClueRespVO.class)); } @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 list = clueService.getClueList(exportReqVO); + public void exportClueExcel(@Valid CrmCluePageReqVO pageReqVO, HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PAGE_SIZE_NONE); + List list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList(); // 导出 Excel - List datas = CrmClueConvert.INSTANCE.convertList02(list); - ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas); + List datas = BeanUtils.toBean(list, CrmClueRespVO.class); + ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, datas); + } + + @PutMapping("/transfer") + @Operation(summary = "线索转移") + @PreAuthorize("@ss.hasPermission('crm:clue:update')") + public CommonResult transfer(@Valid @RequestBody CrmClueTransferReqVO reqVO) { + clueService.transferClue(reqVO, getLoginUserId()); + return success(true); + } + + @PostMapping("/transform") + @Operation(summary = "线索转化为客户") + @PreAuthorize("@ss.hasPermission('crm:clue:update')") + public CommonResult translateCustomer(@Valid @RequestBody CrmClueTransformReqVO reqVO) { + clueService.translateCustomer(reqVO, getLoginUserId()); + return success(Boolean.TRUE); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueCreateReqVO.java deleted file mode 100644 index a23837499..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; - -import lombok.*; -import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - 线索创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmClueCreateReqVO extends CrmClueBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java deleted file mode 100644 index d6457bd56..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java +++ /dev/null @@ -1,66 +0,0 @@ -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; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java deleted file mode 100644 index fe061b365..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java +++ /dev/null @@ -1,52 +0,0 @@ -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; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java index 4d28ebc73..0b6e7a50a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -21,4 +23,11 @@ public class CrmCluePageReqVO extends PageParam { @Schema(description = "手机号", example = "18000000000") private String mobile; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean pool; // null 则表示为不是公海数据 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java index 327e6a00b..b9e41ed82 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java @@ -1,27 +1,80 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.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.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; -import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + @Schema(description = "管理后台 - 线索 Response VO") @Data -@EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class CrmClueRespVO extends CrmClueBaseVO { +@ExcelIgnoreUnannotated +public class CrmClueRespVO { @Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969") + @ExcelProperty("编号") private Long id; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @ExcelProperty(value = "转化状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean transformStatus; @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @ExcelProperty(value = "跟进状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean followUpStatus; + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") + @ExcelProperty("线索名称") + private String name; + + @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") + // TODO 这里需要导出成客户名称 + @ExcelProperty("客户id") + private Long customerId; + + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "电话", example = "18000000000") + @ExcelProperty("电话") + private String telephone; + + @Schema(description = "手机号", example = "18000000000") + @ExcelProperty("手机号") + private String mobile; + + @Schema(description = "地址", example = "北京市海淀区") + @ExcelProperty("地址") + private String address; + + @Schema(description = "负责人编号") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java similarity index 77% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java index b7ff0ef3e..adbc650b9 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java @@ -3,30 +3,25 @@ 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 jakarta.validation.constraints.NotEmpty; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; -import jakarta.validation.constraints.NotEmpty; -import jakarta.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 文档生成 - */ +@Schema(description = "管理后台 - CRM 线索 创建/更新 Request VO") @Data -public class CrmClueBaseVO { +public class CrmClueSaveReqVO { + + @Schema(description = "编号", example = "10969") + private Long id; @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; @@ -46,6 +41,9 @@ public class CrmClueBaseVO { @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactLastTime; + @Schema(description = "负责人编号", example = "2048") + private Long ownerUserId; + @Schema(description = "备注", example = "随便") private String remark; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java new file mode 100644 index 000000000..63bdc1838 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 线索转移 Request VO") +@Data +public class CrmClueTransferReqVO { + + @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; + + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(value = CrmPermissionLevelEnum.class) + private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别。如果 null 说明移除 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransformReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransformReqVO.java new file mode 100644 index 000000000..68bb02b3f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransformReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 线索转化为客户 Request VO") +@Data +public class CrmClueTransformReqVO { + + @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]") + @NotEmpty(message = "线索编号不能为空") + private Set ids; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueUpdateReqVO.java deleted file mode 100644 index 996c38130..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -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 jakarta.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; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java index df55ef413..4e2e73e81 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java @@ -4,32 +4,36 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.NumberUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.convert.contact.ContactConvert; +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.Lists; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -38,10 +42,11 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CONTACT_TYPE; @Tag(name = "管理后台 - CRM 联系人") @RestController @@ -54,21 +59,26 @@ public class CrmContactController { private CrmContactService contactService; @Resource private CrmCustomerService customerService; + @Resource + private CrmContactBusinessService contactBusinessLinkService; @Resource private AdminUserApi adminUserApi; + @Resource + private OperateLogApi operateLogApi; @PostMapping("/create") @Operation(summary = "创建联系人") @PreAuthorize("@ss.hasPermission('crm:contact:create')") - public CommonResult createContact(@Valid @RequestBody CrmContactCreateReqVO createReqVO) { + public CommonResult createContact(@Valid @RequestBody CrmContactSaveReqVO createReqVO) { return success(contactService.createContact(createReqVO, getLoginUserId())); } @PutMapping("/update") @Operation(summary = "更新联系人") + @OperateLog(enable = false) @PreAuthorize("@ss.hasPermission('crm:contact:update')") - public CommonResult updateContact(@Valid @RequestBody CrmContactUpdateReqVO updateReqVO) { + public CommonResult updateContact(@Valid @RequestBody CrmContactSaveReqVO updateReqVO) { contactService.updateContact(updateReqVO); return success(true); } @@ -95,34 +105,45 @@ public class CrmContactController { Map userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList( NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId()))); // 2. 获取客户信息 - List customerList = customerService.getCustomerList(Collections.singletonList(contact.getCustomerId())); + List customerList = customerService.getCustomerList( + Collections.singletonList(contact.getCustomerId())); // 3. 直属上级 - List parentContactList = contactService.getContactList(Collections.singletonList(contact.getParentId())); - return success(ContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList)); + List parentContactList = contactService.getContactList( + Collections.singletonList(contact.getParentId()), getLoginUserId()); + return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList)); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得联系人列表") + @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { + return success(BeanUtils.toBean(contactService.getContactList(ids, getLoginUserId()), CrmContactRespVO.class)); } @GetMapping("/simple-all-list") - @Operation(summary = "获得联系人列表") + @Operation(summary = "获得联系人的精简列表") @PreAuthorize("@ss.hasPermission('crm:contact:query')") - public CommonResult> getSimpleContactList() { - List list = contactService.getContactList(); - return success(ContactConvert.INSTANCE.convertAllList(list)); + public CommonResult> getSimpleContactList() { + List list = contactService.getSimpleContactList(getLoginUserId()); + return success(convertList(list, contact -> // 只返回 id、name 字段 + new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()))); } @GetMapping("/page") @Operation(summary = "获得联系人分页") @PreAuthorize("@ss.hasPermission('crm:contact:query')") public CommonResult> getContactPage(@Valid CrmContactPageReqVO pageVO) { - PageResult pageResult = contactService.getContactPage(pageVO); - return success(convertDetailContactPage(pageResult)); + PageResult pageResult = contactService.getContactPage(pageVO, getLoginUserId()); + return success(buildContactDetailPage(pageResult)); } @GetMapping("/page-by-customer") @Operation(summary = "获得联系人分页,基于指定客户") public CommonResult> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) { Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); - PageResult pageResult = contactService.getContactPageByCustomer(pageVO); - return success(convertDetailContactPage(pageResult)); + PageResult pageResult = contactService.getContactPageByCustomerId(pageVO); + return success(buildContactDetailPage(pageResult)); } @GetMapping("/export-excel") @@ -131,19 +152,30 @@ public class CrmContactController { @OperateLog(type = EXPORT) public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO, HttpServletResponse response) throws IOException { - exportReqVO.setPageNo(PageParam.PAGE_SIZE_NONE); - PageResult pageResult = contactService.getContactPage(exportReqVO); + exportReqVO.setPageNo(PAGE_SIZE_NONE); + PageResult pageResult = contactService.getContactPage(exportReqVO, getLoginUserId()); ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, - convertDetailContactPage(pageResult).getList()); + buildContactDetailPage(pageResult).getList()); + } + + @GetMapping("/operate-log-page") + @Operation(summary = "获得客户操作日志") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult> getCustomerOperateLog(@RequestParam("bizId") Long bizId) { + OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + reqVO.setBizType(CRM_CONTACT_TYPE); + reqVO.setBizId(bizId); + return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class))); } /** - * 转换成详细的联系人分页,即读取关联信息 + * 构建详细的联系人分页结果 * - * @param pageResult 联系人分页 - * @return 详细的联系人分页 + * @param pageResult 简单的联系人分页结果 + * @return 详细的联系人分页结果 */ - private PageResult convertDetailContactPage(PageResult pageResult) { + private PageResult buildContactDetailPage(PageResult pageResult) { List contactList = pageResult.getList(); if (CollUtil.isEmpty(contactList)) { return PageResult.empty(pageResult.getTotal()); @@ -156,8 +188,34 @@ public class CrmContactController { contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); // 3. 直属上级 List parentContactList = contactService.getContactList( - convertSet(contactList, CrmContactDO::getParentId)); - return ContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); + convertSet(contactList, CrmContactDO::getParentId), getLoginUserId()); + return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); + } + + @PutMapping("/transfer") + @Operation(summary = "联系人转移") + @PreAuthorize("@ss.hasPermission('crm:contact:update')") + public CommonResult transfer(@Valid @RequestBody CrmContactTransferReqVO reqVO) { + contactService.transferContact(reqVO, getLoginUserId()); + return success(true); + } + + // ================== 关联/取关联系人 =================== + + @PostMapping("/create-business-list") + @Operation(summary = "创建联系人与商机的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:create-business')") + public CommonResult createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) { + contactBusinessLinkService.createContactBusinessList(createReqVO); + return success(true); + } + + @DeleteMapping("/delete-business-list") + @Operation(summary = "删除联系人与联系人的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')") + public CommonResult deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) { + contactBusinessLinkService.deleteContactBusinessList(deleteReqVO); + return success(true); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java new file mode 100644 index 000000000..9b360f84b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作 +@Data +public class CrmContactBusinessReqVO { + + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878") + @NotNull(message="联系人不能为空") + private Long contactId; + + @Schema(description = "商机编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638") + @NotEmpty(message="商机不能为空") + private List businessIds; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactCreateReqVO.java deleted file mode 100644 index 33f2db852..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - CRM 联系人创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmContactCreateReqVO extends CrmContactBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java index 1adfc341d..75294a1bd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -33,4 +35,8 @@ public class CrmContactPageReqVO extends PageParam { @Schema(description = "微信", example = "zzZ98373") private String wechat; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java index e52f4d0c1..d99ea703c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java @@ -1,28 +1,90 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.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.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; +import lombok.ToString; + import java.time.LocalDateTime; @Schema(description = "管理后台 - CRM 联系人 Response VO") @Data -@EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @ExcelIgnoreUnannotated -public class CrmContactRespVO extends CrmContactBaseVO { +public class CrmContactRespVO { @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") private Long id; - @Schema(description = "创建时间") - @ExcelProperty(value = "创建时间", order = 8) - private LocalDateTime createTime; + @Schema(description = "姓名", example = "芋艿") + @ExcelProperty(value = "姓名", order = 1) + private String name; - @Schema(description = "更新时间") - @ExcelProperty(value = "更新时间", order = 8) - private LocalDateTime updateTime; + @Schema(description = "客户编号", example = "10795") + private Long customerId; + + @Schema(description = "性别") + @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3) + @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX) + private Integer sex; + + @Schema(description = "职位") + @ExcelProperty(value = "职位", order = 3) + private String post; + + @Schema(description = "是否关键决策人") + @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean master; + + @Schema(description = "直属上级", example = "23457") + private Long parentId; + + @Schema(description = "手机号", example = "1387171766") + @ExcelProperty(value = "手机号", order = 4) + private String mobile; + + @Schema(description = "电话", example = "021-0029922") + @ExcelProperty(value = "电话", order = 4) + private String telephone; + + @Schema(description = "QQ", example = "197272662") + @ExcelProperty(value = "QQ", order = 4) + private Long qq; + + @Schema(description = "微信", example = "zzz3883") + @ExcelProperty(value = "微信", order = 4) + private String wechat; + + @Schema(description = "电子邮箱", example = "1111@22.com") + @ExcelProperty(value = "邮箱", order = 4) + private String email; + + @Schema(description = "地区编号", example = "20158") + private Integer areaId; + + @Schema(description = "地址") + @ExcelProperty(value = "地址", order = 5) + private String detailAddress; + + @Schema(description = "备注", example = "你说的对") + @ExcelProperty(value = "备注", order = 6) + private String remark; + + @Schema(description = "负责人用户编号", example = "14334") + private Long ownerUserId; + + @Schema(description = "最后跟进时间") + @ExcelProperty(value = "最后跟进时间", order = 6) + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @ExcelProperty(value = "下次联系时间", order = 6) + private LocalDateTime contactNextTime; @Schema(description = "创建人", example = "25682") private String creator; @@ -31,7 +93,7 @@ public class CrmContactRespVO extends CrmContactBaseVO { @ExcelProperty(value = "创建人", order = 8) private String creatorName; - @ExcelProperty(value = "客户名称",order = 2) + @ExcelProperty(value = "客户名称", order = 2) @Schema(description = "客户名字", example = "test") private String customerName; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java similarity index 50% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java index eb984884d..299b1fbbb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java @@ -2,102 +2,102 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; import cn.iocoder.yudao.framework.common.validation.Mobile; import cn.iocoder.yudao.framework.common.validation.Telephone; -import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; -import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; -import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.*; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; -// TODO zyna:要不按照新的,干掉这个 basevo,都放子类里 -/** - * CRM 联系人 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ +@Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO") @Data -@ExcelIgnoreUnannotated -public class CrmContactBaseVO { +public class CrmContactSaveReqVO { + + @Schema(description = "主键", example = "3167") + private Long id; - @ExcelProperty(value = "姓名",order = 1) @Schema(description = "姓名", example = "芋艿") @NotNull(message = "姓名不能为空") + @DiffLogField(name = "姓名") private String name; @Schema(description = "客户编号", example = "10795") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) private Long customerId; - @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3) - @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX) @Schema(description = "性别") + @DiffLogField(name = "性别", function = SysSexParseFunction.NAME) private Integer sex; @Schema(description = "职位") - @ExcelProperty(value = "职位", order = 3) + @DiffLogField(name = "职位") private String post; @Schema(description = "是否关键决策人") - @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3) - @DictFormat(DictTypeConstants.BOOLEAN_STRING) + @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME) private Boolean master; @Schema(description = "直属上级", example = "23457") + @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME) private Long parentId; - @Schema(description = "手机号",example = "1387171766") + @Schema(description = "手机号", example = "1387171766") @Mobile - @ExcelProperty(value = "手机号",order = 4) + @DiffLogField(name = "手机号") private String mobile; - @Schema(description = "座机",example = "021-0029922") + @Schema(description = "电话", example = "021-0029922") @Telephone - @ExcelProperty(value = "座机",order = 4) + @DiffLogField(name = "电话") private String telephone; - @ExcelProperty(value = "QQ",order = 4) - @Schema(description = "QQ",example = "197272662") + @Schema(description = "QQ", example = "197272662") + @DiffLogField(name = "QQ") private Long qq; - @ExcelProperty(value = "微信",order = 4) - @Schema(description = "微信",example = "zzz3883") + @Schema(description = "微信", example = "zzz3883") + @DiffLogField(name = "微信") private String wechat; - @Schema(description = "电子邮箱",example = "1111@22.com") + @Schema(description = "电子邮箱", example = "1111@22.com") + @DiffLogField(name = "邮箱") @Email - @ExcelProperty(value = "邮箱",order = 4) private String email; - @ExcelProperty(value = "地址",order = 5) - @Schema(description = "地址") - private String address; + @Schema(description = "地区编号", example = "20158") + @DiffLogField(name = "所在地", function = SysAreaParseFunction.NAME) + private Integer areaId; - @Schema(description = "下次联系时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) - @ExcelProperty(value = "下次联系时间",order = 6) - private LocalDateTime nextTime; + @Schema(description = "地址") + @DiffLogField(name = "地址") + private String detailAddress; @Schema(description = "备注", example = "你说的对") - @ExcelProperty(value = "备注",order = 6) + @DiffLogField(name = "备注") private String remark; - @Schema(description = "最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @ExcelProperty(value = "最后跟进时间",order = 6) - private LocalDateTime lastTime; - @Schema(description = "负责人用户编号", example = "14334") @NotNull(message = "负责人不能为空") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) private Long ownerUserId; - @Schema(description = "地区编号", example = "20158") - private Integer areaId; + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DiffLogField(name = "下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "关联商机 ID", example = "122233") + private Long businessId; // 注意:该字段用于在【商机】详情界面「新建联系人」时,自动进行关联 } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSimpleRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSimpleRespVO.java deleted file mode 100644 index 4ebf44a30..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSimpleRespVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.ToString; - -@Schema(description = "管理后台 - CRM 联系人的精简 Response VO") -@Data -@ToString(callSuper = true) -public class CrmContactSimpleRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") - private Long id; - - @Schema(description = "姓名", example = "芋艿") - private String name; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactUpdateReqVO.java deleted file mode 100644 index 0f705d5cb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - CRM 联系人更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmContactUpdateReqVO extends CrmContactBaseVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") - @NotNull(message = "主键不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java deleted file mode 100644 index cc9ae8cd1..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java +++ /dev/null @@ -1,115 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO; -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; -import cn.iocoder.yudao.module.crm.service.contactbusinesslink.CrmContactBusinessLinkService; -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.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; - -@Tag(name = "管理后台 - CRM 联系人商机关联") -@RestController -@RequestMapping("/crm/contact-business-link") -@Validated -public class CrmContactBusinessLinkController { - - @Resource - private CrmContactBusinessLinkService contactBusinessLinkService; - @Resource - private CrmBusinessService crmBusinessService; - - // TODO @zyna:createContactBusinessLink 和 createContactBusinessLinkBatch 是不是合并成一个接口?contactId、List - @PostMapping("/create") - @Operation(summary = "创建联系人商机关联") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')") - public CommonResult createContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO createReqVO) { - return success(contactBusinessLinkService.createContactBusinessLink(createReqVO)); - } - - @PostMapping("/create-batch") - @Operation(summary = "创建联系人商机关联") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')") - @Transactional(rollbackFor = Exception.class) - public CommonResult createContactBusinessLinkBatch( - @Valid @RequestBody List createReqVO) { - createReqVO.stream().forEach(item -> { - CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId()); - if(crmBusinessDO == null){ - throw exception(BUSINESS_NOT_EXISTS); - } - }); - contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO); - return success(true); - } - - // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在更新。 - @PutMapping("/update") - @Operation(summary = "更新联系人商机关联") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:update')") - public CommonResult updateContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO updateReqVO) { - contactBusinessLinkService.updateContactBusinessLink(updateReqVO); - return success(true); - } - - // TODO @zyna:删除,是不是传递 ids? - @DeleteMapping("/delete-batch") - @Operation(summary = "批量删除联系人商机关联") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')") - public CommonResult deleteContactBusinessLinkBatch(@Valid @RequestBody List deleteList) { - contactBusinessLinkService.deleteContactBusinessLink(deleteList); - return success(true); - } - - // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在单个读取; - @GetMapping("/get") - @Operation(summary = "获得联系人商机关联") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')") - public CommonResult getContactBusinessLink(@RequestParam("id") Long id) { - CrmContactBusinessLinkDO contactBusinessLink = contactBusinessLinkService.getContactBusinessLink(id); - return success(BeanUtils.toBean(contactBusinessLink, CrmContactBusinessLinkRespVO.class)); - } - - // TODO @zyna:这个可以转化下,使用客户编号去查询,就是使用 CrmBusinessController 的 getBusinessPageByCustomer 接口;目的是:复用 - @GetMapping("/page-by-contact") - @Operation(summary = "获得联系人商机关联") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')") - public CommonResult> getContactBusinessLinkByContact( - @Valid CrmContactBusinessLinkPageReqVO pageReqVO) { - PageResult contactBusinessLink = contactBusinessLinkService.getContactBusinessLinkPageByContact(pageReqVO); - return success(contactBusinessLink); - } - - // TODO @zyna:这个优化下,搞到 CrmBusinessController 里去,加一个 CrmBusinessController 的 getBusinessPageByContact 接口;目的是: - @GetMapping("/page") - @Operation(summary = "获得联系人商机关联分页") - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')") - public CommonResult> getContactBusinessLinkPage( - @Valid CrmContactBusinessLinkPageReqVO pageReqVO) { - PageResult pageResult = contactBusinessLinkService.getContactBusinessLinkPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, CrmContactBusinessLinkRespVO.class)); - } - - // TODO @zyna:最终梳理完后,应该就 2 个接口,要不直接合并到 CrmContactController 中,不作为独立模块,就关联、接触关联。其实和 user 设置它有哪些岗位、部门是类似的。 - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java deleted file mode 100644 index 17aadf667..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo; - -import com.alibaba.excel.annotation.ExcelProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - CRM 联系人商机关联 Response VO") -@Data -public class CrmContactBusinessLinkRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220") - @ExcelProperty("主键") - private Long id; - - @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878") - private Long contactId; - - @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638") - private Long businessId; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java deleted file mode 100644 index 6f867c511..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - CRM 联系人商机关联新增/修改 Request VO") -@Data -public class CrmContactBusinessLinkSaveReqVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220") - private Long id; - - @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878") - @NotNull(message="联系人不能为空") - private Long contactId; - - @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638") - @NotNull(message="商机不能为空") - private Long businessId; - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java index b1581bc62..c2935184f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java @@ -5,10 +5,11 @@ import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.convert.contract.CrmContractConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; @@ -18,13 +19,13 @@ 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 jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Map; @@ -53,14 +54,14 @@ public class CrmContractController { @PostMapping("/create") @Operation(summary = "创建合同") @PreAuthorize("@ss.hasPermission('crm:contract:create')") - public CommonResult createContract(@Valid @RequestBody CrmContractCreateReqVO createReqVO) { + public CommonResult createContract(@Valid @RequestBody CrmContractSaveReqVO createReqVO) { return success(contractService.createContract(createReqVO, getLoginUserId())); } @PutMapping("/update") @Operation(summary = "更新合同") @PreAuthorize("@ss.hasPermission('crm:contract:update')") - public CommonResult updateContract(@Valid @RequestBody CrmContractUpdateReqVO updateReqVO) { + public CommonResult updateContract(@Valid @RequestBody CrmContractSaveReqVO updateReqVO) { contractService.updateContract(updateReqVO); return success(true); } @@ -78,25 +79,25 @@ public class CrmContractController { @Operation(summary = "获得合同") @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('crm:contract:query')") - public CommonResult getContract(@RequestParam("id") Long id) { + public CommonResult getContract(@RequestParam("id") Long id) { CrmContractDO contract = contractService.getContract(id); - return success(ContractConvert.INSTANCE.convert(contract)); + return success(BeanUtils.toBean(contract, CrmContractRespVO.class)); } @GetMapping("/page") @Operation(summary = "获得合同分页") @PreAuthorize("@ss.hasPermission('crm:contract:query')") - public CommonResult> getContractPage(@Valid CrmContractPageReqVO pageVO) { - PageResult pageResult = contractService.getContractPage(pageVO); - return success(convertDetailContractPage(pageResult)); + public CommonResult> getContractPage(@Valid CrmContractPageReqVO pageVO) { + PageResult pageResult = contractService.getContractPage(pageVO, getLoginUserId()); + return success(buildContractDetailPage(pageResult)); } @GetMapping("/page-by-customer") @Operation(summary = "获得联系人分页,基于指定客户") - public CommonResult> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) { + public CommonResult> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) { Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); - PageResult pageResult = contractService.getContractPageByCustomer(pageVO); - return success(convertDetailContractPage(pageResult)); + PageResult pageResult = contractService.getContractPageByCustomerId(pageVO); + return success(buildContractDetailPage(pageResult)); } @GetMapping("/export-excel") @@ -105,19 +106,19 @@ public class CrmContractController { @OperateLog(type = EXPORT) public void exportContractExcel(@Valid CrmContractPageReqVO exportReqVO, HttpServletResponse response) throws IOException { - PageResult pageResult = contractService.getContractPage(exportReqVO); + PageResult pageResult = contractService.getContractPage(exportReqVO, getLoginUserId()); // 导出 Excel ExcelUtils.write(response, "合同.xls", "数据", CrmContractExcelVO.class, - ContractConvert.INSTANCE.convertList02(pageResult.getList())); + BeanUtils.toBean(pageResult.getList(), CrmContractExcelVO.class)); } /** - * 转换成详细的合同分页,即读取关联信息 + * 构建详细的合同分页结果 * - * @param pageResult 合同分页 - * @return 详细的合同分页 + * @param pageResult 简单的合同分页结果 + * @return 详细的合同分页结果 */ - private PageResult convertDetailContractPage(PageResult pageResult) { + private PageResult buildContractDetailPage(PageResult pageResult) { List contactList = pageResult.getList(); if (CollUtil.isEmpty(contactList)) { return PageResult.empty(pageResult.getTotal()); @@ -128,7 +129,7 @@ public class CrmContractController { // 2. 获取创建人、负责人列表 Map userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - return ContractConvert.INSTANCE.convertPage(pageResult, userMap, customerList); + return CrmContractConvert.INSTANCE.convertPage(pageResult, userMap, customerList); } @PutMapping("/transfer") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java deleted file mode 100644 index cfe746bcb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java +++ /dev/null @@ -1,37 +0,0 @@ -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 CrmContractBaseVO { - - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "创建人", example = "25682") - private String creator; - - @Schema(description = "创建人名字", example = "test") - private String creatorName; - - @Schema(description = "客户名字", example = "test") - private String customerName; - - @Schema(description = "负责人", example = "test") - private String ownerUserName; - - @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer auditStatus; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractCreateReqVO.java deleted file mode 100644 index 17196473a..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -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 CrmContractCreateReqVO extends CrmContractBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java index e2f286a99..94199ada6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -24,4 +26,8 @@ public class CrmContractPageReqVO extends PageParam { @Schema(description = "商机编号", example = "10864") private Long businessId; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java new file mode 100644 index 000000000..1164f4a0c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +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 合同 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmContractRespVO { + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @ExcelProperty("合同编号") + private Long id; + + @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("合同名称") + private String name; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") + @ExcelProperty("客户编号") + private Long customerId; + + @Schema(description = "商机编号", example = "10864") + @ExcelProperty("商机编号") + private Long businessId; + + @Schema(description = "工作流编号", example = "1043") + @ExcelProperty("工作流编号") + private Long processInstanceId; + + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("下单日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime orderDate; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + + // TODO @芋艿:未来应该支持自动生成; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") + @ExcelProperty("合同编号") + private String no; + + @Schema(description = "开始时间") + @ExcelProperty("开始时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime startTime; + + @Schema(description = "结束时间") + @ExcelProperty("结束时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime endTime; + + @Schema(description = "合同金额", example = "5617") + @ExcelProperty("合同金额") + private Integer price; + + @Schema(description = "整单折扣") + @ExcelProperty("整单折扣") + private Integer discountPercent; + + @Schema(description = "产品总金额", example = "19510") + @ExcelProperty("产品总金额") + private Integer productPrice; + + @Schema(description = "联系人编号", example = "18546") + @ExcelProperty("联系人编号") + private Long contactId; + + @Schema(description = "公司签约人", example = "14036") + @ExcelProperty("公司签约人") + private Long signUserId; + + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactLastTime; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTime; + + @Schema(description = "创建人", example = "25682") + @ExcelProperty("创建人") + private String creator; + + @Schema(description = "创建人名字", example = "test") + @ExcelProperty("创建人名字") + private String creatorName; + + @Schema(description = "客户名字", example = "test") + @ExcelProperty("客户名字") + private String customerName; + + @Schema(description = "负责人", example = "test") + @ExcelProperty("负责人") + private String ownerUserName; + + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @ExcelProperty("审批状态") + private Integer auditStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java similarity index 51% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java index a7a8c5d8c..e495bbfa5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java @@ -1,81 +1,101 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; -import jakarta.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 文档生成 - */ +@Schema(description = "管理后台 - CRM 合同创建/更新 Request VO") @Data -public class CrmContractBaseVO { +public class CrmContractSaveReqVO { - // TODO @dhb52:类似 no 字段的 example 要写xia 哈; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + private Long id; @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @DiffLogField(name = "合同名称") @NotNull(message = "合同名称不能为空") private String name; - // TODO @dhb52:这个必须传递 - @Schema(description = "客户编号", example = "18336") + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + @NotNull(message = "客户编号不能为空") private Long customerId; @Schema(description = "商机编号", example = "10864") + @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME) private Long businessId; @Schema(description = "工作流编号", example = "1043") + @DiffLogField(name = "工作流编号") private Long processInstanceId; - // TODO @dhb52:这个必须传递 - @Schema(description = "下单日期") + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) + @DiffLogField(name = "下单日期") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @NotNull(message = "下单日期不能为空") private LocalDateTime orderDate; - // TODO @dhb52:这个必须传递 - @Schema(description = "负责人的用户编号", example = "17144") + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + @NotNull(message = "负责人不能为空") private Long ownerUserId; // TODO @芋艿:未来应该支持自动生成; - // TODO @dhb52:这个必须传递; - @Schema(description = "合同编号") + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") + @DiffLogField(name = "合同编号") + @NotNull(message = "合同编号不能为空") private String no; @Schema(description = "开始时间") + @DiffLogField(name = "开始时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime startTime; @Schema(description = "结束时间") + @DiffLogField(name = "结束时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime endTime; @Schema(description = "合同金额", example = "5617") + @DiffLogField(name = "合同金额") private Integer price; @Schema(description = "整单折扣") + @DiffLogField(name = "整单折扣") private Integer discountPercent; @Schema(description = "产品总金额", example = "19510") + @DiffLogField(name = "产品总金额") private Integer productPrice; @Schema(description = "联系人编号", example = "18546") + @DiffLogField(name = "联系人", function = CrmContactParseFunction.NAME) private Long contactId; @Schema(description = "公司签约人", example = "14036") + @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME) private Long signUserId; @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactLastTime; @Schema(description = "备注", example = "你猜") + @DiffLogField(name = "备注") private String remark; // TODO @dhb52:增加一个 status 字段:具体有哪些值,你来枚举下;主要页面上有个【草稿】【提交审核】的流程,可以看看。然后要对接工作流,这块也可以看看,不确定的地方问我。 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractUpdateReqVO.java deleted file mode 100644 index f0c0e9959..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -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 jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - CRM 合同更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmContractUpdateReqVO extends CrmContractBaseVO { - - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") - @NotNull(message = "合同编号不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http index f6ecb473b..9a6cb93a8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http @@ -1,6 +1,16 @@ -### 请求 /update -GET {{baseUrl}}/crm/customer/page?pageNo=1&pageSize=10&name="张三" +### 请求 /transfer +PUT {{baseUrl}}/crm/customer/transfer +Content-Type: application/-id: {{adminTenentId}}json Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} +tenant +{ + "id": 10, + "newOwnerUserId": 127 +} +### 自定义日志记录结果 +### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9)) + +### diff 日志 +### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java index f06e26e4a..0ea42cad6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java @@ -1,41 +1,50 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 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.logger.OperateLogApi; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.mapstruct.ap.internal.util.Collections; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER_TYPE; @Tag(name = "管理后台 - CRM 客户") @RestController @@ -45,30 +54,33 @@ public class CrmCustomerController { @Resource private CrmCustomerService customerService; - + @Resource + private CrmCustomerPoolConfigService customerPoolConfigService; @Resource private DeptApi deptApi; @Resource private AdminUserApi adminUserApi; + @Resource + private OperateLogApi operateLogApi; @PostMapping("/create") @Operation(summary = "创建客户") @PreAuthorize("@ss.hasPermission('crm:customer:create')") - public CommonResult createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) { + public CommonResult createCustomer(@Valid @RequestBody CrmCustomerSaveReqVO createReqVO) { return success(customerService.createCustomer(createReqVO, getLoginUserId())); } @PutMapping("/update") @Operation(summary = "更新客户") @PreAuthorize("@ss.hasPermission('crm:customer:update')") - public CommonResult updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) { + public CommonResult updateCustomer(@Valid @RequestBody CrmCustomerSaveReqVO updateReqVO) { customerService.updateCustomer(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除客户") - @Parameter(name = "id", description = "编号", required = true) + @Parameter(name = "id", description = "客户编号", required = true) @PreAuthorize("@ss.hasPermission('crm:customer:delete')") public CommonResult deleteCustomer(@RequestParam("id") Long id) { customerService.deleteCustomer(id); @@ -103,10 +115,50 @@ public class CrmCustomerController { } // 2. 拼接数据 + Map poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null : + getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间 Map userMap = adminUserApi.getUserMap( convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap)); + return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); + } + + /** + * 获取距离进入公海的时间 + * + * @param customerList 客户列表 + * @return Map + */ + private Map getPoolDayMap(List customerList) { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (poolConfig == null || !poolConfig.getEnabled()) { + return MapUtil.empty(); + } + return convertMap(customerList, CrmCustomerDO::getId, customer -> { + // 1.1 未成交放入公海天数 + long dealExpireDay = 0; + if (!customer.getDealStatus()) { + dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime()); + } + // 1.2 未跟进放入公海天数 + LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); + long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); + if (contactExpireDay < 0) { + contactExpireDay = 0; + } + // 2. 返回最小的天数 + return Math.min(dealExpireDay, contactExpireDay); + }); + } + + @GetMapping(value = "/list-all-simple") + @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项") + public CommonResult> getSimpleDeptList() { + CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + List list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList(); + return success(convertList(list, customer -> // 只返回 id、name 精简字段 + new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName()))); } @GetMapping("/export-excel") @@ -118,24 +170,35 @@ public class CrmCustomerController { pageVO.setPageSize(PAGE_SIZE_NONE); // 不分页 List list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList(); // 导出 Excel - List datas = CrmCustomerConvert.INSTANCE.convertList02(list); - ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerExcelVO.class, datas); + ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class, + BeanUtils.toBean(list, CrmCustomerRespVO.class)); } @PutMapping("/transfer") - @Operation(summary = "客户转移") + @Operation(summary = "转移客户") @PreAuthorize("@ss.hasPermission('crm:customer:update')") public CommonResult transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) { customerService.transferCustomer(reqVO, getLoginUserId()); return success(true); } - // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了; + @GetMapping("/operate-log-page") + @Operation(summary = "获得客户操作日志") + @Parameter(name = "id", description = "客户编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult> getCustomerOperateLog(@RequestParam("id") Long id) { + OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO(); + reqDTO.setPageSize(PAGE_SIZE_NONE); // 不分页 + reqDTO.setBizType(CRM_CUSTOMER_TYPE); + reqDTO.setBizId(id); + return success(operateLogApi.getOperateLogPage(reqDTO)); + } + @PutMapping("/lock") @Operation(summary = "锁定/解锁客户") @PreAuthorize("@ss.hasPermission('crm:customer:update')") - public CommonResult lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) { - customerService.lockCustomer(updateReqVO); + public CommonResult lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) { + customerService.lockCustomer(lockReqVO, getLoginUserId()); return success(true); } @@ -155,32 +218,16 @@ public class CrmCustomerController { @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") @PreAuthorize("@ss.hasPermission('crm:customer:receive')") public CommonResult receiveCustomer(@RequestParam(value = "ids") List ids) { - customerService.receiveCustomer(ids, getLoginUserId()); + customerService.receiveCustomer(ids, getLoginUserId(), Boolean.TRUE); return success(true); } @PutMapping("/distribute") @Operation(summary = "分配公海给对应负责人") - @Parameters({ - @Parameter(name = "ids", description = "客户编号数组", required = true, example = "1,2,3"), - @Parameter(name = "ownerUserId", description = "分配的负责人编号", required = true, example = "12345") - }) @PreAuthorize("@ss.hasPermission('crm:customer:distribute')") - public CommonResult distributeCustomer(@RequestParam(value = "ids") List ids, - @RequestParam(value = "ownerUserId") Long ownerUserId) { - // 领取公海数据 - customerService.receiveCustomer(ids, ownerUserId); + public CommonResult distributeCustomer(@Valid @RequestBody CrmCustomerDistributeReqVO distributeReqVO) { + customerService.receiveCustomer(distributeReqVO.getIds(), distributeReqVO.getOwnerUserId(), Boolean.FALSE); return success(true); } - // TODO 芋艿:这个接口要调整下 - @GetMapping("/query-all-list") - @Operation(summary = "查询客户列表") - @PreAuthorize("@ss.hasPermission('crm:customer:all')") - public CommonResult> queryAll() { - List crmCustomerDOList = customerService.getCustomerList(); - List data = CrmCustomerConvert.INSTANCE.convertQueryAll(crmCustomerDOList); - return success(data); - } - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java index ec86c99d5..95f4ccd8f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java @@ -3,10 +3,9 @@ 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.limitconfig.CrmCustomerLimitConfigCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService; @@ -17,12 +16,12 @@ 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 jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.util.Collection; import java.util.Map; @@ -46,14 +45,14 @@ public class CrmCustomerLimitConfigController { @PostMapping("/create") @Operation(summary = "创建客户限制配置") @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')") - public CommonResult createCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigCreateReqVO createReqVO) { + public CommonResult createCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigSaveReqVO createReqVO) { return success(customerLimitConfigService.createCustomerLimitConfig(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新客户限制配置") @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:update')") - public CommonResult updateCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigUpdateReqVO updateReqVO) { + public CommonResult updateCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigSaveReqVO updateReqVO) { customerLimitConfigService.updateCustomerLimitConfig(updateReqVO); return success(true); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java index a6da45b7d..ca3b1ac62 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java @@ -1,20 +1,19 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigRespVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; -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 jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - CRM 客户公海配置") @@ -30,8 +29,8 @@ public class CrmCustomerPoolConfigController { @Operation(summary = "获取客户公海规则设置") @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')") public CommonResult getCustomerPoolConfig() { - CrmCustomerPoolConfigDO customerPoolConfig = customerPoolConfigService.getCustomerPoolConfig(); - return success(CrmCustomerConvert.INSTANCE.convert(customerPoolConfig)); + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + return success(BeanUtils.toBean(poolConfig, CrmCustomerPoolConfigRespVO.class)); } @PutMapping("/save") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerCreateReqVO.java deleted file mode 100644 index a81c2095d..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerCreateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -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 jakarta.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; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java new file mode 100644 index 000000000..24113ed12 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - CRM 客户分配公海给对应负责人 Request VO") +@Data +public class CrmCustomerDistributeReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024]") + @NotEmpty(message = "客户编号不能为空") + private List ids; + + @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "负责人不能为空") + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerExcelVO.java deleted file mode 100644 index d49f569b3..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerExcelVO.java +++ /dev/null @@ -1,93 +0,0 @@ -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; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java new file mode 100644 index 000000000..1cf9ff382 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户锁定/解锁 Request VO") +@Data +public class CrmCustomerLockReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + + @Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Boolean lockStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java index 59d6ae360..bded50473 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; -import cn.iocoder.yudao.module.crm.framework.vo.CrmBasePageReqVO; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -10,7 +12,7 @@ import lombok.ToString; @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class CrmCustomerPageReqVO extends CrmBasePageReqVO { +public class CrmCustomerPageReqVO extends PageParam { @Schema(description = "客户名称", example = "赵六") private String name; @@ -27,4 +29,11 @@ public class CrmCustomerPageReqVO extends CrmBasePageReqVO { @Schema(description = "客户来源", example = "1") private Integer source; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean pool; // null 则表示为不是公海数据 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerQueryAllRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerQueryAllRespVO.java deleted file mode 100644 index a66b8d810..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerQueryAllRespVO.java +++ /dev/null @@ -1,17 +0,0 @@ -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 全部客户 Response VO") -@Data -public class CrmCustomerQueryAllRespVO{ - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") - private Long id; - - @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") - private String name; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java index 2cbd85dd3..69c75856f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java @@ -1,9 +1,12 @@ 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.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; 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; @@ -12,45 +15,124 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ @Schema(description = "管理后台 - CRM 客户 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerRespVO extends CrmCustomerBaseVO { +@ExcelIgnoreUnannotated +public class CrmCustomerRespVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty("编号") private Long id; - @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty("客户名称") + private String name; + + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "跟进状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean followUpStatus; - @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "锁定状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean lockStatus; - @Schema(description = "成交状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @Schema(description = "成交状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "成交状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean dealStatus; + @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL) + private Integer level; + + @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) + private Integer source; + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("手机") + private String mobile; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("电话") + private String telephone; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("网址") + private String website; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("QQ") + private String qq; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("wechat") + private String wechat; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("email") + private String email; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("客户描述") + private String description; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") private Long ownerUserId; @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") private String ownerUserName; @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") private String ownerUserDeptName; + @Schema(description = "地区编号", example = "1024") + @ExcelProperty("地区编号") + private Integer areaId; @Schema(description = "地区名称", example = "北京市") + @ExcelProperty("地区名称") private String areaName; + @Schema(description = "详细地址", example = "北京市成华大道") + @ExcelProperty("详细地址") + private String detailAddress; @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactLastTime; + @Schema(description = "下次联系时间") + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") private LocalDateTime createTime; @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") private LocalDateTime updateTime; - @Schema(description = "创建人") + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") private String creator; - @Schema(description = "创建人名字") + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") private String creatorName; + @Schema(description = "距离加入公海时间", example = "1") + private Long poolDay; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java similarity index 57% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java index 3d03ba807..d6d73b142 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java @@ -3,78 +3,104 @@ 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.framework.excel.core.annotations.DictFormat; import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; +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; +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; -/** - * 客户 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ +@Schema(description = "管理后台 - CRM 客户新增/修改 Request VO") @Data -public class CrmCustomerBaseVO { +public class CrmCustomerSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @DiffLogField(name = "客户名称") @NotEmpty(message = "客户名称不能为空") private String name; @Schema(description = "所属行业", example = "1") + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) + @DictFormat(CRM_CUSTOMER_INDUSTRY) private Integer industryId; @Schema(description = "客户等级", example = "2") + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) @InEnum(CrmCustomerLevelEnum.class) private Integer level; @Schema(description = "客户来源", example = "3") + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) private Integer source; @Schema(description = "手机", example = "18000000000") + @DiffLogField(name = "手机") @Mobile private String mobile; @Schema(description = "电话", example = "18000000000") + @DiffLogField(name = "电话") @Telephone private String telephone; @Schema(description = "网址", example = "https://www.baidu.com") + @DiffLogField(name = "网址") private String website; @Schema(description = "QQ", example = "123456789") + @DiffLogField(name = "QQ") @Size(max = 20, message = "QQ长度不能超过 20 个字符") private String qq; - @Schema(description = "wechat", example = "123456789") + @Schema(description = "微信", example = "123456789") + @DiffLogField(name = "微信") @Size(max = 255, message = "微信长度不能超过 255 个字符") private String wechat; - @Schema(description = "email", example = "123456789@qq.com") + @Schema(description = "邮箱", example = "123456789@qq.com") + @DiffLogField(name = "邮箱") @Email(message = "邮箱格式不正确") @Size(max = 255, message = "邮箱长度不能超过 255 个字符") private String email; @Schema(description = "客户描述", example = "任意文字") + @DiffLogField(name = "客户描述") @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") private String description; @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") private String remark; @Schema(description = "地区编号", example = "20158") + @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME) private Integer areaId; @Schema(description = "详细地址", example = "北京市海淀区") + @DiffLogField(name = "详细地址") private String detailAddress; @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactNextTime; + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long ownerUserId; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java index c425520a9..9bdc43532 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java @@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - import jakarta.validation.constraints.NotNull; +import lombok.Data; @Schema(description = "管理后台 - CRM 客户转移 Request VO") @Data public class CrmCustomerTransferReqVO { @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") - @NotNull(message = "联系人编号不能为空") + @NotNull(message = "客户编号不能为空") private Long id; /** diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerUpdateReqVO.java deleted file mode 100644 index 615666a73..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -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 jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - CRM 客户更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerUpdateReqVO extends CrmCustomerBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") - @NotNull(message = "编号不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java deleted file mode 100644 index 7aa372901..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; - -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 { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java index 010d0fc10..8ff03ad66 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java @@ -4,21 +4,32 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; import java.time.LocalDateTime; import java.util.List; @Schema(description = "管理后台 - 客户限制配置 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerLimitConfigRespVO extends CrmCustomerLimitConfigBaseVO { +public class CrmCustomerLimitConfigRespVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27930") private Long id; + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "规则适用人群") + private List userIds; + + @Schema(description = "规则适用部门") + private List deptIds; + + @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384") + private Integer maxCount; + + @Schema(description = "成交客户是否占有拥有客户数") + private Boolean dealCountEnabled; + @Schema(description = "规则适用人群名称") private List users; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java similarity index 52% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigBaseVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java index 07c74f7d2..a0e88e3f6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigBaseVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java @@ -1,33 +1,41 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysDeptParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; -import jakarta.validation.constraints.NotNull; import java.util.List; -/** - * 客户限制配置 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ +@Schema(description = "管理后台 - 客户限制配置创建/更新 Request VO") @Data -public class CrmCustomerLimitConfigBaseVO { +public class CrmCustomerLimitConfigSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27930") + private Long id; @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @NotNull(message = "规则类型不能为空") + @DiffLogField(name = "规则类型") private Integer type; @Schema(description = "规则适用人群") + @DiffLogField(name = "规则适用人群", function = SysAdminUserParseFunction.NAME) private List userIds; @Schema(description = "规则适用部门") + @DiffLogField(name = "规则适用部门", function = SysDeptParseFunction.NAME) private List deptIds; @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384") @NotNull(message = "数量上限不能为空") + @DiffLogField(name = "数量上限") private Integer maxCount; @Schema(description = "成交客户是否占有拥有客户数(当 type = 1 时)") + @DiffLogField(name = "成交客户是否占有拥有客户数") private Boolean dealCountEnabled; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigUpdateReqVO.java deleted file mode 100644 index f3ce86f35..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 客户限制配置更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerLimitConfigUpdateReqVO extends CrmCustomerLimitConfigBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27930") - @NotNull(message = "编号不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigBaseVO.java deleted file mode 100644 index 5df7973be..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigBaseVO.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -/** - * 客户公海配置 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class CrmCustomerPoolConfigBaseVO { - - @Schema(description = "是否启用客户公海", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - @NotNull(message = "是否启用客户公海不能为空") - private Boolean enabled; - - @Schema(description = "未跟进放入公海天数", example = "2") - private Integer contactExpireDays; - - @Schema(description = "未成交放入公海天数", example = "2") - private Integer dealExpireDays; - - @Schema(description = "是否开启提前提醒", example = "true") - private Boolean notifyEnabled; - - @Schema(description = "提前提醒天数", example = "2") - private Integer notifyDays; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java index dc48d6da7..2aeb3402e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java @@ -1,14 +1,27 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; @Schema(description = "管理后台 - CRM 客户公海规则 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerPoolConfigRespVO extends CrmCustomerPoolConfigBaseVO { +public class CrmCustomerPoolConfigRespVO { + + @Schema(description = "是否启用客户公海", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否启用客户公海不能为空") + private Boolean enabled; + + @Schema(description = "未跟进放入公海天数", example = "2") + private Integer contactExpireDays; + + @Schema(description = "未成交放入公海天数", example = "2") + private Integer dealExpireDays; + + @Schema(description = "是否开启提前提醒", example = "true") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + private Integer notifyDays; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java index fa72f5f74..3215f8645 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java @@ -1,34 +1,61 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig; import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + import java.util.Objects; -@Schema(description = "管理后台 - CRM 客户公海配置的保存 Request VO") +@Schema(description = "管理后台 - CRM 客户公海配置的创建/更新 Request VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO { +public class CrmCustomerPoolConfigSaveReqVO { - // TODO @wanwan:AssertTrue 必须 is 开头哈;注意需要 json 忽略下,避免被序列化; - @AssertTrue(message = "客户公海规则设置不正确") - // TODO @wanwan:这个方法,是不是拆成 2 个,一个校验 contactExpireDays、一个校验 dealExpireDays; - public boolean poolEnableValid() { + @Schema(description = "是否启用客户公海", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @DiffLogField(name = "是否启用客户公海") + @NotNull(message = "是否启用客户公海不能为空") + private Boolean enabled; + + @Schema(description = "未跟进放入公海天数", example = "2") + @DiffLogField(name = "未跟进放入公海天数") + private Integer contactExpireDays; + + @Schema(description = "未成交放入公海天数", example = "2") + @DiffLogField(name = "未成交放入公海天数") + private Integer dealExpireDays; + + @Schema(description = "是否开启提前提醒", example = "true") + @DiffLogField(name = "是否开启提前提醒") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + @DiffLogField(name = "提前提醒天数") + private Integer notifyDays; + + @AssertTrue(message = "未成交放入公海天数不能为空") + @JsonIgnore + public boolean isDealExpireDaysValid() { if (!BooleanUtil.isTrue(getEnabled())) { return true; } - return ObjectUtil.isAllNotEmpty(getContactExpireDays(), getDealExpireDays()); + return Objects.nonNull(getDealExpireDays()); } - @AssertTrue(message = "客户公海规则设置不正确") - // TODO @wanwan:这个方法,是不是改成 isNotifyDaysValid() 更好点?本质校验的是 notifyDays 是否为空 - public boolean notifyEnableValid() { + @AssertTrue(message = "未跟进放入公海天数不能为空") + @JsonIgnore + public boolean isContactExpireDaysValid() { + if (!BooleanUtil.isTrue(getEnabled())) { + return true; + } + return Objects.nonNull(getContactExpireDays()); + } + + @AssertTrue(message = "提前提醒天数不能为空") + @JsonIgnore + public boolean isNotifyDaysValid() { if (!BooleanUtil.isTrue(getNotifyEnabled())) { return true; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java new file mode 100644 index 000000000..735f2e887 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Map; + +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.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +@Tag(name = "管理后台 - 跟进记录") +@RestController +@RequestMapping("/crm/follow-up-record") +@Validated +public class CrmFollowUpRecordController { + + @Resource + private CrmFollowUpRecordService followUpRecordService; + @Resource + private CrmContactService contactService; + @Resource + private CrmBusinessService businessService; + + @PostMapping("/create") + @Operation(summary = "创建跟进记录") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')") + public CommonResult createFollowUpRecord(@Valid @RequestBody CrmFollowUpRecordSaveReqVO createReqVO) { + return success(followUpRecordService.createFollowUpRecord(createReqVO)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除跟进记录") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:delete')") + public CommonResult deleteFollowUpRecord(@RequestParam("id") Long id) { + followUpRecordService.deleteFollowUpRecord(id, getLoginUserId()); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得跟进记录") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") + public CommonResult getFollowUpRecord(@RequestParam("id") Long id) { + CrmFollowUpRecordDO followUpRecord = followUpRecordService.getFollowUpRecord(id); + return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得跟进记录分页") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") + public CommonResult> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) { + PageResult pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); + /// 拼接数据 + Map contactMap = convertMap(contactService.getContactList( + convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId); + Map businessMap = convertMap(businessService.getBusinessList( + convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId); + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> { + record.setContactNames(new ArrayList<>()).setBusinessNames(new ArrayList<>()); + record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, + contact -> record.getContactNames().add(contact.getName()))); + record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, + business -> record.getBusinessNames().add(business.getName()))); + }); + return success(voPageResult); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java new file mode 100644 index 000000000..78c28a08f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.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 CrmFollowUpRecordPageReqVO extends PageParam { + + @Schema(description = "数据类型", example = "2") + private Integer bizType; + + @Schema(description = "数据编号", example = "5564") + private Long bizId; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java new file mode 100644 index 000000000..8d4b145b6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_FOLLOW_UP_TYPE; + +@Schema(description = "管理后台 - 跟进记录 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmFollowUpRecordRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28800") + private Long id; + + @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer bizType; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5564") + private Long bizId; + + @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @DictFormat(CRM_FOLLOW_UP_TYPE) + private Integer type; + + @Schema(description = "跟进内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "下次联系时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime nextTime; + + @Schema(description = "关联的商机编号数组") + private List businessIds; + @Schema(description = "关联的商机名称数组") + private List businessNames; + + @Schema(description = "关联的联系人编号数组") + private List contactIds; + @Schema(description = "关联的联系人名称数组") + private List contactNames; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java new file mode 100644 index 000000000..b6d0e13c7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 跟进记录新增/修改 Request VO") +@Data +public class CrmFollowUpRecordSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28800") + private Long id; + + @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "数据类型不能为空") + private Integer bizType; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5564") + @NotNull(message = "数据编号不能为空") + private Long bizId; + + @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "跟进类型不能为空") + private Integer type; + + @Schema(description = "跟进内容", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "跟进内容不能为空") + private String content; + + @Schema(description = "下次联系时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime nextTime; + + @Schema(description = "关联的商机编号数组") + private List businessIds; + + @Schema(description = "关联的联系人编号数组") + private List contactIds; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/CrmMessageController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/CrmMessageController.java new file mode 100644 index 000000000..32a0eb6ca --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/CrmMessageController.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.crm.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.message.CrmMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM消息") +@RestController +@RequestMapping("/crm/message") +@Validated +public class CrmMessageController { + + @Resource + private CrmMessageService crmMessageService; + + // TODO 芋艿:未来可能合并到 CrmCustomerController + @GetMapping("/todayCustomer") // TODO @dbh52:【优先级低】url 使用中划线,项目规范。然后叫 today-customer-page,通过 page 体现出它是个分页接口 + @Operation(summary = "今日需联系客户") + @PreAuthorize("@ss.hasPermission('crm:message:todayCustomer')") + public CommonResult> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO) { + PageResult pageResult = crmMessageService.getTodayCustomerPage(pageReqVO, getLoginUserId()); + return success(BeanUtils.toBean(pageResult, CrmCustomerRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/vo/CrmTodayCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/vo/CrmTodayCustomerPageReqVO.java new file mode 100644 index 000000000..f47dfb468 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/vo/CrmTodayCustomerPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.crm.controller.admin.message.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.enums.message.CrmContactStatusEnum; +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 CrmTodayCustomerPageReqVO extends PageParam { + + // TODO @dbh52:CrmContactStatusEnum 可以直接枚举三个 Integer;一般来说,枚举类尽量给数据模型用,这样枚举类少,更聚焦;这里的枚举,更多是专门给这个接口用的哈 + + @Schema(description = "联系状态", example = "1") + @InEnum(CrmContactStatusEnum.class) + private Integer contactStatus; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java index 32d4a1ab7..5dc3807f5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java @@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionR import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.PostApi; @@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.util.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java index 2ce156ebd..fc4192443 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.product.vo.product.CrmProductPageReqVO; @@ -15,18 +16,21 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -34,9 +38,12 @@ import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_PRODUCT_TYPE; @Tag(name = "管理后台 - CRM 产品") @RestController @@ -48,7 +55,8 @@ public class CrmProductController { private CrmProductService productService; @Resource private CrmProductCategoryService productCategoryService; - + @Resource + private OperateLogApi operateLogApi; @Resource private AdminUserApi adminUserApi; @@ -86,7 +94,7 @@ public class CrmProductController { return success(null); } Map userMap = adminUserApi.getUserMap( - SetUtils.asSet( Long.valueOf(product.getCreator()), product.getOwnerUserId())); + SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId())); CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId()); return success(CrmProductConvert.INSTANCE.convert(product, userMap, category)); } @@ -95,7 +103,7 @@ public class CrmProductController { @Operation(summary = "获得产品分页") @PreAuthorize("@ss.hasPermission('crm:product:query')") public CommonResult> getProductPage(@Valid CrmProductPageReqVO pageVO) { - PageResult pageResult = productService.getProductPage(pageVO); + PageResult pageResult = productService.getProductPage(pageVO, getLoginUserId()); return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal())); } @@ -104,9 +112,9 @@ public class CrmProductController { @PreAuthorize("@ss.hasPermission('crm:product:export')") @OperateLog(type = EXPORT) public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = productService.getProductPage(exportReqVO).getList(); + List list = productService.getProductPage(exportReqVO, getLoginUserId()).getList(); // 导出 Excel ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class, getProductDetailList(list)); @@ -123,4 +131,14 @@ public class CrmProductController { return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList); } + @GetMapping("/operate-log-page") + @Operation(summary = "获得产品操作日志") + @PreAuthorize("@ss.hasPermission('crm:product:query')") + public CommonResult> getProductOperateLog(@RequestParam("bizId") Long bizId) { + OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + reqVO.setBizType(CRM_PRODUCT_TYPE).setBizId(bizId); + return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class))); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java index 6772612f4..bb17806a8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; +import com.mzt.logapi.starter.annotation.DiffLogField; import lombok.*; import io.swagger.v3.oas.annotations.media.Schema; @@ -14,6 +15,7 @@ public class CrmProductCategoryCreateReqVO{ @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") @NotNull(message = "分类名称不能为空") + @DiffLogField(name = "分类名称") private String name; @Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4680") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java index c6c3919e9..01b2ae443 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java @@ -1,9 +1,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.product.vo.product; -import lombok.*; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmProductStatusParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmProductUnitParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; - import jakarta.validation.constraints.NotNull; +import lombok.Data; @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO") @Data @@ -14,28 +16,35 @@ public class CrmProductSaveReqVO { @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "好产品") @NotNull(message = "产品名称不能为空") + @DiffLogField(name = "产品名称") private String name; @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "12306") @NotNull(message = "产品编码不能为空") + @DiffLogField(name = "产品编码") private String no; @Schema(description = "单位", example = "2") + @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME) private Integer unit; @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") @NotNull(message = "价格不能为空") + @DiffLogField(name = "价格") private Long price; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") @NotNull(message = "状态不能为空") + @DiffLogField(name = "状态", function = CrmProductStatusParseFunction.NAME) private Integer status; @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @NotNull(message = "产品分类编号不能为空") + @DiffLogField(name = "产品分类编号") private Long categoryId; @Schema(description = "产品描述", example = "你说的对") + @DiffLogField(name = "产品描述") private String description; @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31926") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java index 45ea6f020..8516ebd66 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java @@ -23,22 +23,24 @@ 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 jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; 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 @@ -60,7 +62,7 @@ public class CrmReceivableController { @Operation(summary = "创建回款") @PreAuthorize("@ss.hasPermission('crm:receivable:create')") public CommonResult createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) { - return success(receivableService.createReceivable(createReqVO)); + return success(receivableService.createReceivable(createReqVO, getLoginUserId())); } @PutMapping("/update") @@ -93,16 +95,16 @@ public class CrmReceivableController { @Operation(summary = "获得回款分页") @PreAuthorize("@ss.hasPermission('crm:receivable:query')") public CommonResult> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) { - PageResult pageResult = receivableService.getReceivablePage(pageReqVO); - return success(convertDetailReceivablePage(pageResult)); + PageResult pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId()); + return success(buildReceivableDetailPage(pageResult)); } @GetMapping("/page-by-customer") @Operation(summary = "获得回款分页,基于指定客户") public CommonResult> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) { Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); - PageResult pageResult = receivableService.getReceivablePageByCustomer(pageReqVO); - return success(convertDetailReceivablePage(pageResult)); + PageResult pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO); + return success(buildReceivableDetailPage(pageResult)); } // TODO 芋艿:后面在优化导出 @@ -111,20 +113,21 @@ public class CrmReceivableController { @PreAuthorize("@ss.hasPermission('crm:receivable:export')") @OperateLog(type = EXPORT) public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO, - HttpServletResponse response) throws IOException { - PageResult pageResult = receivableService.getReceivablePage(exportReqVO); + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId()); // 导出 Excel ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class, - convertDetailReceivablePage(pageResult).getList()); + buildReceivableDetailPage(pageResult).getList()); } /** - * 转换成详细的回款分页,即读取关联信息 + * 构建详细的回款分页结果 * - * @param pageResult 回款分页 - * @return 详细的回款分页 + * @param pageResult 简单的回款分页结果 + * @return 详细的回款分页结果 */ - private PageResult convertDetailReceivablePage(PageResult pageResult) { + private PageResult buildReceivableDetailPage(PageResult pageResult) { List receivableList = pageResult.getList(); if (CollUtil.isEmpty(receivableList)) { return PageResult.empty(pageResult.getTotal()); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java index fe569a087..481914e8b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java @@ -25,22 +25,24 @@ 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 jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; 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 @@ -64,7 +66,7 @@ public class CrmReceivablePlanController { @Operation(summary = "创建回款计划") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) { - return success(receivablePlanService.createReceivablePlan(createReqVO)); + return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); } @PutMapping("/update") @@ -97,7 +99,7 @@ public class CrmReceivablePlanController { @Operation(summary = "获得回款计划分页") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") public CommonResult> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) { - PageResult pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO); + PageResult pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId()); return success(convertDetailReceivablePlanPage(pageResult)); } @@ -105,7 +107,7 @@ public class CrmReceivablePlanController { @Operation(summary = "获得回款计划分页,基于指定客户") public CommonResult> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) { Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); - PageResult pageResult = receivablePlanService.getReceivablePlanPageByCustomer(pageReqVO); + PageResult pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO); return success(convertDetailReceivablePlanPage(pageResult)); } @@ -115,18 +117,19 @@ public class CrmReceivablePlanController { @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')") @OperateLog(type = EXPORT) public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO, - HttpServletResponse response) throws IOException { - PageResult pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO); + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()); // 导出 Excel ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class, convertDetailReceivablePlanPage(pageResult).getList()); } /** - * 转换成详细的回款计划分页,即读取关联信息 + * 构建详细的回款计划分页结果 * - * @param pageResult 回款计划分页 - * @return 详细的回款计划分页 + * @param pageResult 简单的回款计划分页结果 + * @return 详细的回款计划分页结果 */ private PageResult convertDetailReceivablePlanPage(PageResult pageResult) { List receivablePlanList = pageResult.getList(); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java index b9249c2f2..f86aa346d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -19,4 +21,8 @@ public class CrmReceivablePlanPageReqVO extends PageParam { @Schema(description = "合同名称", example = "3473") private Long contractId; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java index 9d0d4eb21..1bca32fa3 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -21,4 +23,8 @@ public class CrmReceivablePageReqVO extends PageParam { @Schema(description = "客户编号", example = "4963") private Long customerId; + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java index affb57b55..63c8ab7d6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.crm.convert.business; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; @@ -9,7 +11,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.util.List; @@ -27,35 +28,21 @@ public interface CrmBusinessConvert { CrmBusinessConvert INSTANCE = Mappers.getMapper(CrmBusinessConvert.class); - CrmBusinessDO convert(CrmBusinessCreateReqVO bean); - - CrmBusinessDO convert(CrmBusinessUpdateReqVO bean); - - CrmBusinessRespVO convert(CrmBusinessDO bean); - List convert(List bean); - - PageResult convertPage(PageResult page); - - List convertList02(List list); - - @Mappings({ - @Mapping(target = "bizId", source = "reqVO.id"), - @Mapping(target = "newOwnerUserId", source = "reqVO.id") - }) + @Mapping(target = "bizId", source = "reqVO.id") CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); - default PageResult convertPage(PageResult page, List customerList, + default PageResult convertPage(PageResult pageResult, List customerList, List statusTypeList, List statusList) { - PageResult result = convertPage(page); + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class); // 拼接关联字段 Map customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName); Map statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName); Map statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName); - result.getList().forEach(type -> type + voPageResult.getList().forEach(type -> type .setCustomerName(customerMap.get(type.getCustomerId())) .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId())) .setStatusName(statusMap.get(type.getStatusId()))); - return result; + return voPageResult; } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java new file mode 100644 index 000000000..2fcd54d84 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.crm.convert.businessproduct; + +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +// TODO @lzxhqs:看看是不是用 BeanUtils 替代了 +/** + * @author lzxhqs + * @version 1.0 + * @title CrmBusinessProductConvert + * @description + * @create 2024/1/12 + */ +@Mapper +public interface CrmBusinessProductConvert { + CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class); + + CrmBusinessProductDO convert(CrmBusinessProductSaveReqVO product); +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java index db49e5a6d..df2532b27 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.convert.businessstatus; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -19,10 +18,6 @@ public interface CrmBusinessStatusConvert { CrmBusinessStatusConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusConvert.class); - CrmBusinessStatusDO convert(CrmBusinessStatusSaveReqVO bean); - - CrmBusinessStatusRespVO convert(CrmBusinessStatusDO bean); - List convertList(List list); PageResult convertPage(PageResult page); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java index ae7e36122..be203b580 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.convert.businessstatustype; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; @@ -15,6 +14,7 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +// TODO @lzxhqs:看看是不是用 BeanUtils 替代了 /** * 商机状态类型 Convert * @@ -25,8 +25,6 @@ public interface CrmBusinessStatusTypeConvert { CrmBusinessStatusTypeConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusTypeConvert.class); - CrmBusinessStatusTypeDO convert(CrmBusinessStatusTypeSaveReqVO bean); - CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean); PageResult convertPage(PageResult page); @@ -40,10 +38,7 @@ public interface CrmBusinessStatusTypeConvert { } default CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean, List statusList) { - // TODO @ljlleo 可以链式赋值,简化成一行; - CrmBusinessStatusTypeRespVO result = convert(bean); - result.setStatusList(statusList); - return result; + return convert(bean).setStatusList(statusList); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java index 76ea428c7..39e607bcb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java @@ -1,13 +1,10 @@ package cn.iocoder.yudao.module.crm.convert.clue; -import java.util.*; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; - +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*; -import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; /** * 线索 Convert @@ -19,14 +16,7 @@ public interface CrmClueConvert { CrmClueConvert INSTANCE = Mappers.getMapper(CrmClueConvert.class); - CrmClueDO convert(CrmClueCreateReqVO bean); - - CrmClueDO convert(CrmClueUpdateReqVO bean); - - CrmClueRespVO convert(CrmClueDO bean); - - PageResult convertPage(PageResult page); - - List convertList02(List list); + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmClueTransferReqVO reqVO, Long userId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java deleted file mode 100644 index 636835be7..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java +++ /dev/null @@ -1,107 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.contact; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*; -import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -// TODO 芋艿:convert 后面在梳理下,略微有点乱 -/** - * CRM 联系人 Convert - * - * @author 芋道源码 - */ -@Mapper -public interface ContactConvert { - - ContactConvert INSTANCE = Mappers.getMapper(ContactConvert.class); - - CrmContactDO convert(CrmContactCreateReqVO bean); - - CrmContactDO convert(CrmContactUpdateReqVO bean); - - CrmContactRespVO convert(CrmContactDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - default PageResult convertPage(PageResult pageResult, Map userMap, - List customerList, List parentContactList) { - List list = converList(pageResult.getList(), userMap, customerList, parentContactList); - return convertPage(pageResult).setList(list); - } - - List convertAllList(List list); - - @Mappings({ - @Mapping(target = "bizId", source = "reqVO.id"), - @Mapping(target = "newOwnerUserId", source = "reqVO.id") - }) - CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId); - - /** - * 转换详情信息 - * - * @param contactDO 联系人 - * @param userMap 用户列表 - * @param crmCustomerDOList 客户 - * @return ContactRespVO - */ - default CrmContactRespVO convert(CrmContactDO contactDO, Map userMap, List crmCustomerDOList, - List contactList) { - CrmContactRespVO contactVO = convert(contactDO); - setUserInfo(contactVO, userMap); - Map ustomerMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId, v -> v)); - Map contactMap = contactList.stream().collect(Collectors.toMap(CrmContactDO::getId, v -> v)); - findAndThen(ustomerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName())); - findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName())); - return contactVO; - } - - default List converList(List contactList, Map userMap, - List customerList, List parentContactList) { - List result = convertList(contactList); - Map parentContactMap = convertMap(parentContactList, CrmContactDO::getId); - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - result.forEach(item -> { - setUserInfo(item, userMap); - findAndThen(customerMap, item.getCustomerId(), customer -> { // TODO @zyna:这里的 { 可以去掉 - item.setCustomerName(customer.getName()); - }); - findAndThen(parentContactMap, item.getParentId(), contactDO -> { // TODO @zyna:这里的 { 可以去掉 - item.setParentName(contactDO.getName()); - }); - }); - return result; - } - - /** - * 设置用户信息 - * - * @param contactRespVO 联系人Response VO - * @param userMap 用户信息 map - */ - static void setUserInfo(CrmContactRespVO contactRespVO, Map userMap) { - contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId())); - findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> { - contactRespVO.setOwnerUserName(user == null ? "" : user.getNickname()); - }); - findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname())); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java new file mode 100644 index 000000000..363fd4f60 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.crm.convert.contact; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * CRM 联系人 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface CrmContactConvert { + + CrmContactConvert INSTANCE = Mappers.getMapper(CrmContactConvert.class); + + CrmContactRespVO convert(CrmContactDO bean); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId); + + default PageResult convertPage(PageResult pageResult, Map userMap, + List customerList, List parentContactList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class); + // 拼接关联字段 + Map parentContactMap = convertMap(parentContactList, CrmContactDO::getId); + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + voPageResult.getList().forEach(item -> { + setUserInfo(item, userMap); + findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); + findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName())); + }); + return voPageResult; + } + + default CrmContactRespVO convert(CrmContactDO contactDO, Map userMap, + List customerList, List parentContactList) { + CrmContactRespVO contactVO = convert(contactDO); + setUserInfo(contactVO, userMap); + Map customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId); + Map contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId); + findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName())); + findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName())); + return contactVO; + } + + static void setUserInfo(CrmContactRespVO contactRespVO, Map userMap) { + contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId())); + findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname())); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java deleted file mode 100644 index dfb71bad5..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.contactbusinessslink; - -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -// TODO @zyna:使用 BeanUtils 慢慢替代现有的 mapstruct 哈 -@Mapper -public interface CrmContactBusinessLinkConvert { - CrmContactBusinessLinkConvert INSTANCE = Mappers.getMapper(CrmContactBusinessLinkConvert.class); - CrmContactBusinessLinkDO convert(CrmContactBusinessLinkSaveReqVO bean); - List convert(List bean); -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/ContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/ContractConvert.java deleted file mode 100644 index 2041ba5de..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/ContractConvert.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.contract; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*; -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * 合同 Convert - * - * @author dhb52 - */ -@Mapper -public interface ContractConvert { - - ContractConvert INSTANCE = Mappers.getMapper(ContractConvert.class); - - CrmContractDO convert(CrmContractCreateReqVO bean); - - CrmContractDO convert(CrmContractUpdateReqVO bean); - - ContractRespVO convert(CrmContractDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - List convertList02(List list); - - @Mappings({ - @Mapping(target = "bizId", source = "reqVO.id"), - @Mapping(target = "newOwnerUserId", source = "reqVO.id") - }) - CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); - - default PageResult convertPage(PageResult pageResult, Map userMap, - List customerList) { - return new PageResult<>(converList(pageResult.getList(), userMap, customerList), pageResult.getTotal()); - } - - default List converList(List contractList, Map userMap, - List customerList) { - List result = convertList(contractList); - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - result.forEach(item -> { - setUserInfo(item, userMap); - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); - }); - return result; - } - - static void setUserInfo(ContractRespVO contract, Map userMap) { - findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname())); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java new file mode 100644 index 000000000..599d998a6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.crm.convert.contract; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 合同 Convert + * + * @author dhb52 + */ +@Mapper +public interface CrmContractConvert { + + CrmContractConvert INSTANCE = Mappers.getMapper(CrmContractConvert.class); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); + + default PageResult convertPage(PageResult pageResult, Map userMap, + List customerList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmContractRespVO.class); + // 拼接关联字段 + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + voPageResult.getList().forEach(contract -> { + findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname())); + findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName())); + }); + return voPageResult; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java index fcf643f66..6a6642968 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java @@ -1,21 +1,18 @@ package cn.iocoder.yudao.module.crm.convert.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; -import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; @@ -30,18 +27,29 @@ public interface CrmCustomerConvert { CrmCustomerConvert INSTANCE = Mappers.getMapper(CrmCustomerConvert.class); - CrmCustomerDO convert(CrmCustomerCreateReqVO bean); + default CrmCustomerRespVO convert(CrmCustomerDO customer, Map userMap, + Map deptMap) { + CrmCustomerRespVO customerResp = BeanUtils.toBean(customer, CrmCustomerRespVO.class); + setUserInfo(customerResp, userMap, deptMap); + return customerResp; + } - CrmCustomerDO convert(CrmCustomerUpdateReqVO bean); - - CrmCustomerRespVO convert(CrmCustomerDO bean); + default PageResult convertPage(PageResult pageResult, Map userMap, + Map deptMap, Map poolDayMap) { + PageResult result = BeanUtils.toBean(pageResult, CrmCustomerRespVO.class); + result.getList().forEach(item -> { + setUserInfo(item, userMap, deptMap); + findAndThen(poolDayMap, item.getId(), item::setPoolDay); + }); + return result; + } /** * 设置用户信息 * - * @param customer CRM 客户 Response VO - * @param userMap 用户信息 map - * @param deptMap 用户部门信息 map + * @param customer CRM 客户 Response VO + * @param userMap 用户信息 map + * @param deptMap 用户部门信息 map */ static void setUserInfo(CrmCustomerRespVO customer, Map userMap, Map deptMap) { customer.setAreaName(AreaUtils.format(customer.getAreaId())); @@ -52,34 +60,7 @@ public interface CrmCustomerConvert { findAndThen(userMap, Long.parseLong(customer.getCreator()), user -> customer.setCreatorName(user.getNickname())); } - List convertList02(List list); - - @Mappings({ - @Mapping(target = "bizId", source = "reqVO.id"), - @Mapping(target = "newOwnerUserId", source = "reqVO.id") - }) + @Mapping(target = "bizId", source = "reqVO.id") CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId); - PageResult convertPage(PageResult page); - - default CrmCustomerRespVO convert(CrmCustomerDO customer, Map userMap, - Map deptMap) { - CrmCustomerRespVO customerResp = convert(customer); - setUserInfo(customerResp, userMap, deptMap); - return customerResp; - } - - default PageResult convertPage(PageResult pageResult, Map userMap, - Map deptMap) { - PageResult result = convertPage(pageResult); - result.getList().forEach(item -> setUserInfo(item, userMap, deptMap)); - return result; - } - - CrmCustomerPoolConfigRespVO convert(CrmCustomerPoolConfigDO customerPoolConfig); - - CrmCustomerPoolConfigDO convert(CrmCustomerPoolConfigSaveReqVO updateReqVO); - - List convertQueryAll(List crmCustomerDO); - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java index 13a59a1ec..81550b378 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java @@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.crm.convert.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; @@ -24,40 +23,20 @@ public interface CrmCustomerLimitConfigConvert { CrmCustomerLimitConfigConvert INSTANCE = Mappers.getMapper(CrmCustomerLimitConfigConvert.class); - CrmCustomerLimitConfigDO convert(CrmCustomerLimitConfigCreateReqVO bean); - - CrmCustomerLimitConfigDO convert(CrmCustomerLimitConfigUpdateReqVO bean); - - CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - default PageResult convertPage(PageResult pageResult, - Map userMap, Map deptMap) { - PageResult result = convertPage(pageResult); - result.getList().forEach(respVo -> fillNameField(userMap, deptMap, respVo)); - return result; + default PageResult convertPage( + PageResult pageResult, + Map userMap, Map deptMap) { + List list = CollectionUtils.convertList(pageResult.getList(), + limitConfig -> convert(limitConfig, userMap, deptMap)); + return new PageResult<>(list, pageResult.getTotal()); } - default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO customerLimitConfig, + default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO limitConfig, Map userMap, Map deptMap) { - CrmCustomerLimitConfigRespVO respVo = convert(customerLimitConfig); - fillNameField(userMap, deptMap, respVo); - return respVo; - } - - /** - * 填充名称字段 - * - * @param userMap 用户映射 - * @param deptMap 部门映射 - * @param respVo 响应实体 - */ - static void fillNameField(Map userMap, Map deptMap, CrmCustomerLimitConfigRespVO respVo) { - respVo.setUsers(CollectionUtils.convertList(respVo.getUserIds(), userMap::get)); - respVo.setDepts(CollectionUtils.convertList(respVo.getDeptIds(), deptMap::get)); + CrmCustomerLimitConfigRespVO limitConfigVO = BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class); + limitConfigVO.setUsers(CollectionUtils.convertList(limitConfigVO.getUserIds(), userMap::get)); + limitConfigVO.setDepts(CollectionUtils.convertList(limitConfigVO.getDeptIds(), deptMap::get)); + return limitConfigVO; } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java index 0f3c77e5f..5b81e67ad 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.crm.convert.permission; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO; @@ -15,6 +16,7 @@ import com.google.common.collect.Multimaps; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -46,10 +48,12 @@ public interface CrmPermissionConvert { return CollectionUtils.convertList(convert(permission), item -> { findAndThen(userMap, item.getUserId(), user -> { item.setNickname(user.getNickname()); - findAndThen(deptMap, user.getDeptId(), deptRespDTO -> { - item.setDeptName(deptRespDTO.getName()); - }); + findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName())); List postRespList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds()); + if (CollUtil.isEmpty(postRespList)) { + item.setPostNames(Collections.emptySet()); + return; + } item.setPostNames(CollectionUtils.convertSet(postRespList, PostRespDTO::getName)); }); return item; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java index 27502103d..3b0c23aae 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; @@ -33,29 +34,19 @@ public interface CrmReceivableConvert { CrmReceivableRespVO convert(CrmReceivableDO bean); - List convertList(List list); - default PageResult convertPage(PageResult pageResult, Map userMap, List customerList, List contractList) { - return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList), pageResult.getTotal()); - } - - default List converList(List receivableList, Map userMap, - List customerList, List contractList) { - List result = convertList(receivableList); + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class); + // 拼接关联字段 Map customerMap = convertMap(customerList, CrmCustomerDO::getId); Map contractMap = convertMap(contractList, CrmContractDO::getId); - result.forEach(item -> { - setUserInfo(item, userMap); - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); - findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo())); + voPageResult.getList().forEach(receivable -> { + findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname())); + findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName())); + findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo())); }); - return result; - } - - static void setUserInfo(CrmReceivableRespVO receivable, Map userMap) { - findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname())); + return voPageResult; } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java index a89140d07..9b6bb3e82 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; @@ -34,33 +35,22 @@ public interface CrmReceivablePlanConvert { CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean); - List convertList(List list); - default PageResult convertPage(PageResult pageResult, Map userMap, List customerList, List contractList, List receivableList) { - return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList, receivableList), pageResult.getTotal()); - } - - default List converList(List receivablePlanList, Map userMap, - List customerList, List contractList, - List receivableList) { - List result = convertList(receivablePlanList); + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class); + // 拼接关联字段 Map customerMap = convertMap(customerList, CrmCustomerDO::getId); Map contractMap = convertMap(contractList, CrmContractDO::getId); Map receivableMap = convertMap(receivableList, CrmReceivableDO::getId); - result.forEach(item -> { - setUserInfo(item, userMap); - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); - findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo())); - findAndThen(receivableMap, item.getReceivableId(), receivable -> item.setReturnType(receivable.getReturnType())); + voPageResult.getList().forEach(receivablePlan -> { + findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname())); + findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName())); + findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo())); + findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType())); }); - return result; - } - - static void setUserInfo(CrmReceivablePlanRespVO receivablePlan, Map userMap) { - findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname())); + return voPageResult; } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java index 2bc1daa41..e11ec5935 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -52,6 +54,7 @@ public class CrmBusinessDO extends BaseDO { * 客户编号 * * TODO @ljileo:这个字段,后续要写下关联的实体哈 + * 关联 {@link CrmCustomerDO#getId()} */ private Long customerId; /** @@ -77,9 +80,9 @@ public class CrmBusinessDO extends BaseDO { */ private String remark; /** - * 1赢单2输单3无效 + * 结束状态 * - * TODO @lijie:搞个枚举; + * 枚举 {@link CrmBizEndStatus} */ private Integer endStatus; /** @@ -93,8 +96,15 @@ public class CrmBusinessDO extends BaseDO { /** * 跟进状态 * - * TODO @lijie:目前就是 Boolean;是否跟进 + * TODO @lzxhqs:目前就是 Boolean;是否跟进 */ private Integer followUpStatus; + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java new file mode 100644 index 000000000..4558684e9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.business; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * 商机产品关联表 DO + * + * @author lzxhqs + */ +@TableName("crm_business_product") +@KeySequence("crm_business_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmBusinessProductDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 商机编号 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + private Long businessId; + + /** + * 产品编号 + * + * 关联 {@link CrmProductDO#getId()} + */ + private Long productId; + + // TODO @lzxhqs:改成 Integer,单位:分。目前整体倾向放大 100 倍哈 + /** + * 产品单价 + */ + private BigDecimal price; + + /** + * 销售价格 + */ + private BigDecimal salesPrice; + + // TODO @lzxhqs:改成 count + /** + * 数量 + */ + private BigDecimal num; + + // TODO @lzxhqs:改成 discountPercent + /** + * 折扣 + */ + private BigDecimal discount; + + // TODO @lzxhqs:改成 totalPrice;总计价格,和现有项目风格一致; + /** + * 小计(折扣后价格) + */ + private BigDecimal subtotal; + + /** + * 单位 + */ + private String unit; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java index f83d0fb27..17ce4f88c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java @@ -35,11 +35,9 @@ public class CrmBusinessStatusDO { */ private String name; /** - * 赢单率 - * - * TODO 这里是不是改成 Integer 存储,百分比 * 100 ; + * 赢单率,百分比 */ - private String percent; + private Integer percent; /** * 排序 */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java index d0d2f11f2..aa9f86251 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -43,6 +44,7 @@ public class CrmBusinessStatusTypeDO extends BaseDO { * 开启状态 * * TODO 改成 Integer,关联 CommonStatus + * 枚举 {@link CommonStatusEnum} */ private Boolean status; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java deleted file mode 100644 index df6e44536..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 商机(销售机会) - */ -package cn.iocoder.yudao.module.crm.dal.dataobject.business; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java index 592301b44..7d85c6ac1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java @@ -73,6 +73,13 @@ public class CrmClueDO extends BaseDO { */ private String remark; + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + // TODO 芋艿:客户级别; // TODO 芋艿:线索来源; // TODO 芋艿:客户行业; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java similarity index 61% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java index ad6c898d7..46185a5ac 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java @@ -1,28 +1,26 @@ -package cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink; +package cn.iocoder.yudao.module.crm.dal.dataobject.contact; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; -// TODO @zyna:可以放到 contact 包下 /** - * CRM 联系人商机关联 DO + * CRM 联系人与商机的关联 DO * * @author 芋道源码 */ -@TableName("crm_contact_business_link") -@KeySequence("crm_contact_business_link_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@TableName("crm_contact_business") +@KeySequence("crm_contact_business_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor -public class CrmContactBusinessLinkDO extends BaseDO { +public class CrmContactBusinessDO extends BaseDO { /** * 主键 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java index 75d6bd565..c2dec247d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.contact; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -29,9 +30,11 @@ public class CrmContactDO extends BaseDO { @TableId private Long id; /** - * 下次联系时间 + * 客户编号 + * + * 关联 {@link CrmCustomerDO#getId()} */ - private LocalDateTime nextTime; + private Long customerId; /** * 手机号 */ @@ -45,21 +48,20 @@ public class CrmContactDO extends BaseDO { */ private String email; /** - * 客户编号 + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 */ - private Long customerId; + private Integer areaId; /** - * 地址 + * 详细地址 */ - private String address; + private String detailAddress; /** * 备注 */ private String remark; - /** - * 最后跟进时间 - */ - private LocalDateTime lastTime; + /** * 直属上级 * @@ -100,10 +102,16 @@ public class CrmContactDO extends BaseDO { private Long ownerUserId; /** - * 所在地 - * - * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + * 最后跟进时间 */ - private Integer areaId; + private LocalDateTime contactLastTime; + /** + * 最后跟进内容 + */ + private String contactLastContent; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java index c5826b7c3..96e4bf520 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java @@ -12,7 +12,7 @@ import java.time.LocalDateTime; // TODO 芋艿:调整下字段 /** - * 客户 DO + * CRM 客户 DO * * @author Wanwan */ @@ -104,17 +104,24 @@ public class CrmCustomerDO extends BaseDO { */ private Long ownerUserId; /** - * 地区编号 + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 */ private Integer areaId; /** * 详细地址 */ private String detailAddress; + /** * 最后跟进时间 */ private LocalDateTime contactLastTime; + /** + * 最后跟进内容 + */ + private String contactLastContent; /** * 下次联系时间 */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java new file mode 100644 index 000000000..896ad0297 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.followup; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 跟进记录 DO + * + * 用于记录客户、联系人的每一次跟进 + * + * @author 芋道源码 + */ +@TableName(value = "crm_follow_up_record", autoResultMap = true) +@KeySequence("crm_follow_up_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmFollowUpRecordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 数据类型 + * + * 枚举 {@link CrmBizTypeEnum} + */ + private Integer bizType; + /** + * 数据编号 + * + * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段 + */ + private Long bizId; + + /** + * 跟进类型 + * + * 关联 {@link DictTypeConstants#CRM_FOLLOW_UP_TYPE} 字典 + */ + private Integer type; + /** + * 跟进内容 + */ + private String content; + /** + * 下次联系时间 + */ + private LocalDateTime nextTime; + + /** + * 关联的商机编号数组 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List businessIds; + /** + * 关联的联系人编号数组 + * + * 关联 {@link CrmContactDO#getId()} + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List contactIds; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java index 0f3964bf7..d01352007 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java @@ -3,12 +3,16 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; +import java.util.List; /** * 商机 Mapper @@ -18,18 +22,43 @@ import java.util.Collection; @Mapper public interface CrmBusinessMapper extends BaseMapperX { - default PageResult selectPage(CrmBusinessPageReqVO reqVO, Collection ids) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .in(CrmBusinessDO::getId, ids) - .likeIfPresent(CrmBusinessDO::getName, reqVO.getName()) + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmBusinessDO::getId, id) + .set(CrmBusinessDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) .orderByDesc(CrmBusinessDO::getId)); } - default PageResult selectPageByCustomer(CrmContractPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eq(CrmBusinessDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 - .likeIfPresent(CrmBusinessDO::getName, reqVO.getName()) + default PageResult selectPageByContactId(CrmBusinessPageReqVO pageReqVO, Collection businessIds) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .in(CrmBusinessDO::getId, businessIds) // 指定商机编号 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) .orderByDesc(CrmBusinessDO::getId)); } + default PageResult selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), + CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmBusinessDO.class) + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) + .orderByDesc(CrmBusinessDO::getId); + return selectJoinPage(pageReqVO, CrmBusinessDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId); + return selectJoinList(CrmBusinessDO.class, query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java new file mode 100644 index 000000000..5750491d8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.business; + + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商机产品 Mapper // TODO @lzxhqs:类注释,和作者之间要有一个空行 + * @author lzxhqs + */ +@Mapper +public interface CrmBusinessProductMapper extends BaseMapperX { + default void deleteByBusinessId(Long id) { // TODO @lzxhqs:第一个方法,和类之间最好空一行; + delete(CrmBusinessProductDO::getBusinessId, id); + } + + default CrmBusinessProductDO selectByBusinessId(Long id) { // TODO @lzxhqs:id 最好改成 businessId,上面也是;这样一看更容易懂 + return selectOne(CrmBusinessProductDO::getBusinessId, id); + } +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java index af10bf8c7..410ebf050 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java @@ -28,4 +28,17 @@ public interface CrmBusinessStatusTypeMapper extends BaseMapperX { - default PageResult selectPage(CrmCluePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(CrmClueDO::getName, reqVO.getName()) - .likeIfPresent(CrmClueDO::getTelephone, reqVO.getTelephone()) - .likeIfPresent(CrmClueDO::getMobile, reqVO.getMobile()) - .orderByDesc(CrmClueDO::getId)); + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmClueDO::getId, id) + .set(CrmClueDO::getOwnerUserId, ownerUserId)); } - default List selectList(CrmClueExportReqVO reqVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(CrmClueDO::getTransformStatus, reqVO.getTransformStatus()) - .eqIfPresent(CrmClueDO::getFollowUpStatus, reqVO.getFollowUpStatus()) - .likeIfPresent(CrmClueDO::getName, reqVO.getName()) - .eqIfPresent(CrmClueDO::getCustomerId, reqVO.getCustomerId()) - .betweenIfPresent(CrmClueDO::getContactNextTime, reqVO.getContactNextTime()) - .likeIfPresent(CrmClueDO::getTelephone, reqVO.getTelephone()) - .likeIfPresent(CrmClueDO::getMobile, reqVO.getMobile()) - .likeIfPresent(CrmClueDO::getAddress, reqVO.getAddress()) - .betweenIfPresent(CrmClueDO::getContactLastTime, reqVO.getContactLastTime()) - .betweenIfPresent(CrmClueDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(CrmClueDO::getId)); + default PageResult selectPage(CrmCluePageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), + CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); + // 拼接自身的查询条件 + query.selectAll(CrmClueDO.class) + .likeIfPresent(CrmClueDO::getName, pageReqVO.getName()) + .likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone()) + .likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile()) + .orderByDesc(CrmClueDO::getId); + return selectJoinPage(pageReqVO, CrmClueDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId); + return selectJoinList(CrmClueDO.class, query); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java index 4c8f82f1a..8b2fb76bd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.contact; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; +import java.util.List; + /** * CRM 联系人 Mapper * @@ -15,22 +22,21 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmContactMapper extends BaseMapperX { - // TODO @puhui999:数据权限 - default PageResult selectPage(CrmContactPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(CrmContactDO::getMobile, reqVO.getMobile()) - .eqIfPresent(CrmContactDO::getTelephone, reqVO.getTelephone()) - .eqIfPresent(CrmContactDO::getEmail, reqVO.getEmail()) - .eqIfPresent(CrmContactDO::getCustomerId, reqVO.getCustomerId()) - .likeIfPresent(CrmContactDO::getName, reqVO.getName()) - .eqIfPresent(CrmContactDO::getQq, reqVO.getQq()) - .eqIfPresent(CrmContactDO::getWechat, reqVO.getWechat()) - .orderByDesc(CrmContactDO::getId)); + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContactDO::getId, id) + .set(CrmContactDO::getOwnerUserId, ownerUserId)); } - default PageResult selectPageByCustomer(CrmContactPageReqVO pageVO) { + default int updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContactDO::getCustomerId, customerId) + .set(CrmContactDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmContactPageReqVO pageVO) { return selectPage(pageVO, new LambdaQueryWrapperX() - .eq(CrmContactDO::getCustomerId, pageVO.getCustomerId()) // 必须传递 + .eq(CrmContactDO::getCustomerId, pageVO.getCustomerId()) // 指定客户编号 .likeIfPresent(CrmContactDO::getName, pageVO.getName()) .eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile()) .eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone()) @@ -40,4 +46,28 @@ public interface CrmContactMapper extends BaseMapperX { .orderByDesc(CrmContactDO::getId)); } + default PageResult selectPage(CrmContactPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), + CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmContactDO.class) + .likeIfPresent(CrmContactDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContactDO::getMobile, pageReqVO.getMobile()) + .eqIfPresent(CrmContactDO::getTelephone, pageReqVO.getTelephone()) + .eqIfPresent(CrmContactDO::getEmail, pageReqVO.getEmail()) + .eqIfPresent(CrmContactDO::getQq, pageReqVO.getQq()) + .eqIfPresent(CrmContactDO::getWechat, pageReqVO.getWechat()) + .orderByDesc(CrmContactDO::getId); + return selectJoinPage(pageReqVO, CrmContactDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId); + return selectJoinList(CrmContactDO.class, query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java deleted file mode 100644 index 1692def86..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * CRM 联系人商机关联 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface CrmContactBusinessLinkMapper extends BaseMapperX { - - default PageResult selectPage(CrmContactBusinessLinkPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId()) - .eqIfPresent(CrmContactBusinessLinkDO::getBusinessId, reqVO.getBusinessId()) - .betweenIfPresent(CrmContactBusinessLinkDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(CrmContactBusinessLinkDO::getId)); - } // TODO @zyna:方法和方法之间要有空行 - default PageResult selectPageByContact(CrmContactBusinessLinkPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId()) - .orderByDesc(CrmContactBusinessLinkDO::getId)); - } -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java new file mode 100644 index 000000000..3eae483bc --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 联系人与商机的关联 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmContactBusinessMapper extends BaseMapperX { + + default CrmContactBusinessDO selectByContactIdAndBusinessId(Long contactId, Long businessId) { + return selectOne(CrmContactBusinessDO::getContactId, contactId, + CrmContactBusinessDO::getBusinessId, businessId); + } + + default void deleteByContactIdAndBusinessId(Long contactId, Collection businessIds) { + delete(new LambdaQueryWrapper() + .eq(CrmContactBusinessDO::getContactId, contactId) + .in(CrmContactBusinessDO::getBusinessId, businessIds)); + } + + default List selectListByContactId(Long contactId) { + return selectList(CrmContactBusinessDO::getContactId, contactId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java index a22912162..eb21a3d5b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.contract; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; +import java.util.List; + /** * CRM 合同 Mapper * @@ -15,22 +22,49 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmContractMapper extends BaseMapperX { - default PageResult selectPage(CrmContractPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(CrmContractDO::getNo, reqVO.getNo()) - .likeIfPresent(CrmContractDO::getName, reqVO.getName()) - .eqIfPresent(CrmContractDO::getCustomerId, reqVO.getCustomerId()) - .eqIfPresent(CrmContractDO::getBusinessId, reqVO.getBusinessId()) - .orderByDesc(CrmContractDO::getId)); + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContractDO::getId, id) + .set(CrmContractDO::getOwnerUserId, ownerUserId)); } - default PageResult selectPageByCustomer(CrmContractPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eq(CrmContractDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 - .likeIfPresent(CrmContractDO::getNo, reqVO.getNo()) - .likeIfPresent(CrmContractDO::getName, reqVO.getName()) - .eqIfPresent(CrmContractDO::getBusinessId, reqVO.getBusinessId()) + default PageResult selectPageByCustomerId(CrmContractPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo()) + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) .orderByDesc(CrmContractDO::getId)); } + default PageResult selectPage(CrmContractPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX mpjLambdaWrapperX = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), + CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + mpjLambdaWrapperX.selectAll(CrmContractDO.class) + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo()) + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) + .orderByDesc(CrmContractDO::getId); + return selectJoinPage(pageReqVO, CrmContractDO.class, mpjLambdaWrapperX); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX mpjLambdaWrapperX = new MPJLambdaWrapperX<>(); + // 构建数据权限连表条件 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId); + return selectJoinList(CrmContractDO.class, mpjLambdaWrapperX); + } + + default Long selectCountByContactId(Long contactId) { + return selectCount(CrmContractDO::getContactId, contactId); + } + default CrmContractDO selectByBizId(Long businessId) { // TODO @lzxhqs:1)方法和方法之间要有空行;2)selectCountByBusinessId,一个是应该求数量,一个是不要缩写 BizId 可读性; + return selectOne(CrmContractDO::getBusinessId, businessId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java index f5fb31b61..08beaf808 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmC import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + /** * 客户限制配置 Mapper * @@ -21,4 +23,15 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX selectListByTypeAndUserIdAndDeptId( + Integer type, Long userId, Long deptId) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(CrmCustomerLimitConfigDO::getType, type); + query.apply("FIND_IN_SET({0}, user_ids) > 0", userId); + if (deptId != null) { + query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId); + } + return selectList(query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java index 0ce9d9f03..125249d14 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java @@ -1,18 +1,21 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryPageUtils; +import cn.iocoder.yudao.module.crm.enums.message.CrmContactStatusEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Mapper; +import org.springframework.lang.Nullable; +import java.time.LocalDate; import java.util.Collection; import java.util.List; @@ -24,41 +27,97 @@ import java.util.List; @Mapper public interface CrmCustomerMapper extends BaseMapperX { + default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) { + return selectCount(new LambdaUpdateWrapper() + .eq(CrmCustomerDO::getLockStatus, lockStatus) + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId)); + } + + default Long selectCountByDealStatusAndOwnerUserId(@Nullable Boolean dealStatus, Long ownerUserId) { + return selectCount(new LambdaQueryWrapperX() + .eqIfPresent(CrmCustomerDO::getDealStatus, dealStatus) + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId)); + } + default int updateOwnerUserIdById(Long id, Long ownerUserId) { return update(new LambdaUpdateWrapper() .eq(CrmCustomerDO::getId, id) .set(CrmCustomerDO::getOwnerUserId, ownerUserId)); } - /** - * 获取客户分页 - * - * @param pageReqVO 请求 - * @param userId 用户编号 - * @param subordinateIds 下属用户编号 - * @param isAdmin 是否为管理 - * @return 客户分页数据 - */ - default PageResult selectPage(CrmCustomerPageReqVO pageReqVO, Long userId, Collection subordinateIds, Boolean isAdmin) { - IPage mpPage = MyBatisUtils.buildPage(pageReqVO); - MPJLambdaWrapperX mpjLambdaWrapperX = new MPJLambdaWrapperX<>(); - // 构建数据权限连表条件 - CrmQueryPageUtils.builderQuery(mpjLambdaWrapperX, pageReqVO, userId, - CrmBizTypeEnum.CRM_CUSTOMER.getType(), CrmCustomerDO::getId, subordinateIds, isAdmin); - mpjLambdaWrapperX.selectAll(CrmCustomerDO.class) + default PageResult selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class) .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName()) .eqIfPresent(CrmCustomerDO::getMobile, pageReqVO.getMobile()) .eqIfPresent(CrmCustomerDO::getIndustryId, pageReqVO.getIndustryId()) .eqIfPresent(CrmCustomerDO::getLevel, pageReqVO.getLevel()) .eqIfPresent(CrmCustomerDO::getSource, pageReqVO.getSource()); - // 特殊:不分页,直接查询全部 - // TODO @puhui999:下面这个,封装一个方法;从 56 到 61 这里哈; - if (PageParam.PAGE_SIZE_NONE.equals(pageReqVO.getPageNo())) { - List list = selectJoinList(CrmCustomerDO.class, mpjLambdaWrapperX); - return new PageResult<>(list, (long) list.size()); + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, userId); + return selectJoinList(CrmCustomerDO.class, query); + } + + /** + * 待办事项 - 今日需联系客户 + * + * @param pageReqVO 分页请求参数 + * @param userId 当前用户ID + * @return 分页结果 + */ + default PageResult selectTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); + + query.selectAll(CrmCustomerDO.class) + .leftJoin(CrmFollowUpRecordDO.class, CrmFollowUpRecordDO::getBizId, CrmCustomerDO::getId) + .eq(CrmFollowUpRecordDO::getType, CrmBizTypeEnum.CRM_CUSTOMER.getType()); + + // 拼接自身的查询条件 + // TODO @dbh52:这里不仅仅要获得 today、tomorrow。而是 today 要获取今天的 00:00:00 这种; + LocalDate today = LocalDate.now(); + LocalDate tomorrow = today.plusDays(1); + LocalDate yesterday = today.minusDays(1); + if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.NEEDED_TODAY.getType())) { + // 今天需联系: + // 1.【客户】的【下一次联系时间】 是【今天】 + // 2. 无法找到【今天】创建的【跟进】记录 + query.between(CrmCustomerDO::getContactNextTime, today, tomorrow) + // TODO @dbh52:是不是查询 CrmCustomerDO::contactLastTime < today?因为今天联系过,应该会更新该字段,减少链表查询; + .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow) + .isNull(CrmFollowUpRecordDO::getId); + } else if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.EXPIRED.getType())) { + // 已逾期: + // 1. 【客户】的【下一次联系时间】 <= 【昨天】 + // 2. 无法找到【今天】创建的【跟进】记录 + // TODO @dbh52:是不是 contactNextTime 在当前时间之前,且 contactLastTime < contactNextTime?说白了,就是下次联系时间超过当前时间,且最后联系时间没去联系; + query.le(CrmCustomerDO::getContactNextTime, yesterday) + .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow) + .isNull(CrmFollowUpRecordDO::getId); + } else if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.ALREADY_CONTACT.getType())) { + // 已联系: + // 1.【客户】的【下一次联系时间】 是【今天】 + // 2. 找到【今天】创建的【跟进】记录 + query.between(CrmCustomerDO::getContactNextTime, today, tomorrow) + // TODO @dbh52:contactLastTime 是今天 + .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow) + .isNotNull(CrmFollowUpRecordDO::getId); + } else { + // TODO: 参数错误,是不是要兜一下底;直接抛出异常就好啦; } - mpPage = selectJoinPage(mpPage, CrmCustomerDO.class, mpjLambdaWrapperX); - return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java index 1461ff6dd..06cf44e4f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import org.apache.ibatis.annotations.Mapper; @@ -11,4 +12,9 @@ import org.apache.ibatis.annotations.Mapper; */ @Mapper public interface CrmCustomerPoolConfigMapper extends BaseMapperX { + + default CrmCustomerPoolConfigDO selectOne() { + return selectOne(new LambdaQueryWrapperX().last("LIMIT 1")); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java new file mode 100644 index 000000000..a9b1dc315 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.followup; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 跟进记录 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmFollowUpRecordMapper extends BaseMapperX { + + default PageResult selectPage(CrmFollowUpRecordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(CrmFollowUpRecordDO::getBizType, reqVO.getBizType()) + .eqIfPresent(CrmFollowUpRecordDO::getBizId, reqVO.getBizId()) + .orderByDesc(CrmFollowUpRecordDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java index 349318502..71c0368ca 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; import java.util.List; /** @@ -28,6 +29,12 @@ public interface CrmPermissionMapper extends BaseMapperX { .eq(CrmPermissionDO::getBizId, bizId)); } + default List selectByBizTypeAndBizIds(Integer bizType, Collection bizIds) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .in(CrmPermissionDO::getBizId, bizIds)); + } + default List selectListByBizTypeAndUserId(Integer bizType, Long userId) { return selectList(new LambdaQueryWrapperX() .eq(CrmPermissionDO::getBizType, bizType) @@ -46,4 +53,17 @@ public interface CrmPermissionMapper extends BaseMapperX { .eq(CrmPermissionDO::getId, id).eq(CrmPermissionDO::getUserId, userId)); } + default int deletePermission(Integer bizType, Long bizId) { + return delete(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId)); + } + + default Long selectListByBiz(Collection bizTypes, Collection bizIds, Collection userIds) { + return selectCount(new LambdaQueryWrapperX() + .in(CrmPermissionDO::getBizType, bizTypes) + .in(CrmPermissionDO::getBizId, bizIds) + .in(CrmPermissionDO::getUserId, userIds)); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java index 5b15e1d12..30a07eec2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; import org.apache.ibatis.annotations.Mapper; /** @@ -15,11 +17,17 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmProductMapper extends BaseMapperX { - default PageResult selectPage(CrmProductPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() + default PageResult selectPage(CrmProductPageReqVO reqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_PRODUCT.getType(), + CrmProductDO::getId, userId, null, Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmProductDO.class) .likeIfPresent(CrmProductDO::getName, reqVO.getName()) .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus()) - .orderByDesc(CrmProductDO::getId)); + .orderByDesc(CrmProductDO::getId); + return selectJoinPage(reqVO, CrmProductDO.class, query); } default CrmProductDO selectByNo(String no) { diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java index 9da130022..a20da86a6 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; +import java.util.List; + /** * 回款 Mapper * @@ -15,15 +22,13 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmReceivableMapper extends BaseMapperX { - default PageResult selectPage(CrmReceivablePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo()) - .eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId()) - .eqIfPresent(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) - .orderByDesc(CrmReceivableDO::getId)); + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmReceivableDO::getId, id) + .set(CrmReceivableDO::getOwnerUserId, ownerUserId)); } - default PageResult selectPageByCustomer(CrmReceivablePageReqVO reqVO) { + default PageResult selectPageByCustomerId(CrmReceivablePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo()) @@ -31,4 +36,24 @@ public interface CrmReceivableMapper extends BaseMapperX { .orderByDesc(CrmReceivableDO::getId)); } + default PageResult selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), + CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmReceivableDO.class) + .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo()) + .eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId()) + .orderByDesc(CrmReceivableDO::getId); + return selectJoinPage(pageReqVO, CrmReceivableDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId); + return selectJoinList(CrmReceivableDO.class, query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java index 52d16f0e1..ed578f1fd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java @@ -3,10 +3,17 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; +import java.util.List; + /** * 回款计划 Mapper * @@ -15,18 +22,37 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmReceivablePlanMapper extends BaseMapperX { - default PageResult selectPage(CrmReceivablePlanPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) - .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId()) - .orderByDesc(CrmReceivablePlanDO::getId)); + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmReceivablePlanDO::getId, id) + .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId)); } - default PageResult selectPageByCustomer(CrmReceivablePlanPageReqVO reqVO) { + default PageResult selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId()) .orderByDesc(CrmReceivablePlanDO::getId)); } + default PageResult selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), + CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmReceivablePlanDO.class) + .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmReceivablePlanDO::getContractId, pageReqVO.getContractId()) + .orderByDesc(CrmReceivablePlanDO::getId); + return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), ids, userId); + return selectJoinList(CrmReceivablePlanDO.class, query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java deleted file mode 100644 index 4a3e65722..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.yudao.module.crm.framework.core; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java new file mode 100644 index 000000000..d8bb50961 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 商机的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmBusinessParseFunction implements IParseFunction { + + public static final String NAME = "getBusinessById"; + + @Resource + private CrmBusinessService businessService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmBusinessDO businessDO = businessService.getBusiness(Long.parseLong(value.toString())); + return businessDO == null ? "" : businessDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java new file mode 100644 index 000000000..91e8fd215 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 联系人的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmContactParseFunction implements IParseFunction { + + public static final String NAME = "getContactById"; + + @Resource + private CrmContactService contactService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmContactDO contactDO = contactService.getContact(Long.parseLong(value.toString())); + return contactDO == null ? "" : contactDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java new file mode 100644 index 000000000..d3c58522e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 合同的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmContractParseFunction implements IParseFunction { + + public static final String NAME = "getContractById"; + + @Resource + private CrmContractService contractService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmContractDO contract = contractService.getContract(Long.parseLong(value.toString())); + return contract == null ? "" : contract.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java new file mode 100644 index 000000000..ae3e0b23f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; + +/** + * 行业的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerIndustryParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerIndustry"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java new file mode 100644 index 000000000..40bb6fb72 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL; + +/** + * 客户等级的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerLevelParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerLevel"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java new file mode 100644 index 000000000..a58c0455d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 客户的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerById"; + + @Resource + private CrmCustomerService customerService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmCustomerDO crmCustomerDO = customerService.getCustomer(Long.parseLong(value.toString())); + return crmCustomerDO == null ? "" : crmCustomerDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java new file mode 100644 index 000000000..95377a88e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE; + +/** + * CRM 客户来源的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerSourceParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerSource"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java new file mode 100644 index 000000000..cf81f5e31 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 产品状态的 {@link IParseFunction} 实现类 + * + * @author anhaohao + */ +@Component +@Slf4j +public class CrmProductStatusParseFunction implements IParseFunction { + + public static final String NAME = "getProductStatusName"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CRM_PRODUCT_STATUS, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java new file mode 100644 index 000000000..898bedd07 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 产品单位的 {@link IParseFunction} 实现类 + * + * @author anhaohao + */ +@Component +@Slf4j +public class CrmProductUnitParseFunction implements IParseFunction { + + public static final String NAME = "getProductUnitName"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CRM_PRODUCT_UNIT, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java new file mode 100644 index 000000000..05f96c586 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 管理员名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysAdminUserParseFunction implements IParseFunction { + + public static final String NAME = "getAdminUserById"; + + @Resource + private AdminUserApi adminUserApi; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取用户信息 + AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString())); + if (user == null) { + log.warn("[apply][获取用户{{}}为空", value); + return ""; + } + // 返回格式 芋道源码(13888888888) + String nickname = user.getNickname(); + if (StrUtil.isEmpty(user.getMobile())) { + return nickname; + } + return StrUtil.format("{}({})", nickname, user.getMobile()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java new file mode 100644 index 000000000..3ccc76912 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 地名的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysAreaParseFunction implements IParseFunction { + + public static final String NAME = "getArea"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return AreaUtils.format(Integer.parseInt(value.toString())); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java new file mode 100644 index 000000000..3e1000bf9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 是否类型的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class SysBooleanParseFunction implements IParseFunction { + + public static final String NAME = "getBoolean"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java new file mode 100644 index 000000000..6d01f9f97 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 管理员名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysDeptParseFunction implements IParseFunction { + + public static final String NAME = "getDeptById"; + + @Resource + private DeptApi deptApi; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取部门信息 + DeptRespDTO dept = deptApi.getDept(Long.parseLong(value.toString())); + if (dept == null) { + log.warn("[apply][获取部门{{}}为空", value); + return ""; + } + return dept.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java new file mode 100644 index 000000000..ccff080a2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 行业的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class SysSexParseFunction implements IParseFunction { + + public static final String NAME = "getSex"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java new file mode 100644 index 000000000..975a2eb51 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java similarity index 87% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java index 410637829..9ef6d4d02 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.framework.core.annotations; +package cn.iocoder.yudao.module.crm.framework.permission.core.annotations; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; @@ -35,9 +35,8 @@ public @interface CrmPermission { /** * 数据编号,通过 Spring EL 表达式获取 - * TODO 数据权限完成后去除 default "" */ - String bizId() default ""; + String bizId(); /** * 操作所需权限级别 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java similarity index 62% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java index 40a05872f..14e7c71fe 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.framework.core.aop; +package cn.iocoder.yudao.module.crm.framework.permission.core.aop; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; @@ -6,25 +6,24 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS; /** * Crm 数据权限校验 AOP 切面 @@ -41,27 +40,47 @@ public class CrmPermissionAspect { @Before("@annotation(crmPermission)") public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { - // TODO 芋艿:临时,方便大家调试 - //if (true) { - // return; - //} - // 获取相关属性值 + // 1.1 获取相关属性值 Map expressionValues = parseExpressions(joinPoint, crmPermission); Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ? crmPermission.bizType()[0].getType() : (Integer) expressionValues.get(crmPermission.bizTypeValue()); // 模块类型 - Long bizId = (Long) expressionValues.get(crmPermission.bizId()); // 模块数据编号 + // 1.2 处理兼容多个 bizId 的情况 + Object object = expressionValues.get(crmPermission.bizId()); // 模块数据编号 + Set bizIds = new HashSet<>(); + if (object instanceof Collection) { + bizIds.addAll(convertSet((Collection) object, item -> Long.parseLong(item.toString()))); + } else { + bizIds.add(Long.parseLong(object.toString())); + } Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别 - // TODO 如果是超级管理员则直接通过 - //if (superAdmin){ - // return; - //} + // 2. 逐个校验权限 + List permissionList = crmPermissionService.getPermissionListByBiz(bizType, bizIds); + Map> multiMap = convertMultiMap(permissionList, CrmPermissionDO::getBizId); + bizIds.forEach(bizId -> validatePermission(bizType, multiMap.get(bizId), permissionLevel)); + } - // 1. 获取数据权限 - List bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId); - if (CollUtil.isEmpty(bizPermissions)) { // 数据权限不存存那么数据也不存在 - throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, CrmBizTypeEnum.getNameByType(bizType)); + private void validatePermission(Integer bizType, List bizPermissions, Integer permissionLevel) { + // 1. 如果是超级管理员则直接通过 + if (CrmPermissionUtils.isCrmAdmin()) { + return; } + // 1.1 没有数据权限的情况 + if (CollUtil.isEmpty(bizPermissions)) { + // 公海数据如果没有团队成员大家也因该有读权限才对 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { + return; + } + // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); + } else { // 1.2 有数据权限但是没有负责人的情况 + if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) { + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { + return; + } + } + } + // 2.1 情况一:如果自己是负责人,则默认有所有权限 CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId())); if (userPermission != null) { @@ -82,9 +101,8 @@ public class CrmPermissionAspect { } } } - // 2.4 没有权限! - // 打个 info 日志,方便后续排查问题、审计 - log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", + // 2.4 没有权限,抛出异常 + log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计 getUserId(), permissionLevel, toJsonString(userPermission)); throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java new file mode 100644 index 000000000..d895fe969 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java new file mode 100644 index 000000000..43bb729b6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core.util; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * 数据权限工具类 + * + * @author HUIHUI + */ +public class CrmPermissionUtils { + + /** + * 校验用户是否是 CRM 管理员 + * + * @return 是/否 + */ + public static boolean isCrmAdmin() { + return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode()); + } + + /** + * 静态内部类实现单例获取 + * + * @author HUIHUI + */ + private static class SingletonManager { + + private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class); + + public static PermissionApi getPermissionApi() { + return PERMISSION_API; + } + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java new file mode 100644 index 000000000..44f408016 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.permission; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/vo/CrmBasePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/vo/CrmBasePageReqVO.java deleted file mode 100644 index 14070fa19..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/vo/CrmBasePageReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.crm.framework.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.crm.enums.common.CrmSceneEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Schema(description = "管理后台 - CRM 分页 Base Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -public class CrmBasePageReqVO extends PageParam { - - /** - * 场景类型,为 null 时则表示全部 - */ - @Schema(description = "场景类型", example = "1") - @InEnum(CrmSceneEnum.class) - private Integer sceneType; - - @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") - private Boolean pool; // null 则表示为不是公海数据 - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java index 82839ec24..8dae99eb8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java @@ -1,15 +1,15 @@ package cn.iocoder.yudao.module.crm.service.business; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; - +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateFollowUpReqBO; import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -27,14 +27,21 @@ public interface CrmBusinessService { * @param userId 用户编号 * @return 编号 */ - Long createBusiness(@Valid CrmBusinessCreateReqVO createReqVO, Long userId); + Long createBusiness(@Valid CrmBusinessSaveReqVO createReqVO, Long userId); /** * 更新商机 * * @param updateReqVO 更新信息 */ - void updateBusiness(@Valid CrmBusinessUpdateReqVO updateReqVO); + void updateBusiness(@Valid CrmBusinessSaveReqVO updateReqVO); + + /** + * 更新商机相关跟进信息 + * + * @param updateFollowUpReqBOList 跟进信息 + */ + void updateContactFollowUpBatch(List updateFollowUpReqBOList); /** * 删除商机 @@ -51,6 +58,14 @@ public interface CrmBusinessService { */ CrmBusinessDO getBusiness(Long id); + /** + * 获得商机列表 + * + * @param ids 编号 + * @return 商机列表 + */ + List getBusinessList(Collection ids, Long userId); + /** * 获得商机列表 * @@ -76,9 +91,19 @@ public interface CrmBusinessService { * 数据权限:基于 {@link CrmCustomerDO} 读取 * * @param pageReqVO 分页查询 - * @return 联系人分页 + * @return 商机分页 */ - PageResult getBusinessPageByCustomer(CrmContractPageReqVO pageReqVO); + PageResult getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO); + + /** + * 获得商机分页,基于指定联系人 + * + * 数据权限:基于 {@link CrmContactDO} 读取 + * + * @param pageReqVO 分页参数 + * @return 商机分页 + */ + PageResult getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO); /** * 商机转移 @@ -88,4 +113,12 @@ public interface CrmBusinessService { */ void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId); + /** + * 获取关联客户的商机数量 + * + * @param customerId 客户编号 + * @return 数量 + */ + Long getBusinessCountByCustomerId(Long customerId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java index 54946d052..a4d08f315 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java @@ -3,29 +3,45 @@ package cn.iocoder.yudao.module.crm.service.business; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO; import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; +import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert; 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.dal.dataobject.business.CrmBusinessProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; +import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; /** * 商机 Service 实现类 @@ -40,43 +56,157 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { private CrmBusinessMapper businessMapper; @Resource - private CrmPermissionService crmPermissionService; + private CrmBusinessProductMapper businessProductMapper; + // TODO @lzxhqs:不直接调用这个 mapper,要调用对方的 service;每个业务独立收敛 + @Resource + private CrmContractMapper contractMapper; + + // TODO @lzxhqs:不直接调用这个 mapper,要调用对方的 service;每个业务独立收敛 + @Resource + private CrmContactBusinessMapper contactBusinessMapper; + @Resource + private CrmPermissionService permissionService; + @Resource + private CrmContactBusinessService contactBusinessService; @Override @Transactional(rollbackFor = Exception.class) - public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) { - // 插入 - CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO); + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}", + success = CRM_BUSINESS_CREATE_SUCCESS) + public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 插入商机 + CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class) + .setOwnerUserId(userId); businessMapper.insert(business); + // TODO 商机待定:插入商机与产品的关联表;校验商品存在 + // TODO lzxhqs:新增时,是不是不用调用这个方法哈; + verifyCrmBusinessProduct(business.getId()); + // TODO @lzxhqs:用 CollUtils.isNotEmpty; + if (!createReqVO.getProducts().isEmpty()) { + createBusinessProducts(createReqVO.getProducts(), business.getId()); + } + // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表 + createContactBusiness(business.getId(), createReqVO.getContactId()); - // 创建数据权限 - crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) - .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + // 2. 创建数据权限 + // 设置当前操作的人为负责人 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) + .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); - // 返回 + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("business", business); return business.getId(); } - @Override - @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", - level = CrmPermissionLevelEnum.WRITE) - public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) { - // 校验存在 - validateBusinessExists(updateReqVO.getId()); - // 更新 - CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO); - businessMapper.updateById(updateObj); + // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈; + /** + * @param businessId 商机id + * @param contactId 联系人id + * @throws + * @description 联系人与商机的关联 + * @author lzxhqs + */ + private void createContactBusiness(Long businessId, Long contactId) { + CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO(); + contactBusiness.setBusinessId(businessId); + contactBusiness.setContactId(contactId); + contactBusinessMapper.insert(contactBusiness); + + } + + // TODO @lzxhqs:这个方法注释格式不对;删除@description,然后把 插入商机产品关联表 作为方法注释; + /** + * @param products 产品集合 + * @description 插入商机产品关联表 + * @author lzxhqs + */ + private void createBusinessProducts(List products, Long businessId) { + // TODO @lzxhqs:可以用 CollectionUtils.convertList; + List list = new ArrayList<>(); + for (CrmBusinessProductSaveReqVO product : products) { + CrmBusinessProductDO businessProductDO = CrmBusinessProductConvert.INSTANCE.convert(product); + businessProductDO.setBusinessId(businessId); + list.add(businessProductDO); + } + businessProductMapper.insertBatch(list); + } + + /** + * @param id businessId + * @description 校验管理的产品存在则删除 + * @author lzxhqs + */ + private void verifyCrmBusinessProduct(Long id) { + CrmBusinessProductDO businessProductDO = businessProductMapper.selectByBusinessId(id); + if (businessProductDO != null) { + //通过商机Id删除 + businessProductMapper.deleteByBusinessId(id); + } + } @Override @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, level = CrmPermissionLevelEnum.WRITE) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_BUSINESS_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) { + // 1. 校验存在 + CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); + + // 2. 更新商机 + CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class); + businessMapper.updateById(updateObj); + // TODO 商机待定:插入商机与产品的关联表;校验商品存在 + // TODO @lzxhqs:更新时,可以调用 CollectionUtils 的 diffList,尽量避免这种先删除再插入;而是新增的插入、变更的更新,没的删除;不然这个表每次更新,会多好多数据; + verifyCrmBusinessProduct(updateReqVO.getId()); + if (!updateReqVO.getProducts().isEmpty()) { + createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId()); + } + + // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表 + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class)); + LogRecordContext.putVariable("businessName", oldBusiness.getName()); + } + + @Override + public void updateContactFollowUpBatch(List updateFollowUpReqBOList) { + businessMapper.updateBatch(BeanUtils.toBean(updateFollowUpReqBOList, CrmBusinessDO.class)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_BUSINESS_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteBusiness(Long id) { // 校验存在 - validateBusinessExists(id); + CrmBusinessDO business = validateBusinessExists(id); + // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段 + validateContractExists(id); + // 删除 businessMapper.deleteById(id); + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("businessName", business.getName()); + } + + /** + * @param businessId 商机id + * @throws + * @description 删除校验合同是关联合同 + * @author lzxhqs + */ + private void validateContractExists(Long businessId) { + CrmContractDO contract = contractMapper.selectByBizId(businessId); + if (contract != null) { + throw exception(BUSINESS_CONTRACT_EXISTS); + } } private CrmBusinessDO validateBusinessExists(Long id) { @@ -87,12 +217,42 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { return crmBusiness; } + @Override - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS,bizId = "#id", level = CrmPermissionLevelEnum.READ) + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_BUSINESS_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) { + // 1 校验商机是否存在 + CrmBusinessDO business = validateBusinessExists(reqVO.getId()); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())); + // 2.2 设置新的负责人 + businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 记录操作日志上下文 + LogRecordContext.putVariable("business", business); + } + + //======================= 查询相关 ======================= + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmBusinessDO getBusiness(Long id) { return businessMapper.selectById(id); } + @Override + public List getBusinessList(Collection ids, Long userId) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return businessMapper.selectBatchIds(ids, userId); + } + @Override public List getBusinessList(Collection ids) { if (CollUtil.isEmpty(ids)) { @@ -103,36 +263,32 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @Override public PageResult getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) { - // 1. 获取当前用户能看的分页数据 - // TODO @puhui999:如果业务的数据量比较大,in 太多可能有性能问题噢;看看是不是搞成 join 连表了;可以微信讨论下; - List permissions = crmPermissionService.getPermissionListByBizTypeAndUserId( - CrmBizTypeEnum.CRM_BUSINESS.getType(), userId); - Set ids = convertSet(permissions, CrmPermissionDO::getBizId); - if (CollUtil.isEmpty(ids)) { // 没得说明没有什么给他看的 - return PageResult.empty(); - } - - // 2. 获取商机分页数据 - return businessMapper.selectPage(pageReqVO, ids); + return businessMapper.selectPage(pageReqVO, userId); } @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) - public PageResult getBusinessPageByCustomer(CrmContractPageReqVO pageReqVO) { - return businessMapper.selectPageByCustomer(pageReqVO); + public PageResult getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO) { + return businessMapper.selectPageByCustomerId(pageReqVO); } @Override - @Transactional(rollbackFor = Exception.class) - public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) { - // 1 校验商机是否存在 - validateBusinessExists(reqVO.getId()); + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#pageReqVO.contactId", level = CrmPermissionLevelEnum.READ) + public PageResult getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO) { + // 1. 查询关联的商机编号 + List contactBusinessList = contactBusinessService.getContactBusinessListByContactId( + pageReqVO.getContactId()); + if (CollUtil.isEmpty(contactBusinessList)) { + return PageResult.empty(); + } + // 2. 查询商机分页 + return businessMapper.selectPageByContactId(pageReqVO, + convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId)); + } - // 2. 数据权限转移 - crmPermissionService.transferPermission( - CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())); - - // 3. TODO 记录转移日志 + @Override + public Long getBusinessCountByCustomerId(Long customerId) { + return businessMapper.selectCount(CrmBusinessDO::getCustomerId, customerId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java index ffad53556..a2fc2d18d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import jakarta.validation.Valid; + +import java.util.Collection; import java.util.List; /** @@ -64,4 +66,12 @@ public interface CrmBusinessStatusService { */ List selectList(CrmBusinessStatusQueryVO queryVO); + /** + * 获得商机状态列表 + * + * @param ids 编号数组 + * @return 商机状态列表 + */ + List getBusinessStatusList(Collection ids); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java index fa6ad3d03..2e49e99d7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java @@ -12,6 +12,7 @@ import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; +import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -77,4 +78,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService { return businessStatusMapper.selectList(queryVO); } + @Override + public List getBusinessStatusList(Collection ids) { + return businessStatusMapper.selectBatchIds(ids); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java index 39dc3128c..20509994e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java @@ -5,8 +5,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; - import jakarta.validation.Valid; + +import java.util.Collection; import java.util.List; /** @@ -63,4 +64,12 @@ public interface CrmBusinessStatusTypeService { */ List selectList(CrmBusinessStatusTypeQueryVO queryVO); + /** + * 获得商机状态类型列表 + * + * @param ids 编号数组 + * @return 商机状态类型列表 + */ + List getBusinessStatusTypeList(Collection ids); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java index 501780abd..d9845976b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java @@ -17,6 +17,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; + +import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -93,14 +95,18 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe // TODO @ljlleo 这个方法,这个参考 validateDeptNameUnique 实现。 private void validateBusinessStatusTypeExists(String name, Long id) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapperX<>(); - if(null != id) { - wrapper.ne(CrmBusinessStatusTypeDO::getId, id); - } - long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name)); - if (cnt > 0) { + CrmBusinessStatusTypeDO businessStatusTypeDO = businessStatusTypeMapper.selectByIdAndName(id, name); + if (businessStatusTypeDO != null) { throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); } +// LambdaQueryWrapper wrapper = new LambdaQueryWrapperX<>(); +// if(null != id) { +// wrapper.ne(CrmBusinessStatusTypeDO::getId, id); +// } +// long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name)); +// if (cnt > 0) { +// throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); +// } } @Override @@ -118,4 +124,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe return businessStatusTypeMapper.selectList(queryVO); } + @Override + public List getBusinessStatusTypeList(Collection ids) { + return businessStatusTypeMapper.selectBatchIds(ids); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateFollowUpReqBO.java new file mode 100644 index 000000000..ccf8d0a0a --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateFollowUpReqBO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.business.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @puhui999:是不是搞个通用的 ReqBO 就好了 +/** + * 商机跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmBusinessUpdateFollowUpReqBO { + + @Schema(description = "商机编号", example = "3167") + @NotNull(message = "商机编号不能为空") + private Long id; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + @NotNull(message = "最后跟进时间不能为空") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + @NotNull(message = "最后更进内容不能为空") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java deleted file mode 100644 index 8995e1242..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 商机(销售机会) - */ -package cn.iocoder.yudao.module.crm.service.business; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java index 26ba0b408..47ece94e2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java @@ -1,10 +1,16 @@ package cn.iocoder.yudao.module.crm.service.clue; -import java.util.*; -import jakarta.validation.*; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*; -import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.service.clue.bo.CrmClueUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; /** * 线索 Service 接口 @@ -19,14 +25,21 @@ public interface CrmClueService { * @param createReqVO 创建信息 * @return 编号 */ - Long createClue(@Valid CrmClueCreateReqVO createReqVO); + Long createClue(@Valid CrmClueSaveReqVO createReqVO); /** * 更新线索 * * @param updateReqVO 更新信息 */ - void updateClue(@Valid CrmClueUpdateReqVO updateReqVO); + void updateClue(@Valid CrmClueSaveReqVO updateReqVO); + + /** + * 更新线索相关的跟进信息 + * + * @param clueUpdateFollowUpReqBO 信息 + */ + void updateClueFollowUp(CrmClueUpdateFollowUpReqBO clueUpdateFollowUpReqBO); /** * 删除线索 @@ -49,22 +62,31 @@ public interface CrmClueService { * @param ids 编号 * @return 线索列表 */ - List getClueList(Collection ids); + List getClueList(Collection ids, Long userId); /** * 获得线索分页 * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return 线索分页 */ - PageResult getCluePage(CrmCluePageReqVO pageReqVO); + PageResult getCluePage(CrmCluePageReqVO pageReqVO, Long userId); /** - * 获得线索列表, 用于 Excel 导出 + * 线索转移 * - * @param exportReqVO 查询条件 - * @return 线索列表 + * @param reqVO 请求 + * @param userId 用户编号 */ - List getClueList(CrmClueExportReqVO exportReqVO); + void transferClue(CrmClueTransferReqVO reqVO, Long userId); + + /** + * 线索转化为客户 + * + * @param reqVO 线索编号 + * @param userId 用户编号 + */ + void translateCustomer(CrmClueTransformReqVO reqVO, Long userId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index 37efb98a5..4a6ff9575 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -2,25 +2,39 @@ package cn.iocoder.yudao.module.crm.service.clue; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueExportReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; 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.dal.mysql.clue.CrmClueMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.clue.bo.CrmClueUpdateFollowUpReqBO; 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.user.AdminUserApi; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; /** * 线索 Service 实现类 @@ -33,38 +47,56 @@ public class CrmClueServiceImpl implements CrmClueService { @Resource private CrmClueMapper clueMapper; + @Resource private CrmCustomerService customerService; + @Resource + private CrmPermissionService crmPermissionService; + + @Resource + private AdminUserApi adminUserApi; + @Override - public Long createClue(CrmClueCreateReqVO createReqVO) { - // 校验客户是否存在 - customerService.validateCustomer(createReqVO.getCustomerId()); + // TODO @min:补充相关几个方法的操作日志; + public Long createClue(CrmClueSaveReqVO createReqVO) { + // 校验关联数据 + validateRelationDataExists(createReqVO); + // 插入 - CrmClueDO clue = CrmClueConvert.INSTANCE.convert(createReqVO); + CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class); clueMapper.insert(clue); // 返回 return clue.getId(); } @Override - public void updateClue(CrmClueUpdateReqVO updateReqVO) { - // 校验存在 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateClue(CrmClueSaveReqVO updateReqVO) { + // 校验线索是否存在 validateClueExists(updateReqVO.getId()); - // 校验客户是否存在 - customerService.validateCustomer(updateReqVO.getCustomerId()); + // 校验关联数据 + validateRelationDataExists(updateReqVO); // 更新 - CrmClueDO updateObj = CrmClueConvert.INSTANCE.convert(updateReqVO); + CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class); clueMapper.updateById(updateObj); } @Override + public void updateClueFollowUp(CrmClueUpdateFollowUpReqBO clueUpdateFollowUpReqBO) { + clueMapper.updateById(BeanUtils.toBean(clueUpdateFollowUpReqBO, CrmClueDO.class)); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteClue(Long id) { // 校验存在 validateClueExists(id); // 删除 clueMapper.deleteById(id); + // 删除数据权限 + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id); } private void validateClueExists(Long id) { @@ -74,26 +106,78 @@ public class CrmClueServiceImpl implements CrmClueService { } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmClueDO getClue(Long id) { return clueMapper.selectById(id); } @Override - public List getClueList(Collection ids) { + public List getClueList(Collection ids, Long userId) { if (CollUtil.isEmpty(ids)) { return ListUtil.empty(); } - return clueMapper.selectBatchIds(ids); + return clueMapper.selectBatchIds(ids, userId); } @Override - public PageResult getCluePage(CrmCluePageReqVO pageReqVO) { - return clueMapper.selectPage(pageReqVO); + public PageResult getCluePage(CrmCluePageReqVO pageReqVO, Long userId) { + return clueMapper.selectPage(pageReqVO, userId); } @Override - public List getClueList(CrmClueExportReqVO exportReqVO) { - return clueMapper.selectList(exportReqVO); + public void transferClue(CrmClueTransferReqVO reqVO, Long userId) { + // 1 校验线索是否存在 + validateClueExists(reqVO.getId()); + + // 2.1 数据权限转移 + crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType())); + // 2.2 设置新的负责人 + clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. TODO 记录转移日志 + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) { + // 校验线索都存在 + Set clueIds = reqVO.getIds(); + List clues = getClueList(clueIds, userId); + if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) { + clueIds.removeAll(convertSet(clues, CrmClueDO::getId)); + // TODO @min:可以使用 StrUtil.join(",", clueIds) 简化这种常见操作 + throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds.stream().map(String::valueOf).collect(Collectors.joining(","))); + } + + // 过滤出未转化的客户 + // TODO @min:1)存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了;2)常见的过滤逻辑,可以使用 CollectionUtils.filterList() + List unTransformClues = clues.stream() + .filter(clue -> ObjectUtil.notEqual(Boolean.TRUE, clue.getTransformStatus())).toList(); + // 传入的线索中包含已经转化的情况,抛出业务异常 + if (ObjectUtil.notEqual(clues.size(), unTransformClues.size())) { + // TODO @min:可以使用 StrUtil.join(",", clueIds) 简化这种常见操作 + clueIds.removeAll(convertSet(unTransformClues, CrmClueDO::getId)); + throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, clueIds.stream().map(String::valueOf).collect(Collectors.joining(","))); + } + + // 遍历线索(未转化的线索),创建对应的客户 + unTransformClues.forEach(clue -> { + // 1. 创建客户 + CrmCustomerSaveReqVO customerSaveReqVO = BeanUtils.toBean(clue, CrmCustomerSaveReqVO.class).setId(null); + Long customerId = customerService.createCustomer(customerSaveReqVO, userId); + // TODO @puhui999:如果有跟进记录,需要一起转过去;提问:艿艿这里是复制线索所有的跟进吗?还是直接把线索相关的跟进 bizType、bizId 全改为关联客户? + // 2. 更新线索 + clueMapper.updateById(new CrmClueDO().setId(clue.getId()) + .setTransformStatus(Boolean.TRUE).setCustomerId(customerId)); + }); + } + + private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { + // 校验负责人 + if (Objects.nonNull(reqVO.getOwnerUserId()) && + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { + throw exception(USER_NOT_EXISTS); + } } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/bo/CrmClueUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/bo/CrmClueUpdateFollowUpReqBO.java new file mode 100644 index 000000000..d4697acc2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/bo/CrmClueUpdateFollowUpReqBO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.clue.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @puhui999:是不是搞个通用的 ReqBO 就好了 +/** + * 线索跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmClueUpdateFollowUpReqBO { + + @Schema(description = "线索编号", example = "3167") + @NotNull(message = "线索编号不能为空") + private Long id; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + @NotNull(message = "最后跟进时间不能为空") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + @NotNull(message = "最后更进内容不能为空") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java deleted file mode 100644 index 5cb8b6ec7..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 线索 - */ -package cn.iocoder.yudao.module.crm.service.clue; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java new file mode 100644 index 000000000..4a6d4c7fb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * CRM 联系人与商机的关联 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmContactBusinessService { + + /** + * 创建联系人与商机的关联 + * + * @param createReqVO 创建信息 + */ + void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO); + + /** + * 删除联系人与商机的关联 + * + * @param deleteReqVO 删除信息 + */ + void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO); + + /** + * 删除联系人与商机的关联,基于联系人编号 + * + * @param contactId 联系人编号 + */ + void deleteContactBusinessByContactId(Long contactId); + + /** + * 获得联系人与商机的关联列表,基于联系人编号 + * + * @param contactId 联系人编号 + * @return 联系人商机关联 + */ + List getContactBusinessListByContactId(Long contactId); + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java new file mode 100644 index 000000000..d2e667919 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.ArrayList; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS; + +// TODO @puhui999:数据权限的校验;每个操作; +/** + * 联系人与商机的关联 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmContactBusinessServiceImpl implements CrmContactBusinessService { + + @Resource + private CrmContactBusinessMapper contactBusinessMapper; + + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private CrmBusinessService businessService; + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private CrmContactService contactService; + + @Override + public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) { + CrmContactDO contact = contactService.getContact(createReqVO.getContactId()); + if (contact == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 遍历处理,考虑到一般数量不会太多,代码处理简单 + List saveDOList = new ArrayList<>(); + createReqVO.getBusinessIds().forEach(businessId -> { + CrmBusinessDO business = businessService.getBusiness(businessId); + if (business == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + // 关联判重 + if (contactBusinessMapper.selectByContactIdAndBusinessId(createReqVO.getContactId(), businessId) != null) { + return; + } + saveDOList.add(new CrmContactBusinessDO(null, createReqVO.getContactId(), businessId)); + }); + // 批量插入 + if (CollUtil.isNotEmpty(saveDOList)) { + contactBusinessMapper.insertBatch(saveDOList); + } + } + + @Override + public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) { + CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId()); + if (contact == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 直接删除 + contactBusinessMapper.deleteByContactIdAndBusinessId( + deleteReqVO.getContactId(), deleteReqVO.getBusinessIds()); + } + + @Override + public void deleteContactBusinessByContactId(Long contactId) { + contactBusinessMapper.delete(CrmContactBusinessDO::getContactId,contactId); + } + + @Override + public List getContactBusinessListByContactId(Long contactId) { + return contactBusinessMapper.selectListByContactId(contactId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java index 85aec14cc..5962db9a8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.module.crm.service.contact; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; - +import cn.iocoder.yudao.module.crm.service.contact.bo.CrmContactUpdateFollowUpReqBO; import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -22,17 +23,17 @@ public interface CrmContactService { * 创建联系人 * * @param createReqVO 创建信息 - * @param userId 用户编号 + * @param userId 用户编号 * @return 编号 */ - Long createContact(@Valid CrmContactCreateReqVO createReqVO, Long userId); + Long createContact(@Valid CrmContactSaveReqVO createReqVO, Long userId); /** * 更新联系人 * * @param updateReqVO 更新信息 */ - void updateContact(@Valid CrmContactUpdateReqVO updateReqVO); + void updateContact(@Valid CrmContactSaveReqVO updateReqVO); /** * 删除联系人 @@ -41,6 +42,29 @@ public interface CrmContactService { */ void deleteContact(Long id); + /** + * 联系人转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferContact(CrmContactTransferReqVO reqVO, Long userId); + + /** + * 更新客户联系人负责人 + * + * @param customerId 客户编号 + * @param ownerUserId 用户编号 + */ + void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId); + + /** + * 更新联系人相关跟进信息 + * + * @param updateFollowUpReqBOList 跟进信息 + */ + void updateContactFollowUpBatch(List updateFollowUpReqBOList); + /** * 获得联系人 * @@ -49,6 +73,15 @@ public interface CrmContactService { */ CrmContactDO getContact(Long id); + /** + * 获得联系人列表 + * + * @param ids 编号 + * @param userId 用户编号 + * @return 联系人列表 + */ + List getContactList(Collection ids, Long userId); + /** * 获得联系人列表 * @@ -57,31 +90,48 @@ public interface CrmContactService { */ List getContactList(Collection ids); + /** + * 获得联系人列表 + * + * @return 联系人列表 + */ + List getContactList(); + + /** + * 获取联系人列表(校验权限) + * + * @param userId 用户编号 + * @return 联系人列表 + */ + List getSimpleContactList(Long userId); + /** * 获得联系人分页 * * 数据权限:基于 {@link CrmContactDO} * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return 联系人分页 */ - PageResult getContactPage(CrmContactPageReqVO pageReqVO); + PageResult getContactPage(CrmContactPageReqVO pageReqVO, Long userId); /** - * 获得联系人分页,基于指定客户 + * 获得联系人分页 * - * 数据权限:基于 {@link CrmCustomerDO} 读取 + * 数据权限:基于 {@link CrmCustomerDO} * - * @param pageReqVO 分页查询 + * @param pageVO 分页查询 * @return 联系人分页 */ - PageResult getContactPageByCustomer(CrmContactPageReqVO pageReqVO); + PageResult getContactPageByCustomerId(CrmContactPageReqVO pageVO); /** - * 获取所有联系人列表 + * 获取关联客户的联系人数量 * - * @return 所有联系人列表 + * @param customerId 客户编号 + * @return 数量 */ - List getContactList(); + Long getContactCountByCustomerId(Long customerId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java index d4c4fe9c2..7b6f83e04 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -3,32 +3,41 @@ package cn.iocoder.yudao.module.crm.service.contact; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBaseVO; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.bo.CrmContactUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static java.util.Collections.singletonList; /** * CRM 联系人 Service 实现类 @@ -45,36 +54,63 @@ public class CrmContactServiceImpl implements CrmContactService { @Resource private CrmCustomerService customerService; @Resource - private CrmPermissionService crmPermissionService; + private CrmPermissionService permissionService; + @Resource + private CrmContractService contractService; + @Resource + private CrmContactBusinessService contactBusinessService; + @Resource + private CrmBusinessService businessService; @Resource private AdminUserApi adminUserApi; @Override @Transactional(rollbackFor = Exception.class) - public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) { - // 1.1 校验 + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_CREATE_SUB_TYPE, bizNo = "{{#contact.id}}", + success = CRM_CONTACT_CREATE_SUCCESS) + public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 校验 validateRelationDataExists(createReqVO); - // 1.2 插入 - CrmContactDO contact = ContactConvert.INSTANCE.convert(createReqVO); + + // 2. 插入联系人 + CrmContactDO contact = BeanUtils.toBean(createReqVO, CrmContactDO.class); contactMapper.insert(contact); - // 2. 创建数据权限 - crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) .setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 如果有关联商机,则需要创建关联 + if (createReqVO.getBusinessId() != null) { + contactBusinessService.createContactBusinessList(new CrmContactBusinessReqVO() + .setContactId(contact.getId()).setBusinessIds(singletonList(createReqVO.getBusinessId()))); + } + + // 5. 记录操作日志 + LogRecordContext.putVariable("contact", contact); return contact.getId(); } @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CONTACT_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateContact(CrmContactUpdateReqVO updateReqVO) { + public void updateContact(CrmContactSaveReqVO updateReqVO) { // 1. 校验存在 - validateContactExists(updateReqVO.getId()); + CrmContactDO oldContact = validateContactExists(updateReqVO.getId()); validateRelationDataExists(updateReqVO); - // 2. 更新 - CrmContactDO updateObj = ContactConvert.INSTANCE.convert(updateReqVO); + + // 2. 更新联系人 + CrmContactDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactDO.class); contactMapper.updateById(updateObj); + + // 3. 记录操作日志 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class)); + LogRecordContext.putVariable("contactName", oldContact.getName()); } /** @@ -82,7 +118,7 @@ public class CrmContactServiceImpl implements CrmContactService { * * @param saveReqVO 新增/修改请求 VO */ - private void validateRelationDataExists(CrmContactBaseVO saveReqVO){ + private void validateRelationDataExists(CrmContactSaveReqVO saveReqVO) { // 1. 校验客户 if (saveReqVO.getCustomerId() != null && customerService.getCustomer(saveReqVO.getCustomerId()) == null) { throw exception(CUSTOMER_NOT_EXISTS); @@ -95,30 +131,90 @@ public class CrmContactServiceImpl implements CrmContactService { if (saveReqVO.getParentId() != null && contactMapper.selectById(saveReqVO.getParentId()) == null) { throw exception(CONTACT_NOT_EXISTS); } - } - - @Override - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) - public void deleteContact(Long id) { - // 校验存在 - validateContactExists(id); - // 删除 - contactMapper.deleteById(id); - } - - private void validateContactExists(Long id) { - if (contactMapper.selectById(id) == null) { - throw exception(CONTACT_NOT_EXISTS); + // 4. 如果有关联商机,则需要校验存在 + if (saveReqVO.getBusinessId() != null && businessService.getBusiness(saveReqVO.getBusinessId()) == null) { + throw exception(BUSINESS_NOT_EXISTS); } } - // TODO 芋艿:是否要做数据权限的校验??? + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CONTACT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteContact(Long id) { + // 1.1 校验存在 + CrmContactDO contact = validateContactExists(id); + // 1.2 校验是否关联合同 + if (contractService.getContractCountByContactId(id) > 0) { + throw exception(CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS); + } + + // 2. 删除联系人 + contactMapper.deleteById(id); + + // 4.1 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id); + // 4.2 删除商机关联 + contactBusinessService.deleteContactBusinessByContactId(id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("contactName", contact.getName()); + } + + private CrmContactDO validateContactExists(Long id) { + CrmContactDO contactDO = contactMapper.selectById(id); + if (contactDO == null) { + throw exception(CONTACT_NOT_EXISTS); + } + return contactDO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CONTACT_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferContact(CrmContactTransferReqVO reqVO, Long userId) { + // 1 校验联系人是否存在 + CrmContactDO contact = validateContactExists(reqVO.getId()); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmContactConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTACT.getType())); + // 2.2 设置新的负责人 + contactMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("contact", contact); + } + + @Override + public void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { + contactMapper.updateOwnerUserIdByCustomerId(customerId, ownerUserId); + } + + @Override + public void updateContactFollowUpBatch(List updateFollowUpReqBOList) { + contactMapper.updateBatch(BeanUtils.toBean(updateFollowUpReqBOList, CrmContactDO.class)); + } + + //======================= 查询相关 ======================= + @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmContactDO getContact(Long id) { return contactMapper.selectById(id); } + @Override + public List getContactList(Collection ids, Long userId) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return contactMapper.selectBatchIds(ids, userId); + } + @Override public List getContactList(Collection ids) { if (CollUtil.isEmpty(ids)) { @@ -127,21 +223,32 @@ public class CrmContactServiceImpl implements CrmContactService { return contactMapper.selectBatchIds(ids); } - @Override - public PageResult getContactPage(CrmContactPageReqVO pageReqVO) { - // TODO puhui999:后面要改成,基于数据权限的查询 - return contactMapper.selectPage(pageReqVO); - } - - @Override - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) - public PageResult getContactPageByCustomer(CrmContactPageReqVO pageReqVO) { - return contactMapper.selectPageByCustomer(pageReqVO); - } - @Override public List getContactList() { return contactMapper.selectList(); } -} + @Override + public List getSimpleContactList(Long userId) { + CrmContactPageReqVO reqVO = new CrmContactPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + return contactMapper.selectPage(reqVO, userId).getList(); + } + + @Override + public PageResult getContactPage(CrmContactPageReqVO pageReqVO, Long userId) { + return contactMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getContactPageByCustomerId(CrmContactPageReqVO pageVO) { + return contactMapper.selectPageByCustomerId(pageVO); + } + + @Override + public Long getContactCountByCustomerId(Long customerId) { + return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/bo/CrmContactUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/bo/CrmContactUpdateFollowUpReqBO.java new file mode 100644 index 000000000..40604353e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/bo/CrmContactUpdateFollowUpReqBO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.contact.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @puhui999:是不是搞个通用的 ReqBO 就好了 +/** + * 联系人跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmContactUpdateFollowUpReqBO { + + @Schema(description = "联系人编号", example = "3167") + @NotNull(message = "联系人编号不能为空") + private Long id; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + @NotNull(message = "最后跟进时间不能为空") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + @NotNull(message = "最后更进内容不能为空") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java deleted file mode 100644 index c265c4355..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.contactbusinesslink; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO; - -import jakarta.validation.Valid; -import java.util.List; - -/** - * CRM 联系人商机关联 Service 接口 - * - * @author 芋道源码 - */ -public interface CrmContactBusinessLinkService { - - /** - * 创建联系人商机关联 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO createReqVO); - - /** - * 创建联系人商机关联 - * - * @param createReqVO 创建信息 - */ - void createContactBusinessLinkBatch(@Valid List createReqVO); - - /** - * 更新联系人商机关联 - * - * @param updateReqVO 更新信息 - */ - void updateContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO updateReqVO); - - /** - * 删除联系人商机关联 - * - * @param createReqVO 删除列表 - */ - void deleteContactBusinessLink(@Valid List createReqVO); - - /** - * 获得联系人商机关联 - * - * @param id 编号 - * @return 联系人商机关联 - */ - CrmContactBusinessLinkDO getContactBusinessLink(Long id); - - /** - * 获得联系人商机关联分页 - * - * @param pageReqVO 编号 - * @return 联系人商机关联 - */ - PageResult getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO); - - /** - * 获得联系人商机关联分页 - * - * @param pageReqVO 分页查询 - * @return 联系人商机关联分页 - */ - PageResult getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO); - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java deleted file mode 100644 index d8ef6a511..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.contactbusinesslink; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO; -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; -import cn.iocoder.yudao.module.crm.convert.contactbusinessslink.CrmContactBusinessLinkConvert; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO; -import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper; -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_BUSINESS_LINK_NOT_EXISTS; - -// TODO @puhui999:数据权限的校验;每个操作; -/** - * 联系人商机关联 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLinkService { - - @Resource - private CrmContactBusinessLinkMapper contactBusinessLinkMapper; - @Resource - private CrmBusinessService crmBusinessService; - - @Override - public Long createContactBusinessLink(CrmContactBusinessLinkSaveReqVO createReqVO) { - CrmContactBusinessLinkDO contactBusinessLink = BeanUtils.toBean(createReqVO, CrmContactBusinessLinkDO.class); - contactBusinessLinkMapper.insert(contactBusinessLink); - return contactBusinessLink.getId(); - } - - @Override - public void createContactBusinessLinkBatch(List createReqVOList) { - // 插入 - // TODO @zyna:如果已经关联过,不用重复插入; - // TODO @zyna:contact 和 business 存在校验,挪到这里,Controller 不用 @Transactional 注解,添加到这里哈。尽量业务都在 Service; - List saveDoList = CrmContactBusinessLinkConvert.INSTANCE.convert(createReqVOList); - contactBusinessLinkMapper.insertBatch(saveDoList); - } - - @Override - public void updateContactBusinessLink(CrmContactBusinessLinkSaveReqVO updateReqVO) { - // 校验存在 - validateContactBusinessLinkExists(updateReqVO.getId()); - // 更新 - CrmContactBusinessLinkDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactBusinessLinkDO.class); - contactBusinessLinkMapper.updateById(updateObj); - } - - @Override - public void deleteContactBusinessLink(List createReqVO) { - // 删除 - createReqVO.forEach(item -> { - contactBusinessLinkMapper.delete(new LambdaQueryWrapperX() - .eq(CrmContactBusinessLinkDO::getBusinessId,item.getBusinessId()) - .eq(CrmContactBusinessLinkDO::getContactId,item.getContactId()) - .eq(CrmContactBusinessLinkDO::getDeleted,0)); - }); - } - - private void validateContactBusinessLinkExists(Long id) { - if (contactBusinessLinkMapper.selectById(id) == null) { - throw exception(CONTACT_BUSINESS_LINK_NOT_EXISTS); - } - } - - @Override - public CrmContactBusinessLinkDO getContactBusinessLink(Long id) { - return contactBusinessLinkMapper.selectById(id); - } - - @Override - public PageResult getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) { - CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO(); - crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId()); - PageResult businessLinkDOS = contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO); - List businessDOS = crmBusinessService.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(), - CrmContactBusinessLinkDO::getBusinessId)); - PageResult pageResult = new PageResult(); - pageResult.setList(CrmBusinessConvert.INSTANCE.convert(businessDOS)); - pageResult.setTotal(businessLinkDOS.getTotal()); - return pageResult; - - } - - @Override - public PageResult getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO) { - return contactBusinessLinkMapper.selectPage(pageReqVO); - } - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java index e9162b1ca..26a9256f2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java @@ -1,14 +1,14 @@ package cn.iocoder.yudao.module.crm.service.contract; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; - +import cn.iocoder.yudao.module.crm.service.contract.bo.CrmContractUpdateFollowUpReqBO; import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -26,14 +26,14 @@ public interface CrmContractService { * @param userId 用户编号 * @return 编号 */ - Long createContract(@Valid CrmContractCreateReqVO createReqVO, Long userId); + Long createContract(@Valid CrmContractSaveReqVO createReqVO, Long userId); /** * 更新合同 * * @param updateReqVO 更新信息 */ - void updateContract(@Valid CrmContractUpdateReqVO updateReqVO); + void updateContract(@Valid CrmContractSaveReqVO updateReqVO); /** * 删除合同 @@ -42,6 +42,21 @@ public interface CrmContractService { */ void deleteContract(Long id); + /** + * 合同转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferContract(CrmContractTransferReqVO reqVO, Long userId); + + /** + * 更新合同相关的更进信息 + * + * @param contractUpdateFollowUpReqBO 信息 + */ + void updateContractFollowUp(CrmContractUpdateFollowUpReqBO contractUpdateFollowUpReqBO); + /** * 获得合同 * @@ -64,9 +79,10 @@ public interface CrmContractService { * 数据权限:基于 {@link CrmContractDO} 读取 * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return 合同分页 */ - PageResult getContractPage(CrmContractPageReqVO pageReqVO); + PageResult getContractPage(CrmContractPageReqVO pageReqVO, Long userId); /** * 获得合同分页,基于指定客户 @@ -76,14 +92,22 @@ public interface CrmContractService { * @param pageReqVO 分页查询 * @return 联系人分页 */ - PageResult getContractPageByCustomer(CrmContractPageReqVO pageReqVO); + PageResult getContractPageByCustomerId(CrmContractPageReqVO pageReqVO); /** - * 合同转移 + * 查询属于某个联系人的合同数量 * - * @param reqVO 请求 - * @param userId 用户编号 + * @param contactId 联系人ID + * @return 合同 */ - void transferContract(CrmContractTransferReqVO reqVO, Long userId); + Long getContractCountByContactId(Long contactId); + + /** + * 获取关联客户的合同数量 + * + * @param customerId 客户编号 + * @return 数量 + */ + Long getContractCountByCustomerId(Long customerId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java index 731711a3a..b9142dd35 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java @@ -3,28 +3,33 @@ package cn.iocoder.yudao.module.crm.service.contract; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.contract.ContractConvert; +import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.contract.bo.CrmContractUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTRACT_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; /** * CRM 合同 Service 实现类 @@ -42,37 +47,66 @@ public class CrmContractServiceImpl implements CrmContractService { private CrmPermissionService crmPermissionService; @Override - public Long createContract(CrmContractCreateReqVO createReqVO, Long userId) { - // 插入 - CrmContractDO contract = ContractConvert.INSTANCE.convert(createReqVO); + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}", + success = CRM_CONTRACT_CREATE_SUCCESS) + public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO + // 插入合同 + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class); contractMapper.insert(contract); // 创建数据权限 crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("contract", contract); return contract.getId(); } @Override @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, level = CrmPermissionLevelEnum.WRITE) - public void updateContract(CrmContractUpdateReqVO updateReqVO) { + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CONTRACT_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateContract(CrmContractSaveReqVO updateReqVO) { + // TODO @合同待定:只有草稿、审批中,可以编辑; // 校验存在 - validateContractExists(updateReqVO.getId()); - // 更新 - CrmContractDO updateObj = ContractConvert.INSTANCE.convert(updateReqVO); + CrmContractDO oldContract = validateContractExists(updateReqVO.getId()); + // 更新合同 + CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class); contractMapper.updateById(updateObj); + // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class)); + LogRecordContext.putVariable("contractName", oldContract.getName()); } + // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + + // TODO @合同待定:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + + @Override @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CONTRACT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteContract(Long id) { + // TODO @合同待定:如果被 CrmReceivableDO 所使用,则不允许删除 // 校验存在 - validateContractExists(id); + CrmContractDO contract = validateContractExists(id); // 删除 contractMapper.deleteById(id); + // 删除数据权限 + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("contractName", contract.getName()); } private CrmContractDO validateContractExists(Long id) { @@ -83,7 +117,32 @@ public class CrmContractServiceImpl implements CrmContractService { return contract; } - // TODO 芋艿:是否要做数据权限的校验??? + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CONTRACT_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferContract(CrmContractTransferReqVO reqVO, Long userId) { + // 1. 校验合同是否存在 + CrmContractDO contract = validateContractExists(reqVO.getId()); + + // 2.1 数据权限转移 + crmPermissionService.transferPermission( + CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType())); + // 2.2 设置负责人 + contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("contract", contract); + } + + @Override + public void updateContractFollowUp(CrmContractUpdateFollowUpReqBO contractUpdateFollowUpReqBO) { + contractMapper.updateById(BeanUtils.toBean(contractUpdateFollowUpReqBO, CrmContractDO.class)); + } + + //======================= 查询相关 ======================= + @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmContractDO getContract(Long id) { @@ -99,27 +158,25 @@ public class CrmContractServiceImpl implements CrmContractService { } @Override - public PageResult getContractPage(CrmContractPageReqVO pageReqVO) { - return contractMapper.selectPage(pageReqVO); + public PageResult getContractPage(CrmContractPageReqVO pageReqVO, Long userId) { + return contractMapper.selectPage(pageReqVO, userId); } @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) - public PageResult getContractPageByCustomer(CrmContractPageReqVO pageReqVO) { - return contractMapper.selectPageByCustomer(pageReqVO); + public PageResult getContractPageByCustomerId(CrmContractPageReqVO pageReqVO) { + return contractMapper.selectPageByCustomerId(pageReqVO); } @Override - @Transactional(rollbackFor = Exception.class) - public void transferContract(CrmContractTransferReqVO reqVO, Long userId) { - // 1 校验合同是否存在 - validateContractExists(reqVO.getId()); - - // 2. 数据权限转移 - crmPermissionService.transferPermission( - ContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType())); - - // 3. TODO 记录转移日志 + public Long getContractCountByContactId(Long contactId) { + return contractMapper.selectCountByContactId(contactId); } + @Override + public Long getContractCountByCustomerId(Long customerId) { + return contractMapper.selectCount(CrmContractDO::getCustomerId, customerId); + } + + // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/bo/CrmContractUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/bo/CrmContractUpdateFollowUpReqBO.java new file mode 100644 index 000000000..57c1f5f50 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/bo/CrmContractUpdateFollowUpReqBO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.contract.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @puhui999:是不是搞个通用的 ReqBO 就好了 +/** + * 合同跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmContractUpdateFollowUpReqBO { + + @Schema(description = "合同编号", example = "3167") + @NotNull(message = "合同编号不能为空") + private Long id; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + @NotNull(message = "最后跟进时间不能为空") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + @NotNull(message = "最后更进内容不能为空") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java index 96090cdfc..f67f377ac 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; - import jakarta.validation.Valid; +import java.util.List; + /** * 客户限制配置 Service 接口 * @@ -21,14 +21,14 @@ public interface CrmCustomerLimitConfigService { * @param createReqVO 创建信息 * @return 编号 */ - Long createCustomerLimitConfig(@Valid CrmCustomerLimitConfigCreateReqVO createReqVO); + Long createCustomerLimitConfig(@Valid CrmCustomerLimitConfigSaveReqVO createReqVO); /** * 更新客户限制配置 * * @param updateReqVO 更新信息 */ - void updateCustomerLimitConfig(@Valid CrmCustomerLimitConfigUpdateReqVO updateReqVO); + void updateCustomerLimitConfig(@Valid CrmCustomerLimitConfigSaveReqVO updateReqVO); /** * 删除客户限制配置 @@ -53,4 +53,12 @@ public interface CrmCustomerLimitConfigService { */ PageResult getCustomerLimitConfigPage(CrmCustomerLimitConfigPageReqVO pageReqVO); + /** + * 查询用户对应的配置列表 + * + * @param type 类型 + * @param userId 用户类型 + */ + List getCustomerLimitConfigListByUserId(Integer type, Long userId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java index 4528eafad..6d6a49f46 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java @@ -1,23 +1,29 @@ package cn.iocoder.yudao.module.crm.service.customer; +import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerLimitConfigMapper; +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; - import java.util.Collection; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_LIMIT_CONFIG_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; /** * 客户限制配置 Service 实现类 @@ -30,37 +36,53 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig @Resource private CrmCustomerLimitConfigMapper customerLimitConfigMapper; + @Resource private DeptApi deptApi; @Resource private AdminUserApi adminUserApi; @Override - public Long createCustomerLimitConfig(CrmCustomerLimitConfigCreateReqVO createReqVO) { + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUB_TYPE, bizNo = "{{#limitId}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUCCESS) + public Long createCustomerLimitConfig(CrmCustomerLimitConfigSaveReqVO createReqVO) { validateUserAndDept(createReqVO.getUserIds(), createReqVO.getDeptIds()); // 插入 - CrmCustomerLimitConfigDO customerLimitConfig = CrmCustomerLimitConfigConvert.INSTANCE.convert(createReqVO); - customerLimitConfigMapper.insert(customerLimitConfig); - // 返回 - return customerLimitConfig.getId(); + CrmCustomerLimitConfigDO limitConfig = BeanUtils.toBean(createReqVO, CrmCustomerLimitConfigDO.class); + customerLimitConfigMapper.insert(limitConfig); + + // 记录操作日志上下文 + LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType())); + LogRecordContext.putVariable("limitId", limitConfig.getId()); + return limitConfig.getId(); } @Override - public void updateCustomerLimitConfig(CrmCustomerLimitConfigUpdateReqVO updateReqVO) { + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUCCESS) + public void updateCustomerLimitConfig(CrmCustomerLimitConfigSaveReqVO updateReqVO) { // 校验存在 - validateCustomerLimitConfigExists(updateReqVO.getId()); + CrmCustomerLimitConfigDO oldLimitConfig = validateCustomerLimitConfigExists(updateReqVO.getId()); validateUserAndDept(updateReqVO.getUserIds(), updateReqVO.getDeptIds()); // 更新 - CrmCustomerLimitConfigDO updateObj = CrmCustomerLimitConfigConvert.INSTANCE.convert(updateReqVO); + CrmCustomerLimitConfigDO updateObj = BeanUtils.toBean(updateReqVO, CrmCustomerLimitConfigDO.class); customerLimitConfigMapper.updateById(updateObj); + + // 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldLimitConfig, CrmCustomerLimitConfigSaveReqVO.class)); } @Override + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS) public void deleteCustomerLimitConfig(Long id) { // 校验存在 - validateCustomerLimitConfigExists(id); + CrmCustomerLimitConfigDO limitConfig = validateCustomerLimitConfigExists(id); // 删除 customerLimitConfigMapper.deleteById(id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType())); } @Override @@ -73,10 +95,12 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig return customerLimitConfigMapper.selectPage(pageReqVO); } - private void validateCustomerLimitConfigExists(Long id) { - if (customerLimitConfigMapper.selectById(id) == null) { + private CrmCustomerLimitConfigDO validateCustomerLimitConfigExists(Long id) { + CrmCustomerLimitConfigDO limitConfigDO = customerLimitConfigMapper.selectById(id); + if (limitConfigDO == null) { throw exception(CUSTOMER_LIMIT_CONFIG_NOT_EXISTS); } + return limitConfigDO; } /** @@ -90,4 +114,11 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig adminUserApi.validateUserList(userIds); } + @Override + public List getCustomerLimitConfigListByUserId(Integer type, Long userId) { + AdminUserRespDTO user = adminUserApi.getUser(userId); + Assert.notNull(user, "用户({})不存在", userId); + return customerLimitConfigMapper.selectListByTypeAndUserIdAndDeptId(type, userId, user.getDeptId()); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java index 069bb9ab6..303a758d2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java @@ -1,16 +1,19 @@ package cn.iocoder.yudao.module.crm.service.customer; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; -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.dal.mysql.customer.CrmCustomerPoolConfigMapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Objects; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + /** * 客户公海配置 Service 实现类 * @@ -19,6 +22,7 @@ import java.util.Objects; @Service @Validated public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigService { + @Resource private CrmCustomerPoolConfigMapper customerPoolConfigMapper; @@ -29,7 +33,7 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe */ @Override public CrmCustomerPoolConfigDO getCustomerPoolConfig() { - return customerPoolConfigMapper.selectOne(new LambdaQueryWrapperX().last("LIMIT 1")); + return customerPoolConfigMapper.selectOne(); } /** @@ -38,15 +42,24 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe * @param saveReqVO 更新信息 */ @Override + @LogRecord(type = CRM_CUSTOMER_POOL_CONFIG_TYPE, subType = CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE, bizNo = "{{#poolConfigId}}", + success = CRM_CUSTOMER_POOL_CONFIG_SUCCESS) public void saveCustomerPoolConfig(CrmCustomerPoolConfigSaveReqVO saveReqVO) { // 存在,则进行更新 CrmCustomerPoolConfigDO dbConfig = getCustomerPoolConfig(); + CrmCustomerPoolConfigDO poolConfig = BeanUtils.toBean(saveReqVO, CrmCustomerPoolConfigDO.class); if (Objects.nonNull(dbConfig)) { - customerPoolConfigMapper.updateById(CrmCustomerConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId())); + customerPoolConfigMapper.updateById(poolConfig.setId(dbConfig.getId())); + // 记录操作日志上下文 + LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.TRUE); + LogRecordContext.putVariable("poolConfigId", poolConfig.getId()); return; } // 不存在,则进行插入 - customerPoolConfigMapper.insert(CrmCustomerConvert.INSTANCE.convert(saveReqVO)); + customerPoolConfigMapper.insert(poolConfig); + // 记录操作日志上下文 + LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.FALSE); + LogRecordContext.putVariable("poolConfigId", poolConfig.getId()); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java index bf22413d4..d40b08118 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; - +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerUpdateFollowUpReqBO; import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -25,14 +26,14 @@ public interface CrmCustomerService { * @param userId 用户编号 * @return 编号 */ - Long createCustomer(@Valid CrmCustomerCreateReqVO createReqVO, Long userId); + Long createCustomer(@Valid CrmCustomerSaveReqVO createReqVO, Long userId); /** * 更新客户 * * @param updateReqVO 更新信息 */ - void updateCustomer(@Valid CrmCustomerUpdateReqVO updateReqVO); + void updateCustomer(@Valid CrmCustomerSaveReqVO updateReqVO); /** * 删除客户 @@ -71,9 +72,8 @@ public interface CrmCustomerService { * 校验客户是否存在 * * @param customerId 客户 id - * @return 客户 */ - CrmCustomerDO validateCustomer(Long customerId); + void validateCustomer(Long customerId); /** * 客户转移 @@ -86,9 +86,17 @@ public interface CrmCustomerService { /** * 锁定/解锁客户 * - * @param updateReqVO 更新信息 + * @param lockReqVO 更新信息 + * @param userId 用户编号 */ - void lockCustomer(@Valid CrmCustomerUpdateReqVO updateReqVO); + void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId); + + /** + * 更新客户相关更进信息 + * + * @param customerUpdateFollowUpReqBO 请求 + */ + void updateCustomerFollowUp(CrmCustomerUpdateFollowUpReqBO customerUpdateFollowUpReqBO); // ==================== 公海相关操作 ==================== @@ -104,15 +112,8 @@ public interface CrmCustomerService { * * @param ids 要领取的客户编号数组 * @param ownerUserId 负责人 + * @param isReceive 是/否领取 */ - void receiveCustomer(List ids, Long ownerUserId); - - /** - * 获取客户列表 - * - * @return 客户列表 - * @author zyna - */ - List getCustomerList(); + void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java index e3723c281..a88b230e3 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java @@ -1,29 +1,51 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO; import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; -import java.util.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; +import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT; import static java.util.Collections.singletonList; /** @@ -39,53 +61,250 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { private CrmCustomerMapper customerMapper; @Resource - private CrmPermissionService crmPermissionService; + private CrmPermissionService permissionService; + @Resource + private CrmCustomerLimitConfigService customerLimitConfigService; + @Resource + @Lazy + private CrmContactService contactService; + @Resource + @Lazy + private CrmBusinessService businessService; + @Resource + @Lazy + private CrmContractService contractService; @Resource private AdminUserApi adminUserApi; @Override @Transactional(rollbackFor = Exception.class) - public Long createCustomer(CrmCustomerCreateReqVO createReqVO, Long userId) { - // 插入 - CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO); + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_CREATE_SUCCESS) + public Long createCustomer(CrmCustomerSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 校验拥有客户是否到达上限 + validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1); + + // 2. 插入客户 + CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class) + .setLockStatus(false).setDealStatus(false) + .setContactLastTime(LocalDateTime.now()); + // TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间 customerMapper.insert(customer); - // 创建数据权限 - crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); return customer.getId(); } @Override @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CUSTOMER_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) { - // 校验存在 - validateCustomerExists(updateReqVO.getId()); + public void updateCustomer(CrmCustomerSaveReqVO updateReqVO) { + Assert.notNull(updateReqVO.getId(), "客户编号不能为空"); + updateReqVO.setOwnerUserId(null); // 更新的时候,要把 updateReqVO 负责人设置为空,避免修改 + // 1. 校验存在 + CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId()); - // 更新 - CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO); + // 2. 更新客户 + CrmCustomerDO updateObj = BeanUtils.toBean(updateReqVO, CrmCustomerDO.class); customerMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class)); + LogRecordContext.putVariable("customerName", oldCustomer.getName()); } @Override @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteCustomer(Long id) { - // 校验存在 - validateCustomerExists(id); + // 1.1 校验存在 + CrmCustomerDO customer = validateCustomerExists(id); + // 1.2 检查引用 + checkCustomerReference(id); - // 删除 + // 2. 删除 customerMapper.deleteById(id); + // 3. 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); } - private void validateCustomerExists(Long id) { - if (customerMapper.selectById(id) == null) { - throw exception(CUSTOMER_NOT_EXISTS); + /** + * 校验客户是否被引用 + * + * @param id 客户编号 + */ + private void checkCustomerReference(Long id) { + if (contactService.getContactCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName()); + } + if (businessService.getBusinessCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_BUSINESS.getName()); + } + if (contractService.getContractCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTRACT.getName()); } } + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CUSTOMER_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) { + // 1.1 校验客户是否存在 + CrmCustomerDO customer = validateCustomerExists(reqVO.getId()); + // 1.2 校验拥有客户是否到达上限 + validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())); + // 2.2 转移后重新设置负责人 + customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("customer", customer); + } + + @Override + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}", + success = CRM_CUSTOMER_LOCK_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#lockReqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void lockCustomer(CrmCustomerLockReqVO lockReqVO, Long userId) { + // 1.1 校验当前客户是否存在 + CrmCustomerDO customer = validateCustomerExists(lockReqVO.getId()); + // 1.2 校验当前是否重复操作锁定/解锁状态 + if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) { + throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK); + } + // 1.3 校验锁定上限。 + if (lockReqVO.getLockStatus()) { + validateCustomerExceedLockLimit(userId); + } + + // 2. 更新锁定状态 + customerMapper.updateById(BeanUtils.toBean(lockReqVO, CrmCustomerDO.class)); + + // 3. 记录操作日志上下文 + // tips: 因为这里使用的是老的状态所以记录时反着记录,也就是 lockStatus 为 true 那么就是解锁反之为锁定 + LogRecordContext.putVariable("customer", customer); + } + + @Override + public void updateCustomerFollowUp(CrmCustomerUpdateFollowUpReqBO customerUpdateFollowUpReqBO) { + customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class)); + } + + // ==================== 公海相关操作 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_POOL_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_POOL_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void putCustomerPool(Long id) { + // 1. 校验存在 + CrmCustomerDO customer = customerMapper.selectById(id); + if (customer == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 1.2. 校验是否为公海数据 + validateCustomerOwnerExists(customer, true); + // 1.3. 校验客户是否锁定 + validateCustomerIsLocked(customer, true); + + // 2.1 设置负责人为 NULL + int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); + if (updateOwnerUserIncr == 0) { + throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); + } + // 2.2 删除负责人数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), + CrmPermissionLevelEnum.OWNER.getLevel()); + + // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 + contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); + + // 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive) { + if (!isReceive && !CrmPermissionUtils.isCrmAdmin()) { // 只有管理员可以分配 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.CRM_CUSTOMER.getName()); + } + + // 1.1 校验存在 + List customers = customerMapper.selectBatchIds(ids); + if (customers.size() != ids.size()) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 1.2. 校验负责人是否存在 + adminUserApi.validateUserList(singletonList(ownerUserId)); + // 1.3. 校验状态 + customers.forEach(customer -> { + // 校验是否已有负责人 + validateCustomerOwnerExists(customer, false); + // 校验是否锁定 + validateCustomerIsLocked(customer, false); + // 校验成交状态 + validateCustomerDeal(customer); + }); + // 1.4 校验负责人是否到达上限 + validateCustomerExceedOwnerLimit(ownerUserId, customers.size()); + + // 2.1 领取公海数据 + List updateCustomers = new ArrayList<>(); + List createPermissions = new ArrayList<>(); + customers.forEach(customer -> { + // 2.1. 设置负责人 + updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId)); + // 2.2. 创建负责人数据权限 + createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + }); + // 2.2 更新客户负责人 + customerMapper.updateBatch(updateCustomers); + // 2.3 创建负责人数据权限 + permissionService.createPermissionBatch(createPermissions); + // TODO @芋艿:要不要处理关联的联系人??? + + // 3. 记录操作日志 + AdminUserRespDTO user = null; + if (!isReceive) { + user = adminUserApi.getUser(ownerUserId); + } + for (CrmCustomerDO customer : customers) { + getSelf().receiveCustomerLog(customer, user == null ? null : user.getNickname()); + } + } + + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_RECEIVE_SUCCESS) + public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) { + // 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); + LogRecordContext.putVariable("ownerUserName", ownerUserName); + } + + //======================= 查询相关 ======================= + @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmCustomerDO getCustomer(Long id) { @@ -102,110 +321,19 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @Override public PageResult getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId) { - boolean admin = false; // TODO 如果是管理员 - return customerMapper.selectPage(pageReqVO, userId, adminUserApi.getSubordinateIds(userId), admin); + return customerMapper.selectPage(pageReqVO, userId); } + // ======================= 校验相关 ======================= + /** * 校验客户是否存在 * * @param customerId 客户 id - * @return 客户 */ @Override - public CrmCustomerDO validateCustomer(Long customerId) { - CrmCustomerDO customer = getCustomer(customerId); - if (Objects.isNull(customer)) { - throw exception(CUSTOMER_NOT_EXISTS); - } - return customer; - } - - @Override - @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) - public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) { - // 1. 校验客户是否存在 - validateCustomer(reqVO.getId()); - - // 2. 数据权限转移 - crmPermissionService.transferPermission( - CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())); - - // 3. TODO 记录转移日志 - } - - @Override - public void lockCustomer(CrmCustomerUpdateReqVO updateReqVO) { - // 校验存在 - validateCustomerExists(updateReqVO.getId()); - // TODO @Joey:可以校验下,如果已经对应的锁定状态,报个业务异常;原因是:后续这个业务会记录操作日志,会记录多了; - // TODO @芋艿:业务完善,增加锁定上限; - - // 更新 - CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO); - customerMapper.updateById(updateObj); - } - - @Override - @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) - public void putCustomerPool(Long id) { - // 1. 校验存在 - CrmCustomerDO customer = customerMapper.selectById(id); - if (customer == null) { - throw exception(CUSTOMER_NOT_EXISTS); - } - // 1.2. 校验是否为公海数据 - validateCustomerOwnerExists(customer, true); - // 1.3. 校验客户是否锁定 - validateCustomerIsLocked(customer, true); - - // 2. 设置负责人为 NULL - int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); - if (updateOwnerUserIncr == 0) { - throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); - } - // 3. 删除负责人数据权限 - crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), - CrmPermissionLevelEnum.OWNER.getLevel()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void receiveCustomer(List ids, Long ownerUserId) { - // 1.1 校验存在 - List customers = customerMapper.selectBatchIds(ids); - if (customers.size() != ids.size()) { - throw exception(CUSTOMER_NOT_EXISTS); - } - // 1.2. 校验负责人是否存在 - adminUserApi.validateUserList(singletonList(ownerUserId)); - // 1.3. 校验状态 - customers.forEach(customer -> { - // 校验是否已有负责人 - validateCustomerOwnerExists(customer, false); - // 校验是否锁定 - validateCustomerIsLocked(customer, false); - // 校验成交状态 - validateCustomerDeal(customer); - }); - - // 2. 领取公海数据 - List updateCustomers = new ArrayList<>(); - List createPermissions = new ArrayList<>(); - customers.forEach(customer -> { - // 2.1. 设置负责人 - updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId)); - // 2.2. 创建负责人数据权限 - createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) - .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); - }); - - // 3.1 更新客户负责人 - customerMapper.updateBatch(updateCustomers); - // 3.2 创建负责人数据权限 - crmPermissionService.createPermissionBatch(createPermissions); + public void validateCustomer(Long customerId) { + validateCustomerExists(customerId); } private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) { @@ -217,11 +345,19 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { throw exception(CUSTOMER_IN_POOL, customer.getName()); } // 负责人已存在 - if (customer.getOwnerUserId() != null) { + if (!pool && customer.getOwnerUserId() != null) { throw exception(CUSTOMER_OWNER_EXISTS, customer.getName()); } } + private CrmCustomerDO validateCustomerExists(Long id) { + CrmCustomerDO customerDO = customerMapper.selectById(id); + if (customerDO == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + return customerDO; + } + private void validateCustomerIsLocked(CrmCustomerDO customer, Boolean pool) { if (customer.getLockStatus()) { throw exception(pool ? CUSTOMER_LOCKED_PUT_POOL_FAIL : CUSTOMER_LOCKED, customer.getName()); @@ -234,9 +370,55 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { } } - @Override - public List getCustomerList() { - return customerMapper.selectList(); + /** + * 校验用户拥有的客户数量,是否到达上限 + * + * @param userId 用户编号 + * @param newCount 附加数量 + */ + private void validateCustomerExceedOwnerLimit(Long userId, int newCount) { + List limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId( + CUSTOMER_OWNER_LIMIT.getType(), userId); + if (CollUtil.isEmpty(limitConfigs)) { + return; + } + Long ownerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(null, userId); + Long dealOwnerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(true, userId); + limitConfigs.forEach(limitConfig -> { + long nowCount = limitConfig.getDealCountEnabled() ? ownerCount : ownerCount - dealOwnerCount; + if (nowCount + newCount > limitConfig.getMaxCount()) { + throw exception(CUSTOMER_OWNER_EXCEED_LIMIT); + } + }); + } + + /** + * 校验用户锁定的客户数量,是否到达上限 + * + * @param userId 用户编号 + */ + private void validateCustomerExceedLockLimit(Long userId) { + List limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId( + CUSTOMER_LOCK_LIMIT.getType(), userId); + if (CollUtil.isEmpty(limitConfigs)) { + return; + } + Long lockCount = customerMapper.selectCountByLockStatusAndOwnerUserId(true, userId); + Integer maxCount = CollectionUtils.getMaxValue(limitConfigs, CrmCustomerLimitConfigDO::getMaxCount); + assert maxCount != null; + if (lockCount >= maxCount) { + throw exception(CUSTOMER_LOCK_EXCEED_LIMIT); + } + } + + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private CrmCustomerServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerUpdateFollowUpReqBO.java new file mode 100644 index 000000000..2ba7fbb05 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerUpdateFollowUpReqBO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.crm.service.customer.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +// TODO @puhui999:是不是搞个通用的 ReqBO 就好了 +/** + * 跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmCustomerUpdateFollowUpReqBO { + + @Schema(description = "主键", example = "3167") + private Long id; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java new file mode 100644 index 000000000..c0fb69f25 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.crm.service.followup; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import jakarta.validation.Valid; + +/** + * 跟进记录 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmFollowUpRecordService { + + /** + * 创建跟进记录 (数据权限基于 bizType、 bizId) + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFollowUpRecord(@Valid CrmFollowUpRecordSaveReqVO createReqVO); + + /** + * 删除跟进记录 (数据权限基于 bizType、 bizId) + * + * @param id 编号 + * @param userId 用户编号 + */ + void deleteFollowUpRecord(Long id, Long userId); + + /** + * 获得跟进记录 + * + * @param id 编号 + * @return 跟进记录 + */ + CrmFollowUpRecordDO getFollowUpRecord(Long id); + + /** + * 获得跟进记录分页 (数据权限基于 bizType、 bizId) + * + * @param pageReqVO 分页查询 + * @return 跟进记录分页 + */ + PageResult getFollowUpRecordPage(CrmFollowUpRecordPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java new file mode 100644 index 000000000..f1c98e7d7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java @@ -0,0 +1,123 @@ +package cn.iocoder.yudao.module.crm.service.followup; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.dal.mysql.followup.CrmFollowUpRecordMapper; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contact.bo.CrmContactUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.followup.handle.CrmFollowUpHandler; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; + +/** + * 跟进记录 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { + + @Resource + private CrmFollowUpRecordMapper crmFollowUpRecordMapper; + + @Resource + private CrmPermissionService permissionService; + @Resource + private List followUpHandlers; + @Resource + private CrmBusinessService businessService; + @Resource + private CrmContactService contactService; + + @Override + @CrmPermission(bizTypeValue = "#createReqVO.bizType", bizId = "#createReqVO.bizId", level = CrmPermissionLevelEnum.WRITE) + public Long createFollowUpRecord(CrmFollowUpRecordSaveReqVO createReqVO) { + // 创建更进记录 + CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); + crmFollowUpRecordMapper.insert(followUpRecord); + + LocalDateTime now = LocalDateTime.now(); + // 2. 更新 bizId 对应的记录; + followUpHandlers.forEach(handler -> handler.execute(followUpRecord, now)); + // 3.1 更新 contactIds 对应的记录 + if (CollUtil.isNotEmpty(createReqVO.getContactIds())) { + // TODO @puhui999:可以用链式设置哈 + contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), contactId -> { + CrmContactUpdateFollowUpReqBO crmContactUpdateFollowUpReqBO = new CrmContactUpdateFollowUpReqBO(); + crmContactUpdateFollowUpReqBO.setId(contactId).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + return crmContactUpdateFollowUpReqBO; + })); + } + // 3.2 需要更新 businessIds、contactIds 对应的记录 + if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) { + businessService.updateContactFollowUpBatch(convertList(createReqVO.getBusinessIds(), businessId -> { + CrmBusinessUpdateFollowUpReqBO crmBusinessUpdateFollowUpReqBO = new CrmBusinessUpdateFollowUpReqBO(); + crmBusinessUpdateFollowUpReqBO.setId(businessId).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + return crmBusinessUpdateFollowUpReqBO; + })); + } + return followUpRecord.getId(); + } + + @Override + public void deleteFollowUpRecord(Long id, Long userId) { + // 校验存在 + CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id); + // 校验权限 + List permissionList = permissionService.getPermissionListByBiz( + followUpRecord.getBizType(), followUpRecord.getBizId()); + boolean condition = anyMatch(permissionList, permission -> + ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), CrmPermissionLevelEnum.OWNER.getLevel())); + if (!condition) { + throw exception(FOLLOW_UP_RECORD_DELETE_DENIED); + } + + // 删除 + crmFollowUpRecordMapper.deleteById(id); + } + + private CrmFollowUpRecordDO validateFollowUpRecordExists(Long id) { + CrmFollowUpRecordDO followUpRecord = crmFollowUpRecordMapper.selectById(id); + if (followUpRecord == null) { + throw exception(FOLLOW_UP_RECORD_NOT_EXISTS); + } + return followUpRecord; + } + + @Override + public CrmFollowUpRecordDO getFollowUpRecord(Long id) { + return crmFollowUpRecordMapper.selectById(id); + } + + + @Override + @CrmPermission(bizTypeValue = "#pageReqVO.bizType", bizId = "#pageReqVO.bizId", level = CrmPermissionLevelEnum.READ) + public PageResult getFollowUpRecordPage(CrmFollowUpRecordPageReqVO pageReqVO) { + return crmFollowUpRecordMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmBusinessFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmBusinessFollowUpHandler.java new file mode 100644 index 000000000..ffba7fb7e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmBusinessFollowUpHandler.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateFollowUpReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Collections; + +/** + * CRM 商机的 {@link CrmFollowUpHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class CrmBusinessFollowUpHandler implements CrmFollowUpHandler { + + @Resource + private CrmBusinessService businessService; + + @Override + public void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now) { + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { + return; + } + + // 更新商机跟进信息 + CrmBusinessUpdateFollowUpReqBO businessUpdateFollowUpReqBO = new CrmBusinessUpdateFollowUpReqBO(); + businessUpdateFollowUpReqBO.setId(followUpRecord.getBizId()).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + businessService.updateContactFollowUpBatch(Collections.singletonList(businessUpdateFollowUpReqBO)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmClueFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmClueFollowUpHandler.java new file mode 100644 index 000000000..ade2b2aaa --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmClueFollowUpHandler.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.service.clue.CrmClueService; +import cn.iocoder.yudao.module.crm.service.clue.bo.CrmClueUpdateFollowUpReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * CRM 线索的 {@link CrmFollowUpHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class CrmClueFollowUpHandler implements CrmFollowUpHandler { + + @Resource + private CrmClueService clueService; + + @Override + public void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now) { + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_LEADS.getType(), followUpRecord.getBizType())) { + return; + } + + // 更新线索跟进信息 + CrmClueUpdateFollowUpReqBO clueUpdateFollowUpReqBO = new CrmClueUpdateFollowUpReqBO(); + clueUpdateFollowUpReqBO.setId(followUpRecord.getBizId()).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + clueService.updateClueFollowUp(clueUpdateFollowUpReqBO); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContactFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContactFollowUpHandler.java new file mode 100644 index 000000000..f38942111 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContactFollowUpHandler.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contact.bo.CrmContactUpdateFollowUpReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Collections; + +/** + * CRM 联系人的 {@link CrmFollowUpHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class CrmContactFollowUpHandler implements CrmFollowUpHandler { + + @Resource + private CrmContactService contactService; + + @Override + public void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now) { + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { + return; + } + + // 更新联系人跟进信息 + CrmContactUpdateFollowUpReqBO contactUpdateFollowUpReqBO = new CrmContactUpdateFollowUpReqBO(); + contactUpdateFollowUpReqBO.setId(followUpRecord.getBizId()).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + contactService.updateContactFollowUpBatch(Collections.singletonList(contactUpdateFollowUpReqBO)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContractFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContractFollowUpHandler.java new file mode 100644 index 000000000..269015fd0 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmContractFollowUpHandler.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.contract.bo.CrmContractUpdateFollowUpReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * CRM 合同的 {@link CrmFollowUpHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class CrmContractFollowUpHandler implements CrmFollowUpHandler { + + @Resource + private CrmContractService contractService; + + @Override + public void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now) { + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { + return; + } + + // 更新合同跟进信息 + CrmContractUpdateFollowUpReqBO contractUpdateFollowUpReqBO = new CrmContractUpdateFollowUpReqBO(); + contractUpdateFollowUpReqBO.setId(followUpRecord.getBizId()).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + contractService.updateContractFollowUp(contractUpdateFollowUpReqBO); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmCustomerFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmCustomerFollowUpHandler.java new file mode 100644 index 000000000..0e0931555 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmCustomerFollowUpHandler.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerUpdateFollowUpReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * CRM 客户的 {@link CrmFollowUpHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class CrmCustomerFollowUpHandler implements CrmFollowUpHandler { + + @Resource + private CrmCustomerService customerService; + + @Override + public void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now) { + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { + return; + } + + // 更新客户跟进信息 + CrmCustomerUpdateFollowUpReqBO customerUpdateFollowUpReqBO = new CrmCustomerUpdateFollowUpReqBO(); + customerUpdateFollowUpReqBO.setId(followUpRecord.getBizId()).setContactNextTime(followUpRecord.getNextTime()) + .setContactLastTime(now).setContactLastContent(followUpRecord.getContent()); + customerService.updateCustomerFollowUp(customerUpdateFollowUpReqBO); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmFollowUpHandler.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmFollowUpHandler.java new file mode 100644 index 000000000..82bcec835 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/handle/CrmFollowUpHandler.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.crm.service.followup.handle; + +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; + +import java.time.LocalDateTime; + +/** + * CRM 跟进信息处理器 handler 接口 + * + * @author HUIHUI + */ +public interface CrmFollowUpHandler { + + // TODO @puhui999:需要考虑,下次联系时间为空; + /** + * 执行更新 + * + * @param followUpRecord 跟进记录 + * @param now 跟进时间 + */ + void execute(CrmFollowUpRecordDO followUpRecord, LocalDateTime now); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageService.java new file mode 100644 index 000000000..c0bdb42e8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageService.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.crm.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import jakarta.validation.Valid; + +/** + * CRM 代办消息 Service 接口 + * + * @author dhb52 + */ +public interface CrmMessageService { + + /** + * TODO @dbh52:注释要写下 + * + * @param pageReqVO + * @return + */ + PageResult getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageServiceImpl.java new file mode 100644 index 000000000..523a64671 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageServiceImpl.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +// TODO @dbh52:注释要写下 +@Component +@Validated +public class CrmMessageServiceImpl implements CrmMessageService { + + @Resource + private CrmCustomerMapper customerMapper; + + @Override + public PageResult getTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { + return customerMapper.selectTodayCustomerPage(pageReqVO, userId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java index 75fb95fda..e822ac2ff 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java @@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -57,10 +57,18 @@ public interface CrmPermissionService { */ void deletePermission(Integer bizType, Long bizId, Integer level); + /** + * 删除数据权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + */ + void deletePermission(Integer bizType, Long bizId); + /** * 批量删除数据权限 * - * @param ids 权限编号 + * @param ids 权限编号 * @param userId 用户编号 */ void deletePermissionBatch(Collection ids, Long userId); @@ -82,6 +90,15 @@ public interface CrmPermissionService { */ List getPermissionListByBiz(Integer bizType, Long bizId); + /** + * 获取数据权限列表,通过 数据类型 x 某个数据 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizIds 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @return Crm 数据权限列表 + */ + List getPermissionListByBiz(Integer bizType, Collection bizIds); + /** * 获取用户参与的模块数据列表 * diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java index 8ca6b9cdd..e8a74d49f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -8,17 +8,19 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -35,7 +37,7 @@ import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnu public class CrmPermissionServiceImpl implements CrmPermissionService { @Resource - private CrmPermissionMapper crmPermissionMapper; + private CrmPermissionMapper permissionMapper; @Resource private AdminUserApi adminUserApi; @@ -43,54 +45,63 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @Override @Transactional(rollbackFor = Exception.class) public Long createPermission(CrmPermissionCreateReqBO createReqBO) { - // TODO @puhui999:排重 + validatePermissionNotExists(Collections.singletonList(createReqBO)); // 1. 校验用户是否存在 adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId())); // 2. 创建 CrmPermissionDO permission = CrmPermissionConvert.INSTANCE.convert(createReqBO); - crmPermissionMapper.insert(permission); + permissionMapper.insert(permission); return permission.getId(); } @Override public void createPermissionBatch(List createReqBOs) { - // TODO @puhui999:排重 + validatePermissionNotExists(createReqBOs); // 1. 校验用户是否存在 adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId)); // 2. 创建 List permissions = CrmPermissionConvert.INSTANCE.convertList(createReqBOs); - crmPermissionMapper.insertBatch(permissions); + permissionMapper.insertBatch(permissions); } @Override @Transactional(rollbackFor = Exception.class) public void updatePermission(CrmPermissionUpdateReqVO updateReqVO) { - // TODO @puhui999:排重 // 1. 校验存在 - validateCrmPermissionExists(updateReqVO.getIds()); + validatePermissionExists(updateReqVO.getIds()); // 2. 更新 List updateDO = CrmPermissionConvert.INSTANCE.convertList(updateReqVO); - crmPermissionMapper.updateBatch(updateDO); + permissionMapper.updateBatch(updateDO); } - private void validateCrmPermissionExists(Collection ids) { - List permissionList = crmPermissionMapper.selectBatchIds(ids); + private void validatePermissionExists(Collection ids) { + List permissionList = permissionMapper.selectBatchIds(ids); if (ObjUtil.notEqual(permissionList.size(), ids.size())) { throw exception(CRM_PERMISSION_NOT_EXISTS); } } + private void validatePermissionNotExists(Collection createReqBOs) { + Set bizTypes = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizType); + Set bizIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizId); + Set userIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId); + Long count = permissionMapper.selectListByBiz(bizTypes, bizIds, userIds); + if (count > 0) { + throw exception(CRM_PERMISSION_CREATE_FAIL); + } + } + @Override @Transactional(rollbackFor = Exception.class) public void transferPermission(CrmPermissionTransferReqBO transferReqBO) { // 1. 校验数据权限:是否是负责人,只有负责人才可以转移 - CrmPermissionDO oldPermission = crmPermissionMapper.selectByBizTypeAndBizIdByUserId( + CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId( transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId()); String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType()); - // TODO 校验是否为超级管理员 || 1 - if (oldPermission == null || !isOwner(oldPermission.getLevel())) { + if (oldPermission == null // 不是拥有者,并且不是超管 + || (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) { throw exception(CRM_PERMISSION_DENIED, bizTypeName); } // 1.1 校验转移对象是否已经是该负责人 @@ -101,45 +112,53 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); // 2. 修改新负责人的权限 - List permissions = crmPermissionMapper.selectByBizTypeAndBizId( + List permissions = permissionMapper.selectByBizTypeAndBizId( transferReqBO.getBizType(), transferReqBO.getBizId()); // 获得所有数据权限 CrmPermissionDO permission = CollUtil.findOne(permissions, item -> ObjUtil.equal(item.getUserId(), transferReqBO.getNewOwnerUserId())); if (permission == null) { - crmPermissionMapper.insert(new CrmPermissionDO().setBizType(transferReqBO.getBizType()) + permissionMapper.insert(new CrmPermissionDO().setBizType(transferReqBO.getBizType()) .setBizId(transferReqBO.getBizId()).setUserId(transferReqBO.getNewOwnerUserId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); } else { - crmPermissionMapper.updateById(new CrmPermissionDO().setId(permission.getId()) + permissionMapper.updateById(new CrmPermissionDO().setId(permission.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); } // 3. 修改老负责人的权限 if (transferReqBO.getOldOwnerPermissionLevel() != null) { - crmPermissionMapper.updateById(new CrmPermissionDO().setId(oldPermission.getId()) + permissionMapper.updateById(new CrmPermissionDO().setId(oldPermission.getId()) .setLevel(transferReqBO.getOldOwnerPermissionLevel())); } else { - crmPermissionMapper.deleteById(oldPermission.getId()); + permissionMapper.deleteById(oldPermission.getId()); } } @Override @Transactional(rollbackFor = Exception.class) public void deletePermission(Integer bizType, Long bizId, Integer level) { - List permissions = crmPermissionMapper.selectListByBizTypeAndBizIdAndLevel( - bizType, bizId, level); // 校验存在 + List permissions = permissionMapper.selectListByBizTypeAndBizIdAndLevel( + bizType, bizId, level); if (CollUtil.isEmpty(permissions)) { throw exception(CRM_PERMISSION_NOT_EXISTS); } // 删除数据权限 - crmPermissionMapper.deleteBatchIds(convertSet(permissions, CrmPermissionDO::getId)); + permissionMapper.deleteBatchIds(convertSet(permissions, CrmPermissionDO::getId)); + } + + @Override + public void deletePermission(Integer bizType, Long bizId) { + int deletedCount = permissionMapper.deletePermission(bizType, bizId); + if (deletedCount == 0) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } } @Override public void deletePermissionBatch(Collection ids, Long userId) { - List permissions = crmPermissionMapper.selectBatchIds(ids); + List permissions = permissionMapper.selectBatchIds(ids); if (CollUtil.isEmpty(permissions)) { throw exception(CRM_PERMISSION_NOT_EXISTS); } @@ -148,19 +167,19 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { throw exception(CRM_PERMISSION_DELETE_FAIL); } // 校验操作人是否为负责人 - CrmPermissionDO permission = crmPermissionMapper.selectByIdAndUserId(permissions.get(0).getBizId(), userId); + CrmPermissionDO permission = permissionMapper.selectByIdAndUserId(permissions.get(0).getBizId(), userId); if (!CrmPermissionLevelEnum.isOwner(permission.getLevel())) { throw exception(CRM_PERMISSION_DELETE_DENIED); } // 删除数据权限 - crmPermissionMapper.deleteBatchIds(ids); + permissionMapper.deleteBatchIds(ids); } @Override public void deleteSelfPermission(Long id, Long userId) { // 校验数据存在且是自己 - CrmPermissionDO permission = crmPermissionMapper.selectByIdAndUserId(id, userId); + CrmPermissionDO permission = permissionMapper.selectByIdAndUserId(id, userId); if (permission == null) { throw exception(CRM_PERMISSION_NOT_EXISTS); } @@ -170,17 +189,22 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { } // 删除 - crmPermissionMapper.deleteById(id); + permissionMapper.deleteById(id); } @Override public List getPermissionListByBiz(Integer bizType, Long bizId) { - return crmPermissionMapper.selectByBizTypeAndBizId(bizType, bizId); + return permissionMapper.selectByBizTypeAndBizId(bizType, bizId); + } + + @Override + public List getPermissionListByBiz(Integer bizType, Collection bizIds) { + return permissionMapper.selectByBizTypeAndBizIds(bizType, bizIds); } @Override public List getPermissionListByBizTypeAndUserId(Integer bizType, Long userId) { - return crmPermissionMapper.selectListByBizTypeAndUserId(bizType, userId); + return permissionMapper.selectListByBizTypeAndUserId(bizType, userId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java index 388f6133b..6399039ba 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java @@ -5,11 +5,13 @@ import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProdu import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductCategoryMapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -17,6 +19,7 @@ import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO.PARENT_ID_NULL; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; /** * CRM 产品分类 Service 实现类 @@ -35,18 +38,26 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService private CrmProductService crmProductService; @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE, bizNo = "{{#productCategoryId}}", + success = CRM_PRODUCT_CATEGORY_CREATE_SUCCESS) public Long createProductCategory(CrmProductCategoryCreateReqVO createReqVO) { // 1.1 校验父分类存在 validateParentProductCategory(createReqVO.getParentId()); // 1.2 分类名称是否存在 validateProductNameExists(null, createReqVO.getParentId(), createReqVO.getName()); - // 2. 插入 + + // 2. 插入分类 CrmProductCategoryDO category = BeanUtils.toBean(createReqVO, CrmProductCategoryDO.class); productCategoryMapper.insert(category); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("productCategoryId", category.getId()); return category.getId(); } @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS) public void updateProductCategory(CrmProductCategoryCreateReqVO updateReqVO) { // 1.1 校验存在 validateProductCategoryExists(updateReqVO.getId()); @@ -54,7 +65,8 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService validateParentProductCategory(updateReqVO.getParentId()); // 1.3 分类名称是否存在 validateProductNameExists(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); - // 2. 更新 + + // 2. 更新分类 CrmProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductCategoryDO.class); productCategoryMapper.updateById(updateObj); } @@ -91,19 +103,20 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService } @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_PRODUCT_CATEGORY_DELETE_SUCCESS) public void deleteProductCategory(Long id) { - // TODO zange:参考 mall: ProductCategoryServiceImpl 补充下必要的参数校验; - // 校验存在 + // 1.1 校验存在 validateProductCategoryExists(id); - // 校验是否还有子分类 + // 1.2 校验是否还有子分类 if (productCategoryMapper.selectCountByParentId(id) > 0) { throw exception(product_CATEGORY_EXISTS_CHILDREN); } - // 校验是否被产品使用 + // 1.3 校验是否被产品使用 if (crmProductService.getProductByCategoryId(id) !=null) { throw exception(PRODUCT_CATEGORY_USED); } - // 删除 + // 2. 删除 productCategoryMapper.deleteById(id); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java index 524df8ddb..e4eabd15a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -60,7 +60,7 @@ public interface CrmProductService { * @param pageReqVO 分页查询 * @return 产品分页 */ - PageResult getProductPage(CrmProductPageReqVO pageReqVO); + PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId); /** * 获得产品 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java index ba7b8ac83..80c38af3c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java @@ -11,22 +11,28 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + -// TODO 芋艿:数据权限 /** * CRM 产品 Service 实现类 * @@ -48,47 +54,60 @@ public class CrmProductServiceImpl implements CrmProductService { private AdminUserApi adminUserApi; @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_CREATE_SUB_TYPE, bizNo = "{{#productId}}", + success = CRM_PRODUCT_CREATE_SUCCESS) public Long createProduct(CrmProductSaveReqVO createReqVO) { - // 校验产品 + // 1. 校验产品 adminUserApi.validateUserList(Collections.singleton(createReqVO.getOwnerUserId())); validateProductNoDuplicate(null, createReqVO.getNo()); validateProductCategoryExists(createReqVO.getCategoryId()); - // 插入产品 + // 2. 插入产品 CrmProductDO product = BeanUtils.toBean(createReqVO, CrmProductDO.class); productMapper.insert(product); - // 插入数据权限 + // 3. 插入数据权限 permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(product.getOwnerUserId()) .setBizType(CrmBizTypeEnum.CRM_PRODUCT.getType()).setBizId(product.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("productId", product.getId()); return product.getId(); } @Override + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_PRODUCT_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateProduct(CrmProductSaveReqVO updateReqVO) { - // 校验产品 + // 1. 校验产品 updateReqVO.setOwnerUserId(null); // 不修改负责人 - validateProductExists(updateReqVO.getId()); + CrmProductDO crmProductDO = validateProductExists(updateReqVO.getId()); validateProductNoDuplicate(updateReqVO.getId(), updateReqVO.getNo()); validateProductCategoryExists(updateReqVO.getCategoryId()); - // 更新产品 + // 2. 更新产品 CrmProductDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductDO.class); productMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(crmProductDO, CrmProductSaveReqVO.class)); } - private void validateProductExists(Long id) { + private CrmProductDO validateProductExists(Long id) { CrmProductDO product = productMapper.selectById(id); if (product == null) { throw exception(PRODUCT_NOT_EXISTS); } + return product; } private void validateProductNoDuplicate(Long id, String no) { CrmProductDO product = productMapper.selectByNo(no); if (product == null - || product.getId().equals(id)) { + || product.getId().equals(id)) { return; } throw exception(PRODUCT_NO_EXISTS); @@ -102,6 +121,9 @@ public class CrmProductServiceImpl implements CrmProductService { } @Override + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_PRODUCT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteProduct(Long id) { // 校验存在 validateProductExists(id); @@ -110,6 +132,7 @@ public class CrmProductServiceImpl implements CrmProductService { } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmProductDO getProduct(Long id) { return productMapper.selectById(id); } @@ -123,8 +146,8 @@ public class CrmProductServiceImpl implements CrmProductService { } @Override - public PageResult getProductPage(CrmProductPageReqVO pageReqVO) { - return productMapper.selectPage(pageReqVO); + public PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId) { + return productMapper.selectPage(pageReqVO, userId); } @Override diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java deleted file mode 100644 index cae179aea..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 产品表 - */ -package cn.iocoder.yudao.module.crm.service.product; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java index ba8a62d04..ded059b28 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java @@ -6,8 +6,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceiv import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -24,7 +24,7 @@ public interface CrmReceivablePlanService { * @param createReqVO 创建信息 * @return 编号 */ - Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO); + Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId); /** * 更新回款计划 @@ -62,9 +62,10 @@ public interface CrmReceivablePlanService { * 数据权限:基于 {@link CrmReceivablePlanDO} 读取 * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return 回款计划分页 */ - PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO); + PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId); /** * 获得回款计划分页,基于指定客户 @@ -74,6 +75,6 @@ public interface CrmReceivablePlanService { * @param pageReqVO 分页查询 * @return 回款计划分页 */ - PageResult getReceivablePlanPageByCustomer(CrmReceivablePlanPageReqVO pageReqVO); + PageResult getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java index 7fe055690..d05647cfb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; @@ -14,21 +15,27 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; // TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈; -// TODO @puhui999:数据权限 + /** * 回款计划 Service 实现类 * @@ -45,9 +52,15 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { private CrmContractService contractService; @Resource private CrmCustomerService customerService; + @Resource + private CrmPermissionService permissionService; @Override - public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO) { + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}", + success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS) + public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) { + // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款 + // TODO @liuhongfeng contractId:校验合同是否存在 // 插入 CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO); receivablePlan.setFinishStatus(false); @@ -55,53 +68,77 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { checkReceivablePlan(receivablePlan); receivablePlanMapper.insert(receivablePlan); - // 返回 + // 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("receivablePlan", receivablePlan); return receivablePlan.getId(); } private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) { - if(ObjectUtil.isNull(receivablePlan.getContractId())){ + if (ObjectUtil.isNull(receivablePlan.getContractId())) { throw exception(CONTRACT_NOT_EXISTS); } CrmContractDO contract = contractService.getContract(receivablePlan.getContractId()); - if(ObjectUtil.isNull(contract)){ + if (ObjectUtil.isNull(contract)) { throw exception(CONTRACT_NOT_EXISTS); } CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId()); - if(ObjectUtil.isNull(customer)){ + if (ObjectUtil.isNull(customer)) { throw exception(CUSTOMER_NOT_EXISTS); } } @Override + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) { + // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑; // 校验存在 - validateReceivablePlanExists(updateReqVO.getId()); + CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId()); // 更新 CrmReceivablePlanDO updateObj = CrmReceivablePlanConvert.INSTANCE.convert(updateReqVO); receivablePlanMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanUpdateReqVO.class)); + LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); } @Override + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteReceivablePlan(Long id) { // 校验存在 - validateReceivablePlanExists(id); + CrmReceivablePlanDO receivablePlan = validateReceivablePlanExists(id); // 删除 receivablePlanMapper.deleteById(id); + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + // 记录操作日志上下文 + LogRecordContext.putVariable("receivablePlan", receivablePlan); } - private void validateReceivablePlanExists(Long id) { - if (receivablePlanMapper.selectById(id) == null) { + private CrmReceivablePlanDO validateReceivablePlanExists(Long id) { + CrmReceivablePlanDO receivablePlan = receivablePlanMapper.selectById(id); + if (receivablePlan == null) { throw exception(RECEIVABLE_PLAN_NOT_EXISTS); } + return receivablePlan; } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmReceivablePlanDO getReceivablePlan(Long id) { return receivablePlanMapper.selectById(id); } @@ -115,14 +152,14 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { } @Override - public PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO) { - return receivablePlanMapper.selectPage(pageReqVO); + public PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) { + return receivablePlanMapper.selectPage(pageReqVO, userId); } @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) - public PageResult getReceivablePlanPageByCustomer(CrmReceivablePlanPageReqVO pageReqVO) { - return receivablePlanMapper.selectPageByCustomer(pageReqVO); + public PageResult getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO) { + return receivablePlanMapper.selectPageByCustomerId(pageReqVO); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java index bf9e5571b..8e9cfa0ea 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java @@ -6,8 +6,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.Crm import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; @@ -22,9 +22,10 @@ public interface CrmReceivableService { * 创建回款 * * @param createReqVO 创建信息 + * @param userId 用户编号 * @return 编号 */ - Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO); + Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO, Long userId); /** * 更新回款 @@ -62,9 +63,10 @@ public interface CrmReceivableService { * 数据权限:基于 {@link CrmReceivableDO} 读取 * * @param pageReqVO 分页查询 + * @param userId 用户编号 * @return 回款分页 */ - PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO); + PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId); /** * 获得回款分页,基于指定客户 @@ -74,6 +76,6 @@ public interface CrmReceivableService { * @param pageReqVO 分页查询 * @return 回款分页 */ - PageResult getReceivablePageByCustomer(CrmReceivablePageReqVO pageReqVO); + PageResult getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java index ac3b71e08..54c4aa82a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; @@ -17,18 +18,24 @@ import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; /** * CRM 回款 Service 实现类 @@ -48,11 +55,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { private CrmCustomerService customerService; @Resource private CrmReceivablePlanService receivablePlanService; + @Resource + private CrmPermissionService permissionService; - // TODO @liuhongfeng:创建还款后,是不是什么时候,要更新 plan? @Override - public Long createReceivable(CrmReceivableCreateReqVO createReqVO) { - // 插入 + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", + success = CRM_RECEIVABLE_CREATE_SUCCESS) + public Long createReceivable(CrmReceivableCreateReqVO createReqVO, Long userId) { + // 插入还款 CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO); if (ObjectUtil.isNull(receivable.getAuditStatus())) { receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus()); @@ -60,63 +70,95 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到 CrmReceivableDO receivable 之前; - // 校验 checkReceivable(receivable); receivableMapper.insert(receivable); + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) + .setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + // TODO @liuhongfeng:需要更新关联的 plan + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("receivable", receivable); return receivable.getId(); } // TODO @liuhongfeng:这里的括号要注意排版; private void checkReceivable(CrmReceivableDO receivable) { + // TODO @liuhongfeng:校验 no 的唯一性 // TODO @liuhongfeng:这个放在参数校验合适 - if(ObjectUtil.isNull(receivable.getContractId())){ + if (ObjectUtil.isNull(receivable.getContractId())) { throw exception(CONTRACT_NOT_EXISTS); } CrmContractDO contract = contractService.getContract(receivable.getContractId()); - if(ObjectUtil.isNull(contract)){ + if (ObjectUtil.isNull(contract)) { throw exception(CONTRACT_NOT_EXISTS); } CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId()); - if(ObjectUtil.isNull(customer)){ + if (ObjectUtil.isNull(customer)) { throw exception(CUSTOMER_NOT_EXISTS); } CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId()); - if(ObjectUtil.isNull(receivablePlan)){ + if (ObjectUtil.isNull(receivablePlan)) { throw exception(RECEIVABLE_PLAN_NOT_EXISTS); } } @Override + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_RECEIVABLE_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) { // 校验存在 - validateReceivableExists(updateReqVO.getId()); + CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId()); + // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改 - // 更新 + // 更新还款 CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO); receivableMapper.updateById(updateObj); + + // TODO @liuhongfeng:需要更新关联的 plan + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableUpdateReqVO.class)); + LogRecordContext.putVariable("receivable", oldReceivable); } + // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + + // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + @Override + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteReceivable(Long id) { + // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除 // 校验存在 - validateReceivableExists(id); + CrmReceivableDO receivable = validateReceivableExists(id); // 删除 receivableMapper.deleteById(id); + + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("receivable", receivable); } - private void validateReceivableExists(Long id) { - if (receivableMapper.selectById(id) == null) { + private CrmReceivableDO validateReceivableExists(Long id) { + CrmReceivableDO receivable = receivableMapper.selectById(id); + if (receivable == null) { throw exception(RECEIVABLE_NOT_EXISTS); } + return receivable; } - // TODO @芋艿:数据权限 @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmReceivableDO getReceivable(Long id) { return receivableMapper.selectById(id); } @@ -129,16 +171,15 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { return receivableMapper.selectBatchIds(ids); } - // TODO @芋艿:数据权限 @Override - public PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO) { - return receivableMapper.selectPage(pageReqVO); + public PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId) { + return receivableMapper.selectPage(pageReqVO, userId); } @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) - public PageResult getReceivablePageByCustomer(CrmReceivablePageReqVO pageReqVO) { - return receivableMapper.selectPageByCustomer(pageReqVO); + public PageResult getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) { + return receivableMapper.selectPageByCustomerId(pageReqVO); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java deleted file mode 100644 index 4004b301d..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 回款 - */ -package cn.iocoder.yudao.module.crm.service.receivable; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryPageUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryPageUtils.java deleted file mode 100644 index 5830a24a3..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryPageUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.crm.util; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; -import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.enums.common.CrmSceneEnum; -import cn.iocoder.yudao.module.crm.framework.vo.CrmBasePageReqVO; -import com.baomidou.mybatisplus.core.toolkit.support.SFunction; -import com.github.yulichang.wrapper.MPJLambdaWrapper; - -import jakarta.annotation.Nullable; -import java.util.Collection; - -/** - * CRM 分页查询工具类 - * - * @author HUIHUI - */ -public class CrmQueryPageUtils { - - // TODO @puhui999:是不是弱化 CrmBasePageReqVO,把 sceneType 作为参数传入。默认其实 pool 不一定要传递的 - /** - * 构造 CRM 数据类型数据分页查询条件 - * - * @param queryMapper 连表查询对象 - * @param reqVO 查询条件 - * @param userId 用户编号 - * @param bizType 数据类型 {@link CrmBizTypeEnum} - * @param bizId 数据编号 - * @param subordinateIds 下属用户编号,可为空 - */ - public static , V extends CrmBasePageReqVO, S> void builderQuery( - T queryMapper, V reqVO, Long userId, - Integer bizType, SFunction bizId, - @Nullable Collection subordinateIds, // TODO @puhui999:subordinateIds 可以优化成 subordinateUserIds - Boolean isAdmin) { - // TODO @puhui999:是不是特殊处理,让这个 util 有状态,这样 isAdmin 直接从这里读取;subordinateIds 也是;这样,可以简化参数; - // 1. 构建数据权限连表条件 - if (ObjUtil.notEqual(isAdmin, Boolean.TRUE)) { // 管理员不需要数据权限 - queryMapper.innerJoin(CrmPermissionDO.class, on -> - on.eq(CrmPermissionDO::getBizType, bizType).eq(CrmPermissionDO::getBizId, bizId) - .eq(CrmPermissionDO::getUserId, userId)); - } - // 2. 拼接公海的查询条件 - if (ObjUtil.equal(reqVO.getPool(), Boolean.TRUE)) { // 情况一:公海 - queryMapper.isNull("owner_user_id"); - } else { // 情况二:不是公海 - queryMapper.isNotNull("owner_user_id"); - } - // 3. 拼接场景的查询条件 - // TODO @puhui999::1 处的数据权限,应该和 3 处的场景是结合的? - // null 时:一种处理 - // 1:一种条件; - // 2:一种条件; - // 3:一种条件; - if (CrmSceneEnum.isOwner(reqVO.getSceneType())) { // 场景一:我负责的数据 - queryMapper.eq("owner_user_id", userId); - } - // TODO puhui999: 这里有一个疑问:如果下属负责的数据权限中没有自己的话还能看吗?回复:不能 - if (CrmSceneEnum.isSubordinate(reqVO.getSceneType()) && CollUtil.isNotEmpty(subordinateIds)) { // 场景三:下属负责的数据 - queryMapper.in("owner_user_id", subordinateIds); - } - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java new file mode 100644 index 000000000..dc849622e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java @@ -0,0 +1,114 @@ +package cn.iocoder.yudao.module.crm.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; +import com.github.yulichang.wrapper.MPJLambdaWrapper; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * CRM 查询工具类 + * + * @author HUIHUI + */ +public class CrmQueryWrapperUtils { + + /** + * 构造 CRM 数据类型数据分页查询条件 + * + * @param query 连表查询对象 + * @param bizType 数据类型 {@link CrmBizTypeEnum} + * @param bizId 数据编号 + * @param userId 用户编号 + * @param sceneType 场景类型 + * @param pool 公海 + */ + public static , S> void appendPermissionCondition(T query, Integer bizType, SFunction bizId, + Long userId, Integer sceneType, Boolean pool) { + final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id"; + // 1. 构建数据权限连表条件 + if (!CrmPermissionUtils.isCrmAdmin() && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员,公海不需要数据权限 + query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对 + .eq(CrmPermissionDO::getUserId, userId)); + } + // 2.1 场景一:我负责的数据 + if (CrmSceneTypeEnum.isOwner(sceneType)) { + query.eq(ownerUserIdField, userId); + } + // 2.2 场景二:我参与的数据 + if (CrmSceneTypeEnum.isInvolved(sceneType)) { + query.ne(ownerUserIdField, userId) + .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()); + } + // 2.3 场景三:下属负责的数据 + if (CrmSceneTypeEnum.isSubordinate(sceneType)) { + List subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId); + if (CollUtil.isEmpty(subordinateUsers)) { + query.eq(ownerUserIdField, -1); // 不返回任何结果 + } else { + query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId)); + } + } + + // 3. 拼接公海的查询条件 + if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一:公海 + query.isNull(ownerUserIdField); + } else { // 情况二:不是公海 + query.isNotNull(ownerUserIdField); + } + } + + /** + * 构造 CRM 数据类型批量数据查询条件 + * + * @param query 连表查询对象 + * @param bizType 数据类型 {@link CrmBizTypeEnum} + * @param bizIds 数据编号 + * @param userId 用户编号 + */ + public static > void appendPermissionCondition(T query, Integer bizType, Collection bizIds, Long userId) { + if (CrmPermissionUtils.isCrmAdmin()) {// 管理员不需要数据权限 + return; + } + + query.innerJoin(CrmPermissionDO.class, on -> + on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds) + .in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId)); + } + + /** + * 静态内部类实现单例获取 + * + * @author HUIHUI + */ + private static class SingletonManager { + + private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class); + + private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class); + + public static AdminUserApi getAdminUserApi() { + return ADMIN_USER_API; + } + + public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() { + return MYBATIS_PLUS_JOIN_PROPERTIES; + } + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java index 6072b72e6..0fd3c447f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java @@ -1,16 +1,14 @@ package cn.iocoder.yudao.module.crm.service.business; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; @@ -39,7 +37,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest { @Test public void testCreateBusiness_success() { // 准备参数 - CrmBusinessCreateReqVO reqVO = randomPojo(CrmBusinessCreateReqVO.class); + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class); // 调用 Long businessId = businessService.createBusiness(reqVO, getLoginUserId()); @@ -56,7 +54,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest { CrmBusinessDO dbBusiness = randomPojo(CrmBusinessDO.class); businessMapper.insert(dbBusiness);// @Sql: 先插入出一条存在的数据 // 准备参数 - CrmBusinessUpdateReqVO reqVO = randomPojo(CrmBusinessUpdateReqVO.class, o -> { + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class, o -> { o.setId(dbBusiness.getId()); // 设置更新的 ID }); @@ -70,7 +68,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateBusiness_notExists() { // 准备参数 - CrmBusinessUpdateReqVO reqVO = randomPojo(CrmBusinessUpdateReqVO.class); + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class); // 调用, 并断言异常 assertServiceException(() -> businessService.updateBusiness(reqVO), BUSINESS_NOT_EXISTS); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java index 3bb7dee63..9738c67b3 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java @@ -2,20 +2,18 @@ package cn.iocoder.yudao.module.crm.service.clue; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueExportReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; @@ -25,6 +23,7 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXIS import static org.junit.jupiter.api.Assertions.*; // TODO 芋艿:单测后续补; + /** * {@link CrmClueServiceImpl} 的单元测试类 * @@ -43,7 +42,7 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest { @Test public void testCreateClue_success() { // 准备参数 - CrmClueCreateReqVO reqVO = randomPojo(CrmClueCreateReqVO.class); + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class); // 调用 Long clueId = clueService.createClue(reqVO); @@ -60,7 +59,7 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest { CrmClueDO dbClue = randomPojo(CrmClueDO.class); clueMapper.insert(dbClue);// @Sql: 先插入出一条存在的数据 // 准备参数 - CrmClueUpdateReqVO reqVO = randomPojo(CrmClueUpdateReqVO.class, o -> { + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class, o -> { o.setId(dbClue.getId()); // 设置更新的 ID }); @@ -74,7 +73,7 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateClue_notExists() { // 准备参数 - CrmClueUpdateReqVO reqVO = randomPojo(CrmClueUpdateReqVO.class); + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class); // 调用, 并断言异常 assertServiceException(() -> clueService.updateClue(reqVO), CLUE_NOT_EXISTS); @@ -90,8 +89,8 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest { // 调用 clueService.deleteClue(id); - // 校验数据不存在了 - assertNull(clueMapper.selectById(id)); + // 校验数据不存在了 + assertNull(clueMapper.selectById(id)); } @Test @@ -106,110 +105,102 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest { @Test @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetCluePage() { - // mock 数据 - CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 - o.setTransformStatus(null); - o.setFollowUpStatus(null); - o.setName(null); - o.setCustomerId(null); - o.setContactNextTime(null); - o.setTelephone(null); - o.setMobile(null); - o.setAddress(null); - o.setContactLastTime(null); - o.setCreateTime(null); - }); - clueMapper.insert(dbClue); - // 测试 transformStatus 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); - // 测试 followUpStatus 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); - // 测试 name 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); - // 测试 customerId 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); - // 测试 contactNextTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); - // 测试 telephone 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); - // 测试 mobile 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); - // 测试 address 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); - // 测试 contactLastTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); - // 测试 createTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); - // 准备参数 - CrmCluePageReqVO reqVO = new CrmCluePageReqVO(); - reqVO.setName(null); - reqVO.setTelephone(null); - reqVO.setMobile(null); + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 + o.setTransformStatus(null); + o.setFollowUpStatus(null); + o.setName(null); + o.setCustomerId(null); + o.setContactNextTime(null); + o.setTelephone(null); + o.setMobile(null); + o.setAddress(null); + o.setContactLastTime(null); + o.setCreateTime(null); + }); + clueMapper.insert(dbClue); + // 测试 transformStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); + // 测试 followUpStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); + // 测试 name 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); + // 测试 customerId 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); + // 测试 contactNextTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); + // 测试 telephone 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); + // 测试 mobile 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); + // 测试 address 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); + // 测试 contactLastTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); + // 测试 createTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); + // 准备参数 + CrmCluePageReqVO reqVO = new CrmCluePageReqVO(); + reqVO.setName(null); + reqVO.setTelephone(null); + reqVO.setMobile(null); - // 调用 - PageResult pageResult = clueService.getCluePage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbClue, pageResult.getList().get(0)); + // 调用 + PageResult pageResult = clueService.getCluePage(reqVO, 1L); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbClue, pageResult.getList().get(0)); } @Test @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetClueList() { - // mock 数据 - CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 - o.setTransformStatus(null); - o.setFollowUpStatus(null); - o.setName(null); - o.setCustomerId(null); - o.setContactNextTime(null); - o.setTelephone(null); - o.setMobile(null); - o.setAddress(null); - o.setContactLastTime(null); - o.setCreateTime(null); - }); - clueMapper.insert(dbClue); - // 测试 transformStatus 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); - // 测试 followUpStatus 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); - // 测试 name 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); - // 测试 customerId 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); - // 测试 contactNextTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); - // 测试 telephone 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); - // 测试 mobile 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); - // 测试 address 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); - // 测试 contactLastTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); - // 测试 createTime 不匹配 - clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); - // 准备参数 - CrmClueExportReqVO reqVO = new CrmClueExportReqVO(); - reqVO.setTransformStatus(null); - reqVO.setFollowUpStatus(null); - reqVO.setName(null); - reqVO.setCustomerId(null); - reqVO.setContactNextTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - reqVO.setTelephone(null); - reqVO.setMobile(null); - reqVO.setAddress(null); - reqVO.setOwnerUserId(null); - reqVO.setContactLastTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - List list = clueService.getClueList(reqVO); - // 断言 - assertEquals(1, list.size()); - assertPojoEquals(dbClue, list.get(0)); + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 + o.setTransformStatus(null); + o.setFollowUpStatus(null); + o.setName(null); + o.setCustomerId(null); + o.setContactNextTime(null); + o.setTelephone(null); + o.setMobile(null); + o.setAddress(null); + o.setContactLastTime(null); + o.setCreateTime(null); + }); + clueMapper.insert(dbClue); + // 测试 transformStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); + // 测试 followUpStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); + // 测试 name 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); + // 测试 customerId 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); + // 测试 contactNextTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); + // 测试 telephone 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); + // 测试 mobile 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); + // 测试 address 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); + // 测试 contactLastTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); + // 测试 createTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); + // 准备参数 + CrmCluePageReqVO reqVO = new CrmCluePageReqVO(); + reqVO.setName(null); + reqVO.setTelephone(null); + reqVO.setMobile(null); + reqVO.setPageSize(PAGE_SIZE_NONE); + // 调用 + List list = clueService.getCluePage(reqVO, 1L).getList(); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbClue, list.get(0)); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java index 35825df19..a511e7c1b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java @@ -2,17 +2,15 @@ package cn.iocoder.yudao.module.crm.service.contract; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; @@ -39,7 +37,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest { @Test public void testCreateContract_success() { // 准备参数 - CrmContractCreateReqVO reqVO = randomPojo(CrmContractCreateReqVO.class); + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class); // 调用 Long contractId = contractService.createContract(reqVO, getLoginUserId()); @@ -56,7 +54,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest { CrmContractDO dbContract = randomPojo(CrmContractDO.class); contractMapper.insert(dbContract);// @Sql: 先插入出一条存在的数据 // 准备参数 - CrmContractUpdateReqVO reqVO = randomPojo(CrmContractUpdateReqVO.class, o -> { + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class, o -> { o.setId(dbContract.getId()); // 设置更新的 ID }); @@ -70,7 +68,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateContract_notExists() { // 准备参数 - CrmContractUpdateReqVO reqVO = randomPojo(CrmContractUpdateReqVO.class); + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class); // 调用, 并断言异常 assertServiceException(() -> contractService.updateContract(reqVO), CONTRACT_NOT_EXISTS); @@ -127,7 +125,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest { reqVO.setNo(null); // 调用 - PageResult pageResult = contractService.getContractPage(reqVO); + PageResult pageResult = contractService.getContractPage(reqVO, getLoginUserId()); // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java index 1d926e670..8f701d987 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java @@ -2,19 +2,17 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -47,7 +45,7 @@ public class CrmCustomerServiceImplTest extends BaseDbUnitTest { @Test public void testCreateCustomer_success() { // 准备参数 - CrmCustomerCreateReqVO reqVO = randomPojo(CrmCustomerCreateReqVO.class); + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class); // 调用 Long customerId = customerService.createCustomer(reqVO, getLoginUserId()); @@ -64,7 +62,7 @@ public class CrmCustomerServiceImplTest extends BaseDbUnitTest { CrmCustomerDO dbCustomer = randomPojo(CrmCustomerDO.class); customerMapper.insert(dbCustomer);// @Sql: 先插入出一条存在的数据 // 准备参数 - CrmCustomerUpdateReqVO reqVO = randomPojo(CrmCustomerUpdateReqVO.class, o -> { + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class, o -> { o.setId(dbCustomer.getId()); // 设置更新的 ID }); @@ -78,7 +76,7 @@ public class CrmCustomerServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateCustomer_notExists() { // 准备参数 - CrmCustomerUpdateReqVO reqVO = randomPojo(CrmCustomerUpdateReqVO.class); + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class); // 调用, 并断言异常 assertServiceException(() -> customerService.updateCustomer(reqVO), CUSTOMER_NOT_EXISTS); @@ -140,38 +138,4 @@ public class CrmCustomerServiceImplTest extends BaseDbUnitTest { //assertPojoEquals(dbCustomer, pageResult.getList().get(0)); } - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetCustomerList() { - // mock 数据 - CrmCustomerDO dbCustomer = randomPojo(CrmCustomerDO.class, o -> { // 等会查询到 - o.setName(null); - o.setMobile(null); - o.setTelephone(null); - o.setWebsite(null); - }); - customerMapper.insert(dbCustomer); - // 测试 name 不匹配 - customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setName(null))); - // 测试 mobile 不匹配 - customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setMobile(null))); - // 测试 telephone 不匹配 - customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setTelephone(null))); - // 测试 website 不匹配 - customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setWebsite(null))); - // 准备参数 - CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); - reqVO.setName("张三"); - reqVO.setMobile("13888888888"); - reqVO.setPageSize(PAGE_SIZE_NONE); - //reqVO.setTelephone(null); - //reqVO.setWebsite(null); - - // 调用 - PageResult pageResult = customerService.getCustomerPage(reqVO, 1L); - // 断言 - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbCustomer, pageResult.getList().get(0)); - } - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java index 41ef4a44e..ecda14822 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java @@ -2,18 +2,16 @@ package cn.iocoder.yudao.module.crm.service.customerlimitconfig; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerLimitConfigMapper; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigServiceImpl; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; @@ -40,7 +38,7 @@ public class CrmCustomerLimitConfigServiceImplTest extends BaseDbUnitTest { @Test public void testCreateCustomerLimitConfig_success() { // 准备参数 - CrmCustomerLimitConfigCreateReqVO reqVO = randomPojo(CrmCustomerLimitConfigCreateReqVO.class); + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class); // 调用 Long customerLimitConfigId = customerLimitConfigService.createCustomerLimitConfig(reqVO); @@ -57,7 +55,7 @@ public class CrmCustomerLimitConfigServiceImplTest extends BaseDbUnitTest { CrmCustomerLimitConfigDO dbCustomerLimitConfig = randomPojo(CrmCustomerLimitConfigDO.class); customerLimitConfigMapper.insert(dbCustomerLimitConfig);// @Sql: 先插入出一条存在的数据 // 准备参数 - CrmCustomerLimitConfigUpdateReqVO reqVO = randomPojo(CrmCustomerLimitConfigUpdateReqVO.class, o -> { + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class, o -> { o.setId(dbCustomerLimitConfig.getId()); // 设置更新的 ID }); @@ -71,7 +69,7 @@ public class CrmCustomerLimitConfigServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateCustomerLimitConfig_notExists() { // 准备参数 - CrmCustomerLimitConfigUpdateReqVO reqVO = randomPojo(CrmCustomerLimitConfigUpdateReqVO.class); + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class); // 调用, 并断言异常 assertServiceException(() -> customerLimitConfigService.updateCustomerLimitConfig(reqVO), CUSTOMER_LIMIT_CONFIG_NOT_EXISTS); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java index 7fa8986b0..4f17abcba 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java @@ -7,12 +7,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceiv import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; @@ -22,6 +22,7 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PL import static org.junit.jupiter.api.Assertions.*; // TODO 芋艿:后续,需要补充测试用例 + /** * {@link CrmReceivablePlanServiceImpl} 的单元测试类 * @@ -43,7 +44,7 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest { CrmReceivablePlanCreateReqVO reqVO = randomPojo(CrmReceivablePlanCreateReqVO.class); // 调用 - Long receivablePlanId = receivablePlanService.createReceivablePlan(reqVO); + Long receivablePlanId = receivablePlanService.createReceivablePlan(reqVO, 1L); // 断言 assertNotNull(receivablePlanId); // 校验记录的属性是否正确 @@ -87,8 +88,8 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest { // 调用 receivablePlanService.deleteReceivablePlan(id); - // 校验数据不存在了 - assertNull(crmReceivablePlanMapper.selectById(id)); + // 校验数据不存在了 + assertNull(crmReceivablePlanMapper.selectById(id)); } @Test @@ -103,34 +104,34 @@ public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest { @Test @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetReceivablePlanPage() { - // mock 数据 - CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class, o -> { // 等会查询到 - o.setPeriod(null); - o.setReturnTime(null); - o.setRemindDays(null); - o.setRemindTime(null); - o.setCustomerId(null); - o.setContractId(null); - o.setOwnerUserId(null); - o.setRemark(null); - o.setCreateTime(null); - }); - crmReceivablePlanMapper.insert(dbReceivablePlan); - // 测试 customerId 不匹配 - crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setCustomerId(null))); - // 测试 contractId 不匹配 - crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setContractId(null))); - // 准备参数 - CrmReceivablePlanPageReqVO reqVO = new CrmReceivablePlanPageReqVO(); - reqVO.setCustomerId(null); - reqVO.setContractId(null); - - // 调用 - PageResult pageResult = receivablePlanService.getReceivablePlanPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbReceivablePlan, pageResult.getList().get(0)); + // mock 数据 + CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class, o -> { // 等会查询到 + o.setPeriod(null); + o.setReturnTime(null); + o.setRemindDays(null); + o.setRemindTime(null); + o.setCustomerId(null); + o.setContractId(null); + o.setOwnerUserId(null); + o.setRemark(null); + o.setCreateTime(null); + }); + crmReceivablePlanMapper.insert(dbReceivablePlan); + // 测试 customerId 不匹配 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setCustomerId(null))); + // 测试 contractId 不匹配 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setContractId(null))); + // 准备参数 + CrmReceivablePlanPageReqVO reqVO = new CrmReceivablePlanPageReqVO(); + reqVO.setCustomerId(null); + reqVO.setContractId(null); + reqVO.setPageSize(PAGE_SIZE_NONE); + // 调用 + PageResult pageResult = receivablePlanService.getReceivablePlanPage(reqVO, 1L); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbReceivablePlan, pageResult.getList().get(0)); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java index 93ac0c99b..04c466924 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java @@ -7,13 +7,13 @@ import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.Crm import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; @@ -43,7 +43,7 @@ public class CrmCrmReceivableServiceImplTest extends BaseDbUnitTest { CrmReceivableCreateReqVO reqVO = randomPojo(CrmReceivableCreateReqVO.class); // 调用 - Long receivableId = receivableService.createReceivable(reqVO); + Long receivableId = receivableService.createReceivable(reqVO, getLoginUserId()); // 断言 assertNotNull(receivableId); // 校验记录的属性是否正确 @@ -133,7 +133,7 @@ public class CrmCrmReceivableServiceImplTest extends BaseDbUnitTest { reqVO.setCustomerId(null); // 调用 - PageResult pageResult = receivableService.getReceivablePage(reqVO); + PageResult pageResult = receivableService.getReceivablePage(reqVO, 1L); // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java index 1e4697d33..f19670061 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java @@ -22,7 +22,9 @@ public interface CodegenTableMapper extends BaseMapperX { .likeIfPresent(CodegenTableDO::getTableName, pageReqVO.getTableName()) .likeIfPresent(CodegenTableDO::getTableComment, pageReqVO.getTableComment()) .likeIfPresent(CodegenTableDO::getClassName, pageReqVO.getClassName()) - .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime())); + .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime()) + .orderByDesc(CodegenTableDO::getUpdateTime) + ); } default List selectListByDataSourceConfigId(Long dataSourceConfigId) { diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm index 5e03326c8..e6d96fbab 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm @@ -5,7 +5,6 @@ import lombok.*; import java.util.*; import jakarta.validation.constraints.*; ## 处理 BigDecimal 字段的引入 -import java.util.*; #foreach ($column in $columns) #if (${column.javaType} == "BigDecimal") import java.math.BigDecimal; diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm index bfe2dc007..835c0192e 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm @@ -63,7 +63,7 @@ export function export${simpleClassName}Excel(params) { responseType: 'blob' }) } -## 特殊:主子表专属逻辑 TODO @puhui999:下面方法的【空格】不太对 +## 特殊:主子表专属逻辑 #foreach ($subTable in $subTables) #set ($index = $foreach.count - 1) #set ($subSimpleClassName = $subSimpleClassNames.get($index)) @@ -76,8 +76,7 @@ export function export${simpleClassName}Excel(params) { // ==================== 子表($subTable.classComment) ==================== ## 情况一:MASTER_ERP 时,需要分查询页子表 - #if ( $table.templateType == 11 ) - + #if ($table.templateType == 11) // 获得${subTable.classComment}分页 export function get${subSimpleClassName}Page(params) { return request({ @@ -88,58 +87,53 @@ export function export${simpleClassName}Excel(params) { } ## 情况二:非 MASTER_ERP 时,需要列表查询子表 #else - #if ( $subTable.subJoinMany ) - + #if ($subTable.subJoinMany) // 获得${subTable.classComment}列表 export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField}, + url: '${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, method: 'get' }) } #else - // 获得${subTable.classComment} export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField}, + url: '${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, method: 'get' }) } #end #end ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 - #if ( $table.templateType == 11 ) + #if ($table.templateType == 11) // 新增${subTable.classComment} export function create${subSimpleClassName}(data) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, + url: '${baseURL}/${subSimpleClassName_strikeCase}/create', method: 'post', data }) } - // 修改${subTable.classComment} export function update${subSimpleClassName}(data) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, + url: '${baseURL}/${subSimpleClassName_strikeCase}/update', method: 'post', data }) } - // 删除${subTable.classComment} export function delete${subSimpleClassName}(id) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id, + url: '${baseURL}/${subSimpleClassName_strikeCase}/delete?id=' + id, method: 'delete' }) } - // 获得${subTable.classComment} export function get${subSimpleClassName}(id) { return request({ - url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id, + url: '${baseURL}/${subSimpleClassName_strikeCase}/get?id=' + id, method: 'get' }) } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm index c4b0b4332..7c2ab277b 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm @@ -1,12 +1,14 @@ import request from '@/config/axios' #set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") +// ${table.classComment} VO export interface ${simpleClassName}VO { #foreach ($column in $columns) #if ($column.createOperation || $column.updateOperation) + // ${column.columnComment} #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") ${column.javaField}: number -#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime") +#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") ${column.javaField}: Date #else ${column.javaField}: ${column.javaType.toLowerCase()} @@ -15,42 +17,44 @@ export interface ${simpleClassName}VO { #end } +// ${table.classComment} API +export const ${simpleClassName}Api = { #if ( $table.templateType != 2 ) -// 查询${table.classComment}分页 -export const get${simpleClassName}Page = async (params) => { - return await request.get({ url: `${baseURL}/page`, params }) -} + // 查询${table.classComment}分页 + get${simpleClassName}Page: async (params: any) => { + return await request.get({ url: `${baseURL}/page`, params }) + }, #else -// 查询${table.classComment}列表 -export const get${simpleClassName}List = async (params) => { - return await request.get({ url: `${baseURL}/list`, params }) -} + // 查询${table.classComment}列表 + get${simpleClassName}List: async (params) => { + return await request.get({ url: `${baseURL}/list`, params }) + }, #end -// 查询${table.classComment}详情 -export const get${simpleClassName} = async (id: number) => { - return await request.get({ url: `${baseURL}/get?id=` + id }) -} + // 查询${table.classComment}详情 + get${simpleClassName}: async (id: number) => { + return await request.get({ url: `${baseURL}/get?id=` + id }) + }, -// 新增${table.classComment} -export const create${simpleClassName} = async (data: ${simpleClassName}VO) => { - return await request.post({ url: `${baseURL}/create`, data }) -} + // 新增${table.classComment} + create${simpleClassName}: async (data: ${simpleClassName}VO) => { + return await request.post({ url: `${baseURL}/create`, data }) + }, -// 修改${table.classComment} -export const update${simpleClassName} = async (data: ${simpleClassName}VO) => { - return await request.put({ url: `${baseURL}/update`, data }) -} + // 修改${table.classComment} + update${simpleClassName}: async (data: ${simpleClassName}VO) => { + return await request.put({ url: `${baseURL}/update`, data }) + }, -// 删除${table.classComment} -export const delete${simpleClassName} = async (id: number) => { - return await request.delete({ url: `${baseURL}/delete?id=` + id }) -} + // 删除${table.classComment} + delete${simpleClassName}: async (id: number) => { + return await request.delete({ url: `${baseURL}/delete?id=` + id }) + }, -// 导出${table.classComment} Excel -export const export${simpleClassName} = async (params) => { - return await request.download({ url: `${baseURL}/export-excel`, params }) -} + // 导出${table.classComment} Excel + export${simpleClassName}: async (params) => { + return await request.download({ url: `${baseURL}/export-excel`, params }) + }, ## 特殊:主子表专属逻辑 #foreach ($subTable in $subTables) #set ($index = $foreach.count - 1) @@ -66,46 +70,47 @@ export const export${simpleClassName} = async (params) => { ## 情况一:MASTER_ERP 时,需要分查询页子表 #if ( $table.templateType == 11 ) -// 获得${subTable.classComment}分页 -export const get${subSimpleClassName}Page = async (params) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params }) -} + // 获得${subTable.classComment}分页 + get${subSimpleClassName}Page: async (params) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params }) + }, ## 情况二:非 MASTER_ERP 时,需要列表查询子表 #else #if ( $subTable.subJoinMany ) -// 获得${subTable.classComment}列表 -export const get${subSimpleClassName}ListBy${SubJoinColumnName} = async (${subJoinColumn.javaField}) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) -} + // 获得${subTable.classComment}列表 + get${subSimpleClassName}ListBy${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) + }, #else -// 获得${subTable.classComment} -export const get${subSimpleClassName}By${SubJoinColumnName} = async (${subJoinColumn.javaField}) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) -} + // 获得${subTable.classComment} + get${subSimpleClassName}By${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) + }, #end #end ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 #if ( $table.templateType == 11 ) -// 新增${subTable.classComment} -export const create${subSimpleClassName} = async (data) => { - return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data }) -} + // 新增${subTable.classComment} + create${subSimpleClassName}: async (data) => { + return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data }) + }, -// 修改${subTable.classComment} -export const update${subSimpleClassName} = async (data) => { - return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data }) -} + // 修改${subTable.classComment} + update${subSimpleClassName}: async (data) => { + return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data }) + }, -// 删除${subTable.classComment} -export const delete${subSimpleClassName} = async (id: number) => { - return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id }) -} + // 删除${subTable.classComment} + delete${subSimpleClassName}: async (id: number) => { + return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id }) + }, -// 获得${subTable.classComment} -export const get${subSimpleClassName} = async (id: number) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id }) -} + // 获得${subTable.classComment} + get${subSimpleClassName}: async (id: number) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id }) + }, #end -#end \ No newline at end of file +#end +} diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm index ed318875e..3996a9caa 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm @@ -114,7 +114,7 @@