Pārlūkot izejas kodu

cid同步master改动

lmx 1 dienu atpakaļ
vecāks
revīzija
9470c16d0b
20 mainītis faili ar 374 papildinājumiem un 6 dzēšanām
  1. 44 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogCallphoneController.java
  2. 14 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java
  3. 12 0
      fs-company/src/main/java/com/fs/company/controller/crm/CustomerAllController.java
  4. 1 1
      fs-service/src/main/java/com/fs/comm/service/CommCallSendService.java
  5. 17 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java
  6. 12 0
      fs-service/src/main/java/com/fs/company/domain/CrmCustomerCallLog.java
  7. 9 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogCallphoneMapper.java
  8. 8 0
      fs-service/src/main/java/com/fs/company/mapper/EasyCallMapper.java
  9. 15 0
      fs-service/src/main/java/com/fs/company/service/ICompanyAiCallDataSyncService.java
  10. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticCallLogCallphoneService.java
  11. 89 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyAiCallDataSyncServiceImpl.java
  12. 40 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogCallphoneServiceImpl.java
  13. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogCallPhoneVO.java
  14. 24 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogDetailSummary.java
  15. 4 0
      fs-service/src/main/java/com/fs/crm/param/CrmCustomeRecoverParam.java
  16. 2 0
      fs-service/src/main/java/com/fs/crm/service/ICrmCustomerService.java
  17. 34 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerServiceImpl.java
  18. 24 5
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml
  19. 4 0
      fs-service/src/main/resources/mapper/company/CrmCustomerCallLogMapper.xml
  20. 10 0
      fs-service/src/main/resources/mapper/company/EasyCallMapper.xml

+ 44 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogCallphoneController.java

@@ -9,6 +9,7 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
+import com.fs.company.service.ICompanyAiCallDataSyncService;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.domain.CrmCustomer;
@@ -17,6 +18,7 @@ import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.framework.service.TokenService;
+import com.fs.wxcid.utils.TenantHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
@@ -41,6 +43,25 @@ public class CompanyVoiceRoboticCallLogCallphoneController extends BaseControlle
     private ICrmCustomerService crmCustomerService;
     private ICrmCustomerService crmCustomerService;
     @Autowired
     @Autowired
     private TokenService tokenService;
     private TokenService tokenService;
+
+    @Autowired
+    private ICompanyAiCallDataSyncService companyAiCallDataSyncService;
+
+    /**
+     * 异步同步 AI 外呼数据(补偿 EasyCall 已完成但未回调的记录)
+     */
+    @PreAuthorize("@ss.hasPermi('company:callphoneDetail:list')")
+    @Log(title = "同步AI外呼数据", businessType = BusinessType.OTHER)
+    @PostMapping("/syncAiCallData")
+    public AjaxResult syncAiCallData() {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        TenantHelper.setTenantId(loginUser.getTenantId());
+        if (!companyAiCallDataSyncService.tryStartSync(companyId)) {
+            return AjaxResult.error("正在同步中,请勿重复操作");
+        }
+        return AjaxResult.success("正在同步中");
+    }
     /**
     /**
      * 查询调用日志_ai打电话列表
      * 查询调用日志_ai打电话列表
      */
      */
@@ -77,6 +98,29 @@ public class CompanyVoiceRoboticCallLogCallphoneController extends BaseControlle
 
 
     }
     }
 
 
