Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

吴树波 1 tydzień temu
rodzic
commit
69746af65a

+ 12 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java

@@ -23,6 +23,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.domain.CompanyVoiceRoboticWx;
 import com.fs.company.domain.CompanyVoiceRoboticWx;
+import com.fs.company.param.AppendCustomersParam;
 import com.fs.company.param.PauseRoboticActiveParam;
 import com.fs.company.param.PauseRoboticActiveParam;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticCalleesService;
 import com.fs.company.service.ICompanyVoiceRoboticCalleesService;
@@ -398,4 +399,15 @@ public class CompanyVoiceRoboticController extends BaseController
         TenantHelper.setTenantId(SecurityUtils.getTenantId());
         TenantHelper.setTenantId(SecurityUtils.getTenantId());
         return companyVoiceRoboticService.pauseRoboticActive(param);
         return companyVoiceRoboticService.pauseRoboticActive(param);
     }
     }
+
+    /**
+     * 追加客户到运行中的普通任务
+     */
+    @PreAuthorize("@ss.hasPermi('system:companyVoiceRobotic:edit')")
+    @Log(title = "追加客户到运行中任务", businessType = BusinessType.INSERT)
+    @PostMapping("/appendCustomers")
+    public R appendCustomers(@RequestBody AppendCustomersParam param){
+        TenantHelper.setTenantId(SecurityUtils.getTenantId());
+        return companyVoiceRoboticService.appendCustomersToRunningTask(param.getTaskId(), param.getCustomerIds());
+    }
 }
 }

+ 25 - 0
fs-service/src/main/java/com/fs/company/param/AppendCustomersParam.java

@@ -0,0 +1,25 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author MixLiu
+ * @date 2026/5/26
+ * @description 追加客户到运行中任务的请求参数
+ */
+@Data
+public class AppendCustomersParam {
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 要追加的CRM客户ID列表
+     */
+    private List<Long> customerIds;
+
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticService.java

@@ -125,4 +125,13 @@ public interface ICompanyVoiceRoboticService extends IService<CompanyVoiceRoboti
      * @return true=暂停中 false=非暂停
      * @return true=暂停中 false=非暂停
      */
      */
     boolean isTaskPaused(Long taskId);
     boolean isTaskPaused(Long taskId);
+
+    /**
+     * 追加客户到运行中的普通任务
+     *
+     * @param taskId      任务ID
+     * @param customerIds 要追加的CRM客户ID列表
+     * @return 追加结果(成功数、重复客户信息)
+     */
+    R appendCustomersToRunningTask(Long taskId, List<Long> customerIds);
 }
 }

+ 184 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -1909,7 +1909,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return new ArrayList<>();
             return new ArrayList<>();
         }
         }
         List<CompanyAiWorkflowExecLog> callLogs = logs.stream().filter(a -> "外呼".equals(a.getNodeName())).collect(Collectors.toList());
         List<CompanyAiWorkflowExecLog> callLogs = logs.stream().filter(a -> "外呼".equals(a.getNodeName())).collect(Collectors.toList());
-        HashMap<Long,String> callContentMap;
+        HashMap<Long,CallContentVO> callContentMap;
         if (null != callLogs && !callLogs.isEmpty()) {
         if (null != callLogs && !callLogs.isEmpty()) {
             callContentMap = selectCallContentByCallLogs(callLogs);
             callContentMap = selectCallContentByCallLogs(callLogs);
         } else {
         } else {
@@ -1929,7 +1929,11 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             vo.setDuration(log.getDuration());
             vo.setDuration(log.getDuration());
             vo.setErrorMessage(log.getErrorMessage());
             vo.setErrorMessage(log.getErrorMessage());
             vo.setOutputData(log.getOutputData());
             vo.setOutputData(log.getOutputData());
-            vo.setNodeContentList(callContentMap.get(log.getId()));
+            CallContentVO callContentVO = callContentMap.get(log.getId());
+            if (callContentVO != null) {
+                vo.setNodeContentList(callContentVO.getCallContent());
+                vo.setNodeRecordPath(callContentVO.getRecordPath());
+            }
             return vo;
             return vo;
         }).collect(Collectors.toList());
         }).collect(Collectors.toList());
     }
     }
