Long 1 неделя назад
Родитель
Сommit
4c5fac983a
29 измененных файлов с 1483 добавлено и 554 удалено
  1. 183 0
      fs-admin/src/main/java/com/fs/aiSipCall/AiSipCallUserController.java
  2. 0 1
      fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallGatewayController.java
  3. 63 50
      fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallOutboundCdrController.java
  4. 53 30
      fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallUserController.java
  5. 15 0
      fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java
  6. 14 0
      fs-company/src/main/java/com/fs/user/FsUserAdminController.java
  7. 44 3
      fs-service/src/main/java/com/fs/aiSipCall/RemoteCommon.java
  8. 5 0
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallGateway.java
  9. 26 0
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallOutboundCdr.java
  10. 12 0
      fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallUser.java
  11. 9 0
      fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallUserMapper.java
  12. 11 15
      fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallOutboundCdrService.java
  13. 25 0
      fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallUserService.java
  14. 36 4
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallGatewayServiceImpl.java
  15. 124 404
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallOutboundCdrServiceImpl.java
  16. 616 34
      fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallUserServiceImpl.java
  17. 8 0
      fs-service/src/main/java/com/fs/aiSipCall/vo/CcExtNumVo.java
  18. 11 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  19. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  20. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  21. 55 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  22. 17 0
      fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java
  23. 24 0
      fs-service/src/main/java/com/fs/his/utils/RandomUtil.java
  24. 18 0
      fs-service/src/main/resources/application-config-dev.yml
  25. 18 0
      fs-service/src/main/resources/application-config-druid-hdt.yml
  26. 40 11
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallOutboundCdrMapper.xml
  27. 31 2
      fs-service/src/main/resources/mapper/aiSipCall/AiSipCallUserMapper.xml
  28. 12 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  29. 8 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

+ 183 - 0
fs-admin/src/main/java/com/fs/aiSipCall/AiSipCallUserController.java

@@ -0,0 +1,183 @@
+package com.fs.aiSipCall;
+
+import com.fs.aiSipCall.domain.AiSipCallUser;
+import com.fs.aiSipCall.service.IAiSipCallUserService;
+import com.fs.aiSipCall.vo.CcExtNumVo;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * sip用户信息Controller
+ * 
+ * @author fs
+ * @date 2026-03-13
+ */
+@RestController
+@RequestMapping("/his/aiSipCall/aiSipCallUser")
+public class AiSipCallUserController extends BaseController
+{
+    @Autowired
+    private IAiSipCallUserService aiSipCallUserService;
+
+    /**
+     * 查询sip用户信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AiSipCallUser aiSipCallUser)
+    {
+        startPage();
+        List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询aiSIP工具条基础配置参数
+     * @param param 参数
+     * @return AjaxResult 结果
+     */
+    @PostMapping("/getToolbarBasicParam")
+    public AjaxResult getToolbarBasicParam(@RequestBody Map<String,String> param)
+    {
+        String extNum = param.get("extNum");
+        if(extNum == null){
+            return AjaxResult.error("分机号参数缺失");
+        }
+        return aiSipCallUserService.getToolbarBasicParam(param);
+    }
+
+    /**
+     * 导出sip用户信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:export')")
+    @Log(title = "sip用户信息", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AiSipCallUser aiSipCallUser)
+    {
+        List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
+        ExcelUtil<AiSipCallUser> util = new ExcelUtil<>(AiSipCallUser.class);
+        return util.exportExcel(list, "sip用户信息数据");
+    }
+
+    /**
+     * 获取sip用户信息详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(aiSipCallUserService.selectAiSipCallUserByUserId(userId));
+    }
+
+    /**
+     * 新增sip用户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:add')")
+    @Log(title = "sip用户信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AiSipCallUser aiSipCallUser)
+    {
+        aiSipCallUser.setUserSource("1");
+        aiSipCallUser.setCreateTime(new Date());
+        return toAjax(aiSipCallUserService.insertAiSipCallUser(aiSipCallUser));
+    }
+
+    /**
+     * 修改sip用户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:edit')")
+    @Log(title = "sip用户信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AiSipCallUser aiSipCallUser)
+    {
+        LoginUser user = getLoginUser();
+        aiSipCallUser.setUpdateBy(user.getUser().getUserName());
+        aiSipCallUser.setUpdateTime(new Date());
+        aiSipCallUser.setUserSource("1");
+        return toAjax(aiSipCallUserService.updateAiSipCallUser(aiSipCallUser));
+    }
+
+    /**
+     * 删除sip用户信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:aiSipCall:aiSipCallUser:remove')")
+    @Log(title = "sip用户信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(aiSipCallUserService.deleteAiSipCallUserByUserIds(userIds));
+    }
+    /**
+     * 获取公司绑定的分机列表
+     */
+    @GetMapping("/getCompanyUnBindExtnum/{companyId}")
+    public AjaxResult getCompanyUnBindExtnum(@PathVariable Long companyId)
+    {
+        if(companyId == null || companyId <= 0){
+            return AjaxResult.error("参数缺失companyId");
+        }
+        return aiSipCallUserService.getCompanyUnBindExtnum(companyId);
+    }
+    /**
+     * 总后台使用获取未绑定的分机列表
+     */
+    @GetMapping("/getUnBindExtnum")
+    public AjaxResult getUnBindExtnum()
+    {
+        return aiSipCallUserService.getUnBindExtnum();
+    }
+    /**
+     * 总后台查询登录用户的sip用户信息列表
+     */
+    @GetMapping("/myCallUser")
+    public AjaxResult myCallUser(AiSipCallUser aiSipCallUser)
+    {
+        LoginUser user = getLoginUser();
+        aiSipCallUser.setSysUserId(user.getUser().getUserId());
+        List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
+        if(!list.isEmpty()){
+            return AjaxResult.success(list.get(0));
+        }else{
+            return AjaxResult.error("未创建sip角色");
+        }
+    }
+
+
+    /**
+     * 公司获取未绑定的分机列表分页
+     */
+    @GetMapping("/getUnBindExtnumPage")
+    public TableDataInfo getUnBindExtnumPage(CcExtNumVo ccExtNum)
+    {
+        return aiSipCallUserService.getUnBindExtnumPage(ccExtNum);
+    }
+    /**
+     * 公司获取分机列表分页
+     */
+    @PostMapping("/getSipExtDetail")
+    public TableDataInfo getSipExtDetail(@RequestBody CcExtNumVo ccExtNum)
+    {
+        return aiSipCallUserService.getSipExtDetail(ccExtNum);
+    }
+
+    /**
+     * 公司批量解绑分机
+     */
+    @PostMapping("/companyBatchUnbindSipExt/{companyId}")
+    public AjaxResult companyBatchUnbindSipExt(@PathVariable Long companyId,@RequestBody List<CcExtNumVo> ccExtNum)
+    {
+        return aiSipCallUserService.companyBatchUnbindSipExt(companyId,ccExtNum);
+    }
+}

+ 0 - 1
fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallGatewayController.java

@@ -31,7 +31,6 @@ public class AiSipCallGatewayController extends BaseController
     /**
      * 查询aiSIP外呼网关列表
      */
-    @PreAuthorize("@ss.hasPermi('company:aiSipCall:gateway:list')")
     @PostMapping("/remoteList")
     public TableDataInfo list(@RequestBody AiSipCallGateway aiSipCallGateway)
     {

+ 63 - 50
fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallOutboundCdrController.java

@@ -1,28 +1,30 @@
 package com.fs.company.controller.company.aiSipCall;
 
-import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
+import java.util.List;
+import java.util.Map;
 import com.fs.aiSipCall.domain.CcCustInfo;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
-import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
-import com.fs.aiSipCall.utils.DateUtils;
+import com.fs.aiSipCall.utils.StringUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.utils.RandomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.StringUtils;
+import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
+import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
 import com.fs.common.utils.poi.ExcelUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.Date;
-import java.util.List;
+import com.fs.common.core.page.TableDataInfo;
 
 /**
  * aiSIP手动外呼通话记录Controller
- * 
+ *
  * @author fs
  * @date 2026-03-19
  */
@@ -33,6 +35,8 @@ public class AiSipCallOutboundCdrController extends BaseController
 {
     @Autowired
     private IAiSipCallOutboundCdrService aiSipCallOutboundCdrService;
+    @Autowired
+    private TokenService tokenService;
 
     /**
      * 查询aiSIP手动外呼通话记录列表
@@ -41,15 +45,11 @@ public class AiSipCallOutboundCdrController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallOutboundCdr.setCompanyId(loginUser.getUser().getCompanyId());
         startPage();
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
-        list.forEach(data -> {
-            data.setStartTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getStartTime())));
-            data.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getAnsweredTime())));
-            data.setEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(data.getEndTime())));
-            data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen()/1000));
-            data.setTimeLenValidStr(DateUtils.formatTimeLength(data.getTimeLenValid()/1000));
-        });
+
         return getDataTable(list);
     }
 
@@ -61,6 +61,8 @@ public class AiSipCallOutboundCdrController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(AiSipCallOutboundCdr aiSipCallOutboundCdr)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallOutboundCdr.setCompanyId(loginUser.getUser().getCompanyId());
         List<AiSipCallOutboundCdr> list = aiSipCallOutboundCdrService.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
         ExcelUtil<AiSipCallOutboundCdr> util = new ExcelUtil<>(AiSipCallOutboundCdr.class);
         return util.exportExcel(list, "aiSIP手动外呼通话记录数据");
@@ -103,7 +105,7 @@ public class AiSipCallOutboundCdrController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('company:aiSipCall:outboundCdr:remove')")
     @Log(title = "aiSIP手动外呼通话记录", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{ids}")
+    @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable String[] ids)
     {
         return toAjax(aiSipCallOutboundCdrService.deleteAiSipCallOutboundCdrByIds(ids));
@@ -114,10 +116,15 @@ public class AiSipCallOutboundCdrController extends BaseController
      * @param phoneNum 手机号
      * @param callType 类型  1呼入 2外呼
      * @param uuid  通话uuid
+     * @param dialMode  明文plaintext  密文encrypted
      */
     @GetMapping("/getCustCommunicationInfo")
-    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum, @RequestParam("callType") Integer callType, @RequestParam("uuid") String uuid) {
-        return aiSipCallOutboundCdrService.getCustCommunicationInfo(phoneNum,callType,uuid);
+    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum,
+                                               @RequestParam("callType") Integer callType,
+                                               @RequestParam("uuid") String uuid,
+                                               @RequestParam(value = "dialMode", required = false) String dialMode
+    ) {
+        return aiSipCallOutboundCdrService.getCustCommunicationInfo(phoneNum,callType,uuid,dialMode);
     }
     /**
      * 新增保存手动外呼沟通记录
@@ -129,39 +136,45 @@ public class AiSipCallOutboundCdrController extends BaseController
         return aiSipCallOutboundCdrService.addCustcallrecord(ccCustInfo);
     }
 
-
     /**
-     * 手动拉取今天aiSIP外呼通话记录列表
+     * 前端传输加密和随机字符串进行解密
      */
-    @PreAuthorize("@ss.hasPermi('company:aiSipCall:outboundCdr:manualPull')")
-    @GetMapping("/manualPull")
-    public AjaxResult manualPull()
+    @PostMapping("/encryptMobile")
+    @ResponseBody
+    public AjaxResult encryptMobile(@RequestBody Map<String,String> request)
     {
-        log.info("开始拉取 人工外呼通话记录");
-        long strat = System.currentTimeMillis();
-        String msg = aiSipCallOutboundCdrService.scheduledGetCallRecord().join();
-        log.info("结束拉取 人工外呼通话记录,耗时:{}ms,结果:{}",System.currentTimeMillis()-strat,msg);
-        return AjaxResult.success(msg);
+        String combined = request.get("data"); // 前端传来的 "原始字符串+随机数"
+        // 随机数为最后6位
+        int randomLen = 6;
+        String original = combined.substring(0, combined.length() - randomLen);
+        try {
+            //1.先解密再加密
+            String decrypted = original.length() > 11 ? PhoneUtil.decryptPhone(original) : original;
+            //测试数据
+            String encrypted = PhoneUtil.xorEncrypt(decrypted);
+            return AjaxResult.success("获取成功",encrypted + RandomUtil.generateRandomCode()) ;
+        } catch (Exception e) {
+            return AjaxResult.error("获取加密手机号失败:" + e);
+        }
     }
 
-
     /**
-     * 同步aiSIP外呼通话记录
+     * 根据UUID同步手动外呼通话记录
      */
-    @PostMapping("/syncByUuid")
-    public AjaxResult syncByUuid(@RequestBody ApiCallRecordByUuidQueryParams req) {
-        if (req == null || StringUtils.isBlank(req.getUuid())) {
-            return AjaxResult.error("uuid不能为空");
-        }
-        if (StringUtils.isBlank(req.getCallType())) {
-            req.setCallType("03");
-        }
-
-        int rows = aiSipCallOutboundCdrService.syncByUuid(req);
-        if (rows > 0) {
-            return AjaxResult.success("同步成功");
+    @PostMapping("/callEndSyncByUuid")
+    @ResponseBody
+    public void callEndSyncByUuid(@RequestBody AiSipCallOutboundCdr request)
+    {
+        if(StringUtils.isNotEmpty(request.getUuid())){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            request.setSourceType("0");
+            request.setCompanyId(loginUser.getCompany().getCompanyId());
+            request.setCompanyUserId(loginUser.getUser().getUserId());
+            request.setCompanyUserName(loginUser.getUser().getUserName());
+            request.setStatus(0);
+            aiSipCallOutboundCdrService.callEndSyncByUuid(request);
+        }else{
+            throw new RuntimeException("获取手动外呼通话记录同步失败,uuid为空");
         }
-        return AjaxResult.error("未查到对应通话记录或同步失败");
     }
-
 }

+ 53 - 30
fs-company/src/main/java/com/fs/company/controller/company/aiSipCall/AiSipCallUserController.java

@@ -1,27 +1,34 @@
 package com.fs.company.controller.company.aiSipCall;
 
-import com.fs.aiSipCall.domain.AiSipCallUser;
-import com.fs.aiSipCall.service.IAiSipCallUserService;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.ServletUtils;
+import com.fs.aiSipCall.domain.AiSipCallUser;
+import com.fs.aiSipCall.service.IAiSipCallUserService;
 import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import com.fs.common.core.page.TableDataInfo;
 
 /**
  * sip用户信息Controller
- * 
+ *
  * @author fs
  * @date 2026-03-13
  */
@@ -41,6 +48,8 @@ public class AiSipCallUserController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(AiSipCallUser aiSipCallUser)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallUser.setCompanyId(loginUser.getUser().getCompanyId());
         startPage();
         List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
         return getDataTable(list);
@@ -54,8 +63,8 @@ public class AiSipCallUserController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(AiSipCallUser aiSipCallUser)
     {
-//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        aiSipCallUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallUser.setCompanyId(loginUser.getCompany().getCompanyId());
         List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
         ExcelUtil<AiSipCallUser> util = new ExcelUtil<>(AiSipCallUser.class);
         return util.exportExcel(list, "sip用户信息数据");
@@ -79,13 +88,10 @@ public class AiSipCallUserController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody AiSipCallUser aiSipCallUser)
     {
-        if(aiSipCallUser.getCompanyUserId() == null){
-            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-            aiSipCallUser.setCompanyId(loginUser.getCompany().getCompanyId());
-            aiSipCallUser.setCompanyUserId(loginUser.getUser().getUserId());
-            aiSipCallUser.setCreateBy(loginUser.getUser().getUserName());
-        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSipCallUser.setCreateBy(loginUser.getUser().getUserName());
         aiSipCallUser.setCreateTime(new Date());
+        aiSipCallUser.setUserSource("0");
         return toAjax(aiSipCallUserService.insertAiSipCallUser(aiSipCallUser));
     }
 
@@ -100,6 +106,7 @@ public class AiSipCallUserController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         aiSipCallUser.setUpdateBy(loginUser.getUser().getUserName());
         aiSipCallUser.setUpdateTime(new Date());
+        aiSipCallUser.setUserSource("0");
         return toAjax(aiSipCallUserService.updateAiSipCallUser(aiSipCallUser));
     }
 
@@ -108,19 +115,22 @@ public class AiSipCallUserController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('company:aiSipCall:aiSipCallUser:remove')")
     @Log(title = "sip用户信息", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{userIds}")
+    @DeleteMapping("/{userIds}")
     public AjaxResult remove(@PathVariable Long[] userIds)
     {
         return toAjax(aiSipCallUserService.deleteAiSipCallUserByUserIds(userIds));
     }
 
     /**
-     * 获取绑定的分机列表
+     * 获取公司绑定的分机列表
      */
-    @GetMapping("/getUnBindExtnum")
-    public AjaxResult getUnBindExtnum()
+    @GetMapping("/getCompanyUnBindExtnum/{companyId}")
+    public AjaxResult getCompanyUnBindExtnum(@PathVariable Long companyId)
     {
-        return aiSipCallUserService.getUnBindExtnum();
+        if(companyId == null || companyId <= 0){
+            return AjaxResult.error("参数缺失companyId");
+        }
+        return aiSipCallUserService.getCompanyUnBindExtnum(companyId);
     }
 
     /**
@@ -133,11 +143,12 @@ public class AiSipCallUserController extends BaseController
         aiSipCallUser.setCompanyUserId(loginUser.getUser().getUserId());
         startPage();
         List<AiSipCallUser> list = aiSipCallUserService.selectAiSipCallUserList(aiSipCallUser);
+        AiSipCallUser callUser = null;
         if(!list.isEmpty()){
-            return AjaxResult.success(list.get(0));
-        }else{
-            return AjaxResult.error("未创建sip角色");
+            callUser = list.get(0);
         }
+
+        return AjaxResult.success(callUser);
     }
 
     /**
@@ -154,4 +165,16 @@ public class AiSipCallUserController extends BaseController
         }
         return aiSipCallUserService.getToolbarBasicParam(param);
     }
+    /**
+     * 登录外呼平台接口
+     * @param param 参数
+     * @return AjaxResult 结果
+     */
+    @PostMapping("/agentLogin")
+    public AjaxResult agentLogin(@RequestBody Map<String,Object> param)
+    {
+
+        return aiSipCallUserService.agentLogin(param);
+    }
+
 }

+ 15 - 0
fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java

@@ -20,6 +20,8 @@ import com.fs.crm.service.ICrmCustomerUserService;
 import com.fs.crm.vo.*;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.utils.RandomUtil;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -454,4 +456,17 @@ public class CrmCustomerController extends BaseController
         return R.ok().put("data",list);
     }
 
+    @ApiOperation("获取SIP手机号")
+    @GetMapping("/getSipPhoneNumber")
+    public R getSipPhoneNumber(@RequestParam Long customerId){
+        CrmCustomer crmCustomer = crmCustomerService.selectCrmCustomerById(customerId);
+        if (crmCustomer == null || StringUtils.isBlank(crmCustomer.getMobile())){
+            return R.error("客户手机号不存在");
+        }
+        String original = crmCustomer.getMobile().trim();
+        String decrypted = original.length() > 11 ? PhoneUtil.decryptPhone(original) : original;
+        String encrypted = PhoneUtil.xorEncrypt(decrypted);
+        return R.ok().put("data", encrypted + RandomUtil.generateRandomCode());
+    }
+
 }

