lmx пре 6 дана
родитељ
комит
f25bf0e341

+ 37 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogCallphoneController.java

@@ -10,6 +10,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
@@ -85,6 +86,10 @@ public class CompanyVoiceRoboticCallLogCallphoneController extends BaseControlle
     public TableDataInfo groupList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone)
     {
         startPage();
+        if(null == companyVoiceRoboticCallLogCallphone.getCompanyId()){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            companyVoiceRoboticCallLogCallphone.setCompanyId(loginUser.getUser().getCompanyId());
+        }
         List<CompanyVoiceRoboticCallLogCallphone> list = companyVoiceRoboticCallLogCallphoneService.selectCompanyVoiceRoboticCallPhoneLogGroupList(companyVoiceRoboticCallLogCallphone);
         return getDataTable(list);
     }
@@ -96,7 +101,9 @@ public class CompanyVoiceRoboticCallLogCallphoneController extends BaseControlle
     @GetMapping("/count")
     public AjaxResult selectCompanyVoiceRoboticCallPhoneLogCount()
     {
-        CompanyVoiceRoboticCallLogCount companyVoiceRoboticCallLogCount = companyVoiceRoboticCallLogCallphoneService.selectCompanyVoiceRoboticCallPhoneLogCount();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getUser().getCompanyId();
+        CompanyVoiceRoboticCallLogCount companyVoiceRoboticCallLogCount = companyVoiceRoboticCallLogCallphoneService.selectCompanyVoiceRoboticCallPhoneLogCount(companyId);
         return AjaxResult.success(companyVoiceRoboticCallLogCount);
     }
 
@@ -166,6 +173,35 @@ public class CompanyVoiceRoboticCallLogCallphoneController extends BaseControlle
         return util.exportExcel(list, "调用日志_ai打电话数据");
     }
 
+
+    /**
+     * 导出详情外呼记录(任务名称、客户名称、解密手机号)
+     */
+    @PreAuthorize("@ss.hasPermi('company:callphonelog:exportPhone')")
+    @Log(title = "外呼记录详情手机号导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportDetailPhone")
+    public AjaxResult exportDetailPhone(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone)
+    {
+        List<CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO> list =
+                companyVoiceRoboticCallLogCallphoneService.listDecryptPhoneExport(companyVoiceRoboticCallLogCallphone);
+        ExcelUtil<CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO> util =
+                new ExcelUtil<>(CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO.class);
+        return util.exportExcel(list, "外呼记录详情手机号");
+    }
+
+    /**
+     * 查看外呼记录解密手机号(无CRM客户时按记录解密)
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customer:queryPhone')")
+    @Log(title = "查看外呼记录手机号", businessType = BusinessType.GRANT)
+    @GetMapping("/queryPhone/{logId}")
+    public AjaxResult queryCallLogPhone(@PathVariable("logId") Long logId)
+    {
+        String mobile = companyVoiceRoboticCallLogCallphoneService.getDecryptPhoneByLogId(logId);
+        AjaxResult success = AjaxResult.success();
+        success.put("mobile", mobile);
+        return success;
+    }
 //    /**
 //     * 导出调用日志_ai打电话列表
 //     */

+ 7 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java

