Explorar el Código

外呼/侧边栏语音素材/app下载统计

三七 hace 2 semanas
padre
commit
3c4b61e361
Se han modificado 74 ficheros con 4822 adiciones y 22 borrados
  1. 35 0
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  2. 123 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallKnowledgeBaseController.java
  3. 129 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallRoleController.java
  4. 98 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallStaffModelsController.java
  5. 209 0
      fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallUserController.java
  6. 6 1
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  7. 29 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  8. 70 14
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceController.java
  9. 29 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java
  10. 30 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java
  11. 70 0
      fs-service/src/main/java/com/fs/aiCallSop/commonConstant/AiSopCallRemoteUrlConstant.java
  12. 71 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallKnowledgeBase.java
  13. 70 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallLineConfig.java
  14. 63 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallMsgLog.java
  15. 54 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallRole.java
  16. 36 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallStaffModels.java
  17. 121 0
      fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallUser.java
  18. 66 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallKnowledgeBaseMapper.java
  19. 62 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallLineConfigMapper.java
  20. 63 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallMsgLogMapper.java
  21. 62 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallRoleMapper.java
  22. 63 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallStaffModelsMapper.java
  23. 70 0
      fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallUserMapper.java
  24. 68 0
      fs-service/src/main/java/com/fs/aiCallSop/param/AiSopCallKnowledgeBaseRequest.java
  25. 19 0
      fs-service/src/main/java/com/fs/aiCallSop/param/ModelListRequest.java
  26. 55 0
      fs-service/src/main/java/com/fs/aiCallSop/param/ModelListResponse.java
  27. 10 0
      fs-service/src/main/java/com/fs/aiCallSop/param/ModelListResult.java
  28. 34 0
      fs-service/src/main/java/com/fs/aiCallSop/param/QwGroupVoiceMsgDoSendListTVO.java
  29. 18 0
      fs-service/src/main/java/com/fs/aiCallSop/param/SendAiCallSopParamDetailsC.java
  30. 75 0
      fs-service/src/main/java/com/fs/aiCallSop/param/VoicesDictRes.java
  31. 88 0
      fs-service/src/main/java/com/fs/aiCallSop/param/VoicesDictResPage.java
  32. 65 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallKnowledgeBaseService.java
  33. 62 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallLineConfigService.java
  34. 63 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallMsgLogService.java
  35. 62 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallRoleService.java
  36. 62 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallStaffModelsService.java
  37. 74 0
      fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallUserService.java
  38. 236 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallKnowledgeBaseServiceImpl.java
  39. 94 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallLineConfigServiceImpl.java
  40. 91 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallMsgLogServiceImpl.java
  41. 94 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallRoleServiceImpl.java
  42. 91 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallStaffModelsServiceImpl.java
  43. 340 0
      fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallUserServiceImpl.java
  44. 240 0
      fs-service/src/main/java/com/fs/aiCallSop/vo/AiCallSopRepVo.java
  45. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  46. 15 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  47. 7 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  48. 5 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  49. 33 0
      fs-service/src/main/java/com/fs/qw/dto/SalesAppDownloadStatDTO.java
  50. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  51. 2 2
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  52. 51 0
      fs-service/src/main/java/com/fs/qw/param/AppDownloadParam.java
  53. 4 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  54. 5 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  55. 92 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  56. 2 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  57. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  58. 10 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java
  59. 8 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java
  60. 55 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java
  61. 124 0
      fs-service/src/main/resources/db/changelog/table/live_group_type.sql
  62. 107 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallKnowledgeBaseMapper.xml
  63. 131 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallLineConfigMapper.xml
  64. 101 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallMsgLogMapper.xml
  65. 108 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallRoleMapper.xml
  66. 73 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallStaffModelsMapper.xml
  67. 128 0
      fs-service/src/main/resources/mapper/aiCallSop/AiSopCallUserMapper.xml
  68. 12 0
      fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml
  69. 69 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  70. 2 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  71. 1 1
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  72. 5 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  73. 41 0
      fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml
  74. 57 2
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

+ 35 - 0
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -740,6 +740,41 @@ public class CompanyUserController extends AppBaseController {
         }
         return R.ok().put("data", audioVO);
     }
+
+    /**
+     * 当只有user_voice_url时,生成表中对应条的voice_url 无语音席位
+     * @param userVoiceUrl  wav格式的语音文件
+     * @param id            qw_sop_temp_voice的id
+     * @return
+     */
+    @GetMapping("/companyUserVoiceNotSeat")
+    public R companyUserVoiceNotSeat( @RequestParam("id") Long id,@RequestParam("userVoiceUrl") String userVoiceUrl){
+
+        AudioVO audioVO = new AudioVO();
+        Long companyUserId = getCompanyUserId();
+        List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + companyUserId);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> collect = sopTempVoices.stream().map(QwSopTempVoice::getId).collect(Collectors.toList());
+            if (collect.contains(id)){
+                return R.ok().put("code",202).put("msg","该语音已进入转换,请完成后再试。");
+            }
+        }
+
+        QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceByIdAndUserVoiceUrl(id);
+        if(qwSopTempVoice != null && qwSopTempVoice.getId() != null){
+            audioVO = AudioUtils.transferAudioSilkFromUrl(userVoiceUrl,  false);
+            if(audioVO != null && audioVO.getUrl() != null){
+                qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                qwSopTempVoice.setUserVoiceUrl(userVoiceUrl);
+                qwSopTempVoice.setDuration(audioVO.getDuration());
+                qwSopTempVoice.setRecordType(1);
+                voiceService.updateQwSopTempVoice(qwSopTempVoice);
+            }
+        }
+        return R.ok().put("data", audioVO);
+    }
+
+
     @GetMapping("/querySopVoiceList")
     public TableDataInfo querySopVoiceList(@Param("recordType") Integer recordType){
         startPage();

+ 123 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallKnowledgeBaseController.java

@@ -0,0 +1,123 @@
+package com.fs.company.controller.aiSipCall;
+
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import com.fs.aiCallSop.service.IAiSopCallKnowledgeBaseService;
+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.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+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.List;
+
+/**
+ * AI外呼知识库Controller
+ *
+ * @author fs
+ * @date 2026-01-30
+ */
+@RestController
+@RequestMapping("/company/aiSopCallKnowledgeBase")
+public class AiSopCallKnowledgeBaseController extends BaseController
+{
+    @Autowired
+    private IAiSopCallKnowledgeBaseService aiSopCallKnowledgeBaseService;
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询AI外呼知识库列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AiSopCallKnowledgeBase aiSopCallKnowledgeBase)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallKnowledgeBase.setCompanyId(loginUser.getCompany().getCompanyId());
+        aiSopCallKnowledgeBase.setCompanyUserId(loginUser.getUser().getUserId());
+        List<AiSopCallKnowledgeBase> list = aiSopCallKnowledgeBaseService.selectAiSopCallKnowledgeBaseList(aiSopCallKnowledgeBase);
+        return getDataTable(list);
+    }
+    /**
+     * 查询AI外呼知识库名称和id
+     */
+    @GetMapping("/idAndNameList")
+    public R idAndNameList()
+    {
+        return aiSopCallKnowledgeBaseService.idAndNameList();
+    }
+
+    /**
+     * 导出AI外呼知识库列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:export')")
+    @Log(title = "AI外呼知识库", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AiSopCallKnowledgeBase aiSopCallKnowledgeBase)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallKnowledgeBase.setCompanyId(loginUser.getCompany().getCompanyId());
+        aiSopCallKnowledgeBase.setCompanyUserId(loginUser.getUser().getUserId());
+        List<AiSopCallKnowledgeBase> list = aiSopCallKnowledgeBaseService.selectAiSopCallKnowledgeBaseList(aiSopCallKnowledgeBase);
+        ExcelUtil<AiSopCallKnowledgeBase> util = new ExcelUtil<>(AiSopCallKnowledgeBase.class);
+        return util.exportExcel(list, "AI外呼知识库数据");
+    }
+
+    /**
+     * 获取AI外呼知识库详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(aiSopCallKnowledgeBaseService.selectAiSopCallKnowledgeBaseById(id));
+    }
+
+    /**
+     * 新增AI外呼知识库
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:add')")
+    @Log(title = "AI外呼知识库", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody AiSopCallKnowledgeBase aiSopCallKnowledgeBase)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallKnowledgeBase.setCompanyId(loginUser.getCompany().getCompanyId());
+        aiSopCallKnowledgeBase.setCompanyUserId(loginUser.getUser().getUserId());
+        aiSopCallKnowledgeBase.setCreateBy(loginUser.getUser().getNickName());
+        return aiSopCallKnowledgeBaseService.insertAiSopCallKnowledgeBase(aiSopCallKnowledgeBase);
+    }
+
+    /**
+     * 修改AI外呼知识库
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:edit')")
+    @Log(title = "AI外呼知识库", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R edit(@RequestBody AiSopCallKnowledgeBase aiSopCallKnowledgeBase)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallKnowledgeBase.setUpdateBy(loginUser.getUser().getNickName());
+        return aiSopCallKnowledgeBaseService.updateAiSopCallKnowledgeBase(aiSopCallKnowledgeBase);
+    }
+
+    /**
+     * 删除AI外呼知识库
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiSopCallKnowledgeBase:remove')")
+    @Log(title = "AI外呼知识库", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public R remove(@PathVariable Long[] ids)
+    {
+        return aiSopCallKnowledgeBaseService.deleteAiSopCallKnowledgeBaseByIds(ids);
+    }
+}

+ 129 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallRoleController.java

@@ -0,0 +1,129 @@
+package com.fs.company.controller.aiSipCall;
+
+import com.fs.aiCallSop.domain.AiSopCallRole;
+import com.fs.aiCallSop.service.IAiSopCallRoleService;
+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.DateUtils;
+import com.fs.common.utils.ServletUtils;
+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.List;
+
+/**
+ * AI外呼角色Controller
+ *
+ * @author fs
+ * @date 2026-01-14
+ */
+@RestController
+@RequestMapping("/aiSopCall/role")
+public class AiSopCallRoleController extends BaseController
+{
+    @Autowired
+    private IAiSopCallRoleService aiSopCallRoleService;
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 查询AI外呼角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AiSopCallRole aiSopCallRole)
+    {
+        startPage();
+        List<AiSopCallRole> list = aiSopCallRoleService.selectAiSopCallRoleList(aiSopCallRole);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出AI外呼角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:export')")
+    @Log(title = "AI外呼角色导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AiSopCallRole aiSopCallRole)
+    {
+        List<AiSopCallRole> list = aiSopCallRoleService.selectAiSopCallRoleList(aiSopCallRole);
+        ExcelUtil<AiSopCallRole> util = new ExcelUtil<AiSopCallRole>(AiSopCallRole.class);
+        return util.exportExcel(list, "AI外呼角色数据");
+    }
+
+    /**
+     * 获取AI外呼角色详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(aiSopCallRoleService.selectAiSopCallRoleById(id));
+    }
+
+    /**
+     * 新增AI外呼角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:add')")
+    @Log(title = "AI外呼角色新增", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AiSopCallRole aiSopCallRole)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallRole.setCreateBy(loginUser.getUsername());
+        aiSopCallRole.setCreateTime(DateUtils.getNowDate());
+        return toAjax(aiSopCallRoleService.insertAiSopCallRole(aiSopCallRole));
+    }
+
+    /**
+     * 修改AI外呼角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:edit')")
+    @Log(title = "AI外呼角色修改", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AiSopCallRole aiSopCallRole)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        aiSopCallRole.setUpdateBy(loginUser.getUsername());
+        aiSopCallRole.setUpdateTime(DateUtils.getNowDate());
+        return toAjax(aiSopCallRoleService.updateAiSopCallRole(aiSopCallRole));
+    }
+
+    /**
+     * 删除AI外呼角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:remove')")
+    @Log(title = "AI外呼角色删除", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(aiSopCallRoleService.deleteAiSopCallRoleByIds(ids));
+    }
+
+    /**
+     * 复制AI外呼角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCall:role:copyRole')")
+    @Log(title = "AI外呼角色复制", businessType = BusinessType.INSERT)
+    @GetMapping("copyRole/{id}")
+    public AjaxResult copyRole(@PathVariable Long id)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        AiSopCallRole aiSopCallRole = aiSopCallRoleService.selectAiSopCallRoleById(id);
+        if(aiSopCallRole == null){
+            return AjaxResult.error("当前复制角色信息不存在");
+        }
+        aiSopCallRole.setId(null);
+        aiSopCallRole.setCreateTime(DateUtils.getNowDate());
+        aiSopCallRole.setCreateBy(loginUser.getUsername());
+        aiSopCallRole.setUpdateBy(null);
+        aiSopCallRole.setUpdateTime(null);
+        return toAjax(aiSopCallRoleService.insertAiSopCallRole(aiSopCallRole));
+    }
+}

+ 98 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallStaffModelsController.java

@@ -0,0 +1,98 @@
+package com.fs.company.controller.aiSipCall;
+
+
+import com.fs.aiCallSop.domain.AiSopCallStaffModels;
+import com.fs.aiCallSop.service.IAiSopCallStaffModelsService;
+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.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.List;
+
+/**
+ * 外呼模型模型Controller
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@RestController
+@RequestMapping("/aiSopCallStaffModels/aiSopCallStaffModels")
+public class AiSopCallStaffModelsController extends BaseController
+{
+    @Autowired
+    private IAiSopCallStaffModelsService aiSopCallStaffModelsService;
+
+    /**
+     * 查询外呼模型模型列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        startPage();
+        List<AiSopCallStaffModels> list = aiSopCallStaffModelsService.selectAiSopCallStaffModelsList(aiSopCallStaffModels);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出外呼模型模型列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:export')")
+    @Log(title = "外呼模型模型", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        List<AiSopCallStaffModels> list = aiSopCallStaffModelsService.selectAiSopCallStaffModelsList(aiSopCallStaffModels);
+        ExcelUtil<AiSopCallStaffModels> util = new ExcelUtil<AiSopCallStaffModels>(AiSopCallStaffModels.class);
+        return util.exportExcel(list, "外呼模型模型数据");
+    }
+
+    /**
+     * 获取外呼模型模型详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(aiSopCallStaffModelsService.selectAiSopCallStaffModelsById(id));
+    }
+
+    /**
+     * 新增外呼模型模型
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:add')")
+    @Log(title = "外呼模型模型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        return toAjax(aiSopCallStaffModelsService.insertAiSopCallStaffModels(aiSopCallStaffModels));
+    }
+
+    /**
+     * 修改外呼模型模型
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:edit')")
+    @Log(title = "外呼模型模型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        return toAjax(aiSopCallStaffModelsService.updateAiSopCallStaffModels(aiSopCallStaffModels));
+    }
+
+    /**
+     * 删除外呼模型模型
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallStaffModels:aiSopCallStaffModels:remove')")
+    @Log(title = "外呼模型模型", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(aiSopCallStaffModelsService.deleteAiSopCallStaffModelsByIds(ids));
+    }
+}

+ 209 - 0
fs-company/src/main/java/com/fs/company/controller/aiSipCall/AiSopCallUserController.java

@@ -0,0 +1,209 @@
+package com.fs.company.controller.aiSipCall;
+
+
+import com.fs.aiCallSop.domain.AiSopCallUser;
+import com.fs.aiCallSop.param.ModelListRequest;
+import com.fs.aiCallSop.param.ModelListResponse;
+import com.fs.aiCallSop.param.VoicesDictRes;
+import com.fs.aiCallSop.param.VoicesDictResPage;
+import com.fs.aiCallSop.service.IAiSopCallUserService;
+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.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 员工角色Controller
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@RestController
+@RequestMapping("/aiSopCallUser/aiSopCallUser")
+public class AiSopCallUserController extends BaseController
+{
+    @Autowired
+    private IAiSopCallUserService aiSopCallUserService;
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 查询员工角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AiSopCallUser aiSopCallUser)
+    {
+        startPage();
+        List<AiSopCallUser> list = aiSopCallUserService.selectAiSopCallUserList(aiSopCallUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出员工角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:export')")
+    @Log(title = "员工角色", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AiSopCallUser aiSopCallUser)
+    {
+        List<AiSopCallUser> list = aiSopCallUserService.selectAiSopCallUserList(aiSopCallUser);
+        ExcelUtil<AiSopCallUser> util = new ExcelUtil<AiSopCallUser>(AiSopCallUser.class);
+        return util.exportExcel(list, "员工角色数据");
+    }
+
+    /**
+     * 获取员工角色详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(aiSopCallUserService.selectAiSopCallUserById(id));
+    }
+
+    /**
+     * 获取员工角色详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:query')")
+    @GetMapping(value = "/staffNumber/{staffNumber}")
+    public R staffNumber(@PathVariable("staffNumber") String staffNumber)
+    {
+        return aiSopCallUserService.selectAiSopCallUserByStaffNumber(staffNumber);
+    }
+
+    /**
+     * 新增员工角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:add')")
+    @Log(title = "员工角色", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody AiSopCallUser aiSopCallUser)
+    {
+        return aiSopCallUserService.insertAiSopCallUser(aiSopCallUser);
+    }
+
+    /**
+     * 修改员工角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:edit')")
+    @Log(title = "员工角色", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R edit(@RequestBody AiSopCallUser aiSopCallUser)
+    {
+        return aiSopCallUserService.updateAiSopCallUser(aiSopCallUser);
+    }
+
+    /**
+     * 删除员工角色
+     */
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:remove')")
+    @Log(title = "员工角色", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{id}")
+    public R remove(@PathVariable Long id)
+    {
+        return aiSopCallUserService.deleteAiSopCallUserById(id);
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('aiSopCallUser:aiSopCallUser:getMode')")
+    @GetMapping("/getMode")
+    public R getMode(@RequestParam(defaultValue = "1") Integer pageNum,
+                     @RequestParam(defaultValue = "10") Integer pageSize,
+                     @RequestParam(required = false) String modelName) {
+
+        // 构建请求参数
+        ModelListRequest request = new ModelListRequest();
+        request.setPage(pageNum);
+        request.setSize(pageSize);
+        request.setLike_key("model_name");
+
+        // 设置查询条件
+        ModelListRequest.Condition condition = new ModelListRequest.Condition();
+        condition.setModel_name(modelName != null ? modelName : "");
+        request.setCondition(condition);
+
+        // 发送请求
+        ModelListResponse response = aiSopCallUserService.getModeList(request);
+
+        if (response.getCode() == 0) {
+            return R.ok().put("data",response.getData());
+        } else {
+            return R.error(response.getMsg());
+        }
+    }
+
+    @GetMapping("/getVoicesDict")
+    public R getMode() {
+
+        ModelListRequest request = new ModelListRequest();
+        request.setDisabled(1);
+        request.setPage(1);
+        request.setSize(10);
+        // 发送请求
+        VoicesDictRes response = aiSopCallUserService.getVoicesDict(request);
+
+        if (response.getCode() == 0) {
+            return R.ok().put("data",response.getData());
+        } else {
+            return R.error(response.getMsg());
+        }
+    }
+    @GetMapping("/getVoicesDictByModelId")
+    public R getVoicesDictByModelId(@RequestParam(defaultValue = "1") Integer pageNum,
+                                    @RequestParam(defaultValue = "10") Integer pageSize,
+                                    @RequestParam(required = false) Integer staffModelsId) {
+        ModelListRequest request = new ModelListRequest();
+        request.setDisabled(1);
+        request.setPage(pageNum);
+        request.setSize(pageSize);
+        ModelListRequest.Condition condition = new ModelListRequest.Condition();
+        condition.setModel_id(staffModelsId);
+        request.setCondition(condition);
+        // 发送请求
+        VoicesDictResPage response = aiSopCallUserService.getVoicesDictByModelId(request);
+
+        if (response.getCode() == 0) {
+            return R.ok().put("data",response.getData());
+        } else {
+            return R.error(response.getMsg());
+        }
+    }
+
+    /**
+     * 上传文件
+     */
+    @PostMapping("/saveUploadOssFile")
+    public R saveUploadOssFile(@RequestParam("file") MultipartFile file,
+                           @RequestParam("fileName") String fileName) {
+        try {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            // 新文件名称
+            fileName = loginUser.getUser().getUserId()+"_"+fileName;
+            // 上传文件
+            String suffix = fileName.substring(fileName.lastIndexOf("."));
+            CloudStorageService storage = OSSFactory.build();
+            String url = storage.uploadSuffix(file.getBytes(), suffix);
+            return Objects.requireNonNull(R.ok().put("message", "文件保存成功")).put("url", url);
+        }
+        catch (Exception e)
+        {
+            return R.error("文件保存失败: " + e.getMessage());
+        }
+    }
+
+}

+ 6 - 1
fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java

@@ -116,12 +116,17 @@ public class FsQwCourseWatchLogController extends BaseController
             return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
         }
     }
-
     @GetMapping("/myQwWatchLogStatisticsList")
     public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId(loginUser.getCompany().getCompanyId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
+
+        if (param.getSearchType()==2){
+            List<Long> deptIds=new ArrayList<>();
+            deptIds.add(loginUser.getUser().getDeptId());
+            param.setDeptIds(deptIds);
+        }
         if (param.getSTime() == null || param.getETime() == null) {
             return getDataTable(new ArrayList<>());
         }

+ 29 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -31,6 +31,7 @@ import com.fs.qw.domain.QwContactWay;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwTag;
 import com.fs.qw.dto.CompanyTransferDTO;
+import com.fs.qw.dto.SalesAppDownloadStatDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwExternalContactVO;
@@ -1150,4 +1151,32 @@ public class QwExternalContactController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/getSalesAppDownloadStats")
+    public TableDataInfo getSalesAppDownloadStats(AppDownloadParam param) {
+        // 设置企业ID(实际项目中从登录信息获取)
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        TableDataInfo salesAppDownloadStats = qwExternalContactService.getSalesAppDownloadStats(param);
+
+        return salesAppDownloadStats;
+    }
+
+    @GetMapping("/exportSalesAppDownloadStats")
+    public AjaxResult exportSalesAppDownloadStats(AppDownloadParam param) {
+        // 设置企业ID(实际项目中从登录信息获取)
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        // 获取所有统计数据(不分页)
+        List<SalesAppDownloadStatDTO> list = qwExternalContactService.exportSalesAppDownloadStats(param);
+
+        // 用 ExcelUtil 导出
+        ExcelUtil<SalesAppDownloadStatDTO> util = new ExcelUtil<>(SalesAppDownloadStatDTO.class);
+        return util.exportExcel(list, "销售APP下载统计");
+    }
+
+
 }

+ 70 - 14
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceController.java

@@ -1,36 +1,39 @@
 package com.fs.company.controller.qw;
 
+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.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
-import com.fs.qw.domain.QwUser;
-import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
+/**
+ * 模板对应的销售语音文件Controller
+ *
+ * @author fs
+ * @date 2026-03-12
+ */
 @RestController
-@RequestMapping("/qw/SopTempVoice")
-public class QwSopTempVoiceController extends BaseController {
-
+@RequestMapping("/qw/QwSopTempVoice")
+public class QwSopTempVoiceController extends BaseController
+{
     @Autowired
-    private TokenService tokenService;
+    private IQwSopTempVoiceService qwSopTempVoiceService;
 
     @Autowired
-    private IQwSopTempVoiceService sopTempVoiceService;
+    private TokenService tokenService;
 
 
     /**
@@ -43,8 +46,61 @@ public class QwSopTempVoiceController extends BaseController {
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
-        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(loginUser.getUser().getUserId(),param.getKeyword());
+        List<QwSopTempVoice> voiceList = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(loginUser.getUser().getUserId(),param.getKeyword());
         return getDataTable(voiceList);
     }
 
+    /**
+     * 查询模板对应的销售语音文件列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoice:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwSopTempVoice qwSopTempVoice)
+    {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwSopTempVoice.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        startPage();
+        List<QwSopTempVoice> list = qwSopTempVoiceService.selectQwSopTempVoiceBySidberList(qwSopTempVoice);
+        return getDataTable(list);
+    }
+
+    /**
+     * 新增模板对应的销售语音文件
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoice:add')")
+    @Log(title = "模板对应的销售语音文件", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody QwSopTempVoice qwSopTempVoice)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwSopTempVoice.setCompanyId(loginUser.getCompany().getCompanyId());
+        qwSopTempVoiceService.insertQwSopTempVoiceByCompany(qwSopTempVoice);
+        return R.ok();
+    }
+
+//    /**
+//     * 修改模板对应的销售语音文件
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoice:edit')")
+//    @Log(title = "模板对应的销售语音文件", businessType = BusinessType.UPDATE)
+//    @PutMapping
+//    public AjaxResult edit(@RequestBody QwSopTempVoice qwSopTempVoice)
+//    {
+//        return toAjax(qwSopTempVoiceService.updateQwSopTempVoice(qwSopTempVoice));
+//    }
+
+    /**
+     * 删除模板对应的销售语音文件
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoice:remove')")
+    @Log(title = "模板对应的销售语音文本", businessType = BusinessType.DELETE)
+    @PostMapping("/removeSidebar")
+    public AjaxResult remove(@RequestBody QwSopTempVoice qwSopTempVoice)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwSopTempVoice.setCompanyId(loginUser.getUser().getCompanyId());
+        return toAjax(qwSopTempVoiceService.deleteQwSopTempVoiceByIdSidebar(qwSopTempVoice));
+    }
 }

+ 29 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java

@@ -30,6 +30,8 @@ import com.fs.qw.service.IQwTagGroupService;
 import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwExternalListByHeavyVO;
 import com.fs.qw.vo.QwTagGroupListVO;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.vo.ExtCourseSopWatchLogVO;
 import com.github.pagehelper.PageHelper;
@@ -77,6 +79,10 @@ public class ApisQwUserController extends BaseController {
     private IQwTagGroupService qwTagGroupService;
 
 
+    @Autowired
+    private IQwSopTempVoiceService sopTempVoiceService;
+
+
     @GetMapping("/details")
     @ApiOperation("会员看课详情")
     public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
@@ -222,4 +228,27 @@ public class ApisQwUserController extends BaseController {
         return R.error();
     }
 
+
+    /**
+     * 获取销售语音列表
+     */
+    @PostMapping("/getQwUserVoice")
+    @ApiOperation("获取销售语音列表")
+    public R getQwUserVoice(@RequestBody FsCourseListBySidebarParam param){
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null) {
+            return R.error("未查询到企业微信账号信息!请重试");
+        }
+
+        if (qwUser.getCompanyUserId() == null) {
+            return R.error("查询到企业微信账号 未绑定销售!!!请先绑定");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword());
+        PageInfo<QwSopTempVoice> result = new PageInfo<>(voiceList);
+        return R.ok().put("data", result);
+    }
+
 }

+ 30 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java

@@ -16,8 +16,10 @@ import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.service.ICompanyPostService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.ExternalContactDetailsParam;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.qw.param.sidebar.ExternalContactInfoParam;
@@ -30,6 +32,8 @@ import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwTagGroupListVO;
 import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
 import com.fs.qw.vo.sidebar.ExternalContactTagVO;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.vo.ExtCourseSopWatchLogVO;
 import com.github.pagehelper.PageHelper;
@@ -73,6 +77,10 @@ public class QwUserController extends BaseController {
     @Autowired
     private ICompanyService companyService;
 
+
+    @Autowired
+    private IQwSopTempVoiceService sopTempVoiceService;
+
     @Autowired
     ICompanyPostService postService;
 
@@ -223,4 +231,26 @@ public class QwUserController extends BaseController {
         }
     }
 
+    /**
+     * 获取销售语音列表
+     */
+    @PostMapping("/getQwUserVoice")
+    @ApiOperation("获取销售语音列表")
+    public R getQwUserVoice(@RequestBody FsCourseListBySidebarParam param){
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null) {
+            return R.error("未查询到企业微信账号信息!请重试");
+        }
+
+        if (qwUser.getCompanyUserId() == null) {
+            return R.error("查询到企业微信账号 未绑定销售!!!请先绑定");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword());
+        PageInfo<QwSopTempVoice> result = new PageInfo<>(voiceList);
+        return R.ok().put("data", result);
+    }
+
 }