+ 14 - 0
fs-company/src/main/java/com/fs/user/FsUserAdminController.java

@@ -24,6 +24,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsExportTaskService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.PhoneUtil;
+import com.fs.his.utils.RandomUtil;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.CustomerTransferApproval;
@@ -243,4 +244,17 @@ public class FsUserAdminController extends BaseController {
         return openIMService.batchSendCourse(batchSendCourseDTO);
     }
 
+    @ApiOperation("获取SIP手机号")
+    @GetMapping("/getSipPhoneNumber")
+    public R getSipPhoneNumber(@RequestParam Long userId){
+        FsUser fsUser = fsUserService.selectFsUserById(userId);
+        if (fsUser == null || StringUtils.isBlank(fsUser.getPhone())){
+            return R.error("用户手机号不存在");
+        }
+        String original = fsUser.getPhone().trim();
+        String decrypted = original.length() > 11 ? PhoneUtil.decryptPhone(original) : original;
+        String encrypted = PhoneUtil.xorEncrypt(decrypted);
+        return R.ok().put("data", encrypted + RandomUtil.generateRandomCode());
+    }
+
 }

+ 44 - 3
fs-service/src/main/java/com/fs/aiSipCall/RemoteCommon.java

@@ -75,13 +75,38 @@ public class RemoteCommon {
      * 修改用户
      */
     public static final String EDIT_USER_OR_UNBING_EXTNUM_API = "/aicall/api/user/editUserOrUnBindExtNum";
-
-
+    /**
+     * 登录外呼平台接口
+     */
+    public static final String AI_CALL_LOGIN_API = "/login";
     /**
      * 查询未分配的分机
      */
     public static final String QUERY_UN_BIND_EXTNUM_API = "/aicall/api/extnum/selectUnBindCcExtNumList";
-
+    /**
+     * 查询公司未分配的分机
+     */
+    public static final String QUERY_COMPANY_UN_BIND_EXTNUM_API = "/aicall/api/extnum/selectCompanyUnBindCcExtNumList";
+    /**
+     * 查询未分配的分机分页
+     */
+    public static final String QUERY_UN_BIND_EXTNUM_PAGE_API = "/aicall/api/extnum/selectUnBindCcExtNumListPage";
+    /**
+     * 通用查询分机列表分页
+     */
+    public static final String QUERY_EXTNUM_PAGE_API = "/aicall/api/extnumPage";
+    /**
+     * 公司批量解绑分机
+     */
+    public static final String CONPANY_BATCH_UNBING_EXTNUM_API = "/aicall/api/companyBatchUnbindSipExt";
+    /**
+     * 新增企业绑定未使用的分机(实际只是公司占位,需销售来绑定)
+     */
+    public static final String QUERY_UN_BIND_EXTNUM_COMPANY_API = "/aicall/api/companyBindExtNum";
+    /**
+     * 删除用户且解绑分机
+     */
+    public static final String DELETE_USER_OR_UNBING_EXTNUM_API = "/aicall/api/deleteUserOrunBindExtNum";
     /**
      * 获取手动外呼客户沟通信息
      */
@@ -123,4 +148,20 @@ public class RemoteCommon {
         }
         return null;
     }