@@ -1939,15 +1943,15 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
      * @param callLogs
      * @param callLogs
      * @return
      * @return
      */
      */
-    public  HashMap<Long,String> selectCallContentByCallLogs(List<CompanyAiWorkflowExecLog> callLogs){
+    public  HashMap<Long,CallContentVO> selectCallContentByCallLogs(List<CompanyAiWorkflowExecLog> callLogs){
         List<Long> ids = callLogs.stream().map(a -> a.getId()).collect(Collectors.toList());
         List<Long> ids = callLogs.stream().map(a -> a.getId()).collect(Collectors.toList());
         if(null == ids || ids.isEmpty()){
         if(null == ids || ids.isEmpty()){
             return new HashMap<>();
             return new HashMap<>();
         }
         }
         List<CallContentVO> callContentVOS = companyAiWorkflowExecLogMapper.selectCallContent(ids);
         List<CallContentVO> callContentVOS = companyAiWorkflowExecLogMapper.selectCallContent(ids);
         if(null != callContentVOS && !callContentVOS.isEmpty()){
         if(null != callContentVOS && !callContentVOS.isEmpty()){
-            HashMap<Long,String> map = new HashMap<>();
-            callContentVOS.forEach(a -> map.put(a.getLogId(),a.getCallContent()));
+            HashMap<Long,CallContentVO> map = new HashMap<>();
+            callContentVOS.forEach(a -> map.put(a.getLogId(), a));
             return map;
             return map;
         }
         }
         else{
         else{
@@ -2228,4 +2232,179 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         }
         }
         return false;
         return false;
     }
     }