+ 70 - 0
fs-service/src/main/java/com/fs/aiCallSop/commonConstant/AiSopCallRemoteUrlConstant.java

@@ -0,0 +1,70 @@
+package com.fs.aiCallSop.commonConstant;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author:peicj
+ * @Description: ai外呼远程接口常量配置
+ * @Date:2026/1/30 16:31
+ */
+@Component
+public class AiSopCallRemoteUrlConstant {
+    /**
+     * 请求地址前缀
+     */
+    public static final String REQ_ADDRESS_PREFIX = "http://43.136.169.132/api/signal-service";
+    /**
+     * 获取员工
+     */
+    public static final String GET_STAFF_NUMBER_URL = REQ_ADDRESS_PREFIX + "/api/getStaffNumber";
+    /**
+     * 创建员工
+     */
+    public static final String GET_STAFF_CREATE_URL = REQ_ADDRESS_PREFIX + "/api/staff/create";
+    /**
+     * 修改和删除员工
+     */
+    public static final String GET_STAFF_URL = REQ_ADDRESS_PREFIX + "/api/staff";
+    /**
+     * 获取模型列表
+     */
+    public static final String GET_MODEL_LIST_URL = REQ_ADDRESS_PREFIX + "/api/models/list";
+    /**
+     * 获取所有语音
+     */
+    public static final String GET_VOICES_LIST_ALL_URL = REQ_ADDRESS_PREFIX + "/api/voices/list-all";
+    /**
+     * 获取语音列表
+     */
+    public static final String GET_VOICES_LIST_URL = REQ_ADDRESS_PREFIX + "/api/voices/list";
+
+    /**
+     * 获取聊天详情
+     */
+    public static final String GET_CHAT_SESSION_URL = REQ_ADDRESS_PREFIX + "/api/chat/session";
+
+    //===================知识库接口begin===================
+    /**
+     * 知识库分页列表 POST
+     */
+    public static final String GET_KNOWLEDGE_LIST_URL = REQ_ADDRESS_PREFIX + "/api/knowledge/list";
+    /**
+     * 知识库列表(不分页) POST
+     */
+    public static final String GET_KNOWLEDGE_LIST_ALL_URL = REQ_ADDRESS_PREFIX + "/api/knowledge/list-all";
+    /**
+     * 知识库创建 POST
+     */
+    public static final String GET_KNOWLEDGE_CREATE_URL = REQ_ADDRESS_PREFIX + "/api/knowledge/create";
+    /**
+     * 知识库修改 PUT
+     * 删除 DELETE
+     * 知识库详情查询 GET
+     */
+    public static final String GET_KNOWLEDGE_URL = REQ_ADDRESS_PREFIX + "/api/knowledge";
+    /**
+     * 知识库批量删除 DELETE
+     */
+    public static final String GET_KNOWLEDGE_DELETE_BATCH_URL = REQ_ADDRESS_PREFIX + "/api/knowledge/delete-batch";
+    //===================知识库接口end===================
+}

+ 71 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallKnowledgeBase.java

@@ -0,0 +1,71 @@
+package com.fs.aiCallSop.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * AI外呼知识库对象 ai_sop_call_knowledge_base
+ *
+ * @author fs
+ * @date 2026-01-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AiSopCallKnowledgeBase extends BaseEntity{
+
+    /** 主键ID */
+    @Excel(name = "主键ID")
+    private Long id;
+
+    /** 知识库名称 */
+    @Excel(name = "知识库名称")
+    private String knowledgeName;
+
+    /** 提供商 可选值: openai、bytedance、alibaba、http
+     * openai OpenAI   bytedance 字节跳动  alibaba  阿里巴巴 http 第三方 HTTP调用
+     * */
+    @Excel(name = "提供商", readConverterExp = "openai=OpenAI,bytedance=字节跳动,alibaba=阿里巴巴,http=第三方 HTTP调用")
+    private String factoryName;
+
+    /** 是否启用 1: 启用 0: 禁用,默认 1 */
+    @Excel(name = "是否启用", readConverterExp = "1=启用,0=禁用")
+    private Integer enable;
+
+    /** 扩展配置,不同厂商字段不同,自由扩展 */
+    @Excel(name = "扩展配置,不同厂商字段不同,自由扩展")
+    private Object metadata;
+
+    /** 企业id */
+    private Long companyId;
+
+    /** 销售id */
+    private Long companyUserId;
+    /** 远程知识库id */
+    private Long remoteKnowledgeId;
+    /** 创建者 */
+    @Excel(name = "创建者")
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT)
+    private Date createTime;
+
+    /** 更新者 */
+    @Excel(name = "更新者")
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT)
+    private Date updateTime;
+
+    /** 备注 */
+    @Excel(name = "备注")
+    private String remark;
+}

+ 70 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallLineConfig.java

@@ -0,0 +1,70 @@
+package com.fs.aiCallSop.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * ai呼叫线路配置对象 ai_sop_call_line_config
+ *
+ * @author fs
+ * @date 2026-01-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AiSopCallLineConfig extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 网关名称 */
+    @Excel(name = "网关名称")
+    private String gatewayName;
+
+    /** 网关用途:1-外呼 */
+    @Excel(name = "网关用途:1-外呼")
+    private Long gatewayPurpose;
+
+    /** profile名称:1-external */
+    @Excel(name = "profile名称:1-external")
+    private Long profileName;
+
+    /** 主叫号码 */
+    @Excel(name = "主叫号码")
+    private String callingNnumber;
+
+    /** 被叫前缀 */
+    @Excel(name = "被叫前缀")
+    private String calledPrefix;
+
+    /** 网关地址 */
+    @Excel(name = "网关地址")
+    private String gatewayAddress;
+
+    /** 外呼语音编码 */
+    @Excel(name = "外呼语音编码")
+    private String outCallVoiceEncoding;
+
+    /** 网关名称描述 */
+    @Excel(name = "网关名称描述")
+    private String gatewayNameDescription;
+
+    /** 网关最大并发数 */
+    @Excel(name = "网关最大并发数")
+    private Long gatewayMaxConcurrency;
+
+    /** 是否认证注册:1-对接模式 */
+    @Excel(name = "是否认证注册:1-对接模式")
+    private Long isCertificationRegistration;
+
+    /** 使用优先级 */
+    @Excel(name = "使用优先级")
+    private Long priorityUsage;
+
+    /** ID */
+    @Excel(name = "ID")
+    private Long companyId;
+
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallMsgLog.java

@@ -0,0 +1,63 @@
+package com.fs.aiCallSop.domain;
+
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * ai外呼记录对象 ai_sop_call_msg_log
+ *
+ * @author fs
+ * @date 2025-12-31
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AiSopCallMsgLog extends BaseEntity {
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String sessionId;
+
+    /** 销售 */
+    @Excel(name = "销售")
+    private String companyUserId;
+
+    /** 销售公司 */
+    @Excel(name = "销售公司")
+    private Long companyId;
+
+    /** 企业微信账号(非主键) */
+    @Excel(name = "企业微信账号(非主键)")
+    private String userId;
+
+    /** 企业微信主体 */
+    @Excel(name = "企业微信主体")
+    private String corpId;
+
+    /** 外部联系人 */
+    @Excel(name = "外部联系人")
+    private String externalUserId;
+
+    /** 外呼状态 */
+    @Excel(name = "外呼状态")
+    private Long status;
+
+    /** 通话文本内容 */
+    @Excel(name = "通话文本内容")
+    private String callContent;
+
+    /** 通话时长 */
+    @Excel(name = "通话时长")
+    private Long callDuration;
+
+    /** 语音地址 */
+    @Excel(name = "语音地址")
+    private String voiceUrl;
+
+
+}

+ 54 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallRole.java

@@ -0,0 +1,54 @@
+package com.fs.aiCallSop.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * AI外呼角色对象 ai_sop_call_role
+ *
+ * @author fs
+ * @date 2026-01-14
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AiSopCallRole extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 外呼角色名称 */
+    @Excel(name = "外呼角色名称")
+    private String callRoleName;
+
+    /** 外呼实现类 1:deppSeek */
+    @Excel(name = "外呼实现类 1:deppSeek")
+    private Long implClass;
+
+    /** 模型并发数 */
+    @Excel(name = "模型并发数")
+    private Long modelConcurrency;
+
+    /** 服务地址 */
+    @Excel(name = "服务地址")
+    private String serviceAddress;
+
+    /** 角色key */
+    @Excel(name = "角色key")
+    private String callRoleKey;
+
+    /** 模型名称 */
+    @Excel(name = "模型名称")
+    private String modelName;
+
+    /** 大模型提示词 */
+    @Excel(name = "大模型提示词")
+    private String reminderWords;
+
+    /** 备注 */
+    @Excel(name = "备注")
+    private String remake;
+
+
+}

+ 36 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallStaffModels.java

@@ -0,0 +1,36 @@
+package com.fs.aiCallSop.domain;
+
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 外呼模型模型对象 ai_sop_call_staff_models
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@Data
+public class AiSopCallStaffModels  {
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 模型id(必填) */
+    @Excel(name = "模型id", readConverterExp = "必=填")
+    private Long modelId;
+
+    /** 模型功能 STT: 语音识别 TTS: 语音合成 LLM: 大模型 ENDTOEND */
+    @Excel(name = "模型功能 STT: 语音识别 TTS: 语音合成 LLM: 大模型 ENDTOEND")
+    private String modelFunction;
+
+    /** 模型工厂 1: 阿里 2: 字节 */
+    @Excel(name = "模型工厂 1: 阿里 2: 字节")
+    private Long modelFactory;
+
+    /** 员工id(修改接口 必填) */
+    @Excel(name = "员工id", readConverterExp = "修=改接口,必=填")
+    private Long staffId;
+
+
+}

+ 121 - 0
fs-service/src/main/java/com/fs/aiCallSop/domain/AiSopCallUser.java

@@ -0,0 +1,121 @@
+package com.fs.aiCallSop.domain;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 员工角色对象 ai_sop_call_user
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@Data
+public class AiSopCallUser{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 员工姓名 */
+    @Excel(name = "员工姓名")
+    private String staffName;
+
+    @TableField(exist = false)
+    private String staff_name;
+
+    /** 员工编号 */
+    @Excel(name = "员工编号")
+    private String staffNumber;
+
+    @TableField(exist = false)
+    private String staff_number;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private String sex;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Long age;
+
+    /** 语音id */
+    @Excel(name = "语音id")
+    private String voiceId;
+
+    @TableField(exist = false)
+    private String voice_id;
+
+    /** 是否克隆语音 1: 是 2: 否 */
+    @Excel(name = "是否克隆语音 1: 是 2: 否")
+    private Long isCloneVoice;
+
+    @TableField(exist = false)
+    private Long is_clone_voice;
+
+    /** 是否强制采样 1: 是 2: 否  一般修改接口使用 */
+    @Excel(name = "是否强制采样 1: 是 2: 否  一般修改接口使用")
+    private Long isForceClone;
+
+    @TableField(exist = false)
+    private Long is_force_clone;
+
+    /** 语音路径 (必填) */
+    @Excel(name = "语音路径 ", readConverterExp = "必=填")
+    private String voicePath;
+
+    @TableField(exist = false)
+    private String voice_path;
+
+    /** 音色对应的文本,用于语音识别 */
+    @Excel(name = "音色对应的文本,用于语音识别")
+    private String voiceText;
+
+    @TableField(exist = false)
+    private String voice_text;
+
+    @Excel(name = "音色对应的文本,用于语音识别")
+    private String remark;
+
+    /** 是否启用 1: 是 2: 否 */
+    @Excel(name = "是否启用 1: 是 2: 否")
+    private Long enable;
+
+    private Long companyUserId;
+
+    /** 模型id(必填) */
+    @Excel(name = "模型id", readConverterExp = "必=填")
+    private Integer staffModelsId;
+
+    @TableField(exist = false)
+    private String model_function;
+
+    @TableField(exist = false)
+    private Integer model_factory;
+
+    /** 本地地址的知识库id */
+    @Excel(name = "远程地址的知识库id")
+    private Long knowledgeBaseId;
+    /** 远程地址的知识库id字段 */
+    private Long knowledge_resource_id;
+
+
+    @TableField(exist = false)
+    private List<staff_models> staff_models=new ArrayList<>(); // 员工模型列表(必填)
+
+    @Data
+    public static class staff_models {
+
+        private Integer model_id; // 模型ID(必填)
+
+        private String model_function;
+        private Integer model_factory; // 模型工厂 1:阿里 2:字节
+
+        private Long staff_id; // 员工ID(修改接口必填)
+
+    }
+
+}