+
+    /**
+     * 发送POST表单提交请求
+     * @param url   地址
+     * @param params 表单参数
+     * @return  String  结果
+     */
+    public static String sendPostForm(String url, java.util.Map<String, Object> params){
+        try{
+            return HttpUtil.post(url, params, 10 * 1000);
+        }catch (Exception e){
+            e.printStackTrace();
+            log.info("sendPostForm error");
+        }
+        return null;
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallGateway.java

@@ -1,5 +1,6 @@
 package com.fs.aiSipCall.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
@@ -82,4 +83,8 @@ public class AiSipCallGateway extends BaseEntity{
 
     private Integer pageNum;
 
+    /** 是否自动外呼 true是  false否 */
+    @TableField(exist = false)
+    private Boolean isAICall;
+
 }

+ 26 - 0
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallOutboundCdr.java

@@ -61,6 +61,8 @@ public class AiSipCallOutboundCdr implements Serializable {
     @Excel(name = "录音文件名")
     private String recordFilename;
 
+    private String wavfile;
+
     /** 对话内容 */
     @Excel(name = "对话内容")
     private String chatContent;
@@ -69,6 +71,30 @@ public class AiSipCallOutboundCdr implements Serializable {
     @Excel(name = "挂断原因")
     private String hangupCause;
 
+    /** 外呼类型(0销售后台 1总后台) */
+    @Excel(name = "外呼类型(0销售后台 1总后台)")
+    private String sourceType;
+    /** 销售公司ID */
+    @Excel(name = "销售公司ID")
+    private Long companyId;
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+    /** 销售公司名称 */
+    @Excel(name = "销售公司名称")
+    private String companyName;
+    /** 销售账号 */
+    @Excel(name = "销售账号")
+    private String companyUserName;
+    /** 总后台用户ID */
+    @Excel(name = "总后台用户ID")
+    private Long sysUserId;
+    /** 总后台用户账号 */
+    @Excel(name = "总后台用户账号")
+    private String sysUserName;
+    /** 状态(0正常1删除) */
+    private Integer status;
+
     /** 通话总时长起止 */
     private Long timeLenStart;
     private Long timeLenEnd;

+ 12 - 0
fs-service/src/main/java/com/fs/aiSipCall/domain/AiSipCallUser.java

@@ -98,5 +98,17 @@ public class AiSipCallUser extends BaseEntity{
     private Long companyId;
     private Long companyUserId;
 
+    /** 分机密码 */
+    private String extPass;
+    /** 用户来源0销售 1总后台 */
+    private String userSource;
+    /** 总后台用户ID */
+    private Long sysUserId;
+    /** 总后台用户账号 */
+    private String sysUserName;
+    /** 分机绑定用户 */
+    private String userCode;
+    /** 销售公司 */
+    private String companyName;
 
 }

+ 9 - 0
fs-service/src/main/java/com/fs/aiSipCall/mapper/AiSipCallUserMapper.java

@@ -2,8 +2,10 @@ package com.fs.aiSipCall.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.aiSipCall.domain.AiSipCallUser;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * sip用户信息Mapper接口
@@ -59,4 +61,11 @@ public interface AiSipCallUserMapper extends BaseMapper<AiSipCallUser>{
      * @return 结果
      */
     int deleteAiSipCallUserByUserIds(Long[] userIds);
+
+    /**
+     * 查询分机号对应的坐席id
+     * @param extNumList 分机号集合
+     * @return 坐席id集合
+     */
+    List<Long> selectByExtNumList(@Param("extNumList") Set<Long> extNumList);
 }

+ 11 - 15
fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallOutboundCdrService.java

@@ -1,24 +1,22 @@
 package com.fs.aiSipCall.service;
 
+import java.util.List;
+
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.CcCustInfo;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
 import com.fs.common.core.domain.AjaxResult;
 
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
 /**
  * aiSIP手动外呼通话记录Service接口
- * 
+ *
  * @author fs
  * @date 2026-03-19
  */
 public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutboundCdr>{
     /**
      * 查询aiSIP手动外呼通话记录
-     * 
+     *
      * @param id aiSIP手动外呼通话记录主键
      * @return aiSIP手动外呼通话记录
      */
@@ -26,7 +24,7 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
 
     /**
      * 查询aiSIP手动外呼通话记录列表
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return aiSIP手动外呼通话记录集合
      */
@@ -34,7 +32,7 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
 
     /**
      * 新增aiSIP手动外呼通话记录
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return 结果
      */
@@ -42,7 +40,7 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
 
     /**
      * 修改aiSIP手动外呼通话记录
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return 结果
      */
@@ -50,7 +48,7 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
 
     /**
      * 批量删除aiSIP手动外呼通话记录
-     * 
+     *
      * @param ids 需要删除的aiSIP手动外呼通话记录主键集合
      * @return 结果
      */
@@ -58,17 +56,15 @@ public interface IAiSipCallOutboundCdrService extends IService<AiSipCallOutbound
 
     /**
      * 删除aiSIP手动外呼通话记录信息
-     * 
+     *
      * @param id aiSIP手动外呼通话记录主键
      * @return 结果
      */
     int deleteAiSipCallOutboundCdrById(String id);
 
-    AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid);
+    AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid, String dialMode);
 
     AjaxResult addCustcallrecord(CcCustInfo ccCustInfo);
 
-    CompletableFuture<String> scheduledGetCallRecord();
-
-    int syncByUuid(ApiCallRecordByUuidQueryParams req);
+    void callEndSyncByUuid(AiSipCallOutboundCdr request);
 }

+ 25 - 0
fs-service/src/main/java/com/fs/aiSipCall/service/IAiSipCallUserService.java

@@ -2,7 +2,10 @@ package com.fs.aiSipCall.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.aiSipCall.domain.AiSipCallUser;
+import com.fs.aiSipCall.vo.CcExtNumVo;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.domain.Company;
 
 import java.util.List;
 import java.util.Map;
@@ -71,4 +74,26 @@ public interface IAiSipCallUserService extends IService<AiSipCallUser>{
      * @return AjaxResult 结果
      */
     AjaxResult getToolbarBasicParam(Map<String,String> param);
+
+    AjaxResult companyUnbindExtNum(List<Company> companyList);
+
+    AjaxResult getCompanyUnBindExtnum(Long companyId);
+
+    TableDataInfo getUnBindExtnumPage(CcExtNumVo ccExtNum);
+
+    TableDataInfo getSipExtDetail(CcExtNumVo ccExtNum);
+
+    /**
+     * 公司批量解绑分机
+     */
+    AjaxResult companyBatchUnbindSipExt(Long companyId,List<CcExtNumVo> ccExtNum);
+
+    /**
+     * 企业绑定分机号
+     * @param companyName 销售公司
+     * @param sipExtNumIds 分机号集合
+     */
+    AjaxResult companyBindExtNum(String companyName,String sipExtNumIds);
+
+    AjaxResult agentLogin(Map<String, Object> param);
 }

+ 36 - 4
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallGatewayServiceImpl.java

@@ -10,6 +10,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.DateUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -24,6 +25,19 @@ import java.util.List;
 @Service
 public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMapper, AiSipCallGateway> implements IAiSipCallGatewayService {
 
+    /** 是否使用自己线路 */
+    @Value(value = "${sip.call.myGateway:false}")
+    private boolean isMyGateway;
+    /** 使用自动线路前缀 */
+    @Value(value = "${sip.call.automaticGatewayPrefix:automatic}")
+    private String automaticGatewayPrefix;
+    /** 使用手动线路前缀 */
+    @Value(value = "${sip.call.manualGatewayPrefix:weizhi}")
+    private String manualGatewayPrefix;
+    /** 使用公共线路前缀 */
+    @Value(value = "${sip.call.publicGatewayPrefix:outbound}")
+    private String publicGatewayPrefix;
+
     /**
      * 查询aiSIP外呼网关
      *
@@ -113,16 +127,34 @@ public class AiSipCallGatewayServiceImpl extends ServiceImpl<AiSipCallGatewayMap
 
     @Override
     public TableDataInfo remoteList(AiSipCallGateway aiSipCallGateway) {
+        if(isMyGateway){
+            //自己线路处理
+            if(aiSipCallGateway.getIsAICall()){
+                aiSipCallGateway.setGwName(automaticGatewayPrefix);
+            }else{
+                aiSipCallGateway.setGwName(manualGatewayPrefix);
+            }
+        }else{
+            //没有给默认线路
+            aiSipCallGateway.setGwName(publicGatewayPrefix);
+        }
+
+        TableDataInfo tableDataInfo = new TableDataInfo();
         //先使用远程网关
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GATEWAY_LIST_API, JSONObject.toJSONString(aiSipCallGateway));
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
-                return JSONObject.parseObject(result, TableDataInfo.class);
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                tableDataInfo = JSONObject.parseObject(result, TableDataInfo.class);
             }else{
-                log.error("获取网关接口失败:{}", jsonObject.getString("msg"));
+                tableDataInfo.setCode(code);
+                tableDataInfo.setMsg("获取网关接口失败:"+jsonObject.getString("msg"));
             }
+        }else{
+            tableDataInfo.setCode(500);
+            tableDataInfo.setMsg("获取网关接口失败:无返回结果");
         }
-        return null;
+        return tableDataInfo;
     }
 }

+ 124 - 404
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallOutboundCdrServiceImpl.java

@@ -1,51 +1,32 @@
 package com.fs.aiSipCall.service.impl;
 
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.json.JSONUtil;
+import java.util.*;
+
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.aiSipCall.RemoteCommon;
-import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
 import com.fs.aiSipCall.domain.CcCustInfo;
-import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
-import com.fs.aiSipCall.param.ApiCallRecordByUuidQueryParams;
-import com.fs.aiSipCall.param.ApiCallRecordQueryParams;
-import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
 import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.redis.RedisCache;
-import com.fs.company.domain.CompanyAiWorkflowExec;
-import com.fs.company.domain.CompanyVoiceRoboticBusiness;
-import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
-import com.fs.company.mapper.CompanyAiWorkflowExecMapper;
-import com.fs.company.mapper.CompanyVoiceRoboticBusinessMapper;
-import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
-import com.fs.company.mapper.EasyCallMapper;
-import com.fs.company.service.CompanyWorkflowEngine;
-import com.fs.company.vo.CidConfigVO;
-import com.fs.company.vo.easycall.EasyCallOutBoundVO;
-import com.fs.system.service.ISysConfigService;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.vo.OptionsVO;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.net.URLDecoder;
 import java.util.stream.Collectors;
 
-import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
+import com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper;
+import com.fs.aiSipCall.domain.AiSipCallOutboundCdr;
+import com.fs.aiSipCall.service.IAiSipCallOutboundCdrService;
+import org.springframework.util.CollectionUtils;
 
 /**
  * aiSIP手动外呼通话记录Service业务层处理
- * 
+ *
  * @author fs
  * @date 2026-03-19
  */
@@ -54,38 +35,58 @@ import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORK
 public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutboundCdrMapper, AiSipCallOutboundCdr> implements IAiSipCallOutboundCdrService {
 
     @Autowired
-    private CompanyVoiceRoboticCallLogCallphoneMapper companyVoiceRoboticCallLogCallphoneMapper;
-    @Autowired
-    private RedisCache redisCache;
-
-    @Autowired
-    private EasyCallMapper easyCallMapper;
-
-    @Autowired
-    private CompanyAiWorkflowExecMapper currentExecutionMapper;
-
-    @Autowired
-    private ObjectMapper objectMapper;
-    @Autowired
-    private CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper;
-
-    @Autowired
-    private CompanyWorkflowEngine companyWorkflowEngine;
-    @Autowired
-    private ISysConfigService configService;
-
-    private static final BigDecimal DEFAULT_CALL_CHARGE = new BigDecimal("0.12");
-    private static final BigDecimal ONE_MINUTES_SECOND = new BigDecimal("60");
-
-
+    private CompanyMapper companyMapper;
 
     @Override
     public AiSipCallOutboundCdr selectAiSipCallOutboundCdrById(String id) {
         return baseMapper.selectAiSipCallOutboundCdrById(id);
     }
+
+    @Override
+    public void callEndSyncByUuid(AiSipCallOutboundCdr request) {
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_OUTBOUNDCDR_LIST_API,JSONObject.toJSONString(request));
+        if(StringUtils.isNotBlank(result)){
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if(code != null && code == 0){
+                String rows = jsonObject.getString("rows");
+                if(StringUtils.isNotBlank(rows)){
+                    List<AiSipCallOutboundCdr> list = JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
+                    if(list != null && !list.isEmpty()){
+                        AiSipCallOutboundCdr data = list.get(0);
+                        data.setSourceType(request.getSourceType());
+                        data.setCompanyId(request.getCompanyId());
+                        data.setCompanyUserId(request.getCompanyUserId());
+                        data.setCompanyUserName(request.getCompanyUserName());
+                        data.setStatus(request.getStatus());
+                        data.setSysUserId(request.getSysUserId());
+                        data.setSysUserName(request.getSysUserName());
+                        // 对opnum字段进行URL解码,防止乱码
+                        if(StringUtils.isNotBlank(data.getOpnum())){
+                            try {
+                                data.setOpnum(URLDecoder.decode(data.getOpnum(), "UTF-8"));
+                            } catch (Exception e) {
+                                log.error("opnum字段URL解码失败,原值:{}", data.getOpnum(), e);
+                            }
+                        }
+                        this.save(data);
+                    }else{
+                        log.error("获取手动外呼记录接口转化数据失败:原数据:{},原因:{}",rows, jsonObject.getString("msg"));
+                    }
+                }else{
+                    log.error("获取手动外呼记录接口获取rows失败:原因:{}",jsonObject.getString("msg"));
+                }
+            }else{
+                log.error("同步手动外呼记录接口失败:返回状态码:{},原因:{}",code, jsonObject.getString("msg"));
+            }
+        }else{
+            log.error("同步手动外呼记录接口失败:无返回结果");
+        }
+    }
+
     /**
      * 查询aiSIP手动外呼通话记录列表
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return aiSIP手动外呼通话记录
      */
@@ -117,12 +118,20 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
         if (StringUtils.isNotBlank(aiSipCallOutboundCdr.getEndTimeEnd())) {
             aiSipCallOutboundCdr.setEndTimeEndLong(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss", aiSipCallOutboundCdr.getEndTimeEnd()).getTime());
         }
-        return baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
+        List<AiSipCallOutboundCdr> list =  baseMapper.selectAiSipCallOutboundCdrList(aiSipCallOutboundCdr);
+        if(!CollectionUtils.isEmpty(list)){
+            // 批量查询公司名称
+            Map<Long, String> companyNameMap = buildCompanyNameMap(list);
+
+            // 处理每条记录
+            list.forEach(data -> processCallRecord(data, companyNameMap));
+        }
+        return list;
     }
 
     /**
      * 新增aiSIP手动外呼通话记录
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return 结果
      */
@@ -134,7 +143,7 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
 
     /**
      * 修改aiSIP手动外呼通话记录
-     * 
+     *
      * @param aiSipCallOutboundCdr aiSIP手动外呼通话记录
      * @return 结果
      */
@@ -146,7 +155,7 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
 
     /**
      * 批量删除aiSIP手动外呼通话记录
-     * 
+     *
      * @param ids 需要删除的aiSIP手动外呼通话记录主键
      * @return 结果
      */
@@ -158,7 +167,7 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
 
     /**
      * 删除aiSIP手动外呼通话记录信息
-     * 
+     *
      * @param id aiSIP手动外呼通话记录主键
      * @return 结果
      */
@@ -169,396 +178,107 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
     }
 
     @Override
-    public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid) {
+    public AjaxResult getCustCommunicationInfo(String phoneNum, Integer callType, String uuid, String dialMode) {
+        if(StringUtils.isNotBlank(dialMode) && dialMode.equals("encrypted")){
+            //密文需要解密
+            phoneNum = PhoneUtil.decryptPhone(phoneNum);
+        }
         String paramStr = "?phoneNum=" + phoneNum + "&callType=" + callType + "&uuid=" + uuid;
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.GET_CUST_COMMUNICATION_INFO_API + paramStr);
+        String msg;
         if (StringUtils.isNotBlank(result)) {
             JSONObject jsonObject = JSONObject.parseObject(result);
             if (jsonObject.getInteger("code") == 0) {
                 return JSONObject.parseObject(result, AjaxResult.class);
             } else {
-                log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
+                msg = "获取手动外呼客户沟通信息失败:" +  jsonObject.getString("msg");
             }
         } else {
-            log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
+            msg = "获取手动外呼客户沟通信息失败:接口返回为空";
         }
-        return AjaxResult.error();
+        return AjaxResult.error(msg);
     }
 
     @Override
     public AjaxResult addCustcallrecord(CcCustInfo ccCustInfo) {
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.ADD_CUSTCALL_RECORD_API,JSONObject.toJSONString(ccCustInfo));
+        String msg;
         if (StringUtils.isNotBlank(result)) {
             JSONObject jsonObject = JSONObject.parseObject(result);
             if (jsonObject.getInteger("code") == 0) {
                 return JSONObject.parseObject(result, AjaxResult.class);
             } else {
-                log.error("取手动外呼客户沟通信息失败:{}", jsonObject.getString("msg"));
+                msg = "新增手动外呼客户沟通信息失败:"+ jsonObject.getString("msg");
             }
         } else {
-            log.error("取手动外呼客户沟通信息失败:{}", "接口返回为空");
-        }
-        return AjaxResult.error();
-    }
-
-    private final AtomicBoolean isRunning = new AtomicBoolean(false);
-    @Override
-    @Async
-    public CompletableFuture<String> scheduledGetCallRecord() {
-        if (!isRunning.compareAndSet(false, true)) {
-            log.error("scheduledGetCallRecord 任务正在执行中,请稍后再试");
-            return CompletableFuture.completedFuture("任务正在执行中,请稍后再试");
-        }
-
-        try {
-            log.info("开始执行 scheduledGetCallRecord 异步任务");
-            String todayStartStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()) + " 00:00:00";
-            String now = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date());
-            long startTime = System.currentTimeMillis();
-
-            // 构建查询参数
-            List<ApiCallRecordQueryParams> paramsList = buildDayQueryParams(todayStartStr,now);
-
-            // 获取远程数据
-            List<AiSipCallOutboundCdr> remoteList = fetchAllRemoteCallRecords(paramsList);
-            if (remoteList.isEmpty()) {
-                log.info("scheduledGetCallRecord 异步任务完成,耗时:{}ms, 结果:当天无最新数据", System.currentTimeMillis() - startTime);
-                return CompletableFuture.completedFuture("当天无最新数据");
-            }
-
-            // 获取本地当天所有外呼记录
-            List<AiSipCallOutboundCdr> localList = baseMapper.selectCurrentDayCallRecords(DateUtils.toStartTime(), System.currentTimeMillis());
-
-            // 筛选并处理新增和更新数据
-            String result = processAndSaveData(remoteList, localList);
-            log.info("scheduledGetCallRecord 异步任务完成,耗时:{}ms, 结果:{}", System.currentTimeMillis() - startTime, result);
-            return CompletableFuture.completedFuture(result);
-        } catch (Exception e) {
-            log.error("scheduledGetCallRecord 异步任务执行失败", e);
-            return CompletableFuture.completedFuture("任务执行失败:" + e.getMessage());
-        } finally {
-            isRunning.set(false);
+            msg = "新增手动外呼客户沟通信息失败:接口返回为空";
         }