+
+    @Override
+    public R appendCustomersToRunningTask(Long taskId, List<Long> customerIds) {
+        // 1. 校验参数
+        if (taskId == null || customerIds == null || customerIds.isEmpty()) {
+            return R.error("参数不能为空");
+        }
+
+        // 2. 校验任务
+        CompanyVoiceRobotic robotic = companyVoiceRoboticMapper.selectCompanyVoiceRoboticById(taskId);
+        if (robotic == null) {
+            return R.error("任务不存在: " + taskId);
+        }
+        if (!robotic.getTaskType().equals(TaskTypeEnum.ORDINARY.getValue())) {
+            return R.error("仅普通任务支持追加客户");
+        }
+        if (!Integer.valueOf(1).equals(robotic.getTaskStatus())) {
+            return R.error("任务不在执行中状态,无法追加客户");
+        }
+        if (robotic.getCompanyAiWorkflowId() == null) {
+            return R.error("任务未配置工作流");
+        }
+
+        // 3. 查询当前任务已有的callees的userId集合,过滤重复
+        List<CompanyVoiceRoboticCallees> existingCallees = companyVoiceRoboticCalleesMapper.selectByRoboticId(taskId);
+        Set<Long> existingUserIds = existingCallees.stream()
+                .map(CompanyVoiceRoboticCallees::getUserId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 分离重复和非重复客户
+        List<Long> duplicateIds = customerIds.stream()
+                .filter(existingUserIds::contains)
+                .collect(Collectors.toList());
+        List<Long> newCustomerIds = customerIds.stream()
+                .filter(id -> !existingUserIds.contains(id))
+                .collect(Collectors.toList());
+
+        if (newCustomerIds.isEmpty()) {
+            String dupNames = getDuplicateCustomerNames(duplicateIds);
+            return R.error("所选客户已存在于任务中" + (dupNames.isEmpty() ? "" : ":" + dupNames));
+        }
+
+        // 4. 批量查询CRM客户信息
+        List<CrmCustomer> crmCustomers = crmCustomerService.selectCrmCustomerListByIds(
+                newCustomerIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
+        Map<Long, CrmCustomer> customerMap = crmCustomers.stream()
+                .collect(Collectors.toMap(CrmCustomer::getCustomerId, c -> c, (a, b) -> a));
+
+        // 5. 判断是否需要加微分配
+        boolean hasAddWxNode = workflowHasAddWxNode(robotic.getCompanyAiWorkflowId());
+
+        int successCount = 0;
+        List<String> errorMessages = new ArrayList<>();
+
+        for (Long customerId : newCustomerIds) {
+            CrmCustomer crmCustomer = customerMap.get(customerId);
+            if (crmCustomer == null) {
+                errorMessages.add("客户不存在: " + customerId);
+                continue;
+            }
+            try {
+                // 5.1 创建CompanyWxClient记录
+                CompanyWxClient client = new CompanyWxClient();
+                client.setRoboticId(taskId);
+                client.setCustomerId(customerId);
+                client.setIsWeCom(robotic.getIsWeCom());
+                companyWxClientServiceImpl.insertCompanyWxClient(client);
+
+                // 5.2 创建CompanyVoiceRoboticCallees记录
+                CompanyVoiceRoboticCallees callee = new CompanyVoiceRoboticCallees();
+                callee.setUserId(crmCustomer.getCustomerId());
+                callee.setUserName(crmCustomer.getCustomerName());
+                callee.setPhone(crmCustomer.getMobile());
+                callee.setRoboticId(robotic.getId());
+                callee.setResult(0);
+                callee.setTaskFlow(robotic.getTaskFlow());
+                callee.setRunTaskFlow(robotic.getRunTaskFlow());
+                callee.setIsWeCom(robotic.getIsWeCom());
+                companyVoiceRoboticCalleesService.save(callee);
+
+                // 5.3 加微分配
+                if (hasAddWxNode && Integer.valueOf(0).equals(robotic.getAddType())) {
+                    allocateWx4SceneTask(robotic, client.getId());
+                } else if (hasAddWxNode && Integer.valueOf(1).equals(robotic.getAddType())) {
+                    String intention = crmCustomer.getIntention();
+                    String queryIntention = intention;
+                    if (!isPositiveInteger(intention)) {
+                        List<SysDictData> customerIntentionLevel = sysDictTypeService.selectDictDataByType("customer_intention_level");
+                        Optional<SysDictData> firstDict = customerIntentionLevel.stream()
+                                .filter(e -> e.getDictLabel().equals(intention)).findFirst();
+                        if (firstDict.isPresent()) {
+                            queryIntention = firstDict.get().getDictValue();
+                        }
+                    }
+                    List<CompanyVoiceRoboticWx> roboticWxList = companyVoiceRoboticWxMapper.selectByRoboticId(taskId, queryIntention);
+                    List<CompanyWxAccount> accountList = new ArrayList<>(companyWxAccountService.listByIds(PubFun.listToNewList(roboticWxList, CompanyVoiceRoboticWx::getAccountId)));
+                    Map<Long, CompanyWxAccount> accountMap = PubFun.listToMapByGroupObject(accountList, CompanyWxAccount::getId);
+                    roboticWxList.forEach(e -> e.setAccount(accountMap.get(e.getAccountId())));
+                    CompanyWxClient companyWxClient = companyWxClientServiceImpl.getOne(new QueryWrapper<CompanyWxClient>().eq("robotic_id", callee.getRoboticId()).eq("customer_id", callee.getUserId()));
+                    if (companyWxClient == null) {
+                        companyWxClient = new CompanyWxClient();
+                    }
+                    companyWxClient.setRoboticId(callee.getRoboticId());
+                    companyWxClient.setNickName(callee.getUserName());
+                    companyWxClient.setPhone(callee.getPhone());
+                    companyWxClient.setCustomerId(callee.getUserId());
+                    companyWxClient.setIntention(intention);
+                    bindCompany(companyWxClient, roboticWxList);
+                    companyWxClientServiceImpl.saveOrUpdate(companyWxClient);
+                }
+
+                // 5.4 创建CompanyVoiceRoboticBusiness记录
+                CompanyVoiceRoboticBusiness business = buildTaskBussiness4SceneTask(robotic, callee);
+
+                // 5.5 初始化工作流实例
+                Map<String, Object> inputVariables = new HashMap<>();
+                inputVariables.put("roboticId", robotic.getId());
+                inputVariables.put("businessId", business.getId());
+                inputVariables.put("cidGroupNo", robotic.getCidGroupNo());
+                inputVariables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
+                inputVariables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
+                ExecutionResult initResult = companyWorkflowEngine.initialize(robotic.getCompanyAiWorkflowId(), inputVariables);
+                if (!initResult.isSuccess()) {
+                    errorMessages.add("客户" + crmCustomer.getCustomerName() + "工作流初始化失败: " + initResult.getErrorMessage());
+                    continue;
+                }
+
+                successCount++;
+            } catch (Exception e) {
+                log.error("追加客户失败 - taskId: {}, customerId: {}", taskId, customerId, e);
+                errorMessages.add("客户" + crmCustomer.getCustomerName() + "追加失败: " + e.getMessage());
+            }
+        }
+
+        // 6. 为新增的callees生成AI标签信息
+        if (successCount > 0) {
+            try {
+                List<CompanyVoiceRoboticCallees> allCallees = companyVoiceRoboticCalleesMapper.selectByRoboticId(taskId);
+                List<CompanyWxClient> companyWxClients = companyWxClientMapper.selectListByRoboticId(taskId);
+                Map<String, CompanyWxClient> clientMp = companyWxClients.stream()
+                        .collect(Collectors.toMap(e -> e.getRoboticId() + "-" + e.getCustomerId(), e -> e, (a, b) -> a));
+                asyncCalleeProcessorService.generateCustomerInfo(allCallees, clientMp, robotic);
+            } catch (Exception e) {
+                log.warn("追加客户后生成AI标签信息异常 - taskId: {}", taskId, e);
+            }
+        }
+
+        // 7. 构建返回结果
+        Map<String, Object> resultData = new HashMap<>();
+        resultData.put("successCount", successCount);
+        if (!duplicateIds.isEmpty()) {
+            resultData.put("duplicateCustomerNames", getDuplicateCustomerNames(duplicateIds));
+        }
+        if (!errorMessages.isEmpty()) {
+            resultData.put("errorMessages", errorMessages);
+        }
+        return R.ok(resultData);
+    }
+
+    /**
+     * 根据客户ID列表获取重复客户名称
+     */
+    private String getDuplicateCustomerNames(List<Long> customerIds) {
+        if (customerIds == null || customerIds.isEmpty()) {
+            return "";
+        }
+        try {
+            List<CrmCustomer> customers = crmCustomerService.selectCrmCustomerListByIds(
+                    customerIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
+            return customers.stream().map(CrmCustomer::getCustomerName).filter(Objects::nonNull).collect(Collectors.joining("、"));
+        } catch (Exception e) {
+            return customerIds.stream().map(String::valueOf).collect(Collectors.joining("、"));
+        }
+    }
 }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/vo/CallContentVO.java

@@ -13,4 +13,6 @@ public class CallContentVO {
     private Long logId;
     private Long logId;
 
 
     private String callContent;
     private String callContent;
+
+    private String recordPath;
 }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/company/vo/WorkflowExecRecordVo.java

@@ -200,5 +200,10 @@ public class WorkflowExecRecordVo {
          * 外呼节点的对话记录
          * 外呼节点的对话记录
          */
          */
         private String nodeContentList;
         private String nodeContentList;
+
+        /**
+         * 外呼节点的录音地址
+         */
+        private String nodeRecordPath;
     }
     }
 }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerServiceImpl.java

