|
@@ -39,6 +39,7 @@ import com.fs.crm.service.impl.CrmCustomerServiceImpl;
|
|
|
import com.fs.enums.ExecutionStatusEnum;
|
|
import com.fs.enums.ExecutionStatusEnum;
|
|
|
import com.fs.enums.NodeTypeEnum;
|
|
import com.fs.enums.NodeTypeEnum;
|
|
|
import com.fs.enums.TaskTypeEnum;
|
|
import com.fs.enums.TaskTypeEnum;
|
|
|
|
|
+import com.fs.his.utils.PhoneUtil;
|
|
|
import com.fs.qw.domain.QwUser;
|
|
import com.fs.qw.domain.QwUser;
|
|
|
import com.fs.qw.mapper.QwUserMapper;
|
|
import com.fs.qw.mapper.QwUserMapper;
|
|
|
import com.fs.qw.service.impl.QwExternalContactServiceImpl;
|
|
import com.fs.qw.service.impl.QwExternalContactServiceImpl;
|
|
@@ -56,12 +57,10 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Method;
|
|
|
-import java.time.temporal.ChronoField;
|
|
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
|
|
import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
|
|
|
-import static java.time.LocalTime.now;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -897,50 +896,48 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("callerResult4EasyCall 切换租户数据源失败: tenantId={}", TenantHelper.getTenantId(), e);
|
|
log.error("callerResult4EasyCall 切换租户数据源失败: tenantId={}", TenantHelper.getTenantId(), e);
|
|
|
}
|
|
}
|
|
|
-// // intent(意向度)由对方异步评估写入,回调时可能尚未赋值,进入延迟重试队列等待
|
|
|
|
|
-// if (StringUtils.isBlank(callPhoneRes.getIntent())) {
|
|
|
|
|
-// String retryKey = EASYCALL_INTENT_RETRY_KEY + result.getUuid();
|
|
|
|
|
-// Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
|
|
-// if (retryCount == null) {
|
|
|
|
|
-// retryCount = 0;
|
|
|
|
|
-// }
|
|
|
|
|
-// if (retryCount < EASYCALL_INTENT_MAX_RETRY) {
|
|
|
|
|
-// redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
|
|
-// log.info("easyCall外呼回调intent意向度暂未评估完成,uuid={},第{}次放入延迟重试队列", result.getUuid(), retryCount + 1);
|
|
|
|
|
-// doRetryCallerResult4EasyCall(result, retryCount + 1);
|
|
|
|
|
-// } else {
|
|
|
|
|
-// // 超过最大重试次数,以 intent 为空(意向未知)兜底继续处理
|
|
|
|
|
-// log.warn("easyCall外呼回调intent意向度在{}次重试后仍为空,uuid={},以意向未知兜底处理", EASYCALL_INTENT_MAX_RETRY, result.getUuid());
|
|
|
|
|
-// redisCache2.deleteObject(retryKey);
|
|
|
|
|
-// doHandleEasyCallResult(callPhoneRes);
|
|
|
|
|
-// }
|
|
|
|
|
-// return;
|
|
|
|
|
-// }
|
|
|
|
|
-// // intent 已有值,直接正常处理
|
|
|
|
|
-// redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
|
|
|
-// doHandleEasyCallResult(callPhoneRes);
|
|
|
|
|
- // dialogue(对话内容)由对方异步写入,回调时可能尚未赋值,进入延迟重试队列等待
|
|
|
|
|
- if (isDialogueEmpty(callPhoneRes.getDialogue()) && !"未接通".equals(callPhoneRes.getIntent())) {
|
|
|
|
|
- String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
|
|
|
|
|
|
|
+ // 【当前启用】cc_call_phone.intent 由 EasyCall 异步评估写入,回调时可能尚未赋值,进入延迟重试队列等待
|
|
|
|
|
+ if (StringUtils.isBlank(resolveCcCallPhoneIntent(callPhoneRes))) {
|
|
|
|
|
+ String retryKey = EASYCALL_INTENT_RETRY_KEY + result.getUuid();
|
|
|
Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
if (retryCount == null) {
|
|
if (retryCount == null) {
|
|
|
retryCount = 0;
|
|
retryCount = 0;
|
|
|
}
|
|
}
|
|
|
- if (retryCount < EASYCALL_DIALOGUE_MAX_RETRY) {
|
|
|
|
|
|
|
+ if (retryCount < EASYCALL_INTENT_MAX_RETRY) {
|
|
|
redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
- log.info("easyCall外呼回调dialogue对话内容暂未写入,uuid={},第{}次放入延迟重试队列", result.getUuid(), retryCount + 1);
|
|
|
|
|
- doRetryDialogue4EasyCall(result, retryCount + 1);
|
|
|
|
|
|
|
+ log.info("easyCall外呼回调intent意向度暂未评估完成,uuid={},第{}次放入延迟重试队列", result.getUuid(), retryCount + 1);
|
|
|
|
|
+ doRetryCallerResult4EasyCall(result, retryCount + 1);
|
|
|
} else {
|
|
} else {
|
|
|
- // 超过最大重试次数,以 dialogue 为空兜底继续处理
|
|
|
|
|
- log.warn("easyCall外呼回调dialogue对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", EASYCALL_DIALOGUE_MAX_RETRY, result.getUuid());
|
|
|
|
|
|
|
+ log.warn("easyCall外呼回调intent意向度在{}次重试后仍为空,uuid={},以意向未知兜底处理", EASYCALL_INTENT_MAX_RETRY, result.getUuid());
|
|
|
redisCache2.deleteObject(retryKey);
|
|
redisCache2.deleteObject(retryKey);
|
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
|
}
|
|
}
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- // dialogue 已有值,直接正常处理
|
|
|
|
|
- redisCache2.deleteObject(EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid());
|
|
|
|
|
|
|
+ redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 【历史保留-自家AI】根据 dialogue 等待后走 AI 意向度,回滚时注释上方 intent 重试并取消下方注释 ==========
|
|
|
|
|
+// // 当前:根据对话内容同步调用自家 AI 计算意向度,不依赖第三方 intent
|
|
|
|
|
+// if (isDialogueEmpty(callPhoneRes.getDialogue()) && !"未接通".equals(callPhoneRes.getIntent())) {
|
|
|
|
|
+// String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
|
|
|
|
|
+// Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
|
|
+// if (retryCount == null) {
|
|
|
|
|
+// retryCount = 0;
|
|
|
|
|
+// }
|
|
|
|
|
+// if (retryCount < EASYCALL_DIALOGUE_MAX_RETRY) {
|
|
|
|
|
+// redisCache2.setCacheObject(retryKey, retryCount + 1, 10, java.util.concurrent.TimeUnit.MINUTES);
|
|
|
|
|
+// log.info("easyCall外呼回调dialogue对话内容暂未写入,uuid={},第{}次放入延迟重试队列", result.getUuid(), retryCount + 1);
|
|
|
|
|
+// doRetryDialogue4EasyCall(result, retryCount + 1);
|
|
|
|
|
+// } else {
|
|
|
|
|
+// log.warn("easyCall外呼回调dialogue对话内容在{}次重试后仍为空,uuid={},以对话为空兜底处理", EASYCALL_DIALOGUE_MAX_RETRY, result.getUuid());
|
|
|
|
|
+// redisCache2.deleteObject(retryKey);
|
|
|
|
|
+// doHandleEasyCallResult(callPhoneRes);
|
|
|
|
|
+// }
|
|
|
|
|
+// return;
|
|
|
|
|
+// }
|
|
|
|
|
+// redisCache2.deleteObject(EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid());
|
|
|
|
|
+// doHandleEasyCallResult(callPhoneRes);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
throw new RuntimeException(e);
|
|
throw new RuntimeException(e);
|
|
|
} finally {
|
|
} finally {
|
|
@@ -1032,8 +1029,8 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
log.error("easyCall intent重试时仍未查询到外呼结果, uuid={}", result.getUuid());
|
|
log.error("easyCall intent重试时仍未查询到外呼结果, uuid={}", result.getUuid());
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- if (StringUtils.isBlank(callPhoneRes.getIntent())) {
|
|
|
|
|
- // intent 仍为空,继续判断是否还有剩余重试次数
|
|
|
|
|
|
|
+ if (StringUtils.isBlank(resolveCcCallPhoneIntent(callPhoneRes))) {
|
|
|
|
|
+ // cc_call_phone.intent 仍为空,继续判断是否还有剩余重试次数
|
|
|
String retryKey = EASYCALL_INTENT_RETRY_KEY + result.getUuid();
|
|
String retryKey = EASYCALL_INTENT_RETRY_KEY + result.getUuid();
|
|
|
Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
Integer retryCount = redisCache2.getCacheObject(retryKey);
|
|
|
if (retryCount == null) {
|
|
if (retryCount == null) {
|
|
@@ -1051,7 +1048,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
// intent 已评估完成,正常处理
|
|
// intent 已评估完成,正常处理
|
|
|
- log.info("easyCall intent重试第{}次成功获取到意向度={},uuid={}", currentRetry, callPhoneRes.getIntent(), result.getUuid());
|
|
|
|
|
|
|
+ log.info("easyCall intent重试第{}次成功获取到意向度={},uuid={}", currentRetry, resolveCcCallPhoneIntent(callPhoneRes), result.getUuid());
|
|
|
redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
|
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
doHandleEasyCallResult(callPhoneRes);
|
|
|
}
|
|
}
|
|
@@ -1150,27 +1147,37 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
// log.error("pushDialogContent4EasyCall 切换租户数据源失败: tenantId={}", TenantHelper.getTenantId(), e);
|
|
// log.error("pushDialogContent4EasyCall 切换租户数据源失败: tenantId={}", TenantHelper.getTenantId(), e);
|
|
|
// }
|
|
// }
|
|
|
// }
|
|
// }
|
|
|
- String intention = null;
|
|
|
|
|
- String intentionDegree = null;
|
|
|
|
|
- if (StringUtils.isNotBlank(callPhoneRes.getDialogue())) {
|
|
|
|
|
- log.info("【验证】意向度来源=自家AI, uuid={}, dialogueLength={}", callPhoneRes.getUuid(),
|
|
|
|
|
- StringUtils.isBlank(callPhoneRes.getDialogue()) ? 0 : callPhoneRes.getDialogue().length());
|
|
|
|
|
- try {
|
|
|
|
|
- intentionDegree = crmCustomerAnalyzeService.aiIntentionDegree(
|
|
|
|
|
- callPhoneRes.getDialogue(),
|
|
|
|
|
- now().getLong(ChronoField.MILLI_OF_SECOND)
|
|
|
|
|
- );
|
|
|
|
|
- log.info("【验证】意向度结果={}, uuid={}", intentionDegree, callPhoneRes.getUuid());
|
|
|
|
|
- intention = getIntention(intentionDegree);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("easyCall意向度AI解析失败,uuid={},将使用意向未知兜底", callPhoneRes.getUuid(), e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // 2) 最终兜底:意向未知
|
|
|
|
|
-// String intention = getIntention(callPhoneRes.getIntent());
|
|
|
|
|
|
|
+ // 【当前启用】读取 cc_call_phone.intent,再转换为系统字典 intention 数值
|
|
|
|
|
+ String intentRaw = resolveCcCallPhoneIntent(callPhoneRes);
|
|
|
|
|
+ String intention = convertEasyCallIntent(intentRaw);
|
|
|
|
|
+ log.info("easyCall意向度来源=EasyCall平台, uuid={}, intent(raw)={}, intention(converted)={}",
|
|
|
|
|
+ callPhoneRes.getUuid(), intentRaw, intention);
|
|
|
if (StringUtils.isEmpty(intention)) {
|
|
if (StringUtils.isEmpty(intention)) {
|
|
|
intention = "0";
|
|
intention = "0";
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 【历史保留-自家AI】回滚时注释上方 EasyCall 逻辑并取消下方注释 ==========
|
|
|
|
|
+// String intention = null;
|
|
|
|
|
+// String intentionDegree = null;
|
|
|
|
|
+// if (StringUtils.isNotBlank(callPhoneRes.getDialogue())) {
|
|
|
|
|
+// log.info("【验证】意向度来源=自家AI, uuid={}, dialogueLength={}", callPhoneRes.getUuid(),
|
|
|
|
|
+// StringUtils.isBlank(callPhoneRes.getDialogue()) ? 0 : callPhoneRes.getDialogue().length());
|
|
|
|
|
+// try {
|
|
|
|
|
+// intentionDegree = crmCustomerAnalyzeService.aiIntentionDegree(
|
|
|
|
|
+// callPhoneRes.getDialogue(),
|
|
|
|
|
+// java.time.LocalTime.now().getLong(java.time.temporal.ChronoField.MILLI_OF_SECOND)
|
|
|
|
|
+// );
|
|
|
|
|
+// log.info("【验证】意向度结果={}, uuid={}", intentionDegree, callPhoneRes.getUuid());
|
|
|
|
|
+// intention = getIntention(intentionDegree);
|
|
|
|
|
+// } catch (Exception e) {
|
|
|
|
|
+// log.error("easyCall意向度AI解析失败,uuid={},将使用意向未知兜底", callPhoneRes.getUuid(), e);
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+// if (StringUtils.isEmpty(intention)) {
|
|
|
|
|
+// intention = "0";
|
|
|
|
|
+// }
|
|
|
|
|
+// // 历史第三方值(仅 intent 字段,未走 AI 时的写法)
|
|
|
|
|
+// // String intention = getIntention(callPhoneRes.getIntent());
|
|
|
CompanyVoiceRoboticCallees callee = companyVoiceRoboticCalleesMapper.selectCompanyVoiceRoboticCalleesById(cacheInfo.getLong("calleeId"));
|
|
CompanyVoiceRoboticCallees callee = companyVoiceRoboticCalleesMapper.selectCompanyVoiceRoboticCalleesById(cacheInfo.getLong("calleeId"));
|
|
|
callee.setUuid(callPhoneRes.getUuid());
|
|
callee.setUuid(callPhoneRes.getUuid());
|
|
|
callee.setIntention(intention);
|
|
callee.setIntention(intention);
|
|
@@ -1230,6 +1237,30 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
return collect.isEmpty() ? null : collect.get(0).getDictValue();
|
|
return collect.isEmpty() ? null : collect.get(0).getDictValue();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 读取 EasyCall cc_call_phone 表原始字段 intent(非本系统 intention 字典值)
|
|
|
|
|
+ */
|
|
|
|
|
+ private String resolveCcCallPhoneIntent(EasyCallCallPhoneVO callPhoneRes) {
|
|
|
|
|
+ if (callPhoneRes == null || StringUtils.isBlank(callPhoneRes.getIntent())) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return callPhoneRes.getIntent().trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将 cc_call_phone.intent 原始值转为系统字典数值 intention(customer_intention_level.dict_value)
|
|
|
|
|
+ */
|
|
|
|
|
+ private String convertEasyCallIntent(String intentRaw) {
|
|
|
|
|
+ if (StringUtils.isBlank(intentRaw)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String t = intentRaw.trim();
|
|
|
|
|
+ if (t.matches("^\\d+$")) {
|
|
|
|
|
+ return t;
|
|
|
|
|
+ }
|
|
|
|
|
+ return getIntention(t);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public void pushBilling(PushIIntentionResult result) {
|
|
public void pushBilling(PushIIntentionResult result) {
|
|
|
Notify notify = result.getNotify();
|
|
Notify notify = result.getNotify();
|
|
|
CompanyVoiceRoboticCallees callee = getResultCalleeInfo(notify);
|
|
CompanyVoiceRoboticCallees callee = getResultCalleeInfo(notify);
|
|
@@ -1764,11 +1795,14 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
Integer pageSize,
|
|
Integer pageSize,
|
|
|
String customerName,
|
|
String customerName,
|
|
|
String customerPhone,
|
|
String customerPhone,
|
|
|
- Boolean onlyCallNode) {
|
|
|
|
|
|
|
+ Boolean onlyCallNode,
|
|
|
|
|
+ String encryptPhone) {
|
|
|
//分页查询主数据
|
|
//分页查询主数据
|
|
|
PageHelper.startPage(pageNum, pageSize);
|
|
PageHelper.startPage(pageNum, pageSize);
|
|
|
-
|
|
|
|
|
- List<WorkflowExecRecordVo> records = companyAiWorkflowExecMapper.selectExecRecordsByRoboticId(roboticId, customerName, customerPhone, onlyCallNode);
|
|
|
|
|
|
|
+ if(StringUtils.isNotBlank(encryptPhone)){
|
|
|
|
|
+ encryptPhone = PhoneUtil.encryptPhone(encryptPhone);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<WorkflowExecRecordVo> records = companyAiWorkflowExecMapper.selectExecRecordsByRoboticId(roboticId, customerName, customerPhone, onlyCallNode,encryptPhone);
|
|
|
|
|
|
|
|
PageInfo<WorkflowExecRecordVo> pageInfo = new PageInfo<>(records);
|
|
PageInfo<WorkflowExecRecordVo> pageInfo = new PageInfo<>(records);
|
|
|
|
|
|
|
@@ -1837,7 +1871,45 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- return buildResult(pageInfo, records);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 统计全量节点数据(不受分页限制):查全量instanceIds,再查全量nodeLogs
|
|
|
|
|
+ List<String> allInstanceIds = companyAiWorkflowExecMapper.selectExecRecordsByRoboticId(
|
|
|
|
|
+ roboticId, null, null, false, null)
|
|
|
|
|
+ .stream()
|
|
|
|
|
+ .map(WorkflowExecRecordVo::getWorkflowInstanceId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ long callDone = 0;
|
|
|
|
|
+ long addWxDone = 0;
|
|
|
|
|
+ long sendMsgDone = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (!allInstanceIds.isEmpty()) {
|
|
|
|
|
+ List<CompanyAiWorkflowExecLog> allNodeLogs = companyAiWorkflowExecLogMapper.selectByInstanceIds(allInstanceIds);
|
|
|
|
|
+ // nodeType: 6=AI外呼电话, 7=AI发送短信, 8=AI添加微信, 9=AI企微添加个微, 10=AI添加微信(新)
|
|
|
|
|
+ callDone = allNodeLogs.stream()
|
|
|
|
|
+ .filter(e -> e.getStatus() == 1)
|
|
|
|
|
+ .filter(e -> e.getNodeType().equals(NodeTypeEnum.AI_CALL_TASK.getValue()))
|
|
|
|
|
+ .count();
|
|
|
|
|
+ addWxDone = allNodeLogs.stream()
|
|
|
|
|
+ .filter(e -> e.getStatus() == 1)
|
|
|
|
|
+ .filter(e -> e.getNodeType().equals(NodeTypeEnum.AI_ADD_WX_TASK.getValue())
|
|
|
|
|
+ || e.getNodeType().equals(NodeTypeEnum.AI_QW_ADD_WX_TASK.getValue())
|
|
|
|
|
+ || e.getNodeType().equals(NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue()))
|
|
|
|
|
+ .count();
|
|
|
|
|
+ sendMsgDone = allNodeLogs.stream()
|
|
|
|
|
+ .filter(e -> e.getStatus() == 1)
|
|
|
|
|
+ .filter(e -> e.getNodeType().equals(NodeTypeEnum.AI_SEND_MSG_TASK.getValue()))
|
|
|
|
|
+ .count();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> result = buildResult(pageInfo, records);
|
|
|
|
|
+ Map<String, Object> stats = new HashMap<>();
|
|
|
|
|
+ stats.put("callDone", callDone);
|
|
|
|
|
+ stats.put("addWxDone", addWxDone);
|
|
|
|
|
+ stats.put("sendMsgDone", sendMsgDone);
|
|
|
|
|
+ result.put("stats", stats);
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private Map<String, Object> buildResult(PageInfo<?> pageInfo, List<?> records) {
|
|
private Map<String, Object> buildResult(PageInfo<?> pageInfo, List<?> records) {
|
|
@@ -1909,7 +1981,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 +2001,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 +2015,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 +2304,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("、"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|