+        return AjaxResult.error(msg);
     }
 
     /**
-     * 构建当天查询参数 - 使用当天 00:00:00 到当前时间
-     * @return 查询参数对象列表(支持多时段查询)
+     * 构建公司名称映射
      */
-    private List<ApiCallRecordQueryParams> buildDayQueryParams(String todayStartStr,String now) {
-
-        List<ApiCallRecordQueryParams> paramsList = new ArrayList<>();
-
-        // 如果时间跨度超过 12 小时,分段查询避免遗漏
-        Date today = DateUtils.parseDate(todayStartStr);
-        long hoursDiff = (System.currentTimeMillis() - today.getTime()) / (1000 * 60 * 60);
-
-        if (hoursDiff > 12) {
-            // 分两段查询:00:00-12:00 和 12:00-当前时间
-            ApiCallRecordQueryParams params1 = createSingleParam(todayStartStr, todayStartStr.substring(0, 10) + " 12:00:00");
-            ApiCallRecordQueryParams params2 = createSingleParam(todayStartStr.substring(0, 10) + " 12:00:00", now);
-            paramsList.add(params1);
-            paramsList.add(params2);
-        } else {
-            // 单段查询:00:00-当前时间
-            paramsList.add(createSingleParam(todayStartStr, now));
+    private Map<Long, String> buildCompanyNameMap(List<AiSipCallOutboundCdr> list) {
+        List<Long> companyIds = list.stream()
+                .map(AiSipCallOutboundCdr::getCompanyId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (companyIds.isEmpty()) {
+            return Collections.emptyMap();
         }
 
-        return paramsList;
-    }
-
-    /**
-     * 创建单个查询参数对象
-     */
-    private ApiCallRecordQueryParams createSingleParam(String startTime, String endTime) {
-        ApiCallRecordQueryParams params = new ApiCallRecordQueryParams();
-        params.setPageNum(1);
-        params.setPageSize(1000); // 减小单次请求量,提升响应速度
-        params.setCallType("03");
-        params.setCalloutTimeStart(startTime);
-        params.setCalloutTimeEnd(endTime);
-        return params;
+        List<OptionsVO> companyOptions = companyMapper.selectByIds(companyIds);
+        return companyOptions.stream()
+                .collect(Collectors.toMap(OptionsVO::getDictValue, OptionsVO::getDictLabel, (v1, v2) -> v1));
     }
-
     /**
-     * 分页轮询获取所有远程通话记录 (带去重和失败重试)
-     * @param paramsList 查询参数列表
-     * @return 通话记录列表 (已去重)
+     * 处理单条通话记录
      */
-    private List<AiSipCallOutboundCdr> fetchAllRemoteCallRecords(List<ApiCallRecordQueryParams> paramsList) {
-        List<AiSipCallOutboundCdr> allRecords = new ArrayList<>();
-        for (ApiCallRecordQueryParams params : paramsList) {
-            int currentPage = 1;
-            boolean hasMore = true;
-
-            while (hasMore) {
-                params.setPageNum(currentPage);
-                List<AiSipCallOutboundCdr> pageData = fetchSinglePageRecords(params);
-
-                // 失败时直接结束
-                if (pageData == null) {
-                    log.error("sip手动外呼同步电话 分页查询第{}页失败,已丢弃该页数据,停止查询", currentPage);
-                    break;
-                }
-
-                if (pageData.isEmpty()) {
-                    log.error("sip手动外呼同步电话 第{}页无数据,查询结束", currentPage);
-                    hasMore = false;
-                } else {
-                    allRecords.addAll(pageData);
-                    log.error("sip手动外呼同步电话 第{}页数据:{},累计总数:{}", currentPage, pageData.size(), allRecords.size());
-
-                    // 如果返回数据少于页大小,说明已是最后一页
-                    if (pageData.size() < params.getPageSize()) {
-                        hasMore = false;
-                    }
-                    currentPage++;
-                }
-
-                // 安全限制:最多拉取 50 页
-                if (currentPage > 50) {
-                    log.error("sip手动外呼同步电话 已达到最大页数限制 50 页,停止查询。已获取数据量:{}", allRecords.size());
-                    hasMore = false;
-                }
-            }
+    private void processCallRecord(AiSipCallOutboundCdr data, Map<Long, String> companyNameMap) {
+        // 设置公司名称
+        if (data.getCompanyId() != null) {
+            data.setCompanyName(companyNameMap.getOrDefault(data.getCompanyId(), ""));
         }
-
-        log.error("sip手动外呼同步电话 远程数据获取完成,总计:{} 条", allRecords.size());
-        return allRecords.isEmpty() ? Collections.emptyList() : allRecords;
+        // 格式化时间字段
+        formatTimestampField(data.getStartTime(), data::setStartTimeStr);
+        formatTimestampField(data.getAnsweredTime(), data::setAnsweredTimeStr);
+        formatTimestampField(data.getEndTime(), data::setEndTimeStr);
+        data.setTimeLenValidStr(DateUtils.formatTimeLength(data.getTimeLenValid()/1000));
+        // 格式化通话时长
+        data.setTimeLenSec(DateUtils.formatTimeLength(data.getTimeLen() / 1000));
+        // 处理录音文件路径
+        formatWavfilePath(data);
+        // 处理电话号码加密显示
+        encryptPhoneNumber(data);
     }
-
     /**
-     * 获取单页数据
-     * @param params 查询参数(需预先设置页码)
-     * @return 单页通话记录列表
+     * 格式化时间戳字段
      */
-    private List<AiSipCallOutboundCdr> fetchSinglePageRecords(ApiCallRecordQueryParams params) {
-        try {
-            String result = RemoteCommon.sendPost(
-                    RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CALL_RECORDS_API,
-                    JSONObject.toJSONString(params)
-            );
-
-            if (StringUtils.isBlank(result)) {
-                log.error("查询第{}页失败:接口返回为空", params.getPageNum());
-                return null;
-            }
-
-            JSONObject jsonObject = JSONObject.parseObject(result);
-            Integer code = jsonObject.getInteger("code");
-
-            if (code == null || code != 0) {
-                String errorMsg = jsonObject.getString("msg") != null
-                        ? jsonObject.getString("msg") : "未知错误";
-                log.error("查询第{}页失败:{}", params.getPageNum(), errorMsg);
-                return null;
-            }
-
-            Long total = jsonObject.getLong("total");
-            if (total == null || total <= 0) {
-                return Collections.emptyList();
-            }
-
-            String rows = jsonObject.getString("rows");
-            if (StringUtils.isBlank(rows)) {
-                return Collections.emptyList();
-            }
-
-            return JSONObject.parseArray(rows, AiSipCallOutboundCdr.class);
-        } catch (Exception e) {
-            log.error("查询第{}页异常", params.getPageNum(), e);
-            return null;
+    private void formatTimestampField(Long timestamp, java.util.function.Consumer<String> setter) {
+        if (timestamp != null && timestamp != 0) {
+            setter.accept(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(timestamp)));
         }
     }
 
     /**
-     * 处理并保存数据 (新增和更新)
-     * @param remoteList 远程数据列表
-     * @param localList 本地数据列表
-     * @return 处理结果信息
+     * 格式化录音文件路径
      */
-    private String processAndSaveData(List<AiSipCallOutboundCdr> remoteList, List<AiSipCallOutboundCdr> localList) {
-        if (remoteList == null || remoteList.isEmpty()) {
-            return "远程数据列表为空,无需处理";
-        }
-
-        if (localList == null || localList.isEmpty()) {
-            log.warn("本地数据列表为空,所有远程数据都将作为新增处理");
-            return processDataWithStats(remoteList, "新增");
+    private void formatWavfilePath(AiSipCallOutboundCdr data) {
+        if (StringUtils.isNotBlank(data.getRecordFilename())) {
+            String filename = data.getRecordFilename().startsWith("/") ? data.getRecordFilename().substring(1) : data.getRecordFilename();
+            data.setWavfile("/recordings/files?filename=" + filename);
         }
-
-        // 构建本地数据的唯一标识映射
-        Set<String> localIdSet = localList.stream()
-                .map(AiSipCallOutboundCdr::getId)
-                .collect(Collectors.toSet());
-
-        // 分类数据:新增和更新
-        List<AiSipCallOutboundCdr> insertList = new ArrayList<>();
-        List<AiSipCallOutboundCdr> updateList = new ArrayList<>();
-
-        for (AiSipCallOutboundCdr remote : remoteList) {
-            if (StringUtils.isBlank(remote.getId())) {
-                continue;
-            }
-            if (localIdSet.contains(remote.getId())) {
-                updateList.add(remote);
-            } else {
-                insertList.add(remote);
-            }
-        }
-
-        log.info("数据筛选完成 - 预计新增:{},预计更新:{}", insertList.size(), updateList.size());
-
-        StringBuilder resultMsg = new StringBuilder();
-        if (!insertList.isEmpty()) {
-            resultMsg.append(processDataWithStats(insertList, "新增"));
-        }
-
-        if (!updateList.isEmpty()) {
-            if (resultMsg.length() > 0) {
-                resultMsg.append(",");
-            }
-            resultMsg.append(processDataWithStats(updateList, "更新"));
-        }
-
-        return resultMsg.toString();
     }
 
     /**
-     * 处理批量数据并统计成功失败信息
-     * @param dataList 数据列表
-     * @param operationType 操作类型:"新增" 或 "更新"
-     * @return 处理结果信息
+     * 加密电话号码
      */
-    private String processDataWithStats(List<AiSipCallOutboundCdr> dataList,String operationType) {
-        log.info("开始处理{}Ai 外呼记录数据,数量:{}", operationType, dataList.size());
-        if (dataList.isEmpty()) {
-            return "无有效数据,无需处理";
+    private void encryptPhoneNumber(AiSipCallOutboundCdr data) {
+        if (StringUtils.isNotBlank(data.getCallee())) {
+            data.setCallee(PhoneUtil.encryptPhone(data.getCallee()));
         }
-
-        int batchSize = 500;
-        int totalSize = dataList.size();
-        int batchCount = (totalSize + batchSize - 1) / batchSize;
-        int successCount = 0;
-        int failCount = 0;
-        List<int[]> failedBatchRanges = new ArrayList<>();
-
-        for (int i = 0; i < batchCount; i++) {
-            int fromIndex = i * batchSize;
-            int toIndex = Math.min(fromIndex + batchSize, totalSize);
-            List<AiSipCallOutboundCdr> batchList = dataList.subList(fromIndex, toIndex);
-
-            try {
-                if ("新增".equals(operationType)) {
-                    this.saveBatch(batchList);
-                } else if ("更新".equals(operationType)) {
-                    this.updateBatchById(batchList);
-                }
-                successCount += batchList.size();
-                log.debug("第{}/{}批{}成功,本批数量:{}", i + 1, batchCount, operationType, batchList.size());
-            } catch (Exception e) {
-                failCount += batchList.size();
-                int[] range = {fromIndex, toIndex - 1};
-                failedBatchRanges.add(range);
-                log.error("第{}批数据{}失败,本批数量:{},起始位:{},结束位:{}",
-                        i + 1, operationType, batchList.size(), fromIndex, toIndex - 1, e);
-            }
-        }
-
-        StringBuilder result = new StringBuilder();
-        result.append(operationType).append("数据处理完成,总计:").append(totalSize).append("条");
-        result.append(",成功:").append(successCount).append("条");
-
-        if (failCount > 0) {
-            result.append(",失败:").append(failCount).append("条");
-            result.append(",失败数据位置:");
-            for (int i = 0; i < failedBatchRanges.size(); i++) {
-                int[] range = failedBatchRanges.get(i);
-                if (i > 0) result.append(";");
-                result.append("[").append(range[0]).append("-").append(range[1]).append("]");
-            }
-        }
-
-        return result.toString();
-    }
-
-    @Override
-    public int syncByUuid(ApiCallRecordByUuidQueryParams req) {
-        if (req == null || StringUtils.isBlank(req.getUuid())) {
-//            throw new ServiceException("uuid不能为空");
-        }
-
-        String callType = StringUtils.isBlank(req.getCallType()) ? "03" : req.getCallType();
-
-//        EasyCallCallPhoneVO callPhoneRes = easyCallMapper.getCallPhoneInfoByUuid(req.getUuid());
-        EasyCallOutBoundVO callPhoneRes = easyCallMapper.getOutBoundInfoByUuid(req.getUuid());
-        String callBackUuid = UUID.randomUUID().toString();
-        CompanyAiWorkflowExec record = currentExecutionMapper.selectByWorkflowInstanceId(req.getWorkflowInstanceId());
-        CompanyVoiceRoboticBusiness business = companyVoiceRoboticBusinessMapper.selectOne(new LambdaQueryWrapper<CompanyVoiceRoboticBusiness>()
-                .eq(CompanyVoiceRoboticBusiness::getId, record.getBusinessKey()));
-        try {
-            Map<String, Object> variablesMap;
-            if (record.getVariables() == null || record.getVariables().isEmpty()) {
-                variablesMap = new HashMap<>();
-            } else {
-                variablesMap = objectMapper.readValue(record.getVariables(), new TypeReference<Map<String, Object>>() {});
-            }
-            variablesMap.put("callBackUuid", callBackUuid);
-            record.setVariables(objectMapper.writeValueAsString(variablesMap));
-
-        } catch (Exception e) {
-            log.error("反序列化上下文变量失败", e);
-        }
-
-
-        CompanyVoiceRoboticCallLogCallphone companyVoiceRoboticCallLogCallphone = new CompanyVoiceRoboticCallLogCallphone();
-        companyVoiceRoboticCallLogCallphone.setCallbackUuid(callBackUuid);
-        companyVoiceRoboticCallLogCallphone.setRoboticId(req.getRoboticId());
-        if (ObjectUtil.isNotEmpty(business)) {
-            companyVoiceRoboticCallLogCallphone.setCallerId(business.getCalleeId());
-        }
-        companyVoiceRoboticCallLogCallphone.setRunTime(new Date(callPhoneRes.getStartTime()));
-        companyVoiceRoboticCallLogCallphone.setRunParam(null);
-        companyVoiceRoboticCallLogCallphone.setResult(null);
-        companyVoiceRoboticCallLogCallphone.setStatus(req.getStatus());
-        companyVoiceRoboticCallLogCallphone.setRecordPath(callPhoneRes.getRecordFilename());
-        companyVoiceRoboticCallLogCallphone.setContentList(callPhoneRes.getChatContent());
-        companyVoiceRoboticCallLogCallphone.setCallerNum(callPhoneRes.getCallee());
-        companyVoiceRoboticCallLogCallphone.setCalleeNum(callPhoneRes.getCaller());
-        companyVoiceRoboticCallLogCallphone.setUuid(req.getUuid());
-        companyVoiceRoboticCallLogCallphone.setCallCreateTime(callPhoneRes.getStartTime());
-        companyVoiceRoboticCallLogCallphone.setCallAnswerTime(callPhoneRes.getAnsweredTime());
-        companyVoiceRoboticCallLogCallphone.setIntention(req.getIntent());
-        companyVoiceRoboticCallLogCallphone.setCompanyId(req.getCompanyId());
-        companyVoiceRoboticCallLogCallphone.setCompanyUserId(req.getCompanyUserId());
-        companyVoiceRoboticCallLogCallphone.setCallTime(Long.valueOf(callPhoneRes.getTimeLen()));
-        companyVoiceRoboticCallLogCallphone.setCallType(Integer.valueOf(callType));
-
-        String json = configService.selectConfigByKey("cid.config");
-        CidConfigVO cidConfigVO = JSONUtil.toBean(json, CidConfigVO.class);
-        BigDecimal callCharge = cidConfigVO.getCallCharge();
-        //
-        if (null == callCharge) {
-            callCharge = DEFAULT_CALL_CHARGE;
-        }
-        //向上取整分钟数
-        BigDecimal divide = new BigDecimal(companyVoiceRoboticCallLogCallphone.getCallTime()).divide(ONE_MINUTES_SECOND, 0, RoundingMode.CEILING);
-        BigDecimal multiply = divide.multiply(callCharge);
-        companyVoiceRoboticCallLogCallphone.setCost(multiply);
-
-
-        int i = companyVoiceRoboticCallLogCallphoneMapper.insertCompanyVoiceRoboticCallLogCallphone(companyVoiceRoboticCallLogCallphone);
-
-        Map<String, Object> param = new HashMap<>();
-        param.put("callBackUuid", callBackUuid);
-        param.put("callSource", "callBack");
-        companyWorkflowEngine.resumeFromBlockingNode(req.getWorkflowInstanceId(),record.getCurrentNodeKey(),param);
-
-        redisCache.deleteObject(EASYCALL_WORKFLOW_REDIS_KEY +  callBackUuid);
-
-
-        return i;
     }
-
-
 }

+ 616 - 34
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallUserServiceImpl.java

@@ -6,15 +6,23 @@ import com.fs.aiSipCall.RemoteCommon;
 import com.fs.aiSipCall.domain.AiSipCallUser;
 import com.fs.aiSipCall.mapper.AiSipCallUserMapper;
 import com.fs.aiSipCall.service.IAiSipCallUserService;
+import com.fs.aiSipCall.vo.CcExtNumVo;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.exception.CustomException;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.system.mapper.SysUserMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * sip用户信息Service业务层处理
@@ -26,12 +34,34 @@ import java.util.Map;
 @Service
 public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, AiSipCallUser> implements IAiSipCallUserService {
 
+
     @Autowired
     private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private CompanyMapper companyMapper;
+    /** 是否使用自己线路 */
+    @Value(value = "${sip.call.myGateway:false}")
+    private boolean isMyGateway;
+    /** 使用手动线路前缀 */
+    @Value(value = "${sip.call.manualGatewayPrefix:weizhi}")
+    private String manualGatewayPrefix;
+    /** 使用公共线路前缀 */
+    @Value(value = "${sip.call.publicGatewayPrefix:outbound}")
+    private String publicGatewayPrefix;
+
+    /** 总后台用户前缀 */
+    @Value(value = "${sip.call.adminUserPrefix:hdt_zt}")
+    private String adminUserPrefix;
+    /** 销售公司前缀 */
+    @Value(value = "${sip.call.companyPrefix:hdt_xg}")
+    private String companyPrefix;
+    /** 销售前缀 */
+    @Value(value = "${sip.call.companyUserPrefix:hdt_xs}")
+    private String companyUserPrefix;
 
     /**
      * 查询sip用户信息
-     * 
+     *
      * @param userId sip用户信息主键
      * @return sip用户信息
      */
@@ -43,7 +73,7 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
 
     /**
      * 查询sip用户信息列表
-     * 
+     *
      * @param aiSipCallUser sip用户信息
      * @return sip用户信息
      */
@@ -55,19 +85,29 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
 
     /**
      * 新增sip用户信息
-     * 
+     *
      * @param aiSipCallUser sip用户信息
      * @return 结果
      */
     @Override
     public int insertAiSipCallUser(AiSipCallUser aiSipCallUser)
     {
+        String namePrefix;
+        if(aiSipCallUser.getUserSource().equals("0")){
+            namePrefix= manualGatewayPrefix+"_"+companyUserPrefix+"_";
+        }else{
+            namePrefix = manualGatewayPrefix+"_"+adminUserPrefix+"_";
+        }
+        if(!aiSipCallUser.getLoginName().startsWith(namePrefix)){
+            aiSipCallUser.setLoginName(namePrefix+aiSipCallUser.getLoginName());
+        }
         //同步新增远程接口
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.ADD_USER_OR_BIND_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
 
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 String data = jsonObject.getString("data");
                 AiSipCallUser remoteAiSipCallUser = JSONObject.parseObject(data, AiSipCallUser.class);
                 if(remoteAiSipCallUser != null){
@@ -76,70 +116,144 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
                     if(StringUtils.isNotBlank(remoteAiSipCallUser.getPhonenumber())){
                         aiSipCallUser.setPhonenumber(remoteAiSipCallUser.getPhonenumber());
                     }
-                    if(StringUtils.isNotBlank(remoteAiSipCallUser.getPassword())){
-                        aiSipCallUser.setPassword(remoteAiSipCallUser.getPassword());
-                    }
                     if(remoteAiSipCallUser.getCreateTime() != null){
                         aiSipCallUser.setCreateTime(remoteAiSipCallUser.getCreateTime());
                     }
-                    int i = baseMapper.insertAiSipCallUser(aiSipCallUser);
-                    if( i> 0){
+                    if(StringUtils.isNotBlank(remoteAiSipCallUser.getExtPass())){
+                        aiSipCallUser.setExtPass(remoteAiSipCallUser.getExtPass());
+                    }
+                    int i;
+                    if(aiSipCallUser.getUserSource().equals("0")){
                         //绑定companyUser的aiSIP外呼用户
-                        return companyUserMapper.updateCompanyUserByAiSipCall(aiSipCallUser.getCompanyUserId(),remoteAiSipCallUser.getUserId());
-                    }else{
-                        log.error("绑定aiSIP外呼用户失败");
+                        i = companyUserMapper.updateCompanyUserByAiSipCall(aiSipCallUser.getCompanyUserId(),remoteAiSipCallUser.getUserId());
+                        if( i> 0){
+                            return baseMapper.insertAiSipCallUser(aiSipCallUser);
+                        }else{
+                            log.error("新增aiSIP外呼用户失败");
+                        }
                     }
                 }else{
                     log.error("新增时解析aiSIP外呼用户数据为空");
                 }
             }else{
-                log.error("新增aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("新增aiSIP外呼用户返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
+                throw new CustomException(jsonObject.getString("msg"));
             }
         }else{
-            log.error("新增aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("新增aiSIP外呼用户失败:接口返回为空");
         }
         return 0;
     }
 
     /**
-     * 修改sip用户信息
-     * 
+     * 修改sip用户信息(总后台随便修改,销售后台的需要归还公司)
+     *
      * @param aiSipCallUser sip用户信息
      * @return 结果
      */
     @Override
     public int updateAiSipCallUser(AiSipCallUser aiSipCallUser)
     {
-        //同步修改远程接口
-        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_USER_OR_UNBING_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
+        String result;
+        if(aiSipCallUser.getUserSource().equals("0")){
+            //查询拼接销售公司
+            if(aiSipCallUser.getCompanyId() == null){
+                log.error("销售修改时公司ID为空");
+                return 0;
+            }else{
+                String companyName;
+                Company company = companyMapper.selectCompanyById(aiSipCallUser.getCompanyId());
+                if(company != null){
+                    companyName = company.getCompanyName();
+                }else{
+                    log.error("删除aiSIP外呼用户失败:当前员工未绑定销售公司");
+                    return 0;
+                }
+                companyName = manualGatewayPrefix + "_"+companyPrefix+"_"+companyName;
+                aiSipCallUser.setCompanyName(companyName);
+            }
+        }
+        result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.EDIT_USER_OR_UNBING_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                if(StringUtils.isBlank(aiSipCallUser.getGatewayIds())){
+                    //处理不选网关的情况
+                    aiSipCallUser.setGatewayIds("");
+                }
+                AiSipCallUser remoteUser = JSONObject.parseObject(jsonObject.getString("data"), AiSipCallUser.class);
+                if(remoteUser != null){
+                    aiSipCallUser.setExtPass(remoteUser.getExtPass());
+                }
                 return baseMapper.updateAiSipCallUser(aiSipCallUser);
             }else{
-                log.error("修改aiSIP外呼任务失败:{}", jsonObject.getString("msg"));
+                log.error("修改aiSIP外呼任务返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
             }
         }else{
-            log.error("修改aiSIP外呼任务失败:{}", "接口返回为空");
+            log.error("修改aiSIP外呼任务失败:接口返回为空");
         }
         return 0;
     }
-
     /**
      * 批量删除sip用户信息
-     * 
+     *
      * @param userIds 需要删除的sip用户信息主键
      * @return 结果
      */
     @Override
     public int deleteAiSipCallUserByUserIds(Long[] userIds)
     {
-        return baseMapper.deleteAiSipCallUserByUserIds(userIds);
+        AiSipCallUser aiSipCallUser = this.selectAiSipCallUserByUserId(userIds[0]);
+        if(aiSipCallUser == null){
+            log.error("删除aiSIP外呼用户失败:aiSIP外呼用户不存在,用户id:" + userIds[0]);
+            return 0;
+        }
+
+        //销售绑定的坐席需要归还给公司,总后台绑定的直接释放坐席
+        if(aiSipCallUser.getUserSource().equals("0")){
+            String companyName;
+            Company company = companyMapper.selectCompanyById(aiSipCallUser.getCompanyId());
+            if(company != null){
+                companyName = company.getCompanyName();
+            }else{
+                log.error("删除aiSIP外呼用户失败:当前员工未绑定销售公司");
+                return 0;
+            }
+            companyName = manualGatewayPrefix + "_"+companyPrefix+"_"+companyName;
+            aiSipCallUser.setUserCode(companyName);
+        }else{
+            aiSipCallUser.setUserCode("");
+        }
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.DELETE_USER_OR_UNBING_EXTNUM_API, JSONObject.toJSONString(aiSipCallUser));
+        if(StringUtils.isNotBlank(result)){
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            int i;
+            if(code == 0){
+                if(aiSipCallUser.getUserSource().equals("0")){
+                    //删除companyUser的aiSIP外呼用户
+                    i = companyUserMapper.updateCompanyUserByAiSipCall(aiSipCallUser.getCompanyUserId(),null);
+                    if( i> 0){
+                        //删除aiSIP外呼用户
+                        return baseMapper.deleteAiSipCallUserByUserId(userIds[0]);
+                    }else{
+                        log.error("删除aiSIP外呼用户失败");
+                    }
+                }
+                return baseMapper.updateAiSipCallUser(aiSipCallUser);
+            }else{
+                log.error("删除aiSIP外呼用户返回code:{},失败原因:{}",code, jsonObject.getString("msg"));
+            }
+        }else{
+            log.error("删除aiSIP外呼用户失败:接口返回为空");
+        }
+        return 0;
     }
 
     /**
      * 删除sip用户信息信息
-     * 
+     *
      * @param userId sip用户信息主键
      * @return 结果
      */
@@ -153,32 +267,500 @@ public class AiSipCallUserServiceImpl extends ServiceImpl<AiSipCallUserMapper, A
     public AjaxResult getUnBindExtnum() {
         //获取未使用的分机号远程接口
         String result = RemoteCommon.sendGet(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_UN_BIND_EXTNUM_API);
+        String msg;
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
                 return JSONObject.parseObject(result, AjaxResult.class);
             }else{
-                log.error("查询aiSIP外呼未绑定分机失败:{}", jsonObject.getString("msg"));
+                msg = jsonObject.getString("msg");
             }
         }else{
-            log.error("查询aiSIP外呼未绑定分机失败:{}", "接口返回为空");
+            msg = "查询aiSIP外呼未绑定分机失败:接口返回为空";
         }
-        return AjaxResult.error();
+        return AjaxResult.error(msg);
     }
 
     @Override
     public AjaxResult getToolbarBasicParam(Map<String,String> param) {
+        String myGateway = param.get("myGateway");
+        //没有配置网关
+        if(StringUtils.isBlank(myGateway)){
+            //查找自己项目的网关
+            if(isMyGateway){
+                //自己线路处理
+                param.put("wgName", manualGatewayPrefix);
+            }else{
+                //没有给默认线路
+                param.put("wgName", publicGatewayPrefix);
+            }
+        }
+
         //先使用远程网关
         String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.PHONEBAR_PARAMS_API,JSONObject.toJSONString(param));
+        String msg;
         if(StringUtils.isNotBlank(result)){
             JSONObject jsonObject = JSONObject.parseObject(result);
-            if(jsonObject.getInteger("code") == 0){
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                return JSONObject.parseObject(result, AjaxResult.class);
+            }else{
+                msg= jsonObject.getString("msg");
+            }
+        }else{
+            msg= "获取电话工具条的网关列表接口失败:接口返回为空";
+        }
+        return AjaxResult.error(msg);
+    }
+
+    @Override
+    public AjaxResult agentLogin(Map<String, Object> param) {
+        String result = RemoteCommon.sendPostForm(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.AI_CALL_LOGIN_API, param);
+        String msg;
+        if (StringUtils.isNotBlank(result)) {
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if (code == 0) {
                 return JSONObject.parseObject(result, AjaxResult.class);
+            } else {
+                msg= jsonObject.getString("msg");
+            }
+        } else {
+            msg= "登录外呼平台接口失败:接口返回为空";
+        }
+        return AjaxResult.error(msg);
+    }
+
+    @Override
+    public TableDataInfo getUnBindExtnumPage(CcExtNumVo ccExtNum) {
+        //获取未使用的分机号远程接口
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_UN_BIND_EXTNUM_PAGE_API, JSONObject.toJSONString(ccExtNum));
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        if(StringUtils.isNotBlank(result)){
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if(code == 0){
+                tableDataInfo = JSONObject.parseObject(result, TableDataInfo.class);
             }else{
-                log.error("获取电话工具条的网关列表接口失败:{}", jsonObject.getString("msg"));
+                tableDataInfo.setCode(code);
+                tableDataInfo.setMsg(jsonObject.getString("msg"));
+            }
+        }else{
+            tableDataInfo.setCode(500);
+            tableDataInfo.setMsg("查询aiSIP外呼未绑定分机分页失败:接口返回为空");
+        }
+        return tableDataInfo;
+    }
+
+    @Override
+    public AjaxResult companyBindExtNum(String companyName,String sipExtNumIds) {
+        Map<String, String> param = new HashMap<>();
+        param.put("companyName", manualGatewayPrefix + "_"+companyPrefix+"_"+companyName);
+        param.put("sipExtNumIds", sipExtNumIds);
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_UN_BIND_EXTNUM_COMPANY_API, JSONObject.toJSONString(param));
+        String msg;
+        if (StringUtils.isNotBlank(result)) {
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if (code == 0) {
+                return JSONObject.parseObject(result, AjaxResult.class);
+            } else {
+                msg = jsonObject.getString("msg");
+            }
+        } else {
+            msg = "公司绑定分机号接口失败:接口返回为空";
+        }
+        return AjaxResult.error(msg);
+    }
+
+    @Override
+    public TableDataInfo getSipExtDetail(CcExtNumVo ccExtNum) {
+        // 保存原始的sipExtNumList用于后续过滤,并转换为Set提升查询性能
+        List<Long> sipExtNumList = ccExtNum.getSipExtNumList();
+        Set<Long> extNumSet = (sipExtNumList != null && !sipExtNumList.isEmpty())
+                ? new HashSet<>(sipExtNumList)
+                : null;
+
+        // 如果有userCode,查询时不传sipExtNumList
+        if(StringUtils.isNotBlank(ccExtNum.getUserCode())){
+            ccExtNum.setSipExtNumList(null);
+        }
+
+        // 调用远程接口查询
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_EXTNUM_PAGE_API, JSONObject.toJSONString(ccExtNum));
+
+        // 处理接口返回为空的情况
+        if (StringUtils.isBlank(result)) {
+            return createErrorResult("查询分机详细信息接口失败:接口返回为空");
+        }
+
+        // 解析返回结果
+        JSONObject jsonObject = JSONObject.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+
+        // 处理接口调用失败的情况
+        if (code == null || code != 0) {
+            String msg = jsonObject.getString("msg");
+            return createErrorResult(msg);
+        }
+
+        // 解析成功结果
+        TableDataInfo dataInfo = JSONObject.parseObject(result, TableDataInfo.class);
+
+        // 如果有userCode且提供了分机号列表,则进行过滤
+        if(StringUtils.isNotBlank(ccExtNum.getUserCode()) && extNumSet != null && !extNumSet.isEmpty()){
+            List<?> rows = dataInfo.getRows();
+            if(rows != null && !rows.isEmpty()){
+                // 使用Stream过滤出属于sipExtNumList的数据
+                List<Object> filteredRows = rows.stream()
+                        .filter(row -> {
+                            Long extNum = extractExtNum(row);
+                            return extNum != null && extNumSet.contains(extNum);
+                        })
+                        .collect(Collectors.toList());
+
+                // 重新组装数据
+                dataInfo.setRows(filteredRows);
+                dataInfo.setTotal(filteredRows.size());
+            }
+        }
+
+        return dataInfo;
+    }
+
+    @Override
+    public AjaxResult getCompanyUnBindExtnum(Long companyId) {
+        String companyName;
+        Company company = companyMapper.selectCompanyById(companyId);
+        if(company != null){
+            companyName = company.getCompanyName();
+        }else{
+            return AjaxResult.error("当前员工未绑定销售公司");
+        }
+        companyName = manualGatewayPrefix + "_"+companyPrefix+"_"+companyName;
+        CcExtNumVo ccExtNum = new CcExtNumVo();
+        ccExtNum.setUserCode(companyName);
+        // 调用远程接口查询
+        String result = RemoteCommon.sendPost(RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.QUERY_COMPANY_UN_BIND_EXTNUM_API, JSONObject.toJSONString(ccExtNum));
+        String msg;
+        if (StringUtils.isNotBlank(result)) {
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            Integer code = jsonObject.getInteger("code");
+            if (code == 0) {
+                return JSONObject.parseObject(result, AjaxResult.class);
+            } else {
+                msg= jsonObject.getString("msg");
+            }
+        } else {
+            msg= "查询公司分机号接口失败:接口返回为空";
+        }
+        return AjaxResult.error(msg);
+    }
+    @Override
+    public AjaxResult companyUnbindExtNum(List<Company> companyList) {
+        if (CollectionUtils.isEmpty(companyList)) {
+            log.error("批量解绑分机:公司列表为空");
+            return AjaxResult.error("公司列表不能为空");
+        }
+
+        List<Map<String, Object>> successResults = new ArrayList<>();
+        List<String> failedCompanies = new ArrayList<>();
+        int totalExtNumCount = 0;
+
+        log.info("开始批量解绑分机,共 {} 家公司", companyList.size());
+
+        for (Company company : companyList) {
+            String companyName = company.getCompanyName();
+            Long companyId = company.getCompanyId();
+
+            try {
+                // 跳过未绑定分机的公司
+                if (StringUtils.isBlank(company.getSipExtNumIds())) {
+                    log.info("公司 [{}] (ID:{}) 未绑定分机号,跳过", companyName, companyId);
+                    continue;
+                }
+
+                // 解析分机号集合
+                Set<Long> extNumSet = parseExtNumIds(company.getSipExtNumIds());
+                if (extNumSet.isEmpty()) {
+                    log.info("公司 [{}] (ID:{}) 没有有效的分机号", companyName, companyId);
+                    failedCompanies.add(companyName + "(无有效分机号)");
+                    continue;
+                }
+
+                log.info("处理公司 [{}] (ID:{}),待解绑分机数: {}", companyName, companyId, extNumSet.size());
+
+                // 调用远程接口释放分机
+                String result = RemoteCommon.sendPost(
+                        RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CONPANY_BATCH_UNBING_EXTNUM_API,
+                        JSONObject.toJSONString(extNumSet)
+                );
+
+                if (StringUtils.isBlank(result)) {
+                    log.info("公司 [{}] (ID:{}) 解绑分机失败:远程接口返回为空", companyName, companyId);
+                    failedCompanies.add(companyName + "(接口返回为空)");
+                    continue;
+                }
+
+                JSONObject jsonObject = JSONObject.parseObject(result);
+                Integer code = jsonObject.getInteger("code");
+
+                if (code == null || code != 0) {
+                    String errorMsg = jsonObject.getString("msg");
+                    log.info("公司 [{}] (ID:{}) 解绑分机失败:{}", companyName, companyId, errorMsg);
+                    failedCompanies.add(companyName + "(" + errorMsg + ")");
+                    continue;
+                }
+
+                // 处理销售坐席解绑
+                List<Long> companyUserExtNums = baseMapper.selectByExtNumList(extNumSet);
+                if (!CollectionUtils.isEmpty(companyUserExtNums)) {
+                    log.info("公司 [{}] (ID:{}) 发现 {} 个销售绑定的分机,开始清理", companyName, companyId, companyUserExtNums.size());
+                    handleSalesUnbind(new HashSet<>(companyUserExtNums));
+                }
+
+                // 更新公司分机号信息
+                AjaxResult updateResult = updateCompanyExtNums(companyId, company.getSipExtNumIds(), extNumSet);
+                Integer updateCode = (Integer) updateResult.get(AjaxResult.CODE_TAG);
+
+                if (updateCode != null && updateCode == 0) {
+                    Map<String, Object> successInfo = new HashMap<>();
+                    successInfo.put("companyName", companyName);
+                    successInfo.put("companyId", companyId);
+                    successInfo.put("removedCount", extNumSet.size());
+                    successResults.add(successInfo);
+                    totalExtNumCount += extNumSet.size();
+                    log.info("公司 [{}] (ID:{}) 解绑成功,移除 {} 个分机", companyName, companyId, extNumSet.size());
+                } else {
+                    String msg = (String) updateResult.get(AjaxResult.MSG_TAG);
+                    log.info("公司 [{}] (ID:{}) 更新数据库失败:{}", companyName, companyId, msg);
+                    failedCompanies.add(companyName + "(更新数据库失败)");
+                }
+
+            } catch (Exception e) {
+                log.info("公司 [{}] (ID:{}) 解绑分机时发生异常", companyName, companyId, e);
+                failedCompanies.add(companyName + "(异常: " + e.getMessage() + ")");
             }
         }
-        return AjaxResult.error();
+
+        // 构建返回结果
+        Map<String, Object> resultMap = new HashMap<>();
+        resultMap.put("successResults", successResults);
+        resultMap.put("failedCompanies", failedCompanies);
+        resultMap.put("totalProcessed", companyList.size());
+        resultMap.put("successCount", successResults.size());
+        resultMap.put("failedCount", failedCompanies.size());
+        resultMap.put("totalRemovedExtNums", totalExtNumCount);
+
+        String summary = String.format("批量解绑完成:总计%d家,成功%d家(共移除%d个分机),失败%d家",
+                companyList.size(), successResults.size(), totalExtNumCount, failedCompanies.size());
+        log.info(summary);
+
+        return failedCompanies.isEmpty()
+                ? AjaxResult.success("所有公司解绑成功", resultMap)
+                : AjaxResult.success(summary, resultMap);
+    }
+
+    @Override
+    public AjaxResult companyBatchUnbindSipExt(Long companyId, List<CcExtNumVo> ccExtNums) {
+        // 参数校验
+        if (companyId == null) {
+            log.error("批量解绑分机:公司ID为空");
+            return AjaxResult.error("公司ID不能为空");
+        }
+        if (CollectionUtils.isEmpty(ccExtNums)) {
+            log.error("批量解绑分机:公司ID={},分机列表为空", companyId);
+            return AjaxResult.error("分机列表不能为空");
+        }
+
+        log.info("开始批量解绑分机:公司ID={},待处理分机数={}", companyId, ccExtNums.size());
+
+        // 获取公司信息并验证
+        Company company = companyMapper.selectCompanyById(companyId);
+        if (company == null) {
+            log.error("批量解绑分机失败:公司ID={} 不存在", companyId);
+            return AjaxResult.error("公司不存在");
+        }
+        if (StringUtils.isBlank(company.getSipExtNumIds())) {
+            log.error("批量解绑分机失败:公司 [{}] (ID:{}) 未绑定分机", company.getCompanyName(), companyId);
+            return AjaxResult.error("公司未绑定分机");
+        }
+
+        // 收集所有待解绑的分机号(去重)
+        Set<Long> extNumSet = ccExtNums.stream()
+                .filter(vo -> vo != null && vo.getExtNum() != null)
+                .map(CcExtNumVo::getExtNum)
+                .collect(Collectors.toSet());
+
+        if (extNumSet.isEmpty()) {
+            log.error("批量解绑分机:公司 [{}] (ID:{}) 没有有效的分机号", company.getCompanyName(), companyId);
+            return AjaxResult.error("没有有效的分机号");
+        }
+
+        log.info("公司 [{}] (ID:{}) 待解绑分机数:{},分机列表:{}",
+                company.getCompanyName(), companyId, extNumSet.size(), extNumSet);
+
+        // 调用远程接口释放公司分机
+        String result = RemoteCommon.sendPost(
+                RemoteCommon.REMOTE_ADDERSS_PREFIX + RemoteCommon.CONPANY_BATCH_UNBING_EXTNUM_API,
+                JSONObject.toJSONString(extNumSet)
+        );
+
+        if (StringUtils.isBlank(result)) {
+            log.error("公司 [{}] (ID:{}) 解绑分机失败:远程接口返回为空", company.getCompanyName(), companyId);
+            return AjaxResult.error("解绑分机失败:接口返回为空");
+        }
+
+        JSONObject jsonObject = JSONObject.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+
+        if (code == null || code != 0) {
+            String errorMsg = jsonObject.getString("msg");
+            log.error("公司 [{}] (ID:{}) 解绑分机失败:{}", company.getCompanyName(), companyId, errorMsg);
+            return AjaxResult.error("解绑分机失败:" + errorMsg);
+        }
+
+        log.info("公司 [{}] (ID:{}) 远程接口解绑成功", company.getCompanyName(), companyId);
+
+        // 处理销售坐席解绑
+        Set<Long> salesExtNums = ccExtNums.stream()
+                .filter(vo -> vo != null && StringUtils.isNotBlank(vo.getUserCode()))
+                .map(CcExtNumVo::getExtNum)
+                .collect(Collectors.toSet());
+
+        if (!salesExtNums.isEmpty()) {
+            log.info("公司 [{}] (ID:{}) 发现 {} 个销售绑定的分机,开始清理",
+                    company.getCompanyName(), companyId, salesExtNums.size());
+            handleSalesUnbind(salesExtNums);
+        }
+
+        // 更新公司分机号信息
+        AjaxResult updateResult = updateCompanyExtNums(companyId, company.getSipExtNumIds(), extNumSet);
+        Integer updateCode = (Integer) updateResult.get(AjaxResult.CODE_TAG);
+
+        if (updateCode != null && updateCode == 200) {
+            log.info("公司 [{}] (ID:{}) 批量解绑成功,共移除 {} 个分机",
+                    company.getCompanyName(), companyId, extNumSet.size());
+            return AjaxResult.success("批量解绑成功,共移除 " + extNumSet.size() + " 个分机");
+        } else {
+            String msg = (String) updateResult.get(AjaxResult.MSG_TAG);
+            log.info("公司 [{}] (ID:{}) 更新数据库失败:{}", company.getCompanyName(), companyId, msg);
+            return AjaxResult.error("更新分机信息失败:" + msg);
+        }
+    }
+
+    /**
+     * 处理销售坐席解绑
+     */
+    private void handleSalesUnbind(Set<Long> salesExtNums) {
+        // 查询销售绑定的坐席ID列表
+        List<Long> aiSipCallUserIds = baseMapper.selectByExtNumList(salesExtNums);
+        if (CollectionUtils.isEmpty(aiSipCallUserIds)) {
+            log.error("未找到销售绑定的分机号对应的坐席id");
+            return;
+        }
+
+        // 清空companyUser的坐席字段
+        int cleanCount = companyUserMapper.cleanBindAiSipCallUserId(aiSipCallUserIds);
+        if (cleanCount <= 0) {
+            log.error("清空销售坐席绑定关系失败");
+            return;
+        }
+
+        // 删除销售坐席
+        Long[] userIdsArray = aiSipCallUserIds.toArray(new Long[0]);
+        int deleteCount = baseMapper.deleteAiSipCallUserByUserIds(userIdsArray);
+        if (deleteCount <= 0) {
+            log.error("删除销售坐席失败");
+        }
+    }
+
+    /**
+     * 更新公司分机号信息
+     */
+    private AjaxResult updateCompanyExtNums(Long companyId, String currentExtNumIds, Set<Long> unbindExtNums) {
+        if (companyId == null) {
+            log.error("更新公司分机号失败:公司ID为空");
+            return AjaxResult.error("公司ID不能为空");
+        }
+        if (StringUtils.isBlank(currentExtNumIds)) {
+            log.error("更新公司分机号:公司ID={} 当前未绑定分机", companyId);
+            return AjaxResult.success("公司未绑定分机,无需更新");
+        }
+        if (CollectionUtils.isEmpty(unbindExtNums)) {
+            log.error("更新公司分机号:公司ID={} 待解绑分机集合为空", companyId);
+            return AjaxResult.success("没有需要解绑的分机");
+        }
+
+        // 解析当前公司分机号
+        Set<Long> companyExtNumSet = parseExtNumIds(currentExtNumIds);
+
+        // 过滤出剩余的分机号并排序
+        List<Long> remainingExtNums = companyExtNumSet.stream()
+                .filter(extNum -> !unbindExtNums.contains(extNum))
+                .sorted()
+                .collect(Collectors.toList());
+
+        // 构建更新后的分机号字符串
+        String remainingExtNumIds = remainingExtNums.isEmpty()
+                ? null
+                : remainingExtNums.stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(","));
+
+        log.info("公司ID={} 更新分机号:原{}个,解绑{}个,剩余{}个",
+                companyId, companyExtNumSet.size(), unbindExtNums.size(), remainingExtNums.size());
+
+        // 更新数据库
+        int updateCount = companyMapper.updateCompanyBindSipExtNum(companyId, remainingExtNumIds);
+        if (updateCount > 0) {
+            log.info("公司ID={} 更新分机号成功,剩余分机:{}", companyId, remainingExtNumIds);
+            return AjaxResult.success("批量解绑公司分机成功");
+        } else {
+            log.info("公司ID={} 更新分机号失败,SQL影响行数为0", companyId);
+            return AjaxResult.error("批量解绑公司分机号失败");
+        }
     }
 
+
+
+
+
+    /**
+     * 解析分机号ID字符串为Set集合
+     */
+    private Set<Long> parseExtNumIds(String extNumIds) {
+        if (StringUtils.isBlank(extNumIds)) {
+            return Collections.emptySet();
+        }
+        return Arrays.stream(extNumIds.split(","))
+                .filter(StringUtils::isNotBlank)
+                .map(String::trim)
+                .map(Long::parseLong)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * 从行对象中提取分机号
+     */
+    private Long extractExtNum(Object row) {
+        if(row instanceof JSONObject){
+            return ((JSONObject) row).getLong("extNum");
+        } else if(row instanceof CcExtNumVo){
+            return ((CcExtNumVo) row).getExtNum();
+        }
+        return null;
+    }
+
+    /**
+     * 创建错误结果
+     */
+    private TableDataInfo createErrorResult(String msg) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setTotal(0);
+        tableDataInfo.setCode(500);
+        tableDataInfo.setMsg(msg);
+        return tableDataInfo;
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/aiSipCall/vo/CcExtNumVo.java

@@ -3,6 +3,7 @@ package com.fs.aiSipCall.vo;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @Author:peicj
@@ -27,4 +28,11 @@ public class CcExtNumVo implements Serializable {
 
     private Long pageNum;
     private Long pageSize;
+
+    /** 分机起 */
+    private Long startExtNum;
+    /** 分机止 */
+    private Long endExtNum;
+    /** 分机号集合 */
+    private List<Long> sipExtNumList;
 }

+ 11 - 0
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -143,4 +143,15 @@ public class Company extends BaseEntity
 
     @TableField(exist = false)
     private List<Long> ids;
+
+    /**
+     * 绑定新sip分机
+     */
+    private String sipExtNumIds;
+    /**
+     * 绑定旧sip分机
+     */
+    private String originalSipExtNumIds;
+    /** IDs */
+    private Long[] companyIds;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -280,4 +280,7 @@ public interface CompanyMapper
 
     @Select("select GROUP_CONCAT(gateway_id) from company_bind_gateway where company_id = #{companyId}")
     String getGateWayList(@Param("companyId") Long companyId);
+
+    @Update("update company set sip_ext_num_ids = #{remainingExtNumIds} where company_id = #{companyId}")
+    int updateCompanyBindSipExtNum(@Param("companyId") Long companyId,@Param("remainingExtNumIds") String remainingExtNumIds);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -385,4 +385,6 @@ public interface CompanyUserMapper
             "OR r.role_name LIKE '%组长%' OR r.role_name LIKE '%管理员%' " +
             "OR u.user_type IN ('00', '02'))")
     List<Long> selectLeaderAndAboveUserIdsByCompanyId(@Param("companyId") Long companyId);
+
+    int cleanBindAiSipCallUserId(@Param("aiSipCallUserIds") List<Long> aiSipCallUserIds);
 }

+ 55 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -9,7 +9,9 @@ import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.aiSipCall.service.IAiSipCallUserService;
 import com.fs.common.constant.FsConstants;
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
@@ -130,6 +132,8 @@ public class CompanyServiceImpl implements ICompanyService
     private LiveOrderMapper liveOrderMapper;
     @Autowired
     private IFsDoctorService doctorService;
+    @Autowired
+    private IAiSipCallUserService aiSipCallUserService;
 
 
     @Override
@@ -268,6 +272,17 @@ public class CompanyServiceImpl implements ICompanyService
             return R.error("管理员帐号已存在");
         }
         company.setCreateTime(DateUtils.getNowDate());