+ 66 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallKnowledgeBaseMapper.java

@@ -0,0 +1,66 @@
+package com.fs.aiCallSop.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * AI外呼知识库Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-30
+ */
+public interface AiSopCallKnowledgeBaseMapper extends BaseMapper<AiSopCallKnowledgeBase>{
+    /**
+     * 查询AI外呼知识库
+     *
+     * @param id AI外呼知识库主键
+     * @return AI外呼知识库
+     */
+    AiSopCallKnowledgeBase selectAiSopCallKnowledgeBaseById(Long id);
+
+    /**
+     * 查询AI外呼知识库列表
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return AI外呼知识库集合
+     */
+    List<AiSopCallKnowledgeBase> selectAiSopCallKnowledgeBaseList(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 新增AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    int insertAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 修改AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    int updateAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 删除AI外呼知识库
+     *
+     * @param id AI外呼知识库主键
+     * @return 结果
+     */
+    int deleteAiSopCallKnowledgeBaseById(Long id);
+
+    /**
+     * 批量删除AI外呼知识库
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallKnowledgeBaseByIds(Long[] ids);
+
+    @Select("select id,knowledge_name from ai_sop_call_knowledge_base")
+    List<AiSopCallKnowledgeBase> idAndNameList();
+}

+ 62 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallLineConfigMapper.java

@@ -0,0 +1,62 @@
+package com.fs.aiCallSop.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallLineConfig;
+
+import java.util.List;
+
+/**
+ * ai呼叫线路配置Mapper接口
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+public interface AiSopCallLineConfigMapper extends BaseMapper<AiSopCallLineConfig>{
+    /**
+     * 查询ai呼叫线路配置
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return ai呼叫线路配置
+     */
+    AiSopCallLineConfig selectAiSopCallLineConfigById(Long id);
+
+    /**
+     * 查询ai呼叫线路配置列表
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return ai呼叫线路配置集合
+     */
+    List<AiSopCallLineConfig> selectAiSopCallLineConfigList(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 新增ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    int insertAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 修改ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    int updateAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 删除ai呼叫线路配置
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return 结果
+     */
+    int deleteAiSopCallLineConfigById(Long id);
+
+    /**
+     * 批量删除ai呼叫线路配置
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallLineConfigByIds(Long[] ids);
+}

+ 63 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallMsgLogMapper.java

@@ -0,0 +1,63 @@
+package com.fs.aiCallSop.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallMsgLog;
+
+import java.util.List;
+
+/**
+ * ai外呼记录Mapper接口
+ *
+ * @author fs
+ * @date 2025-12-31
+ */
+public interface AiSopCallMsgLogMapper extends BaseMapper<AiSopCallMsgLog> {
+    /**
+     * 查询ai外呼记录
+     *
+     * @param id ai外呼记录主键
+     * @return ai外呼记录
+     */
+    AiSopCallMsgLog selectAiSopCallMsgLogById(Long id);
+
+    /**
+     * 查询ai外呼记录列表
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return ai外呼记录集合
+     */
+    List<AiSopCallMsgLog> selectAiSopCallMsgLogList(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 新增ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    int insertAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 修改ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    int updateAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 删除ai外呼记录
+     *
+     * @param id ai外呼记录主键
+     * @return 结果
+     */
+    int deleteAiSopCallMsgLogById(Long id);
+
+    /**
+     * 批量删除ai外呼记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallMsgLogByIds(Long[] ids);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallRoleMapper.java

@@ -0,0 +1,62 @@
+package com.fs.aiCallSop.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallRole;
+
+import java.util.List;
+
+/**
+ * AI外呼角色Mapper接口
+ *
+ * @author fs
+ * @date 2026-01-14
+ */
+public interface AiSopCallRoleMapper extends BaseMapper<AiSopCallRole>{
+    /**
+     * 查询AI外呼角色
+     *
+     * @param id AI外呼角色主键
+     * @return AI外呼角色
+     */
+    AiSopCallRole selectAiSopCallRoleById(Long id);
+
+    /**
+     * 查询AI外呼角色列表
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return AI外呼角色集合
+     */
+    List<AiSopCallRole> selectAiSopCallRoleList(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 新增AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    int insertAiSopCallRole(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 修改AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    int updateAiSopCallRole(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 删除AI外呼角色
+     *
+     * @param id AI外呼角色主键
+     * @return 结果
+     */
+    int deleteAiSopCallRoleById(Long id);
+
+    /**
+     * 批量删除AI外呼角色
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallRoleByIds(Long[] ids);
+}

+ 63 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallStaffModelsMapper.java

@@ -0,0 +1,63 @@
+package com.fs.aiCallSop.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallStaffModels;
+
+import java.util.List;
+
+/**
+ * 外呼模型模型Mapper接口
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface AiSopCallStaffModelsMapper extends BaseMapper<AiSopCallStaffModels> {
+    /**
+     * 查询外呼模型模型
+     *
+     * @param id 外呼模型模型主键
+     * @return 外呼模型模型
+     */
+    AiSopCallStaffModels selectAiSopCallStaffModelsById(Long id);
+
+    /**
+     * 查询外呼模型模型列表
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 外呼模型模型集合
+     */
+    List<AiSopCallStaffModels> selectAiSopCallStaffModelsList(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 新增外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    int insertAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 修改外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    int updateAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 删除外呼模型模型
+     *
+     * @param id 外呼模型模型主键
+     * @return 结果
+     */
+    int deleteAiSopCallStaffModelsById(Long id);
+
+    /**
+     * 批量删除外呼模型模型
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallStaffModelsByIds(Long[] ids);
+}

+ 70 - 0
fs-service/src/main/java/com/fs/aiCallSop/mapper/AiSopCallUserMapper.java

@@ -0,0 +1,70 @@
+package com.fs.aiCallSop.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.aiCallSop.domain.AiSopCallUser;
+
+import java.util.List;
+
+/**
+ * 员工角色Mapper接口
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface AiSopCallUserMapper extends BaseMapper<AiSopCallUser> {
+    /**
+     * 查询员工角色
+     *
+     * @param id 员工角色主键
+     * @return 员工角色
+     */
+    AiSopCallUser selectAiSopCallUserById(Long id);
+
+    /**
+     * 查询员工角色列表
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 员工角色集合
+     */
+    List<AiSopCallUser> selectAiSopCallUserList(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 新增员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    int insertAiSopCallUser(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 修改员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    int updateAiSopCallUser(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 删除员工角色
+     *
+     * @param id 员工角色主键
+     * @return 结果
+     */
+    int deleteAiSopCallUserById(Long id);
+
+    /**
+     * 批量删除员工角色
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallUserByIds(Long[] ids);
+    /**
+     * 解绑已删除的知识库
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int unbindCallUserKnowledge(Long[] ids);
+}

+ 68 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/AiSopCallKnowledgeBaseRequest.java

@@ -0,0 +1,68 @@
+package com.fs.aiCallSop.param;
+
+import cn.hutool.json.JSONUtil;
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @Author:peicj
+ * @Description: 知识库请求参数
+ * @Date:2026/1/30 17:01
+ */
+@Slf4j
+@Data
+public class AiSopCallKnowledgeBaseRequest {
+    /**
+     * 对方的主键ID
+     */
+    private Long id;
+    /**
+     * 知识库名称
+     */
+    private String knowledge_name;
+    /**
+     * 提供商
+     */
+    private String factory_name;
+    /**
+     * 启用状态 0禁用1启用
+     */
+    private Integer enable;
+    /**
+     * 备注
+     */
+    private String remark;
+    /**
+     * 扩展配置,不同厂商字段不同,自由扩展
+     */
+    private Object metadata;
+
+    public AiSopCallKnowledgeBaseRequest conversion(AiSopCallKnowledgeBase aiSopCallKnowledgeBase) {
+        AiSopCallKnowledgeBaseRequest request = new AiSopCallKnowledgeBaseRequest();
+        if(aiSopCallKnowledgeBase.getId() != null){
+            request.setId(aiSopCallKnowledgeBase.getRemoteKnowledgeId());
+        }
+        request.setKnowledge_name(aiSopCallKnowledgeBase.getKnowledgeName());
+        request.setFactory_name(aiSopCallKnowledgeBase.getFactoryName());
+        request.setEnable(aiSopCallKnowledgeBase.getEnable());
+        request.setRemark(aiSopCallKnowledgeBase.getRemark());
+        // 正确处理metadata字段
+        if (aiSopCallKnowledgeBase.getMetadata() != null) {
+            try {
+                // 尝试将metadata字符串解析为JSONObject
+                Object metadataObj = JSONUtil.isJson(aiSopCallKnowledgeBase.getMetadata().toString())
+                        ? JSONUtil.parseObj(aiSopCallKnowledgeBase.getMetadata())
+                        : aiSopCallKnowledgeBase.getMetadata();
+                request.setMetadata(metadataObj);
+            } catch (Exception e) {
+                log.warn("metadata解析失败,使用原始值", e);
+                request.setMetadata(aiSopCallKnowledgeBase.getMetadata());
+            }
+        }
+
+        return request;
+    }
+
+    public AiSopCallKnowledgeBaseRequest() {}
+}

+ 19 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/ModelListRequest.java

@@ -0,0 +1,19 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+@Data
+public class ModelListRequest {
+    private Condition condition;
+    private String like_key;
+    private Integer page;
+    private Integer size;
+    private Integer disabled;
+
+    @Data
+    public static class Condition {
+        private String model_name;
+        private String model_type;
+        private Integer model_id;
+    }
+}

+ 55 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/ModelListResponse.java

@@ -0,0 +1,55 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ModelListResponse {
+    private Integer code;
+    private String msg;
+    private PageData data;
+
+    @Data
+    public static class PageData {
+        private Integer page;
+        private Integer size;
+        private Integer total;
+        private List<ModelRecord> records;
+    }
+
+    @Data
+    public static class ModelRecord {
+        private Long id;
+        private String title;
+        private String modelName;
+        private Integer modelWay;
+        private String modelType;
+        private String version;
+
+//        @JsonProperty("package")  // 指定JSON字段名
+//        private String packageName;  // 使用不同的Java字段名
+
+        private Integer modelFactory;
+        private String modelFunction;
+//        private Integer input_sample_rate;
+//        private Integer input_audio_channels;
+//        private Integer input_stream_cache_time;
+//        private Integer output_sample_rate;
+//        private Integer output_audio_channels;
+//        private Integer output_stream_cache_time;
+//        private Integer audio_enable_vad;
+//        private Integer audio_vad_silence_duration;
+//        private Integer keepalive_interval_ms;
+//        private Double threshold;
+//        private Integer interrupt_duration_ms;
+//        private Map<String, Object> metadata;
+//        private Date created_at;
+        private Integer enable;
+        private String remark;
+        private String greeting;
+//        private Integer idle_prompt_interval_ms;
+//        private String idle_prompt_text;
+//        private Integer idle_prompt_max_count;
+    }
+}

+ 10 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/ModelListResult.java

@@ -0,0 +1,10 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+@Data
+public class ModelListResult {
+    private Integer code;
+    private String msg;
+    private String data;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/QwGroupVoiceMsgDoSendListTVO.java

@@ -0,0 +1,34 @@
+package com.fs.aiCallSop.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+public class QwGroupVoiceMsgDoSendListTVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+
+    private String userId;
+
+    private String externalUserid;
+
+    private String corpId;
+
+    private String sessionId;
+
+    private Long status;
+
+    private Long sendStatus;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date sendTime;
+
+    private Integer chatType;
+
+    private Long aiSopCallRoleId;
+
+}

+ 18 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/SendAiCallSopParamDetailsC.java

@@ -0,0 +1,18 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SendAiCallSopParamDetailsC {
+
+    private List<QwGroupVoiceMsgDoSendListTVO> sendList;
+
+    private String nextExternalUserId;//下一个外部联系人ID
+    private Long nextExternalId;//下一个外部联系人ID
+    private String appKey;
+    private String companyUserName;
+    private Integer type = 0;
+
+}

+ 75 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/VoicesDictRes.java

@@ -0,0 +1,75 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class VoicesDictRes {
+
+
+
+
+    /**
+     * 响应代码
+     */
+    private Integer code;
+
+    /**
+     * 数据列表
+     */
+    private List<ModelData> data;
+
+    /**
+     * 响应消息
+     */
+    private String msg;
+
+
+    /**
+     * 模型数据实体类
+     */
+    @Data
+    public static class ModelData {
+
+        /**
+         * ID
+         */
+        private Integer id;
+
+        /**
+         * 模型工厂
+         */
+        private Integer modelFactory;
+
+        /**
+         * 编码
+         */
+        private String code;
+
+        /**
+         * 模型类型
+         */
+        private String modelType;
+
+        /**
+         * 模型名称
+         */
+        private String modelName;
+
+        /**
+        * 音色id
+        */
+        private String voiceId;
+
+        /**
+         * 音色名称
+         */
+        private String voiceTitle;
+
+
+    }
+
+}
+
+

+ 88 - 0
fs-service/src/main/java/com/fs/aiCallSop/param/VoicesDictResPage.java

@@ -0,0 +1,88 @@
+package com.fs.aiCallSop.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Author:peicj
+ * @Description:
+ * @Date:2026/2/5 10:13
+ */
+@Data
+public class VoicesDictResPage {
+
+    /**
+     * 响应代码
+     */
+    private Integer code;
+
+    /**
+     * 数据列表
+     */
+    private List<DataDetails> data;
+
+    /**
+     * 响应消息
+     */
+    private String msg;
+
+
+    @Data
+    public static class DataDetails {
+
+        private Integer page;
+
+        private Integer size;
+
+        private Integer total;
+
+        private List<VoicesData> records;
+
+    }
+    /**
+     * 模型数据实体类
+     */
+    @Data
+    public static class VoicesData {
+
+        /**
+         * ID
+         */
+        private Integer id;
+
+        /**
+         * 模型工厂
+         */
+        private Integer modelFactory;
+
+        /**
+         * 编码
+         */
+        private String code;
+
+        /**
+         * 模型类型
+         */
+        private String modelType;
+
+        /**
+         * 模型名称
+         */
+        private String modelName;
+
+        /**
+         * 音色id
+         */
+        private String voiceId;
+
+        /**
+         * 音色名称
+         */
+        private String voiceTitle;
+
+
+    }
+
+}
+

+ 65 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallKnowledgeBaseService.java

@@ -0,0 +1,65 @@
+package com.fs.aiCallSop.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import com.fs.common.core.domain.R;
+
+import java.util.List;
+
+/**
+ * AI外呼知识库Service接口
+ *
+ * @author fs
+ * @date 2026-01-30
+ */
+public interface IAiSopCallKnowledgeBaseService extends IService<AiSopCallKnowledgeBase>{
+    /**
+     * 查询AI外呼知识库
+     *
+     * @param id AI外呼知识库主键
+     * @return AI外呼知识库
+     */
+    AiSopCallKnowledgeBase selectAiSopCallKnowledgeBaseById(Long id);
+
+    /**
+     * 查询AI外呼知识库列表
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return AI外呼知识库集合
+     */
+    List<AiSopCallKnowledgeBase> selectAiSopCallKnowledgeBaseList(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 新增AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    R insertAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 修改AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    R updateAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase);
+
+    /**
+     * 批量删除AI外呼知识库
+     *
+     * @param ids 需要删除的AI外呼知识库主键集合
+     * @return 结果
+     */
+    R deleteAiSopCallKnowledgeBaseByIds(Long[] ids);
+
+    /**
+     * 删除AI外呼知识库信息
+     *
+     * @param id AI外呼知识库主键
+     * @return 结果
+     */
+    R deleteAiSopCallKnowledgeBaseById(Long id);
+
+    R idAndNameList();
+}

+ 62 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallLineConfigService.java

@@ -0,0 +1,62 @@
+package com.fs.aiCallSop.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallLineConfig;
+
+import java.util.List;
+
+/**
+ * ai呼叫线路配置Service接口
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+public interface IAiSopCallLineConfigService extends IService<AiSopCallLineConfig>{
+    /**
+     * 查询ai呼叫线路配置
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return ai呼叫线路配置
+     */
+    AiSopCallLineConfig selectAiSopCallLineConfigById(Long id);
+
+    /**
+     * 查询ai呼叫线路配置列表
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return ai呼叫线路配置集合
+     */
+    List<AiSopCallLineConfig> selectAiSopCallLineConfigList(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 新增ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    int insertAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 修改ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    int updateAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig);
+
+    /**
+     * 批量删除ai呼叫线路配置
+     * 
+     * @param ids 需要删除的ai呼叫线路配置主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallLineConfigByIds(Long[] ids);
+
+    /**
+     * 删除ai呼叫线路配置信息
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return 结果
+     */
+    int deleteAiSopCallLineConfigById(Long id);
+}

+ 63 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallMsgLogService.java

@@ -0,0 +1,63 @@
+package com.fs.aiCallSop.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallMsgLog;
+
+import java.util.List;
+
+/**
+ * ai外呼记录Service接口
+ *
+ * @author fs
+ * @date 2025-12-31
+ */
+public interface IAiSopCallMsgLogService extends IService<AiSopCallMsgLog> {
+    /**
+     * 查询ai外呼记录
+     *
+     * @param id ai外呼记录主键
+     * @return ai外呼记录
+     */
+    AiSopCallMsgLog selectAiSopCallMsgLogById(Long id);
+
+    /**
+     * 查询ai外呼记录列表
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return ai外呼记录集合
+     */
+    List<AiSopCallMsgLog> selectAiSopCallMsgLogList(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 新增ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    int insertAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 修改ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    int updateAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog);
+
+    /**
+     * 批量删除ai外呼记录
+     *
+     * @param ids 需要删除的ai外呼记录主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallMsgLogByIds(Long[] ids);
+
+    /**
+     * 删除ai外呼记录信息
+     *
+     * @param id ai外呼记录主键
+     * @return 结果
+     */
+    int deleteAiSopCallMsgLogById(Long id);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallRoleService.java

@@ -0,0 +1,62 @@
+package com.fs.aiCallSop.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallRole;
+
+import java.util.List;
+
+/**
+ * AI外呼角色Service接口
+ *
+ * @author fs
+ * @date 2026-01-14
+ */
+public interface IAiSopCallRoleService extends IService<AiSopCallRole>{
+    /**
+     * 查询AI外呼角色
+     *
+     * @param id AI外呼角色主键
+     * @return AI外呼角色
+     */
+    AiSopCallRole selectAiSopCallRoleById(Long id);
+
+    /**
+     * 查询AI外呼角色列表
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return AI外呼角色集合
+     */
+    List<AiSopCallRole> selectAiSopCallRoleList(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 新增AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    int insertAiSopCallRole(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 修改AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    int updateAiSopCallRole(AiSopCallRole aiSopCallRole);
+
+    /**
+     * 批量删除AI外呼角色
+     *
+     * @param ids 需要删除的AI外呼角色主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallRoleByIds(Long[] ids);
+
+    /**
+     * 删除AI外呼角色信息
+     *
+     * @param id AI外呼角色主键
+     * @return 结果
+     */
+    int deleteAiSopCallRoleById(Long id);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallStaffModelsService.java

@@ -0,0 +1,62 @@
+package com.fs.aiCallSop.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallStaffModels;
+
+import java.util.List;
+
+/**
+ * 外呼模型模型Service接口
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface IAiSopCallStaffModelsService extends IService<AiSopCallStaffModels>{
+    /**
+     * 查询外呼模型模型
+     *
+     * @param id 外呼模型模型主键
+     * @return 外呼模型模型
+     */
+    AiSopCallStaffModels selectAiSopCallStaffModelsById(Long id);
+
+    /**
+     * 查询外呼模型模型列表
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 外呼模型模型集合
+     */
+    List<AiSopCallStaffModels> selectAiSopCallStaffModelsList(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 新增外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    int insertAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 修改外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    int updateAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels);
+
+    /**
+     * 批量删除外呼模型模型
+     *
+     * @param ids 需要删除的外呼模型模型主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallStaffModelsByIds(Long[] ids);
+
+    /**
+     * 删除外呼模型模型信息
+     *
+     * @param id 外呼模型模型主键
+     * @return 结果
+     */
+    int deleteAiSopCallStaffModelsById(Long id);
+}

+ 74 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/IAiSopCallUserService.java

@@ -0,0 +1,74 @@
+package com.fs.aiCallSop.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.aiCallSop.domain.AiSopCallUser;
+import com.fs.aiCallSop.param.ModelListRequest;
+import com.fs.aiCallSop.param.ModelListResponse;
+import com.fs.aiCallSop.param.VoicesDictRes;
+import com.fs.aiCallSop.param.VoicesDictResPage;
+import com.fs.common.core.domain.R;
+
+import java.util.List;
+
+/**
+ * 员工角色Service接口
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface IAiSopCallUserService extends IService<AiSopCallUser> {
+    /**
+     * 查询员工角色
+     *
+     * @param id 员工角色主键
+     * @return 员工角色
+     */
+    AiSopCallUser selectAiSopCallUserById(Long id);
+    R selectAiSopCallUserByStaffNumber(String staffNumber);
+
+    /**
+     * 查询员工角色列表
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 员工角色集合
+     */
+    List<AiSopCallUser> selectAiSopCallUserList(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 新增员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    R insertAiSopCallUser(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 修改员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    R updateAiSopCallUser(AiSopCallUser aiSopCallUser);
+
+    /**
+     * 批量删除员工角色
+     *
+     * @param ids 需要删除的员工角色主键集合
+     * @return 结果
+     */
+    int deleteAiSopCallUserByIds(Long[] ids);
+
+    /**
+     * 删除员工角色信息
+     *
+     * @param id 员工角色主键
+     * @return 结果
+     */
+    R deleteAiSopCallUserById(Long id);
+
+    ModelListResponse getModeList(ModelListRequest request);
+    VoicesDictRes getVoicesDict(ModelListRequest request);
+
+    VoicesDictResPage getVoicesDictByModelId(ModelListRequest request);
+}

+ 236 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallKnowledgeBaseServiceImpl.java

@@ -0,0 +1,236 @@
+package com.fs.aiCallSop.service.impl;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.commonConstant.AiSopCallRemoteUrlConstant;
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import com.fs.aiCallSop.mapper.AiSopCallKnowledgeBaseMapper;
+import com.fs.aiCallSop.mapper.AiSopCallUserMapper;
+import com.fs.aiCallSop.param.AiSopCallKnowledgeBaseRequest;
+import com.fs.aiCallSop.param.ModelListResponse;
+import com.fs.aiCallSop.param.ModelListResult;
+import com.fs.aiCallSop.service.IAiSopCallKnowledgeBaseService;
+import com.fs.common.core.domain.R;
+import io.jsonwebtoken.lang.Collections;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+/**
+ * AI外呼知识库Service业务层处理
+ *
+ * @author fs
+ * @date 2026-01-30
+ */
+@Slf4j
+@Service
+public class AiSopCallKnowledgeBaseServiceImpl extends ServiceImpl<AiSopCallKnowledgeBaseMapper, AiSopCallKnowledgeBase> implements IAiSopCallKnowledgeBaseService {
+
+    @Autowired
+    private AiSopCallUserMapper aiSopCallUserMapper;
+
+    /**
+     * 查询AI外呼知识库
+     *
+     * @param id AI外呼知识库主键
+     * @return AI外呼知识库
+     */
+    @Override
+    public AiSopCallKnowledgeBase selectAiSopCallKnowledgeBaseById(Long id) {
+        return baseMapper.selectAiSopCallKnowledgeBaseById(id);
+    }
+
+
+    @Override
+    public R idAndNameList() {
+        List<AiSopCallKnowledgeBase> list = baseMapper.idAndNameList();
+        Map<String, Object> map = new HashMap<>();
+        map.put("list", list);
+        return R.ok(map);
+    }
+
+    /**
+     * 查询AI外呼知识库列表
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return AI外呼知识库
+     */
+    @Override
+    public List<AiSopCallKnowledgeBase> selectAiSopCallKnowledgeBaseList(AiSopCallKnowledgeBase aiSopCallKnowledgeBase) {
+        return baseMapper.selectAiSopCallKnowledgeBaseList(aiSopCallKnowledgeBase);
+    }
+
+    /**
+     * 新增AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    @Override
+    public R insertAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase) {
+        try {
+            AiSopCallKnowledgeBaseRequest request = new AiSopCallKnowledgeBaseRequest();
+            request = request.conversion(aiSopCallKnowledgeBase);
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(request);
+            String result = HttpUtil.post(AiSopCallRemoteUrlConstant.GET_KNOWLEDGE_CREATE_URL, jsonBody, 5000);
+            // 解析响应
+            ModelListResult bean = JSONUtil.toBean(result, ModelListResult.class);
+            if (bean.getCode() == 0) {
+                aiSopCallKnowledgeBase.setRemoteKnowledgeId(Long.valueOf(bean.getData()));
+                aiSopCallKnowledgeBase.setCreateTime(new Date());
+                baseMapper.insertAiSopCallKnowledgeBase(aiSopCallKnowledgeBase);
+                return R.ok();
+            } else {
+                return R.error("新增知识库失败:" + bean.getCode() + ":" + bean.getMsg());
+            }
+        } catch (Exception e) {
+            log.error("调用新增知识库接口失败", e);
+            return R.error("新增知识库失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 修改AI外呼知识库
+     *
+     * @param aiSopCallKnowledgeBase AI外呼知识库
+     * @return 结果
+     */
+    @Override
+    public R updateAiSopCallKnowledgeBase(AiSopCallKnowledgeBase aiSopCallKnowledgeBase) {
+        try {
+            AiSopCallKnowledgeBaseRequest request = new AiSopCallKnowledgeBaseRequest();
+            request = request.conversion(aiSopCallKnowledgeBase);
+            String jsonBody = JSONUtil.toJsonStr(request);
+            String result = HttpRequest.put(AiSopCallRemoteUrlConstant.GET_KNOWLEDGE_URL)
+                    .timeout(5000)
+                    .body(jsonBody)
+                    .header("Content-Type", "application/json; charset=UTF-8")
+                    .execute()
+                    .body();
+            // 解析响应
+            ModelListResult bean = JSONUtil.toBean(result, ModelListResult.class);
+            if (bean.getCode() == 0) {
+                aiSopCallKnowledgeBase.setUpdateTime(new Date());
+                baseMapper.updateAiSopCallKnowledgeBase(aiSopCallKnowledgeBase);
+                return R.ok();
+            } else {
+                return R.error("修改知识库失败:" + bean.getCode() + ":" + bean.getMsg());
+            }
+        } catch (Exception e) {
+            log.error("调用修改知识库接口失败", e);
+            return R.error("修改知识库失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量删除AI外呼知识库
+     *
+     * @param ids 需要删除的AI外呼知识库主键
+     * @return 结果
+     */
+    @Override
+    public R deleteAiSopCallKnowledgeBaseByIds(Long[] ids) {
+        if (ids == null || ids.length == 0) {
+            return R.error("请选择要删除的知识库");
+        }
+        // 将ids转换为List并查询数据库获取对应的remoteKnowledgeId
+        List<Long> idsList = Arrays.asList(ids);
+        List<AiSopCallKnowledgeBase> databaseData = baseMapper.selectBatchIds(idsList);
+
+        if (Collections.isEmpty(databaseData)) {
+            return R.error("未找到对应的知识库记录");
+        }
+        // 提取remoteKnowledgeId并转换为Long数组
+        Long[] remoteKnowledgeIds = databaseData.stream()
+                .map(AiSopCallKnowledgeBase::getRemoteKnowledgeId)
+                .toArray(Long[]::new);
+
+        if (remoteKnowledgeIds.length == 0) {
+            return R.error("未找到对应的远程知识库ID");
+        }
+        try {
+            Map<String, Object> requestMap = new HashMap<>();
+            requestMap.put("ids", remoteKnowledgeIds);
+            String jsonBody = JSONUtil.toJsonStr(requestMap);
+            // 发送 DELETE 请求
+            String result = HttpRequest.delete(AiSopCallRemoteUrlConstant.GET_KNOWLEDGE_DELETE_BATCH_URL)
+                    .timeout(5000)
+                    .body(jsonBody)
+                    .header("Content-Type", "application/json")
+                    .header("Accept", "application/json")
+                    .execute()
+                    .body();
+
+            // 解析响应
+            ModelListResponse bean = JSONUtil.toBean(result, ModelListResponse.class);
+
+            if (bean.getCode() == 0) {
+                int i = baseMapper.deleteAiSopCallKnowledgeBaseByIds(ids);
+                if(i>0){
+                    //解绑已删除的知识库
+                    aiSopCallUserMapper.unbindCallUserKnowledge(ids);
+                    return R.ok("批量删除知识库成功");
+                }else{
+                    return R.error("批量删除知识库失败");
+                }
+            } else {
+                return R.error("批量删除知识库失败:" + bean.getCode() + ":" + bean.getMsg());
+            }
+        } catch (Exception e) {
+            return R.error("批量删除知识库失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除AI外呼知识库信息
+     *
+     * @param id AI外呼知识库主键
+     * @return 结果
+     */
+    @Override
+    public R deleteAiSopCallKnowledgeBaseById(Long id) {
+        if (id == null) {
+            return R.error("请选择要删除的知识库");
+        }
+        AiSopCallKnowledgeBase aiSopCallKnowledgeBase = baseMapper.selectAiSopCallKnowledgeBaseById(id);
+
+        if (aiSopCallKnowledgeBase == null) {
+            return R.error("未找到对应的知识库记录");
+        }
+        if (aiSopCallKnowledgeBase.getRemoteKnowledgeId() == null || aiSopCallKnowledgeBase.getRemoteKnowledgeId() <= 0) {
+            return R.error("未找到对应的远程知识库ID");
+        }
+        try {
+            // 发送 DELETE 请求
+            String result = HttpRequest.delete(AiSopCallRemoteUrlConstant.GET_KNOWLEDGE_URL + "/" + aiSopCallKnowledgeBase.getRemoteKnowledgeId())
+                    .timeout(5000)
+                    .header("Content-Type", "application/json")
+                    .header("Accept", "application/json")
+                    .execute()
+                    .body();
+
+            // 解析响应
+            ModelListResponse bean = JSONUtil.toBean(result, ModelListResponse.class);
+            if (bean.getCode() == 0) {
+                int i = baseMapper.deleteAiSopCallKnowledgeBaseById(id);
+                if(i> 0){
+                    Long[] ids = new Long[]{id};
+                    //解绑已删除的知识库
+                    aiSopCallUserMapper.unbindCallUserKnowledge(ids);
+                    return R.ok("删除知识库成功");
+                }
+                return R.error("删除知识库失败");
+            } else {
+                return R.error("删除知识库失败:" + bean.getCode() + ":" + bean.getMsg());
+            }
+        } catch (Exception e) {
+            return R.error("删除知识库失败: " + e.getMessage());
+        }
+    }
+
+}

+ 94 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallLineConfigServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.aiCallSop.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.domain.AiSopCallLineConfig;
+import com.fs.aiCallSop.mapper.AiSopCallLineConfigMapper;
+import com.fs.aiCallSop.service.IAiSopCallLineConfigService;
+import com.fs.common.utils.DateUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * ai呼叫线路配置Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+@Service
+public class AiSopCallLineConfigServiceImpl extends ServiceImpl<AiSopCallLineConfigMapper, AiSopCallLineConfig> implements IAiSopCallLineConfigService {
+
+    /**
+     * 查询ai呼叫线路配置
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return ai呼叫线路配置
+     */
+    @Override
+    public AiSopCallLineConfig selectAiSopCallLineConfigById(Long id)
+    {
+        return baseMapper.selectAiSopCallLineConfigById(id);
+    }
+
+    /**
+     * 查询ai呼叫线路配置列表
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return ai呼叫线路配置
+     */
+    @Override
+    public List<AiSopCallLineConfig> selectAiSopCallLineConfigList(AiSopCallLineConfig aiSopCallLineConfig)
+    {
+        return baseMapper.selectAiSopCallLineConfigList(aiSopCallLineConfig);
+    }
+
+    /**
+     * 新增ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    @Override
+    public int insertAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig)
+    {
+        aiSopCallLineConfig.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertAiSopCallLineConfig(aiSopCallLineConfig);
+    }
+
+    /**
+     * 修改ai呼叫线路配置
+     * 
+     * @param aiSopCallLineConfig ai呼叫线路配置
+     * @return 结果
+     */
+    @Override
+    public int updateAiSopCallLineConfig(AiSopCallLineConfig aiSopCallLineConfig)
+    {
+        aiSopCallLineConfig.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateAiSopCallLineConfig(aiSopCallLineConfig);
+    }
+
+    /**
+     * 批量删除ai呼叫线路配置
+     * 
+     * @param ids 需要删除的ai呼叫线路配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallLineConfigByIds(Long[] ids)
+    {
+        return baseMapper.deleteAiSopCallLineConfigByIds(ids);
+    }
+
+    /**
+     * 删除ai呼叫线路配置信息
+     * 
+     * @param id ai呼叫线路配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallLineConfigById(Long id)
+    {
+        return baseMapper.deleteAiSopCallLineConfigById(id);
+    }
+}

+ 91 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallMsgLogServiceImpl.java

@@ -0,0 +1,91 @@
+package com.fs.aiCallSop.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.domain.AiSopCallMsgLog;
+import com.fs.aiCallSop.mapper.AiSopCallMsgLogMapper;
+import com.fs.aiCallSop.service.IAiSopCallMsgLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * ai外呼记录Service业务层处理
+ *
+ * @author fs
+ * @date 2025-12-31
+ */
+@Service
+public class AiSopCallMsgLogServiceImpl extends ServiceImpl<AiSopCallMsgLogMapper, AiSopCallMsgLog> implements IAiSopCallMsgLogService {
+
+    /**
+     * 查询ai外呼记录
+     *
+     * @param id ai外呼记录主键
+     * @return ai外呼记录
+     */
+    @Override
+    public AiSopCallMsgLog selectAiSopCallMsgLogById(Long id)
+    {
+        return baseMapper.selectAiSopCallMsgLogById(id);
+    }
+
+    /**
+     * 查询ai外呼记录列表
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return ai外呼记录
+     */
+    @Override
+    public List<AiSopCallMsgLog> selectAiSopCallMsgLogList(AiSopCallMsgLog aiSopCallMsgLog)
+    {
+        return baseMapper.selectAiSopCallMsgLogList(aiSopCallMsgLog);
+    }
+
+    /**
+     * 新增ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    @Override
+    public int insertAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog)
+    {
+        return baseMapper.insertAiSopCallMsgLog(aiSopCallMsgLog);
+    }
+
+    /**
+     * 修改ai外呼记录
+     *
+     * @param aiSopCallMsgLog ai外呼记录
+     * @return 结果
+     */
+    @Override
+    public int updateAiSopCallMsgLog(AiSopCallMsgLog aiSopCallMsgLog)
+    {
+        return baseMapper.updateAiSopCallMsgLog(aiSopCallMsgLog);
+    }
+
+    /**
+     * 批量删除ai外呼记录
+     *
+     * @param ids 需要删除的ai外呼记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallMsgLogByIds(Long[] ids)
+    {
+        return baseMapper.deleteAiSopCallMsgLogByIds(ids);
+    }
+
+    /**
+     * 删除ai外呼记录信息
+     *
+     * @param id ai外呼记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallMsgLogById(Long id)
+    {
+        return baseMapper.deleteAiSopCallMsgLogById(id);
+    }
+}

+ 94 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallRoleServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.aiCallSop.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.domain.AiSopCallRole;
+import com.fs.aiCallSop.mapper.AiSopCallRoleMapper;
+import com.fs.aiCallSop.service.IAiSopCallRoleService;
+import com.fs.common.utils.DateUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI外呼角色Service业务层处理
+ *
+ * @author fs
+ * @date 2026-01-14
+ */
+@Service
+public class AiSopCallRoleServiceImpl extends ServiceImpl<AiSopCallRoleMapper, AiSopCallRole> implements IAiSopCallRoleService {
+
+    /**
+     * 查询AI外呼角色
+     *
+     * @param id AI外呼角色主键
+     * @return AI外呼角色
+     */
+    @Override
+    public AiSopCallRole selectAiSopCallRoleById(Long id)
+    {
+        return baseMapper.selectAiSopCallRoleById(id);
+    }
+
+    /**
+     * 查询AI外呼角色列表
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return AI外呼角色
+     */
+    @Override
+    public List<AiSopCallRole> selectAiSopCallRoleList(AiSopCallRole aiSopCallRole)
+    {
+        return baseMapper.selectAiSopCallRoleList(aiSopCallRole);
+    }
+
+    /**
+     * 新增AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    @Override
+    public int insertAiSopCallRole(AiSopCallRole aiSopCallRole)
+    {
+        aiSopCallRole.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertAiSopCallRole(aiSopCallRole);
+    }
+
+    /**
+     * 修改AI外呼角色
+     *
+     * @param aiSopCallRole AI外呼角色
+     * @return 结果
+     */
+    @Override
+    public int updateAiSopCallRole(AiSopCallRole aiSopCallRole)
+    {
+        aiSopCallRole.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateAiSopCallRole(aiSopCallRole);
+    }
+
+    /**
+     * 批量删除AI外呼角色
+     *
+     * @param ids 需要删除的AI外呼角色主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallRoleByIds(Long[] ids)
+    {
+        return baseMapper.deleteAiSopCallRoleByIds(ids);
+    }
+
+    /**
+     * 删除AI外呼角色信息
+     *
+     * @param id AI外呼角色主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallRoleById(Long id)
+    {
+        return baseMapper.deleteAiSopCallRoleById(id);
+    }
+}

+ 91 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallStaffModelsServiceImpl.java

@@ -0,0 +1,91 @@
+package com.fs.aiCallSop.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.domain.AiSopCallStaffModels;
+import com.fs.aiCallSop.mapper.AiSopCallStaffModelsMapper;
+import com.fs.aiCallSop.service.IAiSopCallStaffModelsService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 外呼模型模型Service业务层处理
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@Service
+public class AiSopCallStaffModelsServiceImpl extends ServiceImpl<AiSopCallStaffModelsMapper, AiSopCallStaffModels> implements IAiSopCallStaffModelsService {
+
+    /**
+     * 查询外呼模型模型
+     *
+     * @param id 外呼模型模型主键
+     * @return 外呼模型模型
+     */
+    @Override
+    public AiSopCallStaffModels selectAiSopCallStaffModelsById(Long id)
+    {
+        return baseMapper.selectAiSopCallStaffModelsById(id);
+    }
+
+    /**
+     * 查询外呼模型模型列表
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 外呼模型模型
+     */
+    @Override
+    public List<AiSopCallStaffModels> selectAiSopCallStaffModelsList(AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        return baseMapper.selectAiSopCallStaffModelsList(aiSopCallStaffModels);
+    }
+
+    /**
+     * 新增外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    @Override
+    public int insertAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        return baseMapper.insertAiSopCallStaffModels(aiSopCallStaffModels);
+    }
+
+    /**
+     * 修改外呼模型模型
+     *
+     * @param aiSopCallStaffModels 外呼模型模型
+     * @return 结果
+     */
+    @Override
+    public int updateAiSopCallStaffModels(AiSopCallStaffModels aiSopCallStaffModels)
+    {
+        return baseMapper.updateAiSopCallStaffModels(aiSopCallStaffModels);
+    }
+
+    /**
+     * 批量删除外呼模型模型
+     *
+     * @param ids 需要删除的外呼模型模型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallStaffModelsByIds(Long[] ids)
+    {
+        return baseMapper.deleteAiSopCallStaffModelsByIds(ids);
+    }
+
+    /**
+     * 删除外呼模型模型信息
+     *
+     * @param id 外呼模型模型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallStaffModelsById(Long id)
+    {
+        return baseMapper.deleteAiSopCallStaffModelsById(id);
+    }
+}

+ 340 - 0
fs-service/src/main/java/com/fs/aiCallSop/service/impl/AiSopCallUserServiceImpl.java

@@ -0,0 +1,340 @@
+package com.fs.aiCallSop.service.impl;
+
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.aiCallSop.commonConstant.AiSopCallRemoteUrlConstant;
+import com.fs.aiCallSop.domain.AiSopCallKnowledgeBase;
+import com.fs.aiCallSop.domain.AiSopCallUser;
+import com.fs.aiCallSop.mapper.AiSopCallKnowledgeBaseMapper;
+import com.fs.aiCallSop.mapper.AiSopCallUserMapper;
+import com.fs.aiCallSop.param.*;
+import com.fs.aiCallSop.service.IAiSopCallUserService;
+import com.fs.common.core.domain.R;
+import com.fs.company.mapper.CompanyUserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 员工角色Service业务层处理
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@Slf4j
+@Service
+public class AiSopCallUserServiceImpl extends ServiceImpl<AiSopCallUserMapper, AiSopCallUser> implements IAiSopCallUserService {
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private AiSopCallKnowledgeBaseMapper aiSopCallKnowledgeBaseMapper;
+
+    /**
+     * 查询员工角色
+     *
+     * @param id 员工角色主键
+     * @return 员工角色
+     */
+    @Override
+    public AiSopCallUser selectAiSopCallUserById(Long id)
+    {
+        return baseMapper.selectAiSopCallUserById(id);
+    }
+
+    @Override
+    public R selectAiSopCallUserByStaffNumber(String staffNumber) {
+        String url = AiSopCallRemoteUrlConstant.GET_STAFF_NUMBER_URL + "/" + staffNumber;
+
+        try {
+
+            // 发送 DELETE 请求
+            String result = HttpRequest.delete(url)
+                    .timeout(5000)
+                    .header("Content-Type", "application/json")
+                    .header("Accept", "application/json")
+                    .execute()
+                    .body();
+
+            // 解析响应
+            ModelListResult bean = JSONUtil.toBean(result, ModelListResult.class);
+
+            if (bean.getCode() == 0) {
+
+                AiSopCallUser userResult = JSONUtil.toBean(bean.getData(),AiSopCallUser.class);
+
+                return R.ok().put("data",userResult);
+            } else {
+                return R.error("查询信息失败:"+bean.getCode()+":"+bean.getMsg());
+            }
+
+        } catch (Exception e) {
+
+            return R.error("查询信息失败" + e.getMessage());
+        }
+
+    }
+
+    /**
+     * 查询员工角色列表
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 员工角色
+     */
+    @Override
+    public List<AiSopCallUser> selectAiSopCallUserList(AiSopCallUser aiSopCallUser)
+    {
+        return baseMapper.selectAiSopCallUserList(aiSopCallUser);
+    }
+
+    /**
+     * 新增员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    @Override
+    public R insertAiSopCallUser(AiSopCallUser aiSopCallUser)
+    {
+
+        String url = AiSopCallRemoteUrlConstant.GET_STAFF_CREATE_URL;
+
+        try {
+
+            aiSopCallUser.setStaff_name(aiSopCallUser.getStaffName());
+            aiSopCallUser.setStaff_number(aiSopCallUser.getStaffNumber());
+            aiSopCallUser.setIs_clone_voice(aiSopCallUser.getIsCloneVoice());
+            aiSopCallUser.setIs_force_clone(aiSopCallUser.getIsForceClone());
+            aiSopCallUser.setVoice_id(aiSopCallUser.getVoiceId());
+            aiSopCallUser.setVoice_path(aiSopCallUser.getVoicePath());
+            aiSopCallUser.setVoice_text(aiSopCallUser.getVoiceText());
+
+            AiSopCallUser.staff_models models=new AiSopCallUser.staff_models();
+            models.setModel_id(aiSopCallUser.getStaffModelsId());
+            models.setModel_function(aiSopCallUser.getModel_function());
+            models.setModel_factory(aiSopCallUser.getModel_factory());
+            aiSopCallUser.getStaff_models().add(models);
+            if(aiSopCallUser.getKnowledgeBaseId() != null){
+                AiSopCallKnowledgeBase callKnowledgeBase = aiSopCallKnowledgeBaseMapper.selectAiSopCallKnowledgeBaseById(aiSopCallUser.getKnowledgeBaseId());
+                if(callKnowledgeBase != null){
+                    aiSopCallUser.setKnowledge_resource_id(callKnowledgeBase.getRemoteKnowledgeId());
+                }
+            }
+
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(aiSopCallUser);
+
+            String result = HttpUtil.post(url, jsonBody, 5000);
+
+            // 解析响应
+            ModelListResult bean = JSONUtil.toBean(result, ModelListResult.class);
+
+            if (bean.getCode()==0){
+                aiSopCallUser.setId(Long.valueOf(bean.getData()));
+                int i = baseMapper.insertAiSopCallUser(aiSopCallUser);
+                companyUserMapper.updateCompanyUserByAiSopCall(aiSopCallUser.getCompanyUserId(),aiSopCallUser.getId());
+                return R.ok();
+            }else {
+                return R.error("新增失败:"+bean.getCode()+":"+bean.getMsg());
+            }
+
+
+        } catch (Exception e) {
+            log.error("调用模型列表接口失败", e);
+            ModelListResponse response = new ModelListResponse();
+            response.setCode(500);
+            response.setMsg("列表失败:" + e.getMessage());
+
+            return R.error("新增失败"+e.getMessage());
+        }
+
+    }
+
+    /**
+     * 修改员工角色
+     *
+     * @param aiSopCallUser 员工角色
+     * @return 结果
+     */
+    @Override
+    public R updateAiSopCallUser(AiSopCallUser aiSopCallUser)
+    {
+        String url = AiSopCallRemoteUrlConstant.GET_STAFF_URL;
+        try {
+
+            aiSopCallUser.setStaff_name(aiSopCallUser.getStaffName());
+            aiSopCallUser.setStaff_number(aiSopCallUser.getStaffNumber());
+            aiSopCallUser.setIs_clone_voice(aiSopCallUser.getIsCloneVoice());
+            aiSopCallUser.setIs_force_clone(aiSopCallUser.getIsForceClone());
+            aiSopCallUser.setVoice_id(aiSopCallUser.getVoiceId());
+            aiSopCallUser.setVoice_path(aiSopCallUser.getVoicePath());
+            aiSopCallUser.setVoice_text(aiSopCallUser.getVoiceText());
+
+            AiSopCallUser.staff_models models=new AiSopCallUser.staff_models();
+            models.setModel_id(aiSopCallUser.getStaffModelsId());
+            models.setModel_function(aiSopCallUser.getModel_function());
+            models.setStaff_id(aiSopCallUser.getId());
+            aiSopCallUser.getStaff_models().add(models);
+            if(aiSopCallUser.getKnowledgeBaseId() != null){
+                AiSopCallKnowledgeBase callKnowledgeBase = aiSopCallKnowledgeBaseMapper.selectAiSopCallKnowledgeBaseById(aiSopCallUser.getKnowledgeBaseId());
+                if(callKnowledgeBase != null){
+                    aiSopCallUser.setKnowledge_resource_id(callKnowledgeBase.getRemoteKnowledgeId());
+                }
+            }
+
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(aiSopCallUser);
+
+            // 方式1:使用 HttpRequest
+            String result = HttpRequest.put(url)
+                    .timeout(5000)
+                    .body(jsonBody)
+                    .header("Content-Type", "application/json; charset=UTF-8")
+                    .execute()
+                    .body();
+
+            // 解析响应
+            ModelListResponse bean = JSONUtil.toBean(result, ModelListResponse.class);
+
+            if (bean.getCode()==0){
+                int i = baseMapper.updateAiSopCallUser(aiSopCallUser);
+                return R.ok();
+            }else {
+                return R.error("修改失败:"+bean.getCode()+":"+bean.getMsg());
+            }
+
+
+        } catch (Exception e) {
+            log.error("调用模型列表接口失败", e);
+            ModelListResponse response = new ModelListResponse();
+            response.setCode(500);
+            response.setMsg("列表失败:" + e.getMessage());
+
+            return R.error("新增失败"+e.getMessage());
+        }
+
+    }
+
+    /**
+     * 批量删除员工角色
+     *
+     * @param ids 需要删除的员工角色主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiSopCallUserByIds(Long[] ids)
+    {
+        return baseMapper.deleteAiSopCallUserByIds(ids);
+    }
+
+    /**
+     * 删除员工角色信息
+     *
+     * @param id 员工角色主键
+     * @return 结果
+     */
+    @Override
+    public R deleteAiSopCallUserById(Long id)
+    {
+        String url = AiSopCallRemoteUrlConstant.GET_STAFF_URL + "/" + id;
+
+        try {
+
+            // 发送 DELETE 请求
+            String result = HttpRequest.delete(url)
+                    .timeout(5000)
+                    .header("Content-Type", "application/json")
+                    .header("Accept", "application/json")
+                    .execute()
+                    .body();
+
+            // 解析响应
+            ModelListResponse bean = JSONUtil.toBean(result, ModelListResponse.class);
+
+            if (bean.getCode() == 0) {
+                int i = baseMapper.deleteAiSopCallUserById(id);
+                companyUserMapper.deleteCompanyUserByAiSopCallId(id);
+                return R.ok("删除成功");
+            } else {
+                return R.error("删除失败:"+bean.getCode()+":"+bean.getMsg());
+            }
+
+        } catch (Exception e) {
+            log.error("删除员工失败,ID: {}", id, e);
+            return R.error("删除失败: " + e.getMessage());
+        }
+
+
+    }
+
+    @Override
+    public ModelListResponse getModeList(ModelListRequest request) {
+        String url = AiSopCallRemoteUrlConstant.GET_MODEL_LIST_URL;
+
+        try {
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(request);
+
+            String result = HttpUtil.post(url, jsonBody, 5000);
+
+            // 解析响应
+            return JSONUtil.toBean(result, ModelListResponse.class);
+
+        } catch (Exception e) {
+            log.error("调用模型列表接口失败", e);
+            ModelListResponse response = new ModelListResponse();
+            response.setCode(500);
+            response.setMsg("获取模型列表失败:" + e.getMessage());
+            return response;
+        }
+    }
+
+    @Override
+    public VoicesDictRes getVoicesDict(ModelListRequest request) {
+        String url = AiSopCallRemoteUrlConstant.GET_VOICES_LIST_ALL_URL;
+
+        try {
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(request);
+
+            String result = HttpUtil.post(url, jsonBody, 5000);
+
+            // 解析响应
+            return JSONUtil.toBean(result, VoicesDictRes.class);
+
+        } catch (Exception e) {
+            log.error("调用模型列表接口失败", e);
+            VoicesDictRes response = new VoicesDictRes();
+            response.setCode(500);
+            response.setMsg("获取模型列表失败:" + e.getMessage());
+            return response;
+        }
+    }
+
+    @Override
+    public VoicesDictResPage getVoicesDictByModelId(ModelListRequest request) {
+        String url = AiSopCallRemoteUrlConstant.GET_VOICES_LIST_URL;
+
+        try {
+            // 使用 Hutool 发送 POST 请求
+            String jsonBody = JSONUtil.toJsonStr(request);
+
+            String result = HttpUtil.post(url, jsonBody, 5000);
+
+            // 解析响应
+            return JSONUtil.toBean(result, VoicesDictResPage.class);
+
+        } catch (Exception e) {
+            log.error("调用模型列表接口失败", e);
+            VoicesDictResPage response = new VoicesDictResPage();
+            response.setCode(500);
+            response.setMsg("获取模型列表失败:" + e.getMessage());
+            return response;
+        }
+    }
+}

+ 240 - 0
fs-service/src/main/java/com/fs/aiCallSop/vo/AiCallSopRepVo.java

@@ -0,0 +1,240 @@
+package com.fs.aiCallSop.vo;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @Author:peicj
+ * @Description: Ai外呼请求返回
+ * @Date:2026/1/23 9:29
+ */
+@Data
+public class AiCallSopRepVo {
+
+    private Integer code;
+    private String msg;
+    private DataDetails data;
+
+    @Data
+    public static class DataDetails {
+        private SessionDetails session;
+        private List<MessagesDetails> messages;
+        private List<RecordingsDetails> recordings;
+
+    }
+
+    @Data
+    public static class SessionDetails {
+        private Integer id;
+        private String session_id;
+        private String employee_id;
+        private String room_name;
+        private String started_at;
+        private String ended_at;
+        private Integer duration_ms;
+        private String created_at;
+
+    }
+
+    @Data
+    public static class MessagesDetails {
+        private Integer id;
+        private String session_id;
+        private String role;
+        private String text;
+        private String chat_id;
+        private Long ts;
+
+    }
+
+    @Data
+    public static class RecordingsDetails {
+        private Integer id;
+        private String session_id;
+        private String recording_type;
+        private String file_url;
+        private Object started_at;
+        private Object ended_at;
+        private Integer duration_ms;
+        private Integer file_size;
+        private String created_at;
+
+    }
+
+
+    public static void main(String[] args) {
+        String json = "{\n" +
+                "  \"code\": 0,\n" +
+                "  \"msg\": \"success\",\n" +
+                "  \"data\": {\n" +
+                "    \"session\": {\n" +
+                "      \"id\": 3,\n" +
+                "      \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "      \"employee_id\": \"yangge\",\n" +
+                "      \"room_name\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "      \"started_at\": null,\n" +
+                "      \"ended_at\": null,\n" +
+                "      \"duration_ms\": 91122,\n" +
+                "      \"created_at\": \"2026-01-22 23:00:58\"\n" +
+                "    },\n" +
+                "    \"messages\": [\n" +
+                "      {\n" +
+                "        \"id\": 1,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"喂,您好\",\n" +
+                "        \"chat_id\": \"1769093957522841000\",\n" +
+                "        \"ts\": 1769093957522\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 2,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"喂,你好。\",\n" +
+                "        \"chat_id\": \"1769093962297946000\",\n" +
+                "        \"ts\": 1769093962297\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 3,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"您好,咱们的养生课已更新,看到您今天没打卡,特意提醒您观看!\",\n" +
+                "        \"chat_id\": \"1769093963076885000\",\n" +
+                "        \"ts\": 1769093963076\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 4,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"我没时间。\",\n" +
+                "        \"chat_id\": \"1769093970280111000\",\n" +
+                "        \"ts\": 1769093970280\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 5,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"我理解您忙。课程每天凌晨推送,随时可以分段看,链接会自动记录进度,您抽空看看就行,时间很自由。\",\n" +
+                "        \"chat_id\": \"1769093970790866000\",\n" +
+                "        \"ts\": 1769093970790\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 6,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"有积分和福利吗?\",\n" +
+                "        \"chat_id\": \"1769093985653876000\",\n" +
+                "        \"ts\": 1769093985653\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 7,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"每节课学完可领300芳华币,累计能在芳华未来APP兑换刮痧板、泡脚包、生活用品等,运费全免!\",\n" +
+                "        \"chat_id\": \"1769093986179990000\",\n" +
+                "        \"ts\": 1769093986179\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 8,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"在哪里呢?\",\n" +
+                "        \"chat_id\": \"1769093999769471000\",\n" +
+                "        \"ts\": 1769093999769\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 9,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"没听懂您的意思呀,您可以再跟我说说具体是关于养生课哪方面的“在哪里”呢,比如是课程链接在哪里、小程序入口在哪里之类和健康养生课程相关的内容。\",\n" +
+                "        \"chat_id\": \"1769094000390546000\",\n" +
+                "        \"ts\": 1769094000390\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 10,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"我说福利在哪里领?\",\n" +
+                "        \"chat_id\": \"1769094020245093000\",\n" +
+                "        \"ts\": 1769094020245\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 11,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"一点不难领!完整看完课程就自动到账芳华币,能换超多实用好礼,我现在发专属领取指引,两步就能搞定,不会的话我视频手把手教你,保证你领到,不耽误你时间!\",\n" +
+                "        \"chat_id\": \"1769094021008677000\",\n" +
+                "        \"ts\": 1769094021008\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 12,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"user\",\n" +
+                "        \"text\": \"好的,好的,拜拜。\",\n" +
+                "        \"chat_id\": \"1769094041681200000\",\n" +
+                "        \"ts\": 1769094041681\n" +
+                "      },\n" +
+                "      {\n" +
+                "        \"id\": 13,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"role\": \"assistant\",\n" +
+                "        \"text\": \"那先不打扰啦,有啥需要随时联系我哈。\",\n" +
+                "        \"chat_id\": \"1769094042176546000\",\n" +
+                "        \"ts\": 1769094042176\n" +
+                "      }\n" +
+                "    ],\n" +
+                "    \"recordings\": [\n" +
+                "      {\n" +
+                "        \"id\": 3,\n" +
+                "        \"session_id\": \"yangge-123_姜妍_1769093955212\",\n" +
+                "        \"recording_type\": \"audio\",\n" +
+                "        \"file_url\": \"/records/20260122230047.wav\",\n" +
+                "        \"started_at\": null,\n" +
+                "        \"ended_at\": null,\n" +
+                "        \"duration_ms\": 88357,\n" +
+                "        \"file_size\": 2827448,\n" +
+                "        \"created_at\": \"2026-01-22 23:00:58\"\n" +
+                "      }\n" +
+                "    ]\n" +
+                "  }\n" +
+                "}";
+
+        AiCallSopRepVo aiCallSopRepVo = JSONObject.parseObject(json, AiCallSopRepVo.class);
+
+        DataDetails data = aiCallSopRepVo.getData() != null ? aiCallSopRepVo.getData() : null;
+
+        List<MessagesDetails> messages = data != null && data.getMessages() != null ?
+                data.getMessages() : null;
+
+        if (messages != null && !messages.isEmpty()) {
+            String chatText = messages.stream()
+                    .sorted(Comparator.comparing(MessagesDetails::getId))
+                    .map(message -> {
+                        String role = "assistant".equals(message.getRole()) ? "[客服]" :
+                                "user".equals(message.getRole()) ? "[客户]" : message.getRole();
+                        return role + ":" + message.getText();
+                    })
+                    .collect(Collectors.joining("\n"));
+            System.out.println("聊天记录:"+chatText);
+        } else {
+            System.out.println("没有聊天记录");
+        }
+
+        List<RecordingsDetails> recordings = data != null && data.getRecordings() != null ?
+                data.getRecordings() : null;
+        if (recordings != null && !recordings.isEmpty()) {
+            String recordingText = recordings.stream()
+                    .sorted(Comparator.comparing(RecordingsDetails::getId))
+                    .map(RecordingsDetails::getFile_url)
+                    .collect(Collectors.joining(","));
+            System.out.println("聊天语音:"+recordingText);
+        } else {
+            System.out.println("没有聊天语音");
+        }
+    }
+
+}

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

@@ -133,4 +133,6 @@ public interface CompanyDeptMapper
      * @return
      */
     List<DeptFullPathVO> getDeptFullPathList(@Param("companyId") Long companyId);
+
+    List<Long> selectDeptIdsByAncestors(@Param("deptId") Long deptId);
 }

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

@@ -8,6 +8,8 @@ import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.param.VcCompanyUser;
 import com.fs.company.vo.*;
 import com.fs.his.vo.OptionsVO;
+import com.fs.qw.dto.SalesAppDownloadStatDTO;
+import com.fs.qw.param.AppDownloadParam;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
@@ -70,6 +72,17 @@ public interface CompanyUserMapper
 
     public int updateAllCompanyUser(CompanyUser companyUser);
 
+    @Update("<script>" +
+            "update company_user set ai_call_user_id=#{aiSopCallId} where user_id=#{companyUserId} " +
+            "</script>")
+    public int updateCompanyUserByAiSopCall(@Param("companyUserId") Long companyUserId, @Param("aiSopCallId") Long aiSopCallId);
+
+    @Update("<script>" +
+            "update company_user set ai_call_user_id=null where ai_call_user_id=#{aiCallUserId} " +
+            "</script>")
+    public int deleteCompanyUserByAiSopCallId(@Param("aiCallUserId") Long aiCallUserId);
+
+
     /**
      * 删除物业公司管理员信息
      *
@@ -370,4 +383,6 @@ public interface CompanyUserMapper
             "update company_user set ai_sip_call_user_id=#{aiSipCallId} where user_id=#{companyUserId} " +
             "</script>")
     public int updateCompanyUserByAiSipCall(@Param("companyUserId") Long companyUserId, @Param("aiSipCallId") Long aiSipCallId);
+
+    List<SalesAppDownloadStatDTO> getSalesAppDownloadStats1(@Param("param") AppDownloadParam param);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -45,6 +45,7 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.oss.CloudStorageService;
@@ -128,6 +129,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Autowired
     private ICompanyConfigService companyConfigService;
 
+    @Autowired
+    private IQwSopTempVoiceService voiceService;
+
 //    @Autowired
 //    private ICompanyUserRoleService userRoleService;
 
@@ -485,6 +489,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         insertUserRole(user);
         //新增用户需要修改密码
         redisCache.setCacheObject("newCompanyUser:" + user.getCompanyId() + ":" +user.getUserName(),user.getUserId());
+        //语音素材1-10
+        voiceService.insertQwSopTempVoiceModel(user.getUserId());
+
         return rows;
     }
 

+ 5 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -266,6 +266,11 @@ public class FsUser extends BaseEntity
     // 已提现佣金
     private BigDecimal withdrawFinish;
 
+    /**
+    * 第一次登录的app的时间
+    */
+    private Date  firstLoginTime;
+
     public String getNickname() {
         return nickname;
     }

+ 33 - 0
fs-service/src/main/java/com/fs/qw/dto/SalesAppDownloadStatDTO.java

@@ -0,0 +1,33 @@
+package com.fs.qw.dto;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class SalesAppDownloadStatDTO {
+    @Excel(name = "企微客服")
+    private String corpName;
+    @Excel(name = "销售ID")
+    private Long userId;
+    @Excel(name = "部门ID")
+    private Long deptId;
+    @Excel(name = "销售名称")
+    private String nickName;
+    @Excel(name = "已注册客户数")
+    private Integer totalCustomers;
+    @Excel(name = "下载app客户数")
+    private Integer downloadCount;
+    @Excel(name = "未下载app客户数")
+    private Integer notDownloadCount;
+    @Excel(name = "总客户数")
+    private Integer externalTotal;
+
+    @Excel(name = "下载APP比例(%)")
+    private Double downloadRate;
+    public Double getDownloadRate() {
+        if (externalTotal == null || externalTotal == 0) {
+            return 0.0;
+        }
+        return downloadCount * 1.0 / externalTotal;
+    }
+}

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

@@ -554,4 +554,5 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     List<QwUserVO> selectQwUserListByCompanyUserIdAndFsUserId(@Param("companyUserId")Long companyUserId,@Param("fsUserId") Long fsUserId);
 
+    List<Long> selectByCorpId(@Param("corpId")String corpId,@Param("companyId")Long companyId);
 }

+ 2 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java

@@ -107,13 +107,13 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "<if test ='companyId !=null and companyId!=\"\"'>\n" +
             "   and qu.company_id =#{companyId} \n" +
             "</if>" +
-            "<if test ='companyUserId !=null and companyUserId!=\"\"'>\n" +
+            "<if test ='companyUserId !=null and companyUserId!=\"\" and searchType==1 '>\n" +
             "   and qu.company_user_id =#{companyUserId} \n" +
             "</if>" +
             "<if test ='corpId !=null and corpId!=\"\"'>\n" +
             "   and qu.corp_id =#{corpId} \n" +
             "</if>" +
-            "<if test ='ids !=null and ids!=\"\"'>\n" +
+            "<if test ='ids !=null and ids!=\"\" and searchType==1 '>\n" +
             "   and qec.qw_user_id in (${ids})\n" +
             "</if>" +
             "<if test = 'addWays != null and addWays.size()>0' >" +

+ 51 - 0
fs-service/src/main/java/com/fs/qw/param/AppDownloadParam.java

@@ -0,0 +1,51 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class AppDownloadParam {
+    /**
+     * 企微主体ID
+     */
+    private String corpId;
+
+    private String qwUserName;
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 员工姓名(模糊搜索)
+     */
+    private String companyUserName;
+
+    /**
+     * 统计日期,默认当天
+     */
+    private Date statDate;
+
+    /**
+     * 企业ID(可以从登录信息中获取)
+     */
+    private Long companyId;
+
+    Long companyUserId;
+    private List<Long> qwUserIds;
+
+    private int pageNum;
+
+    private int pageSize;
+
+    List<Long> deptIds;
+
+    public Date getStatDate() {
+        if (statDate == null) {
+            return new Date();
+        }
+        return statDate;
+    }
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java

@@ -20,6 +20,10 @@ public class QwWatchLogStatisticsListParam {
 
     private String startDate;
     private String endDate;
+    /**
+    *  搜索类型 1个人 2部门 默认1
+    */
+    private Integer searchType=1;
 
     public String getStartDate() {
         return DateUtils.getStartOfDayString(sTime);

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -3,10 +3,12 @@ package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.hisStore.vo.FsStoreOrderScrmSidebarVO;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.dto.SalesAppDownloadStatDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
 import com.fs.qw.param.sidebar.ExternalContactInfoParam;
@@ -281,5 +283,8 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     QwExternalContact getQwExternalContactDetail(QwExternalContactHParam param);
 
+    TableDataInfo getSalesAppDownloadStats(AppDownloadParam param);
+
+    List<SalesAppDownloadStatDTO> exportSalesAppDownloadStats(AppDownloadParam param);
 
 }

+ 92 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -8,12 +8,14 @@ import com.alibaba.fastjson.JSONException;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.ad.enums.AdUploadType;
 import com.fs.ad.service.IAdHtmlClickLogService;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.PubFun;
@@ -21,6 +23,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogAddwx;
 import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.domain.CompanyWxClient;
+import com.fs.company.mapper.CompanyDeptMapper;
+import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.mapper.CompanyWxAccountMapper;
 import com.fs.company.mapper.CompanyWxClientMapper;
 import com.fs.company.service.IAiAddwxSopTemplateService;
@@ -48,6 +52,7 @@ import com.fs.hisStore.vo.FsStoreOrderScrmSidebarVO;
 import com.fs.ipad.IpadSendUtils;
 import com.fs.ipad.vo.BaseVo;
 import com.fs.qw.domain.*;
+import com.fs.qw.dto.SalesAppDownloadStatDTO;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
@@ -86,6 +91,7 @@ import com.fs.wxwork.dto.WxLoginDTO;
 import com.fs.wxwork.dto.WxLoginResp;
 import com.fs.wxwork.dto.WxWorkResponseDTO;
 import com.fs.wxwork.service.WxWorkServiceNew;
+import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import lombok.extern.slf4j.Slf4j;
@@ -261,6 +267,12 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     private IAiAddwxSopTemplateService templateService;
 
+    @Autowired
+    private CompanyDeptMapper companyDeptMapper;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
     @Override
     public void addQwCourseJob() {
         List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
@@ -6416,5 +6428,85 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return qwExternalContactMapper.getQwExternalContactDetail(param);
     }
 
+    @Override
+    public TableDataInfo getSalesAppDownloadStats(AppDownloadParam param) {
+// 1. 处理部门ID列表
+        List<Long> deptIdList = companyDeptMapper.selectDeptIdsByAncestors(param.getDeptId());
+        param.setDeptIds(deptIdList);
+
+        // 2. 获取企微用户ID列表(优化:只获取有数据的用户)
+        List<Long> qwUserIds = qwUserMapper.selectByCorpId(param.getCorpId(),param.getCompanyId());
+        if (CollectionUtils.isEmpty(qwUserIds)) {
+            return new TableDataInfo(Collections.emptyList(), 0);
+        }
+        param.setQwUserIds(qwUserIds);
+
+        // 3. 查询统计数据(优化:使用分页参数限制查询数量)
+        List<SalesAppDownloadStatDTO> allStatDTOs = companyUserMapper.getSalesAppDownloadStats1(param);
+
+        // 4. 优化合计计算(一次遍历完成所有统计)
+        SalesAppDownloadStatDTO totalStat = new SalesAppDownloadStatDTO();
+        totalStat.setCorpName("合计");
+
+        int externalTotal = 0;
+        int totalCustomers = 0;
+        int downloadCount = 0;
+        int notDownloadCount = 0;
+
+        for (SalesAppDownloadStatDTO dto : allStatDTOs) {
+            externalTotal += Optional.ofNullable(dto.getExternalTotal()).orElse(0);
+            totalCustomers += Optional.ofNullable(dto.getTotalCustomers()).orElse(0);
+            downloadCount += Optional.ofNullable(dto.getDownloadCount()).orElse(0);
+            notDownloadCount += Optional.ofNullable(dto.getNotDownloadCount()).orElse(0);
+        }
+
+        totalStat.setExternalTotal(externalTotal);
+        totalStat.setTotalCustomers(totalCustomers);
+        totalStat.setDownloadCount(downloadCount);
+        totalStat.setNotDownloadCount(notDownloadCount);
+
+        // 5. 内存分页优化
+        int total = allStatDTOs.size();
+        int pageNum = param.getPageNum();
+        int pageSize = param.getPageSize();
+
+        if (pageNum > 0 && pageSize > 0) {
+            int startIndex = (pageNum - 1) * pageSize;
+            int endIndex = Math.min(startIndex + pageSize, total);
+
+            if (startIndex < total) {
+                List<SalesAppDownloadStatDTO> pageData = allStatDTOs.subList(startIndex, endIndex);
+                pageData.add(totalStat);  // 合计行放在最后
+                return new TableDataInfo(pageData, total);
+            } else {
+                return new TableDataInfo(Collections.singletonList(totalStat), total);
+            }
+        }
+
+        // 6. 不分页的情况
+        allStatDTOs.add(totalStat);
+        return new TableDataInfo(allStatDTOs, total);
+    }
+
+    @Override
+    public List<SalesAppDownloadStatDTO> exportSalesAppDownloadStats(AppDownloadParam param) {
+        List<Long> deptIdList = companyDeptMapper.selectDeptIdsByAncestors(param.getDeptId());
+        param.setDeptIds(deptIdList);
+        List<Long> qwUserIds = qwUserMapper.selectByCorpId(param.getCorpId(),param.getCompanyId());
+        param.setQwUserIds(qwUserIds);
+
+        List<SalesAppDownloadStatDTO> allStatDTOs = companyUserMapper.getSalesAppDownloadStats1(param);
+        //  查询统计数据 不分页
+        PageHelper.clearPage();
+        allStatDTOs.forEach(s -> {
+            if (s.getTotalCustomers() == null || s.getTotalCustomers() == 0) {
+                s.setDownloadRate(0.00);
+            } else {
+                double rate = s.getDownloadCount() * 100.0 / s.getExternalTotal();
+                s.setDownloadRate(Double.parseDouble(String.format("%.2f", rate)));
+            }
+        });
+        return allStatDTOs;
+    }
 
 }

+ 2 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1243,7 +1243,8 @@ public class QwUserServiceImpl implements IQwUserService
         }
         WxWorkSetCallbackUrlDTO wxWorkSetCallbackUrlDTO = new WxWorkSetCallbackUrlDTO();
         System.out.println("回调地址"+aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
-        wxWorkSetCallbackUrlDTO.setUrl(aiHostProper.getIpadUrl()+"/msg/callback/"+serverId+"/"+loginParam.getIsNewVersion());
+//        wxWorkSetCallbackUrlDTO.setUrl(aiHostProper.getIpadUrl()+"/msg/callback/"+serverId+"/"+loginParam.getIsNewVersion());
+        wxWorkSetCallbackUrlDTO.setUrl(aiHostProper.getIpadUrl()+"/msg/callback/"+serverId+"/"+true);
         wxWorkSetCallbackUrlDTO.setUuid(data.getUuid());
         wxWorkService.SetCallbackUrl(wxWorkSetCallbackUrlDTO,serverId);
 

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java

@@ -297,7 +297,7 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
                 allDeptIds.addAll(ids);
                 deptIds.addAll(ids);
                 if (c.getParentId() == 0L) {
-                    return null;
+                    return allDeptIds;
                 }
             }
         }

+ 10 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java

@@ -32,6 +32,10 @@ public interface QwSopTempVoiceMapper extends BaseMapper<QwSopTempVoice>{
      */
     List<QwSopTempVoice> selectQwSopTempVoiceList(QwSopTempVoice qwSopTempVoice);
 
+    @DataSource(DataSourceType.SOP)
+    List<QwSopTempVoice> selectQwSopTempVoiceBySidberList(@Param("data") QwSopTempVoice qwSopTempVoice);
+
+
     /**
      * 新增模板对应的销售语音文件
      *
@@ -40,6 +44,8 @@ public interface QwSopTempVoiceMapper extends BaseMapper<QwSopTempVoice>{
      */
     int insertQwSopTempVoice(QwSopTempVoice qwSopTempVoice);
 
+    int insertOrUpdateBatch(List<QwSopTempVoice> qwSopTempVoice);
+
     /**
      * 修改模板对应的销售语音文件
      *
@@ -56,6 +62,10 @@ public interface QwSopTempVoiceMapper extends BaseMapper<QwSopTempVoice>{
      */
     int deleteQwSopTempVoiceById(Long id);
 
+    @DataSource(DataSourceType.SOP)
+    int deleteQwSopTempVoiceByIdSidebar(@Param("data") QwSopTempVoice qwSopTempVoice);
+
+
     /**
      * 批量删除模板对应的销售语音文件
      *

+ 8 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java

@@ -30,6 +30,8 @@ public interface IQwSopTempVoiceService extends IService<QwSopTempVoice>{
      */
     List<QwSopTempVoice> selectQwSopTempVoiceList(QwSopTempVoice qwSopTempVoice);
 
+    List<QwSopTempVoice> selectQwSopTempVoiceBySidberList(QwSopTempVoice qwSopTempVoice);
+
     /**
      * 新增模板对应的销售语音文件
      *
@@ -38,6 +40,9 @@ public interface IQwSopTempVoiceService extends IService<QwSopTempVoice>{
      */
     int insertQwSopTempVoice(QwSopTempVoice qwSopTempVoice);
 
+    int insertQwSopTempVoiceByCompany(QwSopTempVoice qwSopTempVoice);
+
+
     /**
      * 修改模板对应的销售语音文件
      *
@@ -62,6 +67,9 @@ public interface IQwSopTempVoiceService extends IService<QwSopTempVoice>{
      */
     int deleteQwSopTempVoiceById(Long id);
 
+    int deleteQwSopTempVoiceByIdSidebar(QwSopTempVoice qwSopTempVoice);
+
+
 
     /**
      * 同步语音

+ 55 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java

@@ -10,6 +10,7 @@ import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.CompanyQwUserByIdsVo;
@@ -27,6 +28,8 @@ import com.fs.sop.service.IQwSopTempVoiceService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -71,6 +74,14 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
         return baseMapper.selectQwSopTempVoiceList(qwSopTempVoice);
     }
 
+    @Override
+    @DataSource(DataSourceType.SOP)
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public List<QwSopTempVoice> selectQwSopTempVoiceBySidberList(QwSopTempVoice qwSopTempVoice) {
+        return qwSopTempVoiceMapper.selectQwSopTempVoiceBySidberList(qwSopTempVoice);
+    }
+
+
     /**
      * 新增模板对应的销售语音文件
      *
@@ -82,6 +93,44 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
         return baseMapper.insertQwSopTempVoice(qwSopTempVoice);
     }
 
+
+    @Override
+    public int insertQwSopTempVoiceByCompany(QwSopTempVoice tempVoice) {
+
+        //查询当前销售公司下的所有销售
+        CompanyUser user=new CompanyUser();
+        user.setCompanyId(tempVoice.getCompanyId());
+        user.setStatus("0");
+        user.setQwStatus(1);
+        List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserList(user);
+
+        // 批量插入列表
+        List<QwSopTempVoice> insertList = new ArrayList<>();
+
+        for (CompanyUser companyUser : companyUsers) {
+            QwSopTempVoice newVoice = new QwSopTempVoice();
+            newVoice.setCompanyUserId(companyUser.getUserId());
+            newVoice.setCompanyId(companyUser.getCompanyId());
+            newVoice.setVoiceTxt(tempVoice.getVoiceTxt());
+            newVoice.setRecordType(0);
+            insertList.add(newVoice);
+
+            // 分批插入,避免SQL过长
+            if (insertList.size() >= 300) {
+                baseMapper.insertOrUpdateBatch(insertList);
+                insertList.clear();
+            }
+        }
+
+        // 插入剩余数据
+        if (!insertList.isEmpty()) {
+            baseMapper.insertOrUpdateBatch(insertList);
+        }
+
+
+        return 1;
+    }
+
     /**
      * 修改模板对应的销售语音文件
      *
@@ -115,6 +164,12 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
         return baseMapper.deleteQwSopTempVoiceById(id);
     }
 
+    @Override
+    public int deleteQwSopTempVoiceByIdSidebar(QwSopTempVoice qwSopTempVoice) {
+
+        return baseMapper.deleteQwSopTempVoiceByIdSidebar(qwSopTempVoice);
+    }
+
     /**
      * 同步语音
      *

+ 124 - 0
fs-service/src/main/resources/db/changelog/table/live_group_type.sql

@@ -75,3 +75,127 @@ CREATE TABLE `qw_course_finish_tag_rty` (
                                             PRIMARY KEY (`id`) USING BTREE,
                                             UNIQUE KEY `index1` (`qw_user_id`,`corp_id`,`external_user_id`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='企业微信完课打标签-失败重试表';
+
+--changeset sgw:20260709-ai_sop_call_knowledge_base
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_knowledge_base'
+CREATE TABLE `ai_sop_call_knowledge_base` (
+                                              `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+                                              `company_id` bigint DEFAULT NULL COMMENT '企业ID',
+                                              `company_user_id` bigint DEFAULT NULL COMMENT '销售ID',
+                                              `knowledge_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '知识库名称',
+                                              `factory_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '提供商 可选值: openai、bytedance、alibaba、http',
+                                              `enable` bigint DEFAULT '1' COMMENT '是否启用 1: 启用 0: 禁用,默认 1',
+                                              `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
+                                              `metadata` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '扩展配置,不同厂商字段不同,自由扩展',
+                                              `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '创建者',
+                                              `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+                                              `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '更新者',
+                                              `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+                                              `remote_knowledge_id` bigint DEFAULT NULL COMMENT '远程知识库ID',
+                                              PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI外呼知识库';
+
+
+--changeset sgw:20260709-ai_sop_call_line_config
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_line_config'
+CREATE TABLE `ai_sop_call_line_config` (
+                                           `id` bigint NOT NULL AUTO_INCREMENT,
+                                           `gateway_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '网关名称',
+                                           `gateway_purpose` bigint DEFAULT '1' COMMENT '网关用途:1-外呼',
+                                           `profile_name` bigint DEFAULT '1' COMMENT 'profile名称:1-external',
+                                           `calling_nnumber` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '主叫号码',
+                                           `called_prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '被叫前缀',
+                                           `gateway_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '网关地址',
+                                           `out_call_voice_encoding` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '外呼语音编码',
+                                           `gateway_name_description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '网关名称描述',
+                                           `gateway_max_concurrency` int DEFAULT '1' COMMENT '网关最大并发数',
+                                           `is_certification_registration` bigint DEFAULT '1' COMMENT '是否认证注册:1-对接模式',
+                                           `priority_usage` bigint DEFAULT '1' COMMENT '使用优先级',
+                                           `company_id` bigint DEFAULT NULL COMMENT 'ID',
+                                           `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '创建者',
+                                           `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+                                           `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '更新者',
+                                           `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+                                           `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '备注',
+                                           PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='ai呼叫线路配置';
+
+
+
+--changeset sgw:20260709-ai_sop_call_msg_log
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_msg_log'
+CREATE TABLE `ai_sop_call_msg_log` (
+                                       `id` bigint NOT NULL AUTO_INCREMENT,
+                                       `session_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
+                                       `company_user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '销售',
+                                       `company_id` bigint DEFAULT NULL COMMENT '销售公司',
+                                       `user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '企业微信账号(非主键)',
+                                       `corp_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '企业微信主体',
+                                       `external_user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '外部联系人',
+                                       `status` tinyint DEFAULT NULL COMMENT '外呼状态',
+                                       `call_content` longtext CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '通话文本内容',
+                                       `call_duration` bigint DEFAULT NULL COMMENT '通话时长',
+                                       `voice_url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '语音地址',
+                                       PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='ai外呼记录表';
+
+--changeset sgw:20260709-ai_sop_call_role
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_role'
+CREATE TABLE `ai_sop_call_role` (
+                                    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+                                    `call_role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '外呼角色名称',
+                                    `impl_class` bigint NOT NULL COMMENT '外呼实现类 1:deppSeek',
+                                    `model_concurrency` bigint NOT NULL COMMENT '模型并发数',
+                                    `service_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '服务地址',
+                                    `call_role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色key',
+                                    `model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模型名称',
+                                    `reminder_words` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '大模型提示词',
+                                    `remake` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '备注',
+                                    `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者',
+                                    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+                                    `update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者',
+                                    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+                                    PRIMARY KEY (`id`) USING BTREE,
+                                    KEY `idx_call_role_key` (`call_role_key`) USING BTREE,
+                                    KEY `idx_create_time` (`create_time`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI外呼角色表';
+
+
+--changeset sgw:20260709-ai_sop_call_staff_models
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_staff_models'
+CREATE TABLE `ai_sop_call_staff_models` (
+                                            `id` int NOT NULL,
+                                            `model_id` int DEFAULT NULL COMMENT '模型id(必填)',
+                                            `model_function` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '模型功能 STT: 语音识别 TTS: 语音合成 LLM: 大模型 ENDTOEND',
+                                            `model_factory` int DEFAULT NULL COMMENT '模型工厂 1: 阿里 2: 字节',
+                                            `staff_id` int DEFAULT NULL COMMENT '员工id(修改接口 必填)',
+                                            PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='外呼模型模型';
+
+
+--changeset sgw:20260709-ai_sop_call_user
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'ai_sop_call_user'
+CREATE TABLE `ai_sop_call_user` (
+                                    `id` bigint NOT NULL,
+                                    `staff_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '员工姓名',
+                                    `staff_number` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '员工编号',
+                                    `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '性别',
+                                    `age` int DEFAULT NULL COMMENT '年龄',
+                                    `voice_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '语音id',
+                                    `is_clone_voice` tinyint DEFAULT NULL COMMENT '是否克隆语音 1: 是 2: 否',
+                                    `is_force_clone` tinyint DEFAULT NULL COMMENT '是否强制采样 1: 是 2: 否  一般修改接口使用',
+                                    `voice_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '语音路径 (必填)',
+                                    `voice_text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '音色对应的文本,用于语音识别',
+                                    `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注信息',
+                                    `enable` tinyint DEFAULT NULL COMMENT '是否启用 1: 是 2: 否',
+                                    `staff_models_id` int DEFAULT NULL COMMENT '模型id(必填)',
+                                    `company_user_id` bigint DEFAULT NULL COMMENT '绑定的销售id',
+                                    `knowledge_base_id` bigint DEFAULT NULL COMMENT '远程地址的知识库id',
+                                    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='员工角色';

+ 107 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallKnowledgeBaseMapper.xml

@@ -0,0 +1,107 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallKnowledgeBaseMapper">
+
+    <resultMap type="AiSopCallKnowledgeBase" id="AiSopCallKnowledgeBaseResult">
+        <result property="id"    column="id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="knowledgeName"    column="knowledge_name"    />
+        <result property="factoryName"    column="factory_name"    />
+        <result property="enable"    column="enable"    />
+        <result property="remark"    column="remark"    />
+        <result property="metadata"    column="metadata"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remoteKnowledgeId"    column="remote_knowledge_id"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallKnowledgeBaseVo">
+        select id,company_id,company_user_id, knowledge_name, factory_name, enable, remark, metadata,create_by,create_time,update_by,update_time,remote_knowledge_id from ai_sop_call_knowledge_base
+    </sql>
+
+    <select id="selectAiSopCallKnowledgeBaseList" parameterType="AiSopCallKnowledgeBase" resultMap="AiSopCallKnowledgeBaseResult">
+        <include refid="selectAiSopCallKnowledgeBaseVo"/>
+        <where>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="knowledgeName != null  and knowledgeName != ''"> and knowledge_name like concat('%', #{knowledgeName}, '%')</if>
+            <if test="factoryName != null  and factoryName != ''"> and factory_name like concat('%', #{factoryName}, '%')</if>
+            <if test="enable != null "> and enable = #{enable}</if>
+            <if test="metadata != null  and metadata != ''"> and metadata = #{metadata}</if>
+            <if test="remoteKnowledgeId != null "> and remote_knowledge_id = #{remoteKnowledgeId}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectAiSopCallKnowledgeBaseById" parameterType="Long" resultMap="AiSopCallKnowledgeBaseResult">
+        <include refid="selectAiSopCallKnowledgeBaseVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallKnowledgeBase" parameterType="AiSopCallKnowledgeBase" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_sop_call_knowledge_base
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="knowledgeName != null">knowledge_name,</if>
+            <if test="factoryName != null">factory_name,</if>
+            <if test="enable != null">enable,</if>
+            <if test="remark != null">remark,</if>
+            <if test="metadata != null">metadata,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remoteKnowledgeId != null">remote_knowledge_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="knowledgeName != null">#{knowledgeName},</if>
+            <if test="factoryName != null">#{factoryName},</if>
+            <if test="enable != null">#{enable},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="metadata != null">#{metadata},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remoteKnowledgeId != null">#{remoteKnowledgeId},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallKnowledgeBase" parameterType="AiSopCallKnowledgeBase">
+        update ai_sop_call_knowledge_base
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="knowledgeName != null">knowledge_name = #{knowledgeName},</if>
+            <if test="factoryName != null">factory_name = #{factoryName},</if>
+            <if test="enable != null">enable = #{enable},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="metadata != null">metadata = #{metadata},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remoteKnowledgeId != null">remote_knowledge_id = #{remoteKnowledgeId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallKnowledgeBaseById" parameterType="Long">
+        delete from ai_sop_call_knowledge_base where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallKnowledgeBaseByIds" parameterType="String">
+        delete from ai_sop_call_knowledge_base where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 131 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallLineConfigMapper.xml

@@ -0,0 +1,131 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallLineConfigMapper">
+
+    <resultMap type="AiSopCallLineConfig" id="AiSopCallLineConfigResult">
+        <result property="id"    column="id"    />
+        <result property="gatewayName"    column="gateway_name"    />
+        <result property="gatewayPurpose"    column="gateway_purpose"    />
+        <result property="profileName"    column="profile_name"    />
+        <result property="callingNnumber"    column="calling_nnumber"    />
+        <result property="calledPrefix"    column="called_prefix"    />
+        <result property="gatewayAddress"    column="gateway_address"    />
+        <result property="outCallVoiceEncoding"    column="out_call_voice_encoding"    />
+        <result property="gatewayNameDescription"    column="gateway_name_description"    />
+        <result property="gatewayMaxConcurrency"    column="gateway_max_concurrency"    />
+        <result property="isCertificationRegistration"    column="is_certification_registration"    />
+        <result property="priorityUsage"    column="priority_usage"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallLineConfigVo">
+        select id, gateway_name, gateway_purpose, profile_name, calling_nnumber, called_prefix, gateway_address, out_call_voice_encoding, gateway_name_description, gateway_max_concurrency, is_certification_registration, priority_usage, company_id, create_by, create_time, update_by, update_time, remark from ai_sop_call_line_config
+    </sql>
+
+    <select id="selectAiSopCallLineConfigList" parameterType="AiSopCallLineConfig" resultMap="AiSopCallLineConfigResult">
+        <include refid="selectAiSopCallLineConfigVo"/>
+        <where>
+            <if test="gatewayName != null  and gatewayName != ''"> and gateway_name like concat('%', #{gatewayName}, '%')</if>
+            <if test="gatewayPurpose != null "> and gateway_purpose = #{gatewayPurpose}</if>
+            <if test="profileName != null "> and profile_name like concat('%', #{profileName}, '%')</if>
+            <if test="callingNnumber != null  and callingNnumber != ''"> and calling_nnumber = #{callingNnumber}</if>
+            <if test="calledPrefix != null  and calledPrefix != ''"> and called_prefix = #{calledPrefix}</if>
+            <if test="gatewayAddress != null  and gatewayAddress != ''"> and gateway_address = #{gatewayAddress}</if>
+            <if test="outCallVoiceEncoding != null  and outCallVoiceEncoding != ''"> and out_call_voice_encoding = #{outCallVoiceEncoding}</if>
+            <if test="gatewayNameDescription != null  and gatewayNameDescription != ''"> and gateway_name_description = #{gatewayNameDescription}</if>
+            <if test="gatewayMaxConcurrency != null "> and gateway_max_concurrency = #{gatewayMaxConcurrency}</if>
+            <if test="isCertificationRegistration != null "> and is_certification_registration = #{isCertificationRegistration}</if>
+            <if test="priorityUsage != null "> and priority_usage = #{priorityUsage}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+        </where>
+    </select>
+
+    <select id="selectAiSopCallLineConfigById" parameterType="Long" resultMap="AiSopCallLineConfigResult">
+        <include refid="selectAiSopCallLineConfigVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallLineConfig" parameterType="AiSopCallLineConfig" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_sop_call_line_config
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="gatewayName != null">gateway_name,</if>
+            <if test="gatewayPurpose != null">gateway_purpose,</if>
+            <if test="profileName != null">profile_name,</if>
+            <if test="callingNnumber != null">calling_nnumber,</if>
+            <if test="calledPrefix != null">called_prefix,</if>
+            <if test="gatewayAddress != null">gateway_address,</if>
+            <if test="outCallVoiceEncoding != null">out_call_voice_encoding,</if>
+            <if test="gatewayNameDescription != null">gateway_name_description,</if>
+            <if test="gatewayMaxConcurrency != null">gateway_max_concurrency,</if>
+            <if test="isCertificationRegistration != null">is_certification_registration,</if>
+            <if test="priorityUsage != null">priority_usage,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="gatewayName != null">#{gatewayName},</if>
+            <if test="gatewayPurpose != null">#{gatewayPurpose},</if>
+            <if test="profileName != null">#{profileName},</if>
+            <if test="callingNnumber != null">#{callingNnumber},</if>
+            <if test="calledPrefix != null">#{calledPrefix},</if>
+            <if test="gatewayAddress != null">#{gatewayAddress},</if>
+            <if test="outCallVoiceEncoding != null">#{outCallVoiceEncoding},</if>
+            <if test="gatewayNameDescription != null">#{gatewayNameDescription},</if>
+            <if test="gatewayMaxConcurrency != null">#{gatewayMaxConcurrency},</if>
+            <if test="isCertificationRegistration != null">#{isCertificationRegistration},</if>
+            <if test="priorityUsage != null">#{priorityUsage},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallLineConfig" parameterType="AiSopCallLineConfig">
+        update ai_sop_call_line_config
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="gatewayName != null">gateway_name = #{gatewayName},</if>
+            <if test="gatewayPurpose != null">gateway_purpose = #{gatewayPurpose},</if>
+            <if test="profileName != null">profile_name = #{profileName},</if>
+            <if test="callingNnumber != null">calling_nnumber = #{callingNnumber},</if>
+            <if test="calledPrefix != null">called_prefix = #{calledPrefix},</if>
+            <if test="gatewayAddress != null">gateway_address = #{gatewayAddress},</if>
+            <if test="outCallVoiceEncoding != null">out_call_voice_encoding = #{outCallVoiceEncoding},</if>
+            <if test="gatewayNameDescription != null">gateway_name_description = #{gatewayNameDescription},</if>
+            <if test="gatewayMaxConcurrency != null">gateway_max_concurrency = #{gatewayMaxConcurrency},</if>
+            <if test="isCertificationRegistration != null">is_certification_registration = #{isCertificationRegistration},</if>
+            <if test="priorityUsage != null">priority_usage = #{priorityUsage},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallLineConfigById" parameterType="Long">
+        delete from ai_sop_call_line_config where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallLineConfigByIds" parameterType="String">
+        delete from ai_sop_call_line_config where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 101 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallMsgLogMapper.xml

@@ -0,0 +1,101 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallMsgLogMapper">
+
+    <resultMap type="AiSopCallMsgLog" id="AiSopCallMsgLogResult">
+        <result property="id"    column="id"    />
+        <result property="sessionId"    column="session_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="corpId"    column="corp_id"    />
+        <result property="externalUserId"    column="external_user_id"    />
+        <result property="status"    column="status"    />
+        <result property="callContent"    column="call_content"    />
+        <result property="callDuration"    column="call_duration"    />
+        <result property="voiceUrl"    column="voice_url"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallMsgLogVo">
+        select id, session_id, company_user_id, company_id, user_id, corp_id, external_user_id, status, call_content, call_duration, voice_url from ai_sop_call_msg_log
+    </sql>
+
+    <select id="selectAiSopCallMsgLogList" parameterType="AiSopCallMsgLog" resultMap="AiSopCallMsgLogResult">
+        <include refid="selectAiSopCallMsgLogVo"/>
+        <where>
+            <if test="sessionId != null  and sessionId != ''"> and session_id = #{sessionId}</if>
+            <if test="companyUserId != null  and companyUserId != ''"> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="userId != null  and userId != ''"> and user_id = #{userId}</if>
+            <if test="corpId != null  and corpId != ''"> and corp_id = #{corpId}</if>
+            <if test="externalUserId != null  and externalUserId != ''"> and external_user_id = #{externalUserId}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="callContent != null  and callContent != ''"> and call_content = #{callContent}</if>
+            <if test="callDuration != null "> and call_duration = #{callDuration}</if>
+            <if test="voiceUrl != null  and voiceUrl != ''"> and voice_url = #{voiceUrl}</if>
+        </where>
+    </select>
+
+    <select id="selectAiSopCallMsgLogById" parameterType="Long" resultMap="AiSopCallMsgLogResult">
+        <include refid="selectAiSopCallMsgLogVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallMsgLog" parameterType="AiSopCallMsgLog" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_sop_call_msg_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="sessionId != null">session_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="corpId != null">corp_id,</if>
+            <if test="externalUserId != null">external_user_id,</if>
+            <if test="status != null">status,</if>
+            <if test="callContent != null">call_content,</if>
+            <if test="callDuration != null">call_duration,</if>
+            <if test="voiceUrl != null">voice_url,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="sessionId != null">#{sessionId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="corpId != null">#{corpId},</if>
+            <if test="externalUserId != null">#{externalUserId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="callContent != null">#{callContent},</if>
+            <if test="callDuration != null">#{callDuration},</if>
+            <if test="voiceUrl != null">#{voiceUrl},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallMsgLog" parameterType="AiSopCallMsgLog">
+        update ai_sop_call_msg_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="sessionId != null">session_id = #{sessionId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="corpId != null">corp_id = #{corpId},</if>
+            <if test="externalUserId != null">external_user_id = #{externalUserId},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="callContent != null">call_content = #{callContent},</if>
+            <if test="callDuration != null">call_duration = #{callDuration},</if>
+            <if test="voiceUrl != null">voice_url = #{voiceUrl},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallMsgLogById" parameterType="Long">
+        delete from ai_sop_call_msg_log where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallMsgLogByIds" parameterType="String">
+        delete from ai_sop_call_msg_log where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 108 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallRoleMapper.xml

@@ -0,0 +1,108 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallRoleMapper">
+
+    <resultMap type="AiSopCallRole" id="AiSopCallRoleResult">
+        <result property="id"    column="id"    />
+        <result property="callRoleName"    column="call_role_name"    />
+        <result property="implClass"    column="impl_class"    />
+        <result property="modelConcurrency"    column="model_concurrency"    />
+        <result property="serviceAddress"    column="service_address"    />
+        <result property="callRoleKey"    column="call_role_key"    />
+        <result property="modelName"    column="model_name"    />
+        <result property="reminderWords"    column="reminder_words"    />
+        <result property="remake"    column="remake"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallRoleVo">
+        select id, call_role_name, impl_class, model_concurrency, service_address, call_role_key, model_name, reminder_words, remake, create_by, create_time, update_by,update_time from ai_sop_call_role
+    </sql>
+
+    <select id="selectAiSopCallRoleList" parameterType="AiSopCallRole" resultMap="AiSopCallRoleResult">
+        <include refid="selectAiSopCallRoleVo"/>
+        <where>
+            <if test="callRoleName != null  and callRoleName != ''"> and call_role_name like concat('%', #{callRoleName}, '%')</if>
+            <if test="implClass != null "> and impl_class = #{implClass}</if>
+            <if test="modelConcurrency != null "> and model_concurrency = #{modelConcurrency}</if>
+            <if test="serviceAddress != null  and serviceAddress != ''"> and service_address = #{serviceAddress}</if>
+            <if test="callRoleKey != null  and callRoleKey != ''"> and call_role_key = #{callRoleKey}</if>
+            <if test="modelName != null  and modelName != ''"> and model_name like concat('%', #{modelName}, '%')</if>
+            <if test="reminderWords != null  and reminderWords != ''"> and reminder_words = #{reminderWords}</if>
+            <if test="remake != null  and remake != ''"> and remake = #{remake}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectAiSopCallRoleById" parameterType="Long" resultMap="AiSopCallRoleResult">
+        <include refid="selectAiSopCallRoleVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallRole" parameterType="AiSopCallRole" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_sop_call_role
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="callRoleName != null and callRoleName != ''">call_role_name,</if>
+            <if test="implClass != null">impl_class,</if>
+            <if test="modelConcurrency != null">model_concurrency,</if>
+            <if test="serviceAddress != null and serviceAddress != ''">service_address,</if>
+            <if test="callRoleKey != null and callRoleKey != ''">call_role_key,</if>
+            <if test="modelName != null and modelName != ''">model_name,</if>
+            <if test="reminderWords != null">reminder_words,</if>
+            <if test="remake != null">remake,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="callRoleName != null and callRoleName != ''">#{callRoleName},</if>
+            <if test="implClass != null">#{implClass},</if>
+            <if test="modelConcurrency != null">#{modelConcurrency},</if>
+            <if test="serviceAddress != null and serviceAddress != ''">#{serviceAddress},</if>
+            <if test="callRoleKey != null and callRoleKey != ''">#{callRoleKey},</if>
+            <if test="modelName != null and modelName != ''">#{modelName},</if>
+            <if test="reminderWords != null">#{reminderWords},</if>
+            <if test="remake != null">#{remake},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallRole" parameterType="AiSopCallRole">
+        update ai_sop_call_role
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="callRoleName != null and callRoleName != ''">call_role_name = #{callRoleName},</if>
+            <if test="implClass != null">impl_class = #{implClass},</if>
+            <if test="modelConcurrency != null">model_concurrency = #{modelConcurrency},</if>
+            <if test="serviceAddress != null and serviceAddress != ''">service_address = #{serviceAddress},</if>
+            <if test="callRoleKey != null and callRoleKey != ''">call_role_key = #{callRoleKey},</if>
+            <if test="modelName != null and modelName != ''">model_name = #{modelName},</if>
+            <if test="reminderWords != null">reminder_words = #{reminderWords},</if>
+            <if test="remake != null">remake = #{remake},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallRoleById" parameterType="Long">
+        delete from ai_sop_call_role where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallRoleByIds" parameterType="String">
+        delete from ai_sop_call_role where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 73 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallStaffModelsMapper.xml

@@ -0,0 +1,73 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallStaffModelsMapper">
+
+    <resultMap type="AiSopCallStaffModels" id="AiSopCallStaffModelsResult">
+        <result property="id"    column="id"    />
+        <result property="modelId"    column="model_id"    />
+        <result property="modelFunction"    column="model_function"    />
+        <result property="modelFactory"    column="model_factory"    />
+        <result property="staffId"    column="staff_id"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallStaffModelsVo">
+        select id, model_id, model_function, model_factory, staff_id from ai_sop_call_staff_models
+    </sql>
+
+    <select id="selectAiSopCallStaffModelsList" parameterType="AiSopCallStaffModels" resultMap="AiSopCallStaffModelsResult">
+        <include refid="selectAiSopCallStaffModelsVo"/>
+        <where>
+            <if test="modelId != null "> and model_id = #{modelId}</if>
+            <if test="modelFunction != null  and modelFunction != ''"> and model_function = #{modelFunction}</if>
+            <if test="modelFactory != null "> and model_factory = #{modelFactory}</if>
+            <if test="staffId != null "> and staff_id = #{staffId}</if>
+        </where>
+    </select>
+
+    <select id="selectAiSopCallStaffModelsById" parameterType="Long" resultMap="AiSopCallStaffModelsResult">
+        <include refid="selectAiSopCallStaffModelsVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallStaffModels" parameterType="AiSopCallStaffModels">
+        insert into ai_sop_call_staff_models
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="modelId != null">model_id,</if>
+            <if test="modelFunction != null">model_function,</if>
+            <if test="modelFactory != null">model_factory,</if>
+            <if test="staffId != null">staff_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="modelId != null">#{modelId},</if>
+            <if test="modelFunction != null">#{modelFunction},</if>
+            <if test="modelFactory != null">#{modelFactory},</if>
+            <if test="staffId != null">#{staffId},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallStaffModels" parameterType="AiSopCallStaffModels">
+        update ai_sop_call_staff_models
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="modelId != null">model_id = #{modelId},</if>
+            <if test="modelFunction != null">model_function = #{modelFunction},</if>
+            <if test="modelFactory != null">model_factory = #{modelFactory},</if>
+            <if test="staffId != null">staff_id = #{staffId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallStaffModelsById" parameterType="Long">
+        delete from ai_sop_call_staff_models where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallStaffModelsByIds" parameterType="String">
+        delete from ai_sop_call_staff_models where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 128 - 0
fs-service/src/main/resources/mapper/aiCallSop/AiSopCallUserMapper.xml

@@ -0,0 +1,128 @@
+<?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">
+<mapper namespace="com.fs.aiCallSop.mapper.AiSopCallUserMapper">
+
+    <resultMap type="AiSopCallUser" id="AiSopCallUserResult">
+        <result property="id"    column="id"    />
+        <result property="staffName"    column="staff_name"    />
+        <result property="staffNumber"    column="staff_number"    />
+        <result property="sex"    column="sex"    />
+        <result property="age"    column="age"    />
+        <result property="voiceId"    column="voice_id"    />
+        <result property="isCloneVoice"    column="is_clone_voice"    />
+        <result property="isForceClone"    column="is_force_clone"    />
+        <result property="voicePath"    column="voice_path"    />
+        <result property="voiceText"    column="voice_text"    />
+        <result property="remark"    column="remark"    />
+        <result property="enable"    column="enable"    />
+        <result property="staffModelsId"    column="staff_models_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="knowledgeBaseId"    column="knowledge_base_id"    />
+    </resultMap>
+
+    <sql id="selectAiSopCallUserVo">
+        select id, staff_name, staff_number, sex, age, voice_id, is_clone_voice, is_force_clone, voice_path, voice_text, remark, enable, staff_models_id,company_user_id,knowledge_base_id from ai_sop_call_user
+    </sql>
+
+    <select id="selectAiSopCallUserList" parameterType="AiSopCallUser" resultMap="AiSopCallUserResult">
+        <include refid="selectAiSopCallUserVo"/>
+        <where>
+            <if test="staffName != null  and staffName != ''"> and staff_name like concat('%', #{staffName}, '%')</if>
+            <if test="staffNumber != null "> and staff_number = #{staffNumber}</if>
+            <if test="sex != null  and sex != ''"> and sex = #{sex}</if>
+            <if test="age != null "> and age = #{age}</if>
+            <if test="voiceId != null  and voiceId != ''"> and voice_id = #{voiceId}</if>
+            <if test="isCloneVoice != null "> and is_clone_voice = #{isCloneVoice}</if>
+            <if test="isForceClone != null "> and is_force_clone = #{isForceClone}</if>
+            <if test="voicePath != null  and voicePath != ''"> and voice_path = #{voicePath}</if>
+            <if test="voiceText != null  and voiceText != ''"> and voice_text = #{voiceText}</if>
+            <if test="enable != null "> and enable = #{enable}</if>
+            <if test="staffModelsId != null "> and staff_models_id = #{staffModelsId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="knowledgeBaseId != null "> and knowledge_base_id = #{knowledgeBaseId}</if>
+        </where>
+    </select>
+
+    <select id="selectAiSopCallUserById" parameterType="Long" resultMap="AiSopCallUserResult">
+        <include refid="selectAiSopCallUserVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAiSopCallUser" parameterType="AiSopCallUser" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_sop_call_user
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="staffName != null">staff_name,</if>
+            <if test="staffNumber != null">staff_number,</if>
+            <if test="sex != null">sex,</if>
+            <if test="age != null">age,</if>
+            <if test="voiceId != null">voice_id,</if>
+            <if test="isCloneVoice != null">is_clone_voice,</if>
+            <if test="isForceClone != null">is_force_clone,</if>
+            <if test="voicePath != null">voice_path,</if>
+            <if test="voiceText != null">voice_text,</if>
+            <if test="remark != null">remark,</if>
+            <if test="enable != null">enable,</if>
+            <if test="staffModelsId != null">staff_models_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="knowledgeBaseId != null">knowledge_base_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="staffName != null">#{staffName},</if>
+            <if test="staffNumber != null">#{staffNumber},</if>
+            <if test="sex != null">#{sex},</if>
+            <if test="age != null">#{age},</if>
+            <if test="voiceId != null">#{voiceId},</if>
+            <if test="isCloneVoice != null">#{isCloneVoice},</if>
+            <if test="isForceClone != null">#{isForceClone},</if>
+            <if test="voicePath != null">#{voicePath},</if>
+            <if test="voiceText != null">#{voiceText},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="enable != null">#{enable},</if>
+            <if test="staffModelsId != null">#{staffModelsId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="knowledgeBaseId != null">#{knowledgeBaseId},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiSopCallUser" parameterType="AiSopCallUser">
+        update ai_sop_call_user
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="staffName != null">staff_name = #{staffName},</if>
+            <if test="staffNumber != null">staff_number = #{staffNumber},</if>
+            <if test="sex != null">sex = #{sex},</if>
+            <if test="age != null">age = #{age},</if>
+            <if test="voiceId != null">voice_id = #{voiceId},</if>
+            <if test="isCloneVoice != null">is_clone_voice = #{isCloneVoice},</if>
+            <if test="isForceClone != null">is_force_clone = #{isForceClone},</if>
+            <if test="voicePath != null">voice_path = #{voicePath},</if>
+            <if test="voiceText != null">voice_text = #{voiceText},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="enable != null">enable = #{enable},</if>
+            <if test="staffModelsId != null">staff_models_id = #{staffModelsId},</if>
+            <if test="knowledgeBaseId != null">knowledge_base_id = #{knowledgeBaseId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiSopCallUserById" parameterType="Long">
+        delete from ai_sop_call_user where id = #{id}
+    </delete>
+
+    <delete id="deleteAiSopCallUserByIds" parameterType="String">
+        delete from ai_sop_call_user where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <update id="unbindCallUserKnowledge" parameterType="String">
+        update ai_sop_call_user set knowledge_base_id = null where knowledge_base_id in
+        <foreach item="knowledgeBaseId" collection="array" open="(" separator="," close=")">
+            #{knowledgeBaseId}
+        </foreach>
+    </update>
+</mapper>

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

@@ -226,4 +226,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM dept_path
         ORDER BY full_path
     </select>
+
+
+    <!-- 查询部门ID列表(自身+所有子部门) -->
+    <select id="selectDeptIdsByAncestors" resultType="java.lang.Long">
+        SELECT dept_id
+        FROM company_dept
+        WHERE dept_id = #{deptId}
+           OR FIND_IN_SET(#{deptId}, ancestors)
+        ORDER BY dept_id
+    </select>
+
+
 </mapper>

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

@@ -842,4 +842,73 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         set cid_server_id = null
         where user_id = #{companyUserId}
     </update>
+
+    <select id="getSalesAppDownloadStats1" resultType="com.fs.qw.dto.SalesAppDownloadStatDTO">
+        <if test="param.qwUserIds != null and param.qwUserIds.size() > 0">
+            SELECT
+            qw.qw_user_id,
+            qw.company_user_id,
+            qw.qw_user_name as corpName,
+            cu.nick_name,
+            cu.dept_id,
+            IFNULL(cust.totalCustomers, 0) AS totalCustomers,
+            IFNULL(cust.downloadCount, 0) AS downloadCount,
+            IFNULL(cust.totalCustomers, 0) - IFNULL(cust.downloadCount, 0) AS notDownloadCount,
+            (SELECT COUNT(*)
+            FROM qw_external_contact ec2
+            WHERE ec2.qw_user_id = qw.id
+            AND ec2.create_time &lt;= #{param.statDate}) AS externalTotal
+            FROM qw_user qw
+            inner JOIN company_user cu
+            ON qw.company_user_id = cu.user_id
+            LEFT JOIN (
+            SELECT
+            ec.qw_user_id,
+            COUNT(*) AS totalCustomers,
+            SUM(u.history_app IS NOT NULL) AS downloadCount
+            FROM qw_external_contact ec
+            LEFT JOIN fs_user u
+            ON u.user_id = ec.fs_user_id
+            and u.first_login_time &lt;= #{param.statDate}
+            WHERE ec.qw_user_id IN
+            <foreach collection="param.qwUserIds" item="id" open="(" separator="," close=")">
+                #{id}
+            </foreach>
+            AND ec.fs_user_id IS NOT NULL
+            AND ec.create_time &lt;= #{param.statDate}
+            GROUP BY ec.qw_user_id
+            ) AS cust
+            ON qw.id = cust.qw_user_id
+            WHERE qw.id IN
+            <foreach collection="param.qwUserIds" item="id" open="(" separator="," close=")">
+                #{id}
+            </foreach>
+            and qw.corp_id = #{param.corpId}
+            <if test="param.companyUserName != null and param.companyUserName != ''">
+                AND cu.nick_name LIKE CONCAT('%', #{param.companyUserName}, '%')
+            </if>
+            <if test="param.qwUserName != null and param.qwUserName != ''">
+                AND qw.qw_user_name LIKE CONCAT('%', #{param.qwUserName}, '%')
+            </if>
+            <if test="param.deptIds != null and param.deptIds.size() > 0">
+                and  cu.dept_id IN
+                <foreach collection="param.deptIds" item="deptId" open="(" separator="," close=")">
+                    #{deptId}
+                </foreach>
+            </if>
+            order by qw.qw_user_name
+        </if>
+        <if test="param.qwUserIds == null or param.qwUserIds.size() == 0">
+            SELECT
+            cu.user_id,
+            cu.dept_id,
+            cu.nick_name,
+            0 AS totalCustomers,
+            0 AS downloadCount,
+            0 AS notDownloadCount,
+            0 AS externalTotal
+            FROM company_user cu
+            WHERE user_id = #{param.companyUserId}
+        </if>
+    </select>
 </mapper>

+ 2 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -52,6 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="level" column="level"/>
         <result property="appOpenId"    column="app_open_id"    />
         <result property="appleKey"    column="apple_key"    />
+        <result property="firstLoginTime"    column="first_login_time"    />
         <result property="historyApp"    column="history_app"    />
     </resultMap>
 
@@ -709,6 +710,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">app_id = #{appId},</if>
             <if test="appOpenId != null">app_open_id = #{appOpenId},</if>
             <if test="appleKey != null">apple_key = #{appleKey},</if>
+            <if test="firstLoginTime != null">first_login_time = #{firstLoginTime},</if>
             <if test="historyApp != null">history_app = #{historyApp},</if>
             <if test="isOfficialAccountAuth != null">is_official_account_auth = #{isOfficialAccountAuth},</if>
         </trim>

+ 1 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -112,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectQwExternalContactListVOByIds" resultType="com.fs.qw.param.QwExternalContactVOTime">
-            select id,tag_ids,remark,create_time,fs_user_id,avatar from qw_external_contact
+        select id,tag_ids,remark,create_time,fs_user_id,avatar,name,external_user_id,level  from qw_external_contact
           where id in
                 <foreach collection="ids" item="id" open="(" separator="," close=")">
                 #{id}

+ 5 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -254,6 +254,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where qwt.qw_user_id = #{userId}
           and date(qwt.create_time) = #{date}
           and qwt.status = 0
+          and qwt.type = 2
     </select>
 
     <select id="selectQwUserByFsUserId" resultType="com.fs.qw.vo.sidebar.ExternalContactQwUserVO">
@@ -449,4 +450,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           and qu.company_user_id = #{companyUserId}
     </select>
 
+    <select id="selectByCorpId" resultType="java.lang.Long">
+        select id from qw_user where corp_id = #{corpId}
+                                 and company_id = #{companyId}
+    </select>
 </mapper>

+ 41 - 0
fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml

@@ -7,6 +7,7 @@
     <resultMap type="QwSopTempVoice" id="QwSopTempVoiceResult">
         <result property="id"    column="id"    />
         <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
         <result property="tempId"    column="temp_id"    />
         <result property="ruleId"    column="rule_id"    />
         <result property="contentId"    column="content_id"    />
@@ -210,4 +211,44 @@
         <if test="keyword != null and keyword!='' ">  and voice_txt like concat('%', #{keyword}, '%') </if>
     </select>
 
+
+
+    <select id="selectQwSopTempVoiceBySidberList" parameterType="QwSopTempVoice" resultMap="QwSopTempVoiceResult">
+        <include refid="selectQwSopTempVoiceVo"/>
+        <where>
+            1=1
+            and temp_id is null
+            <if test="data.companyId != null "> and company_id = #{data.companyId}</if>
+            <if test="data.recordType != null"> and record_type= #{data.recordType}</if>
+        </where>
+        GROUP BY company_id, voice_txt
+    </select>
+
+
+    <delete id="deleteQwSopTempVoiceByIdSidebar" parameterType="QwSopTempVoice">
+        delete from qw_sop_temp_voice
+        where
+            temp_id is null  and company_id=#{data.companyId} and voice_txt=#{data.voiceTxt}
+    </delete>
+
+
+    <insert id="insertOrUpdateBatch" parameterType="java.util.List">
+        insert into qw_sop_temp_voice (
+        company_user_id,
+        company_id,
+        voice_txt,
+        record_type
+        ) values
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.companyUserId},
+            #{item.companyId},
+            #{item.voiceTxt},
+            #{item.recordType}
+            )
+        </foreach>
+        ON DUPLICATE KEY UPDATE
+        update_time = NOW()
+    </insert>
+
 </mapper>

+ 57 - 2
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -361,6 +361,11 @@ public class AppLoginController extends AppBaseController{
                         userMap.setJpushId(param.getJpushId());
                     }
                     userMap.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
+                    userMap.setSource(param.getSource()!=null ? param.getSource() : null);
+                    userMap.setHistoryApp("app");
+                    if(userMap.getFirstLoginTime() == null){
+                        userMap.setFirstLoginTime(new Date());
+                    }
                     userService.updateFsUser(userMap);
                     user=appleUser;
                 }
@@ -368,6 +373,10 @@ public class AppLoginController extends AppBaseController{
                     user = new FsUser();
                     user.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
                     user.setSource(param.getSource()!=null ? param.getSource() : null);
+                    user.setHistoryApp("app");
+                    if(user.getFirstLoginTime() == null){
+                        user.setFirstLoginTime(new Date());
+                    }
                     user.setNickName(nickname);
                     user.setAvatar(avatar);
                     user.setAppOpenId(openid);
@@ -418,7 +427,10 @@ public class AppLoginController extends AppBaseController{
                     }
 
                     userMap.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
-
+                    userMap.setHistoryApp("app");
+                    if(userMap.getFirstLoginTime() == null){
+                        userMap.setFirstLoginTime(new Date());
+                    }
                     if(userService.updateFsUser(userMap)>0){
                         userService.deleteFsUserByUserId(appleUser.getUserId());
                     }else {
@@ -457,6 +469,10 @@ public class AppLoginController extends AppBaseController{
                     }
                     // 更新登录设备信息
                     userMap.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
+                    userMap.setHistoryApp("app");
+                    if(userMap.getFirstLoginTime() == null){
+                        userMap.setFirstLoginTime(new Date());
+                    }
 
                     userService.updateFsUser(userMap);
                 }
@@ -610,6 +626,10 @@ public class AppLoginController extends AppBaseController{
         FsUser newUser = new FsUser();
         newUser.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
         newUser.setSource(param.getSource()!=null ? param.getSource() : null);
+        newUser.setHistoryApp("app");
+        if (newUser.getFirstLoginTime() == null){
+            newUser.setFirstLoginTime(new Date());
+        }
         newUser.setAppleKey(param.getAppleKey());
         newUser.setPhone(param.getPhone());
         newUser.setPassword(Md5Utils.hash(param.getPassword()));
@@ -644,7 +664,10 @@ public class AppLoginController extends AppBaseController{
         } else {
             userMap = createNewAppleUser(param, param.getPhone());
         }
-
+        userMap.setHistoryApp("app");
+        if (userMap.getFirstLoginTime() == null) {
+            userMap.setFirstLoginTime(new Date());
+        }
         userMap.setLoginDevice(param.getLoginDevice());
         userMap.setSource(param.getSource());
         if ("匿名用户**".equals(userMap.getNickName())) {
@@ -799,6 +822,10 @@ public class AppLoginController extends AppBaseController{
             if (userMap.getUserId().equals(user.getUserId())) {
                 user.setPhone(param.getPhone());
                 user.setLoginDevice(param.getLoginDevice());
+                user.setHistoryApp("app");
+                if (user.getFirstLoginTime() == null) {
+                    user.setFirstLoginTime(new Date());
+                }
                 user.setSource(param.getSource());
                 String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
                 user.setLastIp(ipAddr);
@@ -819,6 +846,10 @@ public class AppLoginController extends AppBaseController{
                 deleteUser = userMap;
             }
             keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
+            keepUser.setHistoryApp("app");
+            if (keepUser.getFirstLoginTime() == null) {
+                keepUser.setFirstLoginTime(new Date());
+            }
             keepUser.setSource(param.getSource());
             keepUser.setUnionId(user.getUnionId());
             keepUser.setPhone(param.getPhone());
@@ -832,9 +863,17 @@ public class AppLoginController extends AppBaseController{
         }
         user.setPhone(param.getPhone());
         user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null );
+        user.setHistoryApp("app");
+        if (user.getFirstLoginTime() == null) {
+            user.setFirstLoginTime(new Date());
+        }
         user.setSource(param.getSource()!= null ? param.getSource() : null);
         userMap = new FsUser();
         userMap.setLoginDevice(param.getLoginDevice());
+        userMap.setHistoryApp("app");
+        if (userMap.getFirstLoginTime() == null) {
+            userMap.setFirstLoginTime(new Date());
+        }
         userMap.setSource(param.getSource());
         userMap.setUserId(user.getUserId());
         userMap.setPhone(param.getPhone());
@@ -893,6 +932,10 @@ public class AppLoginController extends AppBaseController{
                     if (userByUnionId.getUserId().equals(user.getUserId())) {
                         user.setPhone(param.getPhone());
                         user.setLoginDevice(param.getLoginDevice());
+                        user.setHistoryApp("app");
+                        if (user.getFirstLoginTime() == null) {
+                            user.setFirstLoginTime(new Date());
+                        }
                         user.setSource(param.getSource());
                         user.setUnionId(unionid);
                         String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
@@ -913,6 +956,10 @@ public class AppLoginController extends AppBaseController{
                     keepUser.setPhone(param.getPhone());
                     keepUser.setSource(param.getSource() != null ? param.getSource() : null );
                     keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
+                    keepUser.setHistoryApp("app");
+                    if (keepUser.getFirstLoginTime() == null) {
+                        keepUser.setFirstLoginTime(new Date());
+                    }
                     keepUser.setNickName(nickname);
                     keepUser.setAvatar(avatar);
                     if (keepUser.getPassword() == null && deleteUser.getPassword() != null) {
@@ -931,6 +978,10 @@ public class AppLoginController extends AppBaseController{
                 }else {
                     user.setSource(param.getSource() != null ? param.getSource() : null );
                     user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
+                    user.setHistoryApp("app");
+                    if (user.getFirstLoginTime() == null) {
+                        user.setFirstLoginTime(new Date());
+                    }
                     user.setNickName(nickname);
                     user.setAvatar(avatar);
                     user.setSex(sex);
@@ -1367,6 +1418,10 @@ public class AppLoginController extends AppBaseController{
     private FsUser createNewUser(FsUserLoginParam param) {
         FsUser newUser = new FsUser();
         newUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
+        newUser.setHistoryApp("app");
+        if (newUser.getFirstLoginTime() == null) {
+            newUser.setFirstLoginTime(new Date());
+        }
         newUser.setSource(param.getSource() != null ? param.getSource() : null );
         newUser.setNickName("匿名用户**");
         newUser.setPhone(param.getPhone());