@@ -173,6 +173,13 @@ public class CompanyVoiceRoboticCallLogCallphone extends BaseEntity{
 
     @TableField(exist = false)
     private Integer runningCount;
+    /** 详情筛选-手机号(明文) */
+    @TableField(exist = false)
+    private String phone;
+
+    /** 详情筛选-加密手机号(匹配 callees.phone) */
+    @TableField(exist = false)
+    private String encryptedPhone;
 
     public static CompanyVoiceRoboticCallLogCallphone initCallLog( String runParam, Long keyId, Long taskId,Long companyId) {
         CompanyVoiceRoboticCallLogCallphone log = new CompanyVoiceRoboticCallLogCallphone();

+ 5 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogCallphoneMapper.java

@@ -3,6 +3,7 @@ package com.fs.company.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyVoiceRoboticCallees;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import com.fs.crm.vo.CustomerCallStatVO;
@@ -83,7 +84,7 @@ public interface CompanyVoiceRoboticCallLogCallphoneMapper extends BaseMapper<Co
 
     List<CompanyVoiceRoboticCallLogCallphone> selectCompanyVoiceRoboticCallPhoneLogGroupList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
 
-    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount();
+    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount(@Param("companyId") Long companyId);
 
 
     List<CompanyVoiceRoboticCallLogCallPhoneVO> listByRoboticId(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
@@ -106,4 +107,7 @@ public interface CompanyVoiceRoboticCallLogCallphoneMapper extends BaseMapper<Co
      * 按 customer_id(= callees.user_id)批量统计累计 AI 外呼总数与接通数
      */
     List<CustomerCallStatVO> selectAiCallStatTotal(@Param("customerIds") List<Long> customerIds);
+
+    List<CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO> listDecryptPhoneExport(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
 }

+ 10 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticCallLogCallphoneService.java

@@ -2,6 +2,7 @@ package com.fs.company.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 
@@ -79,7 +80,7 @@ public interface ICompanyVoiceRoboticCallLogCallphoneService extends IService<Co
 
     List<CompanyVoiceRoboticCallLogCallphone> selectCompanyVoiceRoboticCallPhoneLogGroupList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
 
-    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount();
+    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount(Long companyId);
 
     List<CompanyVoiceRoboticCallLogCallPhoneVO> listByRoboticId(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
 
@@ -92,4 +93,12 @@ public interface ICompanyVoiceRoboticCallLogCallphoneService extends IService<Co
      * @return 影响行数
      */
     int markHandleFlag(Long logId);
+
+    List<CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO> listDecryptPhoneExport(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
+
+    /**
+     * 根据外呼记录ID获取解密后的手机号
+     */
+    String getDecryptPhoneByLogId(Long logId);
 }

+ 122 - 8
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogCallphoneServiceImpl.java

@@ -23,9 +23,7 @@ import com.fs.company.mapper.CompanyWxAccountMapper;
 import com.fs.company.service.CompanyWorkflowEngine;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticService;
-import com.fs.company.vo.CidConfigVO;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
-import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
+import com.fs.company.vo.*;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
 import com.fs.core.config.TenantConfigContext;
 import com.fs.crm.service.ICrmCustomerPropertyService;
@@ -365,17 +363,67 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
                     companyVoiceRoboticCallLog.setCallCreateTime(createTime);
                     Long answerTime = result.getCallEndTime();
                     companyVoiceRoboticCallLog.setCallAnswerTime(answerTime);
-                    String intention = result.getIntent();
+                    // 【当前启用】读取 cc_call_phone.intent,再转换为系统字典 intention 数值
+                    String intentRaw = StringUtils.isNotBlank(result.getIntent()) ? result.getIntent().trim() : null;
                     String intentf = null;
+                    final String intentionLabel = intentRaw;
                     List<SysDictData> customerIntentionLevel = sysDictTypeService.selectDictDataByType("customer_intention_level");
-                    if (!isPositiveInteger(intention)) {
-                        Optional<SysDictData> firstDict = customerIntentionLevel.stream().filter(e -> e.getDictLabel().equals(intention)).findFirst();
+                    if (!isPositiveInteger(intentionLabel)) {
+                        Optional<SysDictData> firstDict = customerIntentionLevel.stream().filter(e -> e.getDictLabel().equals(intentionLabel)).findFirst();
                         if (firstDict.isPresent()) {
                             SysDictData sysDictData = firstDict.get();
                             intentf = sysDictData.getDictValue();
                         }
+                    } else {
+                        intentf = intentionLabel;
+                    }
+                    if (StringUtils.isBlank(intentf)) {
+                        intentf = "0";
                     }
                     companyVoiceRoboticCallLog.setIntention(intentf);
+
+                    // ========== 【历史保留】回滚时可注释上方 EasyCall 逻辑并取消下方对应注释 ==========
+                    // 方案A:仅使用 EasyCall intent 字段(改动前的本文件写法)
+//                    String intention = result.getIntent();
+//                    String intentf = null;
+//                    List<SysDictData> customerIntentionLevel = sysDictTypeService.selectDictDataByType("customer_intention_level");
+//                    if (!isPositiveInteger(intention)) {
+//                        Optional<SysDictData> firstDict = customerIntentionLevel.stream().filter(e -> e.getDictLabel().equals(intention)).findFirst();
+//                        if (firstDict.isPresent()) {
+//                            SysDictData sysDictData = firstDict.get();
+//                            intentf = sysDictData.getDictValue();
+//                        }
+//                    }
+//                    companyVoiceRoboticCallLog.setIntention(intentf);
+
+                    // 方案B:自家 AI 根据 dialogue 计算意向度
+//                    String intention = null;
+//                    if (StringUtils.isNotBlank(result.getDialogue())) {
+//                        try {
+//                            intention = crmCustomerAnalyzeService.aiIntentionDegree(
+//                                    result.getDialogue(),
+//                                    java.time.LocalTime.now().getLong(java.time.temporal.ChronoField.MILLI_OF_SECOND)
+//                            );
+//                        } catch (Exception e) {
+//                            log.error("easyCall回调日志意向度AI解析失败,uuid={}", result.getUuid(), e);
+//                        }
+//                    }
+//                    String intentf = null;
+//                    final String intentionLabel = intention;
+//                    List<SysDictData> customerIntentionLevel = sysDictTypeService.selectDictDataByType("customer_intention_level");
+//                    if (!isPositiveInteger(intentionLabel)) {
+//                        Optional<SysDictData> firstDict = customerIntentionLevel.stream().filter(e -> e.getDictLabel().equals(intentionLabel)).findFirst();
+//                        if (firstDict.isPresent()) {
+//                            SysDictData sysDictData = firstDict.get();
+//                            intentf = sysDictData.getDictValue();
+//                        }
+//                    } else {
+//                        intentf = intentionLabel;
+//                    }
+//                    if (StringUtils.isBlank(intentf)) {
+//                        intentf = "0";
+//                    }
+//                    companyVoiceRoboticCallLog.setIntention(intentf);
                     if(null != result.getValidTimeLen() && Integer.valueOf(0).compareTo(result.getValidTimeLen()) < 0){
                         BigDecimal divide = new BigDecimal(result.getValidTimeLen()).divide(new BigDecimal(1000), 0, RoundingMode.CEILING);
                         companyVoiceRoboticCallLog.setCallTime(divide.longValue());
@@ -521,8 +569,8 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
     }
 
     @Override
-    public CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount() {
-        return baseMapper.selectCompanyVoiceRoboticCallPhoneLogCount();
+    public CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticCallPhoneLogCount(Long companyId) {
+        return baseMapper.selectCompanyVoiceRoboticCallPhoneLogCount(companyId);
     }
 
     @Override
@@ -551,4 +599,70 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
         updateObj.setHandleFlag(1);
         return baseMapper.updateCompanyVoiceRoboticCallLogCallphone(updateObj);
     }
+
+    @Override
+    public String getDecryptPhoneByLogId(Long logId) {
+        CompanyVoiceRoboticCallLogCallphone log = selectCompanyVoiceRoboticCallLogCallphoneByLogId(logId);
+        if (log == null) {
+            return null;
+        }
+        if (log.getCallerId() != null) {
+            CompanyVoiceRoboticCallees callees = companyVoiceRoboticCalleesMapper.selectCompanyVoiceRoboticCalleesById(log.getCallerId());
+            if (callees != null && StringUtils.isNotEmpty(callees.getPhone())) {
+                return PhoneUtil.decryptPhone(callees.getPhone());
+            }
+        }
+        if (StringUtils.isNotEmpty(log.getCallerNum())) {
+            return PhoneUtil.decryptPhone(log.getCallerNum());
+        }
+        return null;
+    }
+
+    @Override
+    public List<CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO> listDecryptPhoneExport(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone) {
+        prepareDetailPhoneQuery(companyVoiceRoboticCallLogCallphone);
+        List<CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO> rows = baseMapper.listDecryptPhoneExport(companyVoiceRoboticCallLogCallphone);
+        List<CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO> exportList = new ArrayList<>();
+        if (rows == null || rows.isEmpty()) {
+            return exportList;
+        }
+        Map<String, String> intentionLabelMap = buildIntentionLabelMap();
+        for (CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO row : rows) {
+            CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO vo = new CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO();
+            vo.setRoboticName(row.getRoboticName());
+            vo.setUserName(row.getUserName());
+            vo.setIntention(resolveIntentionLabel(row.getIntention(), intentionLabelMap));
+            vo.setPhone(PhoneUtil.decryptPhone(row.getPhone()));
+            exportList.add(vo);
+        }
+        return exportList;
+    }
+
+    private Map<String, String> buildIntentionLabelMap() {
+        Map<String, String> map = new HashMap<>();
+        List<SysDictData> dictList = sysDictTypeService.selectDictDataByType("customer_intention_level");
+        if (dictList != null) {
+            for (SysDictData dict : dictList) {
+                map.put(dict.getDictValue(), dict.getDictLabel());
+            }
+        }
+        return map;
+    }
+
+    private String resolveIntentionLabel(String intention, Map<String, String> intentionLabelMap) {
+        if (StringUtils.isEmpty(intention)) {
+            return "";
+        }
+        String label = intentionLabelMap.get(intention);
+        if (StringUtils.isNotEmpty(label)) {
+            return label;
+        }
+        return intention;
+    }
+
+    private void prepareDetailPhoneQuery(CompanyVoiceRoboticCallLogCallphone query) {
+        if (StringUtils.isNotEmpty(query.getPhone())) {
+            query.setEncryptedPhone(PhoneUtil.encryptPhone(query.getPhone()));
+        }
+    }
 }

+ 83 - 51
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -797,54 +797,51 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
                     );
                     // 切换 Redis 租户上下文
                     RedisTenantContext.setTenantId(TenantHelper.getTenantId());
-                    isSwitched = true;
                 } catch (Exception 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);
                     if (retryCount == null) {
                         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);
-                        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 {
-                        // 超过最大重试次数,以 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);
                         doHandleEasyCallResult(callPhoneRes);
                     }
                     return;
                 }
-                // dialogue 已有值,直接正常处理
-                redisCache2.deleteObject(EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid());
+                redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
                 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) {
                 throw new RuntimeException(e);
             } finally {
@@ -1054,27 +1051,37 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
 //                    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)) {
                 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"));
             callee.setUuid(callPhoneRes.getUuid());
             callee.setIntention(intention);
@@ -2325,4 +2332,29 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
                 new LambdaQueryWrapper<CompanyVoiceRobotic>().eq(CompanyVoiceRobotic::getTaskId, Long.valueOf(taskId)));
         return robotic != null ? robotic.getCompanyId() : null;
     }