+
+        //处理sip分机号
+        if(StringUtils.isNotEmpty(company.getSipExtNumIds())){
+            AjaxResult result = aiSipCallUserService.companyBindExtNum(company.getCompanyName(),company.getSipExtNumIds());
+            Integer code = (Integer) result.get(AjaxResult.CODE_TAG);
+            if(code != null && code != 0){
+                String msg = (String) result.get(AjaxResult.MSG_TAG);
+                throw new RuntimeException(StringUtils.isNotEmpty( msg) ? msg : "新增SIP分机号绑定失败");
+            }
+        }
+
         if(companyMapper.insertCompany(company)>0){
             //写入帐号信息
             //创建部门
@@ -394,6 +409,39 @@ public class CompanyServiceImpl implements ICompanyService
         if(company.isUpdateMiniApp()){
             bindMiniApp(company);
         }
+
+        //处理sip分机号
+        if(StringUtils.isNotEmpty(company.getSipExtNumIds())){
+            AjaxResult result;
+            if(StringUtils.isNotEmpty(company.getOriginalSipExtNumIds())){
+                //如果有旧值比较
+                // 将原始分机号和新分机号都转换为Set进行比较
+                Set<String> originalSet = new HashSet<>(Arrays.asList(company.getOriginalSipExtNumIds().split(",")));
+                Set<String> newSet = new HashSet<>(Arrays.asList(company.getSipExtNumIds().split(",")));
+
+                // 计算需要新增的分机号(在新集合中但不在原始集合中的)
+                Set<String> toAddSet = new HashSet<>(newSet);
+                toAddSet.removeAll(originalSet);
+
+                // 只绑定新增的分机号(去重后的数据)
+                if (!toAddSet.isEmpty()) {
+                    String toAddStr = String.join(",", toAddSet);
+                    result = aiSipCallUserService.companyBindExtNum(company.getCompanyName(), toAddStr);
+                } else {
+                    // 如果没有需要新增的,创建一个成功的结果
+                    result = new AjaxResult(0, "操作成功", null);
+                }
+            }else{
+                //否则直接新增
+                result = aiSipCallUserService.companyBindExtNum(company.getCompanyName(), company.getSipExtNumIds());
+            }
+            Integer code = (Integer) result.get(AjaxResult.CODE_TAG);
+            if(code != null && code != 0){
+                String msg = (String) result.get(AjaxResult.MSG_TAG);
+                throw new RuntimeException(StringUtils.isNotEmpty( msg) ? msg : "修改SIP分机号绑定失败");
+            }
+        }
+
         return companyMapper.updateCompany(company);
     }
     // 绑定小程序