+    /**
+     * AI外呼记录明细列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:callphoneDetail:list')")
+    @GetMapping("/detailList")
+    public TableDataInfo detailList(CompanyVoiceRoboticCallLogCallphone param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyVoiceRoboticCallLogCallphoneService.prepareDetailListQuery(param, loginUser.getCompany().getCompanyId());
+        startPage();
+        List<CompanyVoiceRoboticCallLogCallPhoneVO> list = companyVoiceRoboticCallLogCallphoneService.selectDetailList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * AI外呼记录明细统计
+     */
+    @PreAuthorize("@ss.hasPermi('company:callphoneDetail:list')")
+    @GetMapping("/detailSummary")
+    public AjaxResult detailSummary(CompanyVoiceRoboticCallLogCallphone param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyVoiceRoboticCallLogCallphoneService.prepareDetailListQuery(param, loginUser.getCompany().getCompanyId());
+        return AjaxResult.success(companyVoiceRoboticCallLogCallphoneService.selectDetailSummary(param));
+    }
 
 
     /**
     /**
      * 查询调用日志_发送短信列表(按照任务id分组,任务id-任务名称-查询总任务数量-成功数量)
      * 查询调用日志_发送短信列表(按照任务id分组,任务id-任务名称-查询总任务数量-成功数量)

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

@@ -115,6 +115,20 @@ public class CompanyVoiceRoboticController extends BaseController
         companyVoiceRobotic.setCompanyId(loginUser.getCompany().getCompanyId());
         companyVoiceRobotic.setCompanyId(loginUser.getCompany().getCompanyId());
         return R.ok().put("data", companyVoiceRoboticService.selectCompanyVoiceRoboticListCompany(companyVoiceRobotic));
         return R.ok().put("data", companyVoiceRoboticService.selectCompanyVoiceRoboticListCompany(companyVoiceRobotic));
     }
     }
+
+    /**
+     * 外呼任务下拉选项(分页 + 名称模糊搜索,按当前登录公司过滤)
+     */
+    @PreAuthorize("@ss.hasPermi('company:callphoneDetail:list')")
+    @GetMapping("/selectOptions")
+    public TableDataInfo selectOptions(CompanyVoiceRobotic companyVoiceRobotic) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyVoiceRobotic.setCompanyId(loginUser.getCompany().getCompanyId());
+        startPage();
+        List<CompanyVoiceRobotic> list = companyVoiceRoboticService.selectCompanyVoiceRoboticListCompany(companyVoiceRobotic);
+        return getDataTable(list);
+    }
+
     @PreAuthorize("@ss.hasPermi('system:companyVoiceRobotic:list')")
     @PreAuthorize("@ss.hasPermi('system:companyVoiceRobotic:list')")
     @GetMapping("/calleesList")
     @GetMapping("/calleesList")
     public TableDataInfo calleesList(Long id){
     public TableDataInfo calleesList(Long id){

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

@@ -62,4 +62,16 @@ public class CustomerAllController extends BaseController {
         param.setCompanyUserId(loginUser.getUser().getUserId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
         return crmCustomerService.recover(param, operName);
         return crmCustomerService.recover(param, operName);
     }
     }
+
+    @ApiOperation("批量回收公海")
+    @PreAuthorize("@ss.hasPermi('crm:customer:recover')")
+    @PostMapping("/batchRecover")
+    public R batchRecover(@RequestBody CrmCustomeRecoverParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String operName = loginUser.getUsername();
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        param.setCompanyUserId(loginUser.getUser().getUserId());
+        return crmCustomerService.batchRecover(param, operName);
+    }
 }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/comm/service/CommCallSendService.java

@@ -130,7 +130,7 @@ public class CommCallSendService {
         callbackInfo.put("nodeKey", param.getNodeKey());
         callbackInfo.put("nodeKey", param.getNodeKey());
         callbackInfo.put("workflowInstanceId", param.getWorkflowInstanceId());
         callbackInfo.put("workflowInstanceId", param.getWorkflowInstanceId());
         callbackInfo.put("calleeId", param.getCalleeId());
         callbackInfo.put("calleeId", param.getCalleeId());
-        redisCache.setCacheObject(EASYCALL_WORKFLOW_REDIS_KEY + callBackUuid, callbackInfo.toJSONString(), 15, TimeUnit.DAYS);
+        redisCache.setCacheObject(EASYCALL_WORKFLOW_REDIS_KEY + callBackUuid, callbackInfo.toJSONString());
 
 
         Long batchId = resolveBatchId(param);
         Long batchId = resolveBatchId(param);
         EasyCallCommonAddCallListParam addListParam = new EasyCallCommonAddCallListParam();
         EasyCallCommonAddCallListParam addListParam = new EasyCallCommonAddCallListParam();

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

@@ -181,6 +181,23 @@ public class CompanyVoiceRoboticCallLogCallphone extends BaseEntity{
     @TableField(exist = false)
     @TableField(exist = false)
     private String encryptedPhone;
     private String encryptedPhone;
 
 
+    /** 详情筛选-运行时间起(yyyy-MM-dd) */
+    @TableField(exist = false)
+    private String beginRunTime;
+
+    /** 详情筛选-运行时间止(yyyy-MM-dd) */
+    @TableField(exist = false)
+    private String endRunTime;
+
+    /** 详情筛选-客户类型为「无」 */
+    @TableField(exist = false)
+    private Boolean intentionEmpty;
+
+    /**
+     * 重试次数
+     */
+    private Integer retryCount;
+
     public static CompanyVoiceRoboticCallLogCallphone initCallLog( String runParam, Long keyId, Long taskId,Long companyId) {
     public static CompanyVoiceRoboticCallLogCallphone initCallLog( String runParam, Long keyId, Long taskId,Long companyId) {
         CompanyVoiceRoboticCallLogCallphone log = new CompanyVoiceRoboticCallLogCallphone();
         CompanyVoiceRoboticCallLogCallphone log = new CompanyVoiceRoboticCallLogCallphone();
         log.callerId = keyId;
         log.callerId = keyId;

+ 12 - 0
fs-service/src/main/java/com/fs/company/domain/CrmCustomerCallLog.java

@@ -175,6 +175,18 @@ public class CrmCustomerCallLog extends BaseEntity {
      */
      */
     private Long maxCallTime;
     private Long maxCallTime;
 
 
+    /**
+     * 查询条件:呼叫开始时间-起(yyyy-MM-dd)
+     * 非持久化字段
+     */
+    private String callBeginTime;
+
+    /**
+     * 查询条件:呼叫开始时间-止(yyyy-MM-dd)
+     * 非持久化字段
+     */
+    private String callEndTime;
+
     /**
     /**
      * 计费分钟数
      * 计费分钟数
      */
      */

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

@@ -6,6 +6,7 @@ import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogDetailSummary;
 import com.fs.crm.vo.CustomerCallStatVO;
 import com.fs.crm.vo.CustomerCallStatVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 
 
@@ -108,6 +109,14 @@ public interface CompanyVoiceRoboticCallLogCallphoneMapper extends BaseMapper<Co
      */
      */
     List<CustomerCallStatVO> selectAiCallStatTotal(@Param("customerIds") List<Long> customerIds);
     List<CustomerCallStatVO> selectAiCallStatTotal(@Param("customerIds") List<Long> customerIds);
 
 
+    List<CompanyVoiceRoboticCallLogCallPhoneVO> selectDetailList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
+    CompanyVoiceRoboticCallLogDetailSummary selectDetailSummary(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
     List<CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO> listDecryptPhoneExport(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
     List<CompanyVoiceRoboticCallLogCallPhoneDecryptQueryVO> listDecryptPhoneExport(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
 
 
+    /**
+     * 查询执行中任务下、外呼记录仍为执行中的 callbackUuid 列表
+     */
+    List<String> selectRunningCallbackUuidsByCompanyId(@Param("companyId") Long companyId);
 }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/company/mapper/EasyCallMapper.java

@@ -8,6 +8,8 @@ import com.fs.company.vo.easycall.EasyCallOutBoundVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.stereotype.Repository;
 import org.springframework.stereotype.Repository;
 
 
+import java.util.List;
+
 /**
 /**
  * @author MixLiu
  * @author MixLiu
  * @date 2026/3/7 10:43
  * @date 2026/3/7 10:43
@@ -27,4 +29,10 @@ public interface EasyCallMapper {
     @DataSource(DataSourceType.EASYCALL)
     @DataSource(DataSourceType.EASYCALL)
     InboundCallInfo selectInboundCallbackInfoByUuid(@Param("uuid") String uuid);
     InboundCallInfo selectInboundCallbackInfoByUuid(@Param("uuid") String uuid);
 
 
+    /**
+     * 根据 callbackUuid 批量查询 EasyCall 已外呼完成的话单 uuid
+     */
+    @DataSource(DataSourceType.EASYCALL)
+    List<String> selectCompletedUuidsByCallbackUuids(@Param("callbackUuids") List<String> callbackUuids);
+
 }
 }

+ 15 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyAiCallDataSyncService.java

@@ -0,0 +1,15 @@
+package com.fs.company.service;
+
+/**
+ * AI外呼数据同步服务
+ */
+public interface ICompanyAiCallDataSyncService {
+
+    /**
+     * 尝试启动异步同步任务(同公司防重复)
+     *
+     * @param companyId 公司ID
+     * @return true-已启动;false-已有任务在运行
+     */
+    boolean tryStartSync(Long companyId);
+}

+ 8 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticCallLogCallphoneService.java

@@ -5,6 +5,7 @@ import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneDecryptExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogDetailSummary;
 
 
 import java.util.List;
 import java.util.List;
 
 
@@ -101,4 +102,11 @@ public interface ICompanyVoiceRoboticCallLogCallphoneService extends IService<Co
      * 根据外呼记录ID获取解密后的手机号
      * 根据外呼记录ID获取解密后的手机号
      */
      */
     String getDecryptPhoneByLogId(Long logId);
     String getDecryptPhoneByLogId(Long logId);
+
+    void prepareDetailListQuery(CompanyVoiceRoboticCallLogCallphone query, Long companyId);
+
+    List<CompanyVoiceRoboticCallLogCallPhoneVO> selectDetailList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
+    CompanyVoiceRoboticCallLogDetailSummary selectDetailSummary(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone);
+
 }
 }

+ 89 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyAiCallDataSyncServiceImpl.java

@@ -0,0 +1,89 @@
+package com.fs.company.service.impl;
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
+import com.fs.company.mapper.EasyCallMapper;
+import com.fs.company.service.ICompanyAiCallDataSyncService;
+import com.fs.company.service.ICompanyVoiceRoboticService;
+import com.fs.company.vo.CdrDetailVo;
+import com.fs.wxcid.utils.TenantHelper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * AI外呼数据同步:补偿 EasyCall 已外呼完成但未回调入库的记录
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CompanyAiCallDataSyncServiceImpl implements ICompanyAiCallDataSyncService {
+
+    private static final String SYNC_LOCK_PREFIX = "ai_call_sync:company:";
+    private static final int BATCH_SIZE = 1000;
+    private static final long LOCK_TIMEOUT_HOURS = 2L;
+
+    private final RedisCache redisCache;
+    private final CompanyVoiceRoboticCallLogCallphoneMapper callLogCallphoneMapper;
+    private final EasyCallMapper easyCallMapper;
+    private final ICompanyVoiceRoboticService companyVoiceRoboticService;
+
+
+    @Override
+    public boolean tryStartSync(Long companyId) {
+        String lockKey = buildLockKey(companyId);
+        if (!redisCache.setIfAbsent(lockKey, "1", LOCK_TIMEOUT_HOURS, TimeUnit.HOURS)) {
+            return false;
+        }
+        doSyncAiCallDataAsync(companyId, lockKey);
+        return true;
+    }
+
+    @Async("callLogExcutor")
+    public void doSyncAiCallDataAsync(Long companyId, String lockKey) {
+        try {
+            List<String> callbackUuids = callLogCallphoneMapper.selectRunningCallbackUuidsByCompanyId(companyId);
+            if (callbackUuids == null || callbackUuids.isEmpty()) {
+                log.info("syncAiCallData: 无待补偿外呼记录, companyId={}", companyId);
+                return;
+            }
+            log.info("syncAiCallData: 开始同步, companyId={}, 待检查callbackUuid数={}", companyId, callbackUuids.size());
+            int triggered = 0;
+            for (int i = 0; i < callbackUuids.size(); i += BATCH_SIZE) {
+                List<String> batch = callbackUuids.subList(i, Math.min(i + BATCH_SIZE, callbackUuids.size()));
+                List<String> completedUuids = easyCallMapper.selectCompletedUuidsByCallbackUuids(batch);
+                if (completedUuids == null || completedUuids.isEmpty()) {
+                    continue;
+                }
+                for (String uuid : completedUuids) {
+                    try {
+                        CdrDetailVo cdrDetailVo = new CdrDetailVo();
+                        cdrDetailVo.setUuid(uuid);
+                        cdrDetailVo.setCdrType("outbound");
+                        companyVoiceRoboticService.callerResult4EasyCall(cdrDetailVo);
+                        triggered++;
+                    } catch (Exception e) {
+                        log.error("syncAiCallData: 触发回调失败, uuid={}, companyId={}", uuid, companyId, e);
+                    }
+                }
+            }
+            log.info("syncAiCallData: 同步完成, companyId={}, 触发回调数={}", companyId, triggered);
+        } catch (Exception e) {
+            log.error("syncAiCallData: 同步异常, companyId={}", companyId, e);
+        } finally {
+            redisCache.deleteObject(lockKey);
+        }
+    }
+
+    private String buildLockKey(Long companyId) {
+        Long tenantId = TenantHelper.getTenantId();
+        if (tenantId != null) {
+            return SYNC_LOCK_PREFIX + tenantId + ":" + companyId;
+        }
+        return SYNC_LOCK_PREFIX + companyId;
+    }
+}

+ 40 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogCallphoneServiceImpl.java

@@ -561,6 +561,46 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
     public List<CompanyVoiceRoboticCallLogCallPhoneVO> listByRoboticId(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone) {
     public List<CompanyVoiceRoboticCallLogCallPhoneVO> listByRoboticId(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone) {
         return baseMapper.listByRoboticId(companyVoiceRoboticCallLogCallphone);
         return baseMapper.listByRoboticId(companyVoiceRoboticCallLogCallphone);
     }
     }
+
+    @Override
+    public void prepareDetailListQuery(CompanyVoiceRoboticCallLogCallphone query, Long companyId) {
+        query.setCompanyId(companyId);
+        prepareDetailPhoneQuery(query);
+    }
+
+    @Override
+    public List<CompanyVoiceRoboticCallLogCallPhoneVO> selectDetailList(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone) {
+        List<CompanyVoiceRoboticCallLogCallPhoneVO> list = baseMapper.selectDetailList(companyVoiceRoboticCallLogCallphone);
+        if (list == null || list.isEmpty()) {
+            return list;
+        }
+        for (CompanyVoiceRoboticCallLogCallPhoneVO vo : list) {
+            if (StringUtils.isNotEmpty(vo.getCallerNum())) {
+                vo.setCallerNum(PhoneUtil.decryptPhoneMk(vo.getCallerNum()));
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public CompanyVoiceRoboticCallLogDetailSummary selectDetailSummary(CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone) {
+        CompanyVoiceRoboticCallLogDetailSummary summary = baseMapper.selectDetailSummary(companyVoiceRoboticCallLogCallphone);
+        if (summary == null) {
+            summary = new CompanyVoiceRoboticCallLogDetailSummary();
+            summary.setTotalCount(0L);
+            summary.setSuccessCount(0L);
+            summary.setFailCount(0L);
+            summary.setConnectedCount(0L);
+            summary.setTotalBillingMinute(0L);
+        }
+        if (summary.getTotalBillingMinute() == null) {
+            summary.setTotalBillingMinute(0L);
+        }
+        long total = summary.getTotalCount() == null ? 0L : summary.getTotalCount();
+        long connected = summary.getConnectedCount() == null ? 0L : summary.getConnectedCount();
+        summary.setConnectRate(total > 0 ? (int) Math.round(connected * 100.0 / total) : 0);
+        return summary;
+    }
     /**
     /**
      * 判断整数
      * 判断整数
      *
      *

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

@@ -87,4 +87,7 @@ public class CompanyVoiceRoboticCallLogCallPhoneVO {
 
 
     /** 是否警告(0否 1是)用于敏感词 */
     /** 是否警告(0否 1是)用于敏感词 */
     private Integer isWarning;
     private Integer isWarning;
+
+    /** 计费分钟数:通话时长向上取整,未满1分钟按1分钟 */
+    private Integer billingMinute;
 }
 }

+ 24 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVoiceRoboticCallLogDetailSummary.java

@@ -0,0 +1,24 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+/**
+ * AI外呼记录明细统计
+ */
+@Data
+public class CompanyVoiceRoboticCallLogDetailSummary {
+
+    private Long totalCount;
+
+    private Long successCount;
+
+    private Long failCount;
+
+    private Long connectedCount;
+
+    /** 接通率(百分比,整数) */
+    private Integer connectRate;
+
+    /** 计费分钟合计(未满1分钟按1分钟计) */
+    private Long totalBillingMinute;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/crm/param/CrmCustomeRecoverParam.java

@@ -3,6 +3,8 @@ package com.fs.crm.param;
 
 
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.List;
+
 @Data
 @Data
 public class CrmCustomeRecoverParam extends BaseQueryParam
 public class CrmCustomeRecoverParam extends BaseQueryParam
 {
 {
@@ -10,6 +12,8 @@ public class CrmCustomeRecoverParam extends BaseQueryParam
 
 
     private Long customerUserId;
     private Long customerUserId;
 
 
+    private List<Long> customerUserIds;
+
     private Long companyUserId;
     private Long companyUserId;
 
 
     private Long companyId;
     private Long companyId;

+ 2 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerService.java

@@ -101,6 +101,8 @@ public interface ICrmCustomerService
 
 
     R recover(CrmCustomeRecoverParam param, String operName);
     R recover(CrmCustomeRecoverParam param, String operName);
 
 
+    R batchRecover(CrmCustomeRecoverParam param, String operName);
+
     R assignUser(CrmCustomeAssignUserParam param, String operName);
     R assignUser(CrmCustomeAssignUserParam param, String operName);
 
 
     Integer selectCrmCustomerCountByType(Long companyId, int type);
     Integer selectCrmCustomerCountByType(Long companyId, int type);

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

@@ -381,6 +381,40 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
         return R.ok();
         return R.ok();
     }
     }
 
 
+    @Override
+    public R batchRecover(CrmCustomeRecoverParam param, String operName) {
+        if (param.getCustomerUserIds() == null || param.getCustomerUserIds().isEmpty()) {
+            return R.error("请选择要回收的客户");
+        }
+        int successCount = 0;
+        int failCount = 0;
+        StringBuilder failMsg = new StringBuilder();
+        for (Long customerUserId : param.getCustomerUserIds()) {
+            CrmCustomeRecoverParam recoverParam = new CrmCustomeRecoverParam();
+            recoverParam.setCustomerUserId(customerUserId);
+            recoverParam.setCompanyId(param.getCompanyId());
+            recoverParam.setCompanyUserId(param.getCompanyUserId());
+            R result = recover(recoverParam, operName);
+            if (Integer.valueOf(200).equals(result.get("code"))) {
+                successCount++;
+            } else {
+                failCount++;
+                if (failMsg.length() > 0) {
+                    failMsg.append(";");
+                }
+                failMsg.append(result.get("msg"));
+            }
+        }
+        if (failCount == 0) {
+            return R.ok("成功回收" + successCount + "个客户");
+        }
+        if (successCount == 0) {
+            return R.error("回收失败:" + failMsg);
+        }
+        return R.ok("成功回收" + successCount + "个客户,失败" + failCount + "个:" + failMsg);
+    }
+
+
     @Override
     @Override
     @Transactional
     @Transactional
     public R recover(CrmCustomeRecoverParam param, String operName) {
     public R recover(CrmCustomeRecoverParam param, String operName) {

+ 24 - 5
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml

@@ -200,6 +200,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select * from company_voice_robotic_call_log_callphone where callback_uuid = #{uuid}
         select * from company_voice_robotic_call_log_callphone where callback_uuid = #{uuid}
     </select>
     </select>
 
 
+    <select id="selectRunningCallbackUuidsByCompanyId" resultType="java.lang.String">
+        SELECT DISTINCT t1.callback_uuid
+        FROM company_voice_robotic_call_log_callphone t1
+                 INNER JOIN company_voice_robotic cvr ON cvr.id = t1.robotic_id
+        WHERE cvr.company_id = #{companyId}
+          AND cvr.task_status = 1
+          AND (cvr.del_flag = 0 OR cvr.del_flag IS NULL)
+          AND t1.status = 1
+          AND t1.callback_uuid IS NOT NULL
+          AND t1.callback_uuid != ''
+    </select>
+
     <select id="countTodayCallsByBusinessId" resultType="int">
     <select id="countTodayCallsByBusinessId" resultType="int">
         SELECT IFNULL(COUNT(*), 0)
         SELECT IFNULL(COUNT(*), 0)
         FROM company_voice_robotic_callees es
         FROM company_voice_robotic_callees es
@@ -251,13 +263,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
     </select>
 
 
 
 
-    <select id="listByRoboticId" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO">
+    <select id="listByRoboticId" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO" parameterType="com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone">
         SELECT
         SELECT
         t1.*,
         t1.*,
         t2.company_name,
         t2.company_name,
         t3.nick_name as companyUserName,
         t3.nick_name as companyUserName,
-        cvr.name as robotic_name
+        cvr.name as robotic_name,
+        ce.user_id as customer_id
         FROM company_voice_robotic_call_log_callphone t1
         FROM company_voice_robotic_call_log_callphone t1
+        left join company_voice_robotic_callees ce on ce.id = t1.caller_id
         left join company t2 on t1.company_id = t2.company_id
         left join company t2 on t1.company_id = t2.company_id
         left join company_user t3 on t3.user_id = t1.company_user_id
         left join company_user t3 on t3.user_id = t1.company_user_id
         left join company_voice_robotic cvr on cvr.id = t1.robotic_id
         left join company_voice_robotic cvr on cvr.id = t1.robotic_id
@@ -273,9 +287,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="callerNum != null and callerNum != ''">
         <if test="callerNum != null and callerNum != ''">
             and t1.caller_num like concat('%', #{callerNum}, '%')
             and t1.caller_num like concat('%', #{callerNum}, '%')
         </if>
         </if>
-        <if test="intention != null and intention != ''">
-            and t1.intention = #{intention}
-        </if>
+        <choose>
+            <when test="intentionEmpty != null and intentionEmpty">
+                and (t1.intention is null or t1.intention = '' or t1.intention = '0')
+            </when>
+            <when test="intention != null and intention != ''">
+                and t1.intention = #{intention}
+            </when>
+        </choose>
         <if test="isConnected != null and isConnected == 1">
         <if test="isConnected != null and isConnected == 1">
             and t1.call_time &gt; 0
             and t1.call_time &gt; 0
         </if>
         </if>

+ 4 - 0
fs-service/src/main/resources/mapper/company/CrmCustomerCallLogMapper.xml

@@ -42,6 +42,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND date_format(create_time,'%Y-%m-%d') &gt;= #{beginTime}
                 AND date_format(create_time,'%Y-%m-%d') &gt;= #{beginTime}
                 AND date_format(create_time,'%Y-%m-%d') &lt;= #{endTime}
                 AND date_format(create_time,'%Y-%m-%d') &lt;= #{endTime}
             </if>
             </if>
+            <if test="callBeginTime != null and callBeginTime != '' and callEndTime != null and callEndTime != ''">
+                AND FROM_UNIXTIME(call_create_time / 1000, '%Y-%m-%d') &gt;= #{callBeginTime}
+                AND FROM_UNIXTIME(call_create_time / 1000, '%Y-%m-%d') &lt;= #{callEndTime}
+            </if>
         </where>
         </where>
         order by create_time desc
         order by create_time desc
     </select>
     </select>

+ 10 - 0
fs-service/src/main/resources/mapper/company/EasyCallMapper.xml

@@ -24,4 +24,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             limit 1
             limit 1
     </select>
     </select>
 
 
+
+    <select id="selectCompletedUuidsByCallbackUuids" resultType="java.lang.String">
+        SELECT uuid
+        FROM cc_call_phone
+        WHERE callout_time != 0
+        AND JSON_UNQUOTE(JSON_EXTRACT(biz_json, '$.callBackUuid')) IN
+        <foreach collection="callbackUuids" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+    </select>
 </mapper>
 </mapper>