+
+    /**
+     * 读取 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);
+    }
+
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO.java

@@ -0,0 +1,23 @@
+package com.fs.company.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 外呼记录详情导出(解密手机号)
+ */
+@Data
+public class CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO {
+
+    @Excel(name = "任务名称")
+    private String roboticName;
+
+    @Excel(name = "客户名称")
+    private String userName;
+
+    @Excel(name = "客户类型")
+    private String intention;
+
+    @Excel(name = "手机号")
+    private String phone;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO.java

@@ -0,0 +1,19 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+/**
+ * 外呼记录详情导出查询中间对象
+ */
+@Data
+public class CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO {
+
+    private String roboticName;
+
+    private String userName;
+
+    /** 客户类型字典值(存库为数字) */
+    private String intention;
+
+    private String phone;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogCallPhoneVO.java

@@ -24,6 +24,9 @@ public class CompanyVoiceRoboticCallLogCallPhoneVO {
     @Excel(name = "caller_id")
     private Long callerId;
 
+    /** 客户ID(callees.user_id) */
+    private Long customerId;
+
     /** 记录调用时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @Excel(name = "记录调用时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")

+ 43 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml

@@ -234,6 +234,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         inner join company_voice_robotic cvr on cvr.id = t1.robotic_id
         <where>
             <if test="roboticId != null">and robotic_id = #{roboticId}</if>
+            <if test="companyId != null"> and cvr.company_id = #{companyId}</if>
         </where>
         group by robotic_id
         order by t1.run_time desc
@@ -246,6 +247,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             sum(case when status = 2 and run_time &gt;= CURDATE() and run_time &lt; DATE_ADD(CURDATE(), INTERVAL 1 DAY) then 1 else 0 end) as todaySuccessCount
         from company_voice_robotic_call_log_callphone cp
             inner join company_voice_robotic cvr on cvr.id = cp.robotic_id
+        where cvr.company_id = #{companyId}
     </select>
 
 
@@ -343,5 +345,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY ce.user_id
     </select>
 
+    <select id="listDecryptPhoneExport" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO" parameterType="com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone">
+        SELECT
+        cvr.name as robotic_name,
+        ce.user_name as user_name,
+        t1.intention as intention,
+        ce.phone as phone
+        FROM company_voice_robotic_call_log_callphone t1
+        left join company_voice_robotic_callees ce on ce.id = t1.caller_id
+        left join company_voice_robotic cvr on cvr.id = t1.robotic_id
+        where 1=1
+        <if test="roboticId != null">and t1.robotic_id = #{roboticId}</if>
+        <if test="callerId != null">and t1.caller_id = #{callerId}</if>
+        <if test="callerIds != null and callerIds.size() > 0">
+            AND t1.caller_id IN
+            <foreach collection='callerIds' item='item' open='(' separator=',' close=')'>
+                #{item}
+            </foreach>
+        </if>
+        <if test="callerNum != null and callerNum != ''">
+            and t1.caller_num like concat('%', #{callerNum}, '%')
+        </if>
+        <if test="encryptedPhone != null and encryptedPhone != ''">
+            and ce.phone = #{encryptedPhone}
+        </if>
+        <if test="intention != null and intention != ''">
+            and t1.intention = #{intention}
+        </if>
+        <if test="isConnected != null and isConnected == 1">
+            and t1.call_time &gt; 0
+        </if>
+        <if test="isConnected != null and isConnected == 0">
+            and (t1.call_time is null or t1.call_time = 0)
+        </if>
+        <if test="minCallTime != null">
+            and t1.call_time &gt;= #{minCallTime}
+        </if>
+        <if test="maxCallTime != null">
+            and t1.call_time &lt;= #{maxCallTime}
+        </if>
+        order by t1.run_time desc
+    </select>
 
 </mapper>