@@ -416,6 +464,13 @@ public class CompanyServiceImpl implements ICompanyService
     @Override
     public int deleteCompanyByIds(Long[] companyIds)
     {
+        //处理绑定的sip分机号
+        Company queryCompany = new Company();
+        queryCompany.setCompanyIds(companyIds);
+        List<Company> companyList = companyMapper.selectCompanyList(queryCompany);
+        if(companyList != null && !companyList.isEmpty()){
+            aiSipCallUserService.companyUnbindExtNum(companyList);
+        }
         int i = companyMapper.deleteCompanyByIds(companyIds);
         //删除对应部门
         if (i > 0){

+ 17 - 0
fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java

@@ -77,4 +77,21 @@ public class PhoneUtil {
 
         return text;
     }
+
+    private static final String PUBLIC_KEY_STR = "hdt112233";
+
+    /**
+     * XOR 加密(返回 Base64)(对于解析后的电话明文加密,适用于前端解密)
+     * @param data  明文电话
+     * @return String 密文
+     */
+    public static String xorEncrypt(String data) {
+        byte[] dataBytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+        byte[] keyBytes = PUBLIC_KEY_STR.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+        byte[] result = new byte[dataBytes.length];
+        for (int i = 0; i < dataBytes.length; i++) {
+            result[i] = (byte) (dataBytes[i] ^ keyBytes[i % keyBytes.length]);
+        }
+        return Base64.getEncoder().encodeToString(result);
+    }
 }

+ 24 - 0
fs-service/src/main/java/com/fs/his/utils/RandomUtil.java

@@ -0,0 +1,24 @@
+package com.fs.his.utils;
+
+import java.security.SecureRandom;
+
+/**
+ * @Author:peicj
+ * @Description: 生成随机数
+ * @Date:2026/4/14 9:19
+ */
+public class RandomUtil {
+
+    private static final String CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    private static final int LENGTH = 6;
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    public static String generateRandomCode() {
+        StringBuilder sb = new StringBuilder(LENGTH);
+        for (int i = 0; i < LENGTH; i++) {
+            int index = RANDOM.nextInt(CHAR_SET.length());
+            sb.append(CHAR_SET.charAt(index));
+        }
+        return sb.toString();
+    }
+}

+ 18 - 0
fs-service/src/main/resources/application-config-dev.yml

@@ -133,3 +133,21 @@ jst:
 feishu:
   appId: "cli_a92ff75ce0785bc0"
   appSecret: "DCQPW11pTY48zSdwAnqPwhJfihe0kP8G"
+
+# sip外呼配置
+sip:
+  call:
+    #是否有自己线路
+    myGateway: false
+    # 自动线路前缀
+    automaticGatewayPrefix: HDT
+    # 手动线路前缀
+    manualGatewayPrefix: hdt
+    # 公共线路前缀
+    publicGatewayPrefix: outbound
+    #总后台用户前缀,用于绑定分机
+    adminUserPrefix: ad
+    #销售公司前缀,用于绑定分机
+    companyPrefix: cp
+    #销售前缀,用于绑定分机
+    companyUserPrefix: cu

+ 18 - 0
fs-service/src/main/resources/application-config-druid-hdt.yml

@@ -106,3 +106,21 @@ wx_miniapp_temp:
 feishu:
   appId: "cli_a92ff75ce0785bc0"
   appSecret: "DCQPW11pTY48zSdwAnqPwhJfihe0kP8G"
+
+# sip外呼配置
+sip:
+  call:
+    #是否有自己线路
+    myGateway: true
+    # 自动线路前缀
+    automaticGatewayPrefix: HDT
+    # 手动线路前缀
+    manualGatewayPrefix: hdt
+    # 公共线路前缀
+    publicGatewayPrefix: outbound
+    #总后台用户前缀,用于绑定分机
+    adminUserPrefix: ad
+    #销售公司前缀,用于绑定分机
+    companyPrefix: cp
+    #销售前缀,用于绑定分机
+    companyUserPrefix: cu

+ 40 - 11
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallOutboundCdrMapper.xml

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.aiSipCall.mapper.AiSipCallOutboundCdrMapper">
-    
+
     <resultMap type="AiSipCallOutboundCdr" id="AiSipCallOutboundCdrResult">
         <result property="id"    column="id"    />
         <result property="caller"    column="caller"    />
@@ -19,15 +19,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="recordFilename"    column="record_filename"    />
         <result property="chatContent"    column="chat_content"    />
         <result property="hangupCause"    column="hangup_cause"    />
+        <result property="sourceType"    column="source_type"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyUserName"    column="company_user_name"    />
+        <result property="sysUserId"    column="sys_user_id"    />
+        <result property="sysUserName"    column="sys_user_name"    />
+        <result property="status"    column="status"    />
     </resultMap>
 
     <sql id="selectAiSipCallOutboundCdrVo">
-        select id, caller, opnum, callee, start_time, answered_time, end_time, uuid, call_type, time_len, time_len_valid, record_filename, chat_content, hangup_cause from ai_sip_call_outbound_cdr
+        select * from ai_sip_call_outbound_cdr
     </sql>
 
     <select id="selectAiSipCallOutboundCdrList" parameterType="AiSipCallOutboundCdr" resultMap="AiSipCallOutboundCdrResult">
         <include refid="selectAiSipCallOutboundCdrVo"/>
-        <where>  
+        <where>
             <if test="caller != null  and caller != ''"> and caller = #{caller}</if>
             <if test="opnum != null  and opnum != ''"> and opnum = #{opnum}</if>
             <if test="callee != null  and callee != ''"> and callee = #{callee}</if>
@@ -49,15 +56,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="answeredTimeEndLong != null  and answeredTimeEndLong != ''"> and answered_time &lt;= #{answeredTimeEndLong}</if>
             <if test="endTimeStartLong != null  and endTimeStartLong != ''"> and end_time &gt;= #{endTimeStartLong}</if>
             <if test="endTimeEndLong != null  and endTimeEndLong != ''"> and end_time &lt;= #{endTimeEndLong}</if>
+            <if test="sourceType != null  and sourceType != ''"> and source_type = #{sourceType}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="companyUserName != null  and companyUserName != ''"> and company_user_name = #{companyUserName}</if>
+            <if test="sysUserId != null"> and sys_user_id = #{sysUserId}</if>
+            <if test="sysUserName != null  and sysUserName != ''"> and sys_user_name = #{sysUserName}</if>
+            <if test="status != null"> and status = #{status}</if>
         </where>
-         order by end_time desc
+        order by start_time desc
     </select>
-    
+
     <select id="selectAiSipCallOutboundCdrById" parameterType="String" resultMap="AiSipCallOutboundCdrResult">
         <include refid="selectAiSipCallOutboundCdrVo"/>
         where id = #{id}
     </select>
-        
+
     <insert id="insertAiSipCallOutboundCdr" parameterType="AiSipCallOutboundCdr">
         insert into ai_sip_call_outbound_cdr
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -75,7 +89,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">record_filename,</if>
             <if test="chatContent != null">chat_content,</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause,</if>
-         </trim>
+            <if test="sourceType != null">source_type,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyUserName != null">company_user_name,</if>
+            <if test="sysUserId != null">sys_user_id,</if>
+            <if test="sysUserName != null">sys_user_name,</if>
+            <if test="status != null">status,</if>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="caller != null and caller != ''">#{caller},</if>
@@ -91,7 +112,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">#{recordFilename},</if>
             <if test="chatContent != null">#{chatContent},</if>
             <if test="hangupCause != null and hangupCause != ''">#{hangupCause},</if>
-         </trim>
+            <if test="sourceType != null">#{sourceType},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyUserName != null and companyUserName != ''">#{companyUserName},</if>
+            <if test="sysUserId != null">#{sysUserId},</if>
+            <if test="sysUserName != null">#{sysUserName},</if>
+            <if test="status != null">#{status},</if>
+        </trim>
     </insert>
 
     <update id="updateAiSipCallOutboundCdr" parameterType="AiSipCallOutboundCdr">
@@ -110,6 +138,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recordFilename != null and recordFilename != ''">record_filename = #{recordFilename},</if>
             <if test="chatContent != null">chat_content = #{chatContent},</if>
             <if test="hangupCause != null and hangupCause != ''">hangup_cause = #{hangupCause},</if>
+            <if test="status != null">status = #{status},</if>
         </trim>
         where id = #{id}
     </update>
@@ -119,7 +148,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteAiSipCallOutboundCdrByIds" parameterType="String">
-        delete from ai_sip_call_outbound_cdr where id in 
+        delete from ai_sip_call_outbound_cdr where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>

+ 31 - 2
fs-service/src/main/resources/mapper/aiSipCall/AiSipCallUserMapper.xml

@@ -31,10 +31,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyId"    column="company_id"    />
         <result property="companyUserId"    column="company_user_id"    />
         <result property="gatewayIds"    column="gateway_ids"    />
+        <result property="extPass"    column="ext_pass"    />
+        <result property="userSource"    column="user_source"    />
+        <result property="sysUserId"    column="sys_user_id"    />
+        <result property="sysUserName"    column="sys_user_name"    />
     </resultMap>
 
     <sql id="selectAiSipCallUserVo">
-        select user_id, dept_id, login_name, user_name, user_type, email, phonenumber, sex, avatar, password, salt, status, del_flag, login_ip, login_date, pwd_update_date, remark, logo, ext_num, create_by, create_time, update_by, update_time, company_id,company_user_id,gateway_ids from ai_sip_call_user
+        select * from ai_sip_call_user
     </sql>
 
     <select id="selectAiSipCallUserList" parameterType="AiSipCallUser" resultMap="AiSipCallUserResult">
@@ -58,14 +62,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="extNum != null  and extNum != ''"> and ext_num = #{extNum}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="extPass != null "> and ext_pass = #{extPass}</if>
+            <if test="userSource != null  and userSource != ''"> and user_source = #{userSource}</if>
+            <if test="sysUserId != null "> and sys_user_id = #{sysUserId}</if>
+            <if test="sysUserName != null  and sysUserName != ''"> and sys_user_name like concat('%', #{sysUserName}, '%')</if>
         </where>
+        order by create_time desc
     </select>
     
     <select id="selectAiSipCallUserByUserId" parameterType="Long" resultMap="AiSipCallUserResult">
         <include refid="selectAiSipCallUserVo"/>
         where user_id = #{userId}
     </select>
-        
+
     <insert id="insertAiSipCallUser" parameterType="AiSipCallUser">
         insert into ai_sip_call_user
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -95,6 +104,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="extNum != null and extNum != ''">ext_num,</if>
             <if test="companyId != null ">company_id,</if>
             <if test="gatewayIds != null and gatewayIds != ''">gateway_ids,</if>
+            <if test="extPass != null and extPass != ''">ext_pass,</if>
+            <if test="userSource != null and userSource != ''">user_source,</if>
+            <if test="sysUserId != null">sys_user_id,</if>
+            <if test="sysUserName != null and sysUserName != ''">sys_user_name,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -123,6 +136,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="extNum != null and extNum != ''">#{extNum},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="gatewayIds != null and gatewayIds != ''">#{gatewayIds},</if>
+            <if test="extPass != null and extPass != ''">#{extPass},</if>
+            <if test="userSource != null and userSource != ''">#{userSource},</if>
+            <if test="sysUserId != null">#{sysUserId},</if>
+            <if test="sysUserName != null and sysUserName != ''">#{sysUserName},</if>
         </trim>
     </insert>
 
@@ -154,6 +171,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="extNum != null and extNum != ''">ext_num = #{extNum},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="gatewayIds != null and gatewayIds != ''">gateway_ids = #{gatewayIds},</if>
+            <if test="extPass != null">ext_pass = #{extPass},</if>
+            <if test="userSource != null">user_source = #{userSource},</if>
+            <if test="sysUserId != null">sys_user_id = #{sysUserId},</if>
+            <if test="sysUserName != null">sys_user_name = #{sysUserName},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -168,4 +189,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{userId}
         </foreach>
     </delete>
+
+    <select id="selectByExtNumList" resultType="java.lang.Long">
+        select user_id from ai_sip_call_user
+        where user_source = '0' and ext_num in
+        <foreach collection="extNumList" item="extNum" open="(" separator="," close=")">
+            #{extNum}
+        </foreach>
+    </select>
 </mapper>

+ 12 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -43,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="maxPadNum"    column="max_pad_num"    />
         <result property="deptId"   column="dept_id" />
         <result property="openRedPacket"   column="open_red_packet" />
+        <result property="sipExtNumIds"   column="sip_ext_num_ids" />
     </resultMap>
 
     <sql id="selectCompanyVo">
@@ -66,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseMiniAppId != null "> and course_mini_app_id = #{courseMiniAppId}</if>
             <if test="deptId != null">and dept_id = #{deptId}</if>
             <if test="customMiniAppId != null "> and custom_mini_app_id = #{customMiniAppId}</if>
+            <if test="sipExtNumIds != null  and sipExtNumIds != ''"> and sip_ext_num_ids = #{sipExtNumIds}</if>
         </where>
     </select>
 
@@ -124,6 +126,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxPadNum != null">max_pad_num,</if>
             <if test="deptId != null">dept_id,</if>
             <if test="openRedPacket != null">open_red_packet,</if>
+            <if test="sipExtNumIds != null and sipExtNumIds != ''">sip_ext_num_ids,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyName != null">#{companyName},</if>
@@ -161,6 +164,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxPadNum != null">#{maxPadNum},</if>
             <if test="deptId != null">#{deptId},</if>
             <if test="openRedPacket != null">#{open_red_packet},</if>
+            <if test="sipExtNumIds != null and sipExtNumIds != ''">#{sipExtNumIds},</if>
          </trim>
     </insert>
 
@@ -205,6 +209,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deptId != null">dept_id = #{deptId},</if>
             <if test="openRedPacket != null">open_red_packet = #{openRedPacket},</if>
             <if test="redPackageMoney != null">red_package_money = #{redPackageMoney},</if>
+            <if test="sipExtNumIds != null">sip_ext_num_ids = #{sipExtNumIds},</if>
         </trim>
         where company_id = #{companyId}
     </update>
@@ -292,4 +297,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+    <select id="selectByIds" resultType="com.fs.his.vo.OptionsVO">
+        select company_id dictValue,company_name dictLabel from company where company_id in
+        <foreach collection="ids" item="companyId" open="(" close=")" separator=",">
+            #{companyId}
+        </foreach>
+    </select>
+
 </mapper>

+ 8 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -816,4 +816,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where user_id = #{companyUserId}
     </update>
 
+    <update id="cleanBindAiSipCallUserId">
+        update company_user set ai_sip_call_user_id = null
+        where ai_sip_call_user_id in
+        <foreach collection="aiSipCallUserIds" item="aiSipCallUserId" open="(" separator="," close=")">
+            #{aiSipCallUserId}
+        </foreach>
+    </update>
+
 </mapper>