@@ -587,7 +587,7 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
         List<CrmCustomer> batchList = new ArrayList<>();
         List<CrmCustomer> batchList = new ArrayList<>();
         List<CrmCompanyLineCustomerImportParam> validParams = new ArrayList<>();
         List<CrmCompanyLineCustomerImportParam> validParams = new ArrayList<>();
         List<String> phoneList = PubFun.listToNewList(list, e -> PhoneUtil.encryptPhone(e.getMobile()));
         List<String> phoneList = PubFun.listToNewList(list, e -> PhoneUtil.encryptPhone(e.getMobile()));
-        List<CrmCustomer> crmList = crmCustomerMapper.selectList(new QueryWrapper<CrmCustomer>().in("mobile", phoneList));
+        List<CrmCustomer> crmList = crmCustomerMapper.selectList(new QueryWrapper<CrmCustomer>().in("mobile", phoneList).eq("company_id",companyId));
 
 
 
 
         for (CrmCompanyLineCustomerImportParam customer : list) {
         for (CrmCompanyLineCustomerImportParam customer : list) {

+ 1 - 1
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java

@@ -48,7 +48,7 @@ public interface QwGroupChatMapper
             "FROM " +
             "FROM " +
             "    qw_group_chat gc " +
             "    qw_group_chat gc " +
             "LEFT JOIN qw_group_chat_user gcu ON gc.chat_id = gcu.chat_id  " +
             "LEFT JOIN qw_group_chat_user gcu ON gc.chat_id = gcu.chat_id  " +
-            " left join qw_user qu on gc.owner=qu.qw_user_id  and qu.is_del=0 and qu.corp_id=gc.corp_id " +
+            " left join qw_user qu on gc.owner=qu.qw_open_user_id  and qu.is_del=0 and qu.corp_id=gc.corp_id " +
             " left join company_user cu on cu.qw_user_id=qu.id " +
             " left join company_user cu on cu.qw_user_id=qu.id " +
             "    AND gc.corp_id = gcu.corp_id  " +
             "    AND gc.corp_id = gcu.corp_id  " +
             "<where> " +
             "<where> " +

+ 1 - 0
fs-service/src/main/resources/db/tenant-initTable.sql

@@ -2735,6 +2735,7 @@ CREATE TABLE `company_voice_robotic_call_log_callphone`
     `answered_ext_num` varchar(200) NULL DEFAULT NULL COMMENT '接听分机号码',
     `answered_ext_num` varchar(200) NULL DEFAULT NULL COMMENT '接听分机号码',
     `manual_answered_time` bigint NULL DEFAULT NULL COMMENT '人工接听时间',
     `manual_answered_time` bigint NULL DEFAULT NULL COMMENT '人工接听时间',
     `manual_answered_time_len` bigint NULL DEFAULT NULL COMMENT '人工接听时长',
     `manual_answered_time_len` bigint NULL DEFAULT NULL COMMENT '人工接听时长',
+    `hangup_type` varchar(255) NULL DEFAULT NULL COMMENT '挂断类型',
     PRIMARY KEY (`log_id`) USING BTREE,
     PRIMARY KEY (`log_id`) USING BTREE,
     INDEX              `robotic_id_and_caller_id_idx`(`robotic_id`, `caller_id`) USING BTREE,
     INDEX              `robotic_id_and_caller_id_idx`(`robotic_id`, `caller_id`) USING BTREE,
     INDEX              `company_and_company_user_idx`(`company_id`, `company_user_id`) USING BTREE,
     INDEX              `company_and_company_user_idx`(`company_id`, `company_user_id`) USING BTREE,

+ 2 - 1
fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml

@@ -165,7 +165,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectCallContent" resultType="com.fs.company.vo.CallContentVO" >
     <select id="selectCallContent" resultType="com.fs.company.vo.CallContentVO" >
         SELECT
         SELECT
             t1.id as logId,
             t1.id as logId,
-            t2.content_list as callContent
+            t2.content_list as callContent,
+            t2.record_path as recordPath
         FROM
         FROM
             company_ai_workflow_exec_log t1
             company_ai_workflow_exec_log t1
                 INNER JOIN company_voice_robotic_call_log_callphone t2
                 INNER JOIN company_voice_robotic_call_log_callphone t2