Browse Source

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 month ago
parent
commit
fd1cd77a25
100 changed files with 2639 additions and 664 deletions
  1. 1 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  2. 5 2
      fs-admin/src/main/java/com/fs/task/CrmCustomerAiProcessingTask.java
  3. 5 0
      fs-company/src/main/java/com/fs/company/controller/aicall/CcLlmAgentAccountController.java
  4. 2 5
      fs-company/src/main/java/com/fs/company/controller/company/GeneralCustomerEntryController.java
  5. 7 0
      fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java
  6. 1 0
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  7. 1 1
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  8. 4 6
      fs-company/src/test/java/com/mixLiu/test/mixLiuTester.java
  9. 7 0
      fs-service/src/main/java/com/fs/aiChat/mapper/InterestAiChatSessionMapper.java
  10. 7 0
      fs-service/src/main/java/com/fs/aicall/mapper/CompanyBindAiModelMapper.java
  11. 9 0
      fs-service/src/main/java/com/fs/aicall/service/ICompanyBindAiModelService.java
  12. 9 0
      fs-service/src/main/java/com/fs/aicall/service/impl/CompanyBindAiModelServiceImpl.java
  13. 4 7
      fs-service/src/main/java/com/fs/company/config/AsyncCalleeConfig.java
  14. 1 1
      fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java
  15. 2 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRobotic.java
  16. 1 1
      fs-service/src/main/java/com/fs/company/param/ExecutionContext.java
  17. 21 0
      fs-service/src/main/java/com/fs/company/service/IAsyncCalleeProcessorService.java
  18. 473 0
      fs-service/src/main/java/com/fs/company/service/impl/AsyncCalleeProcessorServiceImpl.java
  19. 17 288
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  20. 1 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java
  21. 48 11
      fs-service/src/main/java/com/fs/company/service/impl/GeneralCustomerEntryServiceImpl.java
  22. 3 3
      fs-service/src/main/java/com/fs/company/util/PhoneNumberUtil.java
  23. 14 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  24. 5 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  25. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyUserMapper.java
  26. 8 0
      fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java
  27. 0 1
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  28. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  29. 21 20
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  30. 22 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  31. 3 0
      fs-service/src/main/java/com/fs/course/vo/CourseStatisticsDetailVO.java
  32. 42 0
      fs-service/src/main/java/com/fs/course/vo/FSActualCompletionVO.java
  33. 4 0
      fs-service/src/main/java/com/fs/crm/domain/CrmCustomerAnalyze.java
  34. 12 7
      fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerAnalyzeMapper.java
  35. 33 2
      fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerMapper.java
  36. 7 0
      fs-service/src/main/java/com/fs/crm/param/CrmCustomerListQueryParam.java
  37. 7 0
      fs-service/src/main/java/com/fs/crm/param/CrmLineCustomerListQueryParam.java
  38. 9 7
      fs-service/src/main/java/com/fs/crm/service/ICrmCustomerAnalyzeService.java
  39. 125 11
      fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerAnalyzeServiceImpl.java
  40. 15 9
      fs-service/src/main/java/com/fs/crm/utils/CrmCustomerAiTagUtil.java
  41. 7 0
      fs-service/src/main/java/com/fs/crm/vo/CrmCustomerListQueryVO.java
  42. 7 0
      fs-service/src/main/java/com/fs/crm/vo/CrmLineCustomerListQueryVO.java
  43. 78 0
      fs-service/src/main/java/com/fs/his/domain/FsHealthPulse.java
  44. 83 0
      fs-service/src/main/java/com/fs/his/domain/FsHealthSurface.java
  45. 84 0
      fs-service/src/main/java/com/fs/his/domain/FsUserAdditionalData.java
  46. 74 0
      fs-service/src/main/java/com/fs/his/mapper/FsHealthPulseMapper.java
  47. 77 0
      fs-service/src/main/java/com/fs/his/mapper/FsHealthSurfaceMapper.java
  48. 68 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserAdditionalDataMapper.java
  49. 10 0
      fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java
  50. 16 0
      fs-service/src/main/java/com/fs/his/param/FsHealthPulseListUParam.java
  51. 16 0
      fs-service/src/main/java/com/fs/his/param/FsHealthSurfaceListUParam.java
  52. 2 0
      fs-service/src/main/java/com/fs/his/param/FsHealthTongueListUParam.java
  53. 65 0
      fs-service/src/main/java/com/fs/his/service/IFsUserAdditionalDataService.java
  54. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  55. 98 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserAdditionalDataServiceImpl.java
  56. 69 0
      fs-service/src/main/java/com/fs/his/vo/FsHealthPulseListVO.java
  57. 83 0
      fs-service/src/main/java/com/fs/his/vo/FsHealthSurfaceListVO.java
  58. 16 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  59. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  60. 3 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductUserEndCategoryMapper.java
  61. 2 2
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreUserEndCategoryScrmService.java
  62. 63 14
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  63. 102 17
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  64. 1 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  65. 20 5
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java
  66. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java
  67. 0 83
      fs-service/src/main/java/com/fs/watch/domain/FsMonitorDataType.java
  68. 96 0
      fs-service/src/main/java/com/fs/watch/domain/WatchAudioMsgLog.java
  69. 0 108
      fs-service/src/main/java/com/fs/watch/domain/WatchDeviceBeginnerGuide.java
  70. 56 0
      fs-service/src/main/java/com/fs/watch/domain/WatchDeviceWeek.java
  71. 89 0
      fs-service/src/main/java/com/fs/watch/domain/WatchSosCallLogs.java
  72. 4 0
      fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserHealthReportVo.java
  73. 4 0
      fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserHealthVo.java
  74. 4 0
      fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserVo.java
  75. 4 0
      fs-service/src/main/java/com/fs/watch/domain/vo/AppWatchSleepDataVo.java
  76. 69 0
      fs-service/src/main/java/com/fs/watch/domain/vo/WatchDeviceInfoAndIotExportVO.java
  77. 0 9
      fs-service/src/main/java/com/fs/watch/domain/vo/WatchDeviceInfoAndUserIdVo.java
  78. 2 0
      fs-service/src/main/java/com/fs/watch/domain/vo/WatchSleepDataVo.java
  79. 2 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchAlarmDataMapper.java
  80. 12 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchAudioMsgLogMapper.java
  81. 4 8
      fs-service/src/main/java/com/fs/watch/mapper/WatchBloodPressureDataMapper.java
  82. 2 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceInfoClicMapper.java
  83. 4 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceInfoMapper.java
  84. 68 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceWeekMapper.java
  85. 2 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchDoctorMapper.java
  86. 2 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchHeartRateDataMapper.java
  87. 36 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchSosCallLogsMapper.java
  88. 2 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchSpo2DataMapper.java
  89. 15 0
      fs-service/src/main/java/com/fs/watch/param/DeviceSendParam.java
  90. 0 1
      fs-service/src/main/java/com/fs/watch/param/WatchChartQueryParam.java
  91. 15 0
      fs-service/src/main/java/com/fs/watch/param/WatchDeviceInfoQueryParam.java
  92. 8 0
      fs-service/src/main/java/com/fs/watch/service/DeviceSetUpService.java
  93. 7 0
      fs-service/src/main/java/com/fs/watch/service/WatchAudioMsgLogService.java
  94. 1 1
      fs-service/src/main/java/com/fs/watch/service/WatchBasicInfoService.java
  95. 0 24
      fs-service/src/main/java/com/fs/watch/service/WatchDeviceBeginnerGuideService.java
  96. 3 0
      fs-service/src/main/java/com/fs/watch/service/WatchDeviceInfoClicService.java
  97. 2 0
      fs-service/src/main/java/com/fs/watch/service/WatchDeviceInfoService.java
  98. 11 0
      fs-service/src/main/java/com/fs/watch/service/WatchDeviceWeekService.java
  99. 33 0
      fs-service/src/main/java/com/fs/watch/service/WatchSosCallLogsService.java
  100. 63 0
      fs-service/src/main/java/com/fs/watch/service/impl/DeviceSetUpServiceImpl.java

+ 1 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -679,7 +679,7 @@ public class FsStoreOrderScrmController extends BaseController {
         if (order.getCompanyUserId() != null) {
             CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
             Company company = companyService.selectCompanyById(companyUser.getCompanyId());
-            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyUserName(companyUser.getNickName());
             order.setCompanyName(company.getCompanyName());
         } else if (order.getCompanyId() != null) {
             Company company = companyService.selectCompanyById(order.getCompanyId());

+ 5 - 2
fs-admin/src/main/java/com/fs/task/CrmCustomerAiProcessingTask.java

@@ -1,5 +1,6 @@
 package com.fs.task;
 
+import com.fs.crm.service.ICrmCustomerAnalyzeService;
 import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -20,6 +21,7 @@ public class CrmCustomerAiProcessingTask {
     private final RedisTemplate redisTemplate;
 
     private static final String CRM_AI_REDIS_KEY = "crm:AI:data:processing";
+    private final ICrmCustomerAnalyzeService crmCustomerAnalyzeService;
 
     // 自定义线程池
     private final ExecutorService executorService = new ThreadPoolExecutor(
@@ -100,8 +102,9 @@ public class CrmCustomerAiProcessingTask {
                 // 获取数据
                 String customerId = data.get("customerId");
                 String dataJson = data.get("data");
-                //todo 业务!!!!!!1.ai沟通总结2.流失风险等级3.沟通摘要4.客户画像8.客户关注点9.客户意向度
-
+                String logId = data.get("logId");
+                //todo 业务!!!!!!1.ai沟通总结2.流失风险等级3.沟通摘要4.客户画像8.客户关注点9.客户意向度 //都要异步处理
+                crmCustomerAnalyzeService.aiGeneratedCustomerPortrait(customerId,dataJson,logId);
 
 
                 // 模拟业务处理

+ 5 - 0
fs-company/src/main/java/com/fs/company/controller/aicall/CcLlmAgentAccountController.java

@@ -282,6 +282,11 @@ public class CcLlmAgentAccountController extends BaseController
     @ResponseBody
     public AjaxResult remove(String ids)
     {
+        //获取公司id
+        Long companyId = getCurrentCompanyId();
+        //删除公司绑定的AI模型
+        companyBindAiModelService.deleteBindAiModelByCompanyIdAndModelIds(companyId,ids);
+
         return toAjax(ccLlmAgentAccountService.deleteCcLlmAgentAccountByIds(ids));
     }
 

+ 2 - 5
fs-company/src/main/java/com/fs/company/controller/company/GeneralCustomerEntryController.java

@@ -4,10 +4,7 @@ import com.fs.common.core.domain.R;
 import com.fs.company.param.EntryCustomerParam;
 import com.fs.company.service.IGeneralCustomerEntryService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @author MixLiu
@@ -22,7 +19,7 @@ public class GeneralCustomerEntryController {
     IGeneralCustomerEntryService iGeneralCustomerEntryService;
 
     @PostMapping("/entryCustomer")
-    public R entryCustomer(EntryCustomerParam param){
+    public R entryCustomer(@RequestBody EntryCustomerParam param){
         iGeneralCustomerEntryService.entryCustomer(param);
        return R.ok("success");
     }

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

@@ -190,6 +190,13 @@ public class CrmCustomerController extends BaseController
 //        if(loginUser.getCompany().getCompanyId()==116){   // 河北湘银信息咨询服务有限公司(JZ-1)客户假删除不显示
 //            param.setCompanyId(0L);
 //        }
+        //默认值处理
+        if(param.getIntentionDegreeGt() != null && param.getIntentionDegreeGt() == 0){
+            param.setIntentionDegreeGt(null);
+        }
+        if(param.getIntentionDegreelt() != null && param.getIntentionDegreelt() == 0){
+            param.setIntentionDegreelt(null);
+        }
         if (param.getIsReceive() != null && param.getIsReceive() == 0){
             CrmLineCustomerListQueryParam param1 = new CrmLineCustomerListQueryParam();
             BeanUtils.copyProperties(param,param1);

+ 1 - 0
fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -135,6 +135,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/qw/user/selectCloudByCompany").anonymous()
                 .antMatchers("/live/LiveMixLiuTestOpen/**").anonymous()
                 .antMatchers("/app/common/callbackAfterSendSingleMsgCommand").anonymous()
+                .antMatchers("/company/general/customer/**").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 1 - 1
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -258,7 +258,7 @@ public class FsStoreOrderScrmController extends BaseController
         if (order.getCompanyUserId() != null) {
             CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
             Company company = companyService.selectCompanyById(companyUser.getCompanyId());
-            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyUserName(companyUser.getNickName());
             order.setCompanyName(company.getCompanyName());
         } else if (order.getCompanyId() != null) {
             Company company = companyService.selectCompanyById(order.getCompanyId());

+ 4 - 6
fs-company/src/test/java/com/mixLiu/test/mixLiuTester.java

@@ -33,12 +33,10 @@ public class mixLiuTester {
 
     @Test
     public void test11(){
-        FsDoctor doctorMap = new FsDoctor();
-        doctorMap.setDoctorId(1L);
-        doctorMap.setJpushId("123");
+        String substring = "18580336425".substring(7, 11);
+        System.out.println(substring);
 
-        if(doctorMap.getStatus() != 1){
-            System.out.println("开始删除处方医生");
-        }
     }
+
+
 }

+ 7 - 0
fs-service/src/main/java/com/fs/aiChat/mapper/InterestAiChatSessionMapper.java

@@ -5,6 +5,7 @@ import com.fs.aiChat.domain.DoctorAiChatLog;
 import com.fs.aiChat.domain.InterestAiChatMsg;
 import com.fs.aiChat.domain.InterestAiSession;
 import com.fs.aiChat.domain.SessionRoleInfo;
+import com.fs.aiChat.param.InterestAiMessage;
 import com.fs.his.domain.FsInterestAiMsg;
 import com.fs.his.domain.FsInterestAiRole;
 import com.fs.his.domain.FsInterestAiSession;
@@ -56,4 +57,10 @@ public interface InterestAiChatSessionMapper extends BaseMapper<DoctorAiChatLog>
 
 
     FsInterestAiRole selectFsInterestAiRoleByRoleId(Long roleId);
+
+    Integer selectSessionIdByUserAndRole(@Param("userId") Long userId, @Param("roleId") Integer roleId);
+
+    FsInterestAiSession selectSessionBySessionId(@Param("sessionId") String sessionId);
+
+    Integer selectAiMsgBySessionIdAndMsg(@Param("param") InterestAiMessage message);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/aicall/mapper/CompanyBindAiModelMapper.java

@@ -87,4 +87,11 @@ public interface CompanyBindAiModelMapper
      * @return 结果
      */
     public int batchInsertCompanyBindAiModel(@Param("companyId") Long companyId, @Param("modelIds") List<Long> modelIds);
+
+    /**
+     * 批量删除·1、公司ID、模型ID
+     * @param companyId
+     * @param modelIds 模型ID列表
+     * **/
+    void deleteBindAiModelByCompanyIdAndModelIds(@Param("companyId") Long companyId, @Param("modelIds") String[] modelIds);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/aicall/service/ICompanyBindAiModelService.java

@@ -102,4 +102,13 @@ public interface ICompanyBindAiModelService
      * @return 结果
      */
     public int batchBindCompaniesToModel(Long modelId, List<Long> companyIds);
+
+    /**
+     * 批量删除对应公司模型关系表
+     *
+     * @param companyId 公司ID
+     * @param modelIds 模型ID列表
+     * @return 结果
+     */
+    void deleteBindAiModelByCompanyIdAndModelIds(Long companyId, String modelIds);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/aicall/service/impl/CompanyBindAiModelServiceImpl.java

@@ -3,11 +3,15 @@ package com.fs.aicall.service.impl;
 import com.fs.aicall.domain.CompanyBindAiModel;
 import com.fs.aicall.mapper.CompanyBindAiModelMapper;
 import com.fs.aicall.service.ICompanyBindAiModelService;
+import com.fs.aicall.utils.StringUtils;
+import com.fs.common.core.text.Convert;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 销售公司与AI模型绑定表Service实现类
@@ -167,4 +171,9 @@ public class CompanyBindAiModelServiceImpl implements ICompanyBindAiModelService
         }
         return result;
     }
+
+    @Override
+    public void deleteBindAiModelByCompanyIdAndModelIds(Long companyId, String modelIds) {
+        companyBindAiModelMapper.deleteBindAiModelByCompanyIdAndModelIds(companyId, Convert.toStrArray(modelIds));
+    }
 }

+ 4 - 7
fs-service/src/main/java/com/fs/company/config/AsyncConfig.java → fs-service/src/main/java/com/fs/company/config/AsyncCalleeConfig.java

@@ -10,20 +10,17 @@ import java.util.concurrent.ThreadPoolExecutor;
 
 @Configuration
 @EnableAsync
-public class AsyncConfig {
+public class AsyncCalleeConfig {
     @Bean(name = "calleeTaskExecutor")
     public Executor calleeTaskExecutor() {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        int cpuCores = Runtime.getRuntime().availableProcessors();
-        executor.setCorePoolSize(cpuCores);
-        executor.setMaxPoolSize(20);
-        executor.setQueueCapacity(1000);
+        executor.setCorePoolSize(10);
+        executor.setMaxPoolSize(50);
+        executor.setQueueCapacity(500);
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
         executor.setKeepAliveSeconds(60);
-        executor.setAllowCoreThreadTimeOut(false);
         executor.setWaitForTasksToCompleteOnShutdown(true);
         executor.setAwaitTerminationSeconds(60);
-
         executor.initialize();
         return executor;
     }

+ 1 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java

@@ -69,7 +69,7 @@ public class CompanyAiWorkflowExec {
 
     /** 业务键值 */
     @Excel(name = "业务键值")
-    private String businessKey;
+    private Long businessKey;
 
     /**
      * 开始节点key

+ 2 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRobotic.java

@@ -131,6 +131,8 @@ public class CompanyVoiceRobotic {
     private String qwUserId;
     private Integer taskType;
     private Integer sceneType;
+    @TableField(exist = false)
+    private String sceneTypeName;
     private LocalTime availableStartTime;
     private LocalTime availableEndTime;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/company/param/ExecutionContext.java

@@ -53,7 +53,7 @@ public class ExecutionContext {
     /**
      * 业务关键id
      */
-    private String businessId;
+    private Long businessId;
     /**
      * 设置本地变量
      */

+ 21 - 0
fs-service/src/main/java/com/fs/company/service/IAsyncCalleeProcessorService.java

@@ -0,0 +1,21 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyVoiceRobotic;
+import com.fs.company.domain.CompanyVoiceRoboticCallees;
+import com.fs.company.domain.CompanyWxClient;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 异步调用服务加入日志
+ * **/
+public interface IAsyncCalleeProcessorService {
+    /**
+     * 生成客户信息日志
+     * @param calleesList 客户列表对象
+     * @param clientMp 添加个微信账号对象
+     * @param robotic 任务
+     * **/
+    void generateCustomerInfo(List<CompanyVoiceRoboticCallees> calleesList, Map<String, CompanyWxClient> clientMp, CompanyVoiceRobotic robotic);
+}

+ 473 - 0
fs-service/src/main/java/com/fs/company/service/impl/AsyncCalleeProcessorServiceImpl.java

@@ -0,0 +1,473 @@
+package com.fs.company.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.*;
+import com.fs.company.service.IAsyncCalleeProcessorService;
+import com.fs.company.util.RandomNameGeneratorUtil;
+import com.fs.company.vo.CompanyNodeInfoVo;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+import com.fs.his.config.CidPhoneConfig;
+import com.fs.system.service.ISysConfigService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+
+@Service
+public class AsyncCalleeProcessorServiceImpl implements IAsyncCalleeProcessorService {
+
+    private static final Logger log = LoggerFactory.getLogger(AsyncCalleeProcessorServiceImpl.class);
+
+    private static final int BATCH_SIZE = 1500;
+    private static final int CPU_YIELD_INTERVAL = 500;
+    private static final int SLEEP_INTERVAL = 2000;
+    private static final int SLEEP_MILLIS = 10;
+    private static final int PHONE_LENGTH = 11;
+    private static final char DIGIT_ZERO = '0';
+    private static final int RADIX_TEN = 10;
+
+    private final ISysConfigService configService;
+    private final CompanyWorkflowEdgeMapper edgeMapper;
+    private final CompanyConfigMapper companyConfigMapper;
+    private final CompanyWorkflowMapper companyWorkflowMapper;
+    private final CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper;
+    private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
+    private final CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper;
+    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
+
+    AsyncCalleeProcessorServiceImpl(CompanyConfigMapper companyConfigMapper,
+                                    ISysConfigService configService,
+                                    CompanyWorkflowEdgeMapper edgeMapper,
+                                    CompanyWorkflowMapper companyWorkflowMapper,
+                                    CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper,
+                                    CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper,
+                                    CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper,
+                                    CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper) {
+        this.companyConfigMapper = companyConfigMapper;
+        this.configService = configService;
+        this.edgeMapper = edgeMapper;
+        this.companyWorkflowMapper = companyWorkflowMapper;
+        this.companyAiWorkflowExecMapper = companyAiWorkflowExecMapper;
+        this.companyAiWorkflowExecLogMapper = companyAiWorkflowExecLogMapper;
+        this.companyVoiceRoboticBusinessMapper = companyVoiceRoboticBusinessMapper;
+        this.companyVoiceRoboticCalleesMapper = companyVoiceRoboticCalleesMapper;
+    }
+
+    @Override
+    @Async("calleeTaskExecutor")
+    public void generateCustomerInfo(List<CompanyVoiceRoboticCallees> calleesList,
+                                     Map<String, CompanyWxClient> clientMp,
+                                     CompanyVoiceRobotic robotic) {
+        if (calleesList == null || calleesList.isEmpty() || robotic == null) {
+            return;
+        }
+
+        Thread currentThread = Thread.currentThread();
+        if (currentThread.isInterrupted()) {
+            log.info("任务被中断,退出处理");
+            return;
+        }
+
+        CidPhoneConfig phoneConfig = loadPhoneConfig(robotic.getCompanyId());
+        if (phoneConfig == null || !Boolean.TRUE.equals(phoneConfig.getEnablePhoneConfig())) {
+            log.warn("电话配置未启用或为空,companyId: {}", robotic.getCompanyId());
+            return;
+        }
+
+        CompanyWorkflow workflow = loadWorkflow(robotic.getCompanyAiWorkflowId());
+        if (workflow == null) {
+            log.warn("工作流不存在,workflowId: {}", robotic.getCompanyAiWorkflowId());
+            return;
+        }
+
+        CompanyNodeInfoVo nodeInfoVo = loadNodeInfo(workflow.getWorkflowId(), workflow.getStartNodeKey());
+        if (nodeInfoVo == null) {
+            log.warn("节点信息不存在,workflowId: {}, startNodeKey: {}", workflow.getWorkflowId(), workflow.getStartNodeKey());
+            return;
+        }
+
+        List<CompanyVoiceRoboticCallees> batchToInsert = new ArrayList<>(BATCH_SIZE);
+        int processedCount = 0;
+
+        for (CompanyVoiceRoboticCallees callees : calleesList) {
+            if (currentThread.isInterrupted()) {
+                log.info("任务被中断,已处理 {} 条", processedCount);
+                break;
+            }
+
+            try {
+                generatePhoneNumberInBatch(phoneConfig, callees, batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
+                processedCount++;
+
+                if (processedCount % CPU_YIELD_INTERVAL == 0) {
+                    Thread.yield();
+                }
+
+                if (processedCount % SLEEP_INTERVAL == 0) {
+                    Thread.sleep(SLEEP_MILLIS);
+                }
+            } catch (InterruptedException e) {
+                log.info("任务被中断,已处理 {} 条", processedCount);
+                Thread.currentThread().interrupt();
+                break;
+            } catch (Exception e) {
+                log.error("处理被叫信息异常,phone: {}", callees.getPhone(), e);
+            }
+        }
+
+        log.info("generateCustomerInfo处理完成,共处理 {} 条", processedCount);
+    }
+
+    private CidPhoneConfig loadPhoneConfig(Long companyId) {
+        try {
+            CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(companyId, "cid.config");
+            if (companyConfig != null && StringUtils.isNotEmpty(companyConfig.getConfigValue())) {
+                return JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
+            }
+            String json = configService.selectConfigByKey("his.store");
+            if (StringUtils.isNotEmpty(json)) {
+                return JSONObject.parseObject(json, CidPhoneConfig.class);
+            }
+        } catch (Exception e) {
+            log.error("加载电话配置异常,companyId: {}", companyId, e);
+        }
+        return null;
+    }
+
+    private CompanyWorkflow loadWorkflow(Long workflowId) {
+        try {
+            return companyWorkflowMapper.selectCompanyWorkflowById(workflowId);
+        } catch (Exception e) {
+            log.error("加载工作流异常,workflowId: {}", workflowId, e);
+            return null;
+        }
+    }
+
+    private CompanyNodeInfoVo loadNodeInfo(Long workflowId, String startNodeKey) {
+        try {
+            return edgeMapper.slectNodeInfoByWorkflowId(workflowId, startNodeKey);
+        } catch (Exception e) {
+            log.error("加载节点信息异常,workflowId: {}, startNodeKey: {}", workflowId, startNodeKey, e);
+            return null;
+        }
+    }
+
+    private void generatePhoneNumberInBatch(CidPhoneConfig config,
+                                            CompanyVoiceRoboticCallees callees,
+                                            List<CompanyVoiceRoboticCallees> batchToInsert,
+                                            Map<String, CompanyWxClient> clientMp,
+                                            CompanyVoiceRobotic robotic,
+                                            CompanyWorkflow workflow,
+                                            CompanyNodeInfoVo nodeInfoVo) {
+        String basePhone = callees.getPhone();
+        if (basePhone == null || basePhone.length() != PHONE_LENGTH) {
+            return;
+        }
+
+        int start = config.getStartIndex();
+        int end = config.getEndIndex();
+        int totalCount = config.getGenerateCount();
+
+        if (!isValidRange(start, end)) {
+            return;
+        }
+
+        int startIdx = start - 1;
+        int endIdx = end - 1;
+        char[] baseChars = basePhone.toCharArray();
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        int nameIndex = 0;
+
+        while (nameIndex < totalCount) {
+            if (Thread.currentThread().isInterrupted()) {
+                break;
+            }
+
+            int currentBatchSize = Math.min(BATCH_SIZE, totalCount - nameIndex);
+
+            for (int i = 0; i < currentBatchSize; i++) {
+                CompanyVoiceRoboticCallees roboticCallees = createCalleesWithPhone(
+                        baseChars, startIdx, endIdx, random, callees);
+                batchToInsert.add(roboticCallees);
+            }
+
+            nameIndex += currentBatchSize;
+
+            if (batchToInsert.size() >= BATCH_SIZE || nameIndex >= totalCount) {
+                flushGeneratedCalleesBatch(batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
+            }
+
+            if (nameIndex % SLEEP_INTERVAL == 0) {
+                try {
+                    Thread.sleep(SLEEP_MILLIS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+        }
+    }
+
+    private boolean isValidRange(int start, int end) {
+        return start >= 1 && start <= PHONE_LENGTH
+                && end >= 1 && end <= PHONE_LENGTH
+                && start <= end;
+    }
+
+    private CompanyVoiceRoboticCallees createCalleesWithPhone(char[] baseChars, int startIdx, int endIdx,
+                                                              ThreadLocalRandom random,
+                                                              CompanyVoiceRoboticCallees template) {
+        char[] newChars = baseChars.clone();
+        for (int j = startIdx; j <= endIdx; j++) {
+            newChars[j] = (char) (DIGIT_ZERO + random.nextInt(RADIX_TEN));
+        }
+
+        CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
+        roboticCallees.setPhone(new String(newChars));
+        roboticCallees.setRoboticId(template.getRoboticId());
+        roboticCallees.setTaskFlow(template.getTaskFlow());
+        roboticCallees.setRunTaskFlow(template.getRunTaskFlow());
+        roboticCallees.setIsWeCom(template.getIsWeCom());
+        roboticCallees.setUserId(0L);
+        roboticCallees.setUserName(RandomNameGeneratorUtil.generateOne());
+        roboticCallees.setIsGenerate(1);
+
+        return roboticCallees;
+    }
+
+    private void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
+                                            Map<String, CompanyWxClient> clientMap,
+                                            CompanyVoiceRobotic robotic,
+                                            CompanyWorkflow workflow,
+                                            CompanyNodeInfoVo nodeInfoVo) {
+        if (batchToInsert == null || batchToInsert.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
+            if (rows > 0) {
+                generateVoiceRoboticBusiness(batchToInsert, clientMap, robotic, workflow, nodeInfoVo);
+            }
+        } catch (Exception e) {
+            log.error("批量插入被叫数据异常,size: {}", batchToInsert.size(), e);
+        } finally {
+            batchToInsert.clear();
+        }
+    }
+
+    private void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
+                                              Map<String, CompanyWxClient> clientMp,
+                                              CompanyVoiceRobotic robotic,
+                                              CompanyWorkflow workflow,
+                                              CompanyNodeInfoVo nodeInfoVo) {
+        if (calleesList == null || calleesList.isEmpty()) {
+            return;
+        }
+
+        List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>(calleesList.size());
+        Date date = new Date();
+
+        for (CompanyVoiceRoboticCallees callees : calleesList) {
+            CompanyVoiceRoboticBusiness business = createBusiness(callees, clientMp, date);
+            addList.add(business);
+        }
+
+        if (!addList.isEmpty()) {
+            flushGeneratedBusinessBatch(addList, robotic, workflow, nodeInfoVo);
+        }
+    }
+
+    private CompanyVoiceRoboticBusiness createBusiness(CompanyVoiceRoboticCallees callees,
+                                                       Map<String, CompanyWxClient> clientMp,
+                                                       Date date) {
+        CompanyVoiceRoboticBusiness business = new CompanyVoiceRoboticBusiness();
+        business.setRoboticId(callees.getRoboticId());
+        business.setCalleeId(callees.getId());
+
+        String clientKey = callees.getRoboticId() + "-" + callees.getUserId();
+        CompanyWxClient client = clientMp.get(clientKey);
+        business.setWxClientId(client != null ? client.getId() : null);
+
+        business.setAddWxDone(0);
+        business.setCallPhoneDone(0);
+        business.setSendMsgDone(0);
+        business.setCreateTime(date);
+        business.setIsGenerate(1);
+
+        return business;
+    }
+
+    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,
+                                             CompanyVoiceRobotic robotic,
+                                             CompanyWorkflow workflow,
+                                             CompanyNodeInfoVo nodeInfoVo) {
+        if (batchToInsert == null || batchToInsert.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
+            if (rows > 0) {
+                generateWorkflowExecRecords(batchToInsert, robotic, workflow, nodeInfoVo);
+            }
+        } catch (Exception e) {
+            log.error("批量插入业务数据异常,size: {}", batchToInsert.size(), e);
+        } finally {
+            batchToInsert.clear();
+        }
+    }
+
+    private void generateWorkflowExecRecords(List<CompanyVoiceRoboticBusiness> businessList,
+                                             CompanyVoiceRobotic robotic,
+                                             CompanyWorkflow workflow,
+                                             CompanyNodeInfoVo nodeInfoVo) {
+        if (businessList == null || businessList.isEmpty()) {
+            return;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        List<CompanyAiWorkflowExec> startExecList = new ArrayList<>(businessList.size());
+        List<CompanyAiWorkflowExec> targetExecList = new ArrayList<>(businessList.size());
+
+        for (CompanyVoiceRoboticBusiness business : businessList) {
+            if (Thread.currentThread().isInterrupted()) {
+                break;
+            }
+
+            CompanyAiWorkflowExec startExec = createStartExec(business, robotic, workflow, now);
+            startExecList.add(startExec);
+
+            CompanyAiWorkflowExec targetExec = createTargetExec(startExec, nodeInfoVo, now);
+            targetExecList.add(targetExec);
+
+            if (targetExecList.size() >= BATCH_SIZE) {
+                persistExecLogs(targetExecList, startExecList);
+            }
+        }
+
+        if (!targetExecList.isEmpty()) {
+            persistExecLogs(targetExecList, startExecList);
+        }
+    }
+
+    private CompanyAiWorkflowExec createStartExec(CompanyVoiceRoboticBusiness business,
+                                                  CompanyVoiceRobotic robotic,
+                                                  CompanyWorkflow workflow,
+                                                  LocalDateTime now) {
+        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
+        exec.setWorkflowInstanceId(generateInstanceId());
+        exec.setWorkflowId(robotic.getCompanyAiWorkflowId());
+        exec.setCurrentNodeKey(workflow.getStartNodeKey());
+        exec.setCurrentNodeType(NodeTypeEnum.START.getValue());
+        exec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
+        exec.setStatus(ExecutionStatusEnum.SUCCESS.getValue());
+        exec.setStartTime(now);
+        exec.setVariables(buildVariables(robotic, business));
+        exec.setBusinessKey(business.getId());
+        exec.setStartNodeKey(workflow.getStartNodeKey());
+        exec.setEndNodeKey(workflow.getEndNodeKey());
+        exec.setCidGroupNo(robotic.getCidGroupNo());
+        exec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
+        exec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
+        exec.setIsGenerate(1);
+        return exec;
+    }
+
+    private CompanyAiWorkflowExec createTargetExec(CompanyAiWorkflowExec startExec,
+                                                   CompanyNodeInfoVo nodeInfoVo,
+                                                   LocalDateTime now) {
+        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
+        exec.setWorkflowInstanceId(startExec.getWorkflowInstanceId());
+        exec.setWorkflowId(startExec.getWorkflowId());
+        exec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
+        exec.setCurrentNodeName(nodeInfoVo.getNodeName());
+        exec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
+        exec.setStatus(ExecutionStatusEnum.INTERRUPT.getValue());
+        exec.setStartTime(now);
+        exec.setVariables(startExec.getVariables());
+        exec.setBusinessKey(startExec.getBusinessKey());
+        exec.setStartNodeKey(startExec.getStartNodeKey());
+        exec.setEndNodeKey(startExec.getEndNodeKey());
+        exec.setCidGroupNo(startExec.getCidGroupNo());
+        exec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
+        exec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
+        exec.setIsGenerate(1);
+        return exec;
+    }
+
+    private String buildVariables(CompanyVoiceRobotic robotic, CompanyVoiceRoboticBusiness business) {
+        JSONObject variables = new JSONObject(5);
+        variables.put("roboticId", robotic.getId());
+        variables.put("businessId", business.getId());
+        variables.put("cidGroupNo", robotic.getCidGroupNo());
+        variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
+        variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
+        return variables.toJSONString();
+    }
+
+    private void persistExecLogs(List<CompanyAiWorkflowExec> workflowExecs,
+                                 List<CompanyAiWorkflowExec> startExecList) {
+        if (workflowExecs == null || workflowExecs.isEmpty()) {
+            return;
+        }
+
+        try {
+            int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
+            if (rows > 0) {
+                workflowExecLogBatchInsert(startExecList);
+                workflowExecs.forEach(exec -> exec.setStatus(ExecutionStatusEnum.FAILURE.getValue()));
+                workflowExecLogBatchInsert(workflowExecs);
+            }
+        } catch (Exception e) {
+            log.error("持久化执行日志异常,size: {}", workflowExecs.size(), e);
+        } finally {
+            workflowExecs.clear();
+            startExecList.clear();
+        }
+    }
+
+    private void workflowExecLogBatchInsert(List<CompanyAiWorkflowExec> workflowExecs) {
+        if (workflowExecs == null || workflowExecs.isEmpty()) {
+            return;
+        }
+
+        List<CompanyAiWorkflowExecLog> batch = new ArrayList<>(workflowExecs.size());
+        Date date = new Date();
+
+        for (CompanyAiWorkflowExec w : workflowExecs) {
+            CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
+            execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
+            execLog.setNodeKey(w.getCurrentNodeKey());
+            execLog.setNodeName(w.getCurrentNodeName());
+            execLog.setNodeType(w.getCurrentNodeType());
+            execLog.setInputData(w.getVariables());
+            execLog.setStatus(w.getStatus());
+            execLog.setOutputData("null");
+            execLog.setStartTime(date);
+            execLog.setEndTime(date);
+            execLog.setIsGenerate(1);
+            batch.add(execLog);
+        }
+
+        try {
+            companyAiWorkflowExecLogMapper.batchInsert(batch);
+        } catch (Exception e) {
+            log.error("批量插入执行日志异常,size: {}", batch.size(), e);
+        } finally {
+            batch.clear();
+        }
+    }
+
+    private String generateInstanceId() {
+        return "wf_" + System.currentTimeMillis() + "_" +
+                UUID.randomUUID().toString().replace("-", "");
+    }
+}

+ 17 - 288
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -23,7 +23,6 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.ExecutionContext;
 import com.fs.company.service.*;
-import com.fs.company.util.RandomNameGeneratorUtil;
 import com.fs.company.vo.*;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
 import com.fs.crm.domain.CrmCustomer;
@@ -33,14 +32,11 @@ import com.fs.crm.service.impl.CrmCustomerServiceImpl;
 import com.fs.enums.ExecutionStatusEnum;
 import com.fs.enums.NodeTypeEnum;
 import com.fs.enums.TaskTypeEnum;
-import com.fs.his.config.CidPhoneConfig;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
-import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.mapper.SysDictDataMapper;
-import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.impl.SysDictTypeServiceImpl;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -49,11 +45,8 @@ import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-
-import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
-
 import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
 
 
@@ -75,7 +68,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyVoiceRoboticMapper companyVoiceRoboticMapper;
     private final AiCallService aiCallService;
     private final CrmCustomerServiceImpl crmCustomerService;
-    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
 
     private final CompanyVoiceRoboticCalleesServiceImpl companyVoiceRoboticCalleesService;
     private final ICompanyWxAccountService companyWxAccountService;
@@ -90,8 +82,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
 
     private final CompanyWxClientServiceImpl companyWxClientServiceImpl;
 
-    private final ISysConfigService configService;
-
     private final SysDictDataMapper sysDictDataMapper;
 
     private final SmsServiceImpl smsService;
@@ -111,23 +101,17 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyWorkflowEngine companyWorkflowEngine;
     private final CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper;
     private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
+
+    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;
     private final RedisCache redisCache2;
     private final CompanyAiWorkflowServerMapper companyAiWorkflowServerMapper;
     private final QwUserMapper qwUserMapper;
     private final EasyCallMapper easyCallMapper;
-
-    private final SysConfigMapper sysConfigMapper;
-    private final CompanyConfigMapper companyConfigMapper;
-
-    private final CompanyWorkflowMapper companyWorkflowMapper;
-
-    private final CompanyWorkflowEdgeMapper edgeMapper;
-
     private final QwExternalContactServiceImpl qwExternalContactService;
 
     private final SysDictTypeServiceImpl sysDictTypeService;
 
-    final int BATCH_SIZE = 1500;
+    private final IAsyncCalleeProcessorService asyncCalleeProcessorService;
 
     /** EasyCall intent 意向度重试队列 Redis key 前缀,value 为已重试次数 */
     private static final String EASYCALL_INTENT_RETRY_KEY = "easycall:intent:retry:";
@@ -160,10 +144,20 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     public List<CompanyVoiceRobotic> selectCompanyVoiceRoboticList(CompanyVoiceRobotic companyVoiceRobotic){
         return companyVoiceRoboticMapper.selectCompanyVoiceRoboticList(companyVoiceRobotic);
     }
+
     @Override
     @DataScope(deptAlias = "d", userAlias = "u")
-    public List<CompanyVoiceRobotic> selectCompanyVoiceRoboticListCompany(CompanyVoiceRobotic companyVoiceRobotic){
-        return companyVoiceRoboticMapper.selectCompanyVoiceRoboticListCompany(companyVoiceRobotic);
+    public List<CompanyVoiceRobotic> selectCompanyVoiceRoboticListCompany(CompanyVoiceRobotic companyVoiceRobotic) {
+        List<CompanyVoiceRobotic> companyVoiceRobotics = companyVoiceRoboticMapper.selectCompanyVoiceRoboticListCompany(companyVoiceRobotic);
+        List<SysDictData> taskSceneType = DictUtils.getDictCache("task_scene_type");
+        companyVoiceRobotics.forEach(a -> {
+            if (null != a.getSceneType()) {
+                taskSceneType.stream().filter(b -> b.getDictValue().equals(a.getSceneType().toString())).findFirst().ifPresent(c -> {
+                    a.setSceneTypeName(c.getDictLabel());
+                });
+            }
+        });
+        return companyVoiceRobotics;
     }
 
     /**
@@ -1416,42 +1410,12 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     }
     public void buildTaskBussiness(CompanyVoiceRobotic robotic) {
         List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper.selectByRoboticId(robotic.getId());
-        //获取电话生成配置
-        CidPhoneConfig phoneConfig = null;
-        //获取销售公司手机配置
-        CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(robotic.getCompanyId(), "cid.config");
-        //如果配置为空就获取总后台配置
-        if(companyConfig == null){
-            String json = configService.selectConfigByKey("his.store");
-            if(StringUtils.isNotEmpty(json)){
-                phoneConfig = JSONObject.parseObject(json,CidPhoneConfig.class);
-            }
-        } else {
-            phoneConfig = JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
-        }
-
-        //获取工作流主表信息
-        CompanyWorkflow workflow = companyWorkflowMapper.selectCompanyWorkflowById(robotic.getCompanyAiWorkflowId());
-
-        //获取相关节点信息
-        CompanyNodeInfoVo nodeInfoVo = edgeMapper.slectNodeInfoByWorkflowId(workflow.getWorkflowId(), workflow.getStartNodeKey());
-
         List<CompanyWxClient> companyWxClients = companyWxClientMapper.selectListByRoboticId(robotic.getId());
         Map<String, CompanyWxClient> clientMp = companyWxClients.stream().collect(Collectors.toMap(e -> e.getRoboticId() + "-" + e.getCustomerId(), e -> e));
         List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>();
-        List<CompanyVoiceRoboticCallees> batchToInsert = new LinkedList<>();
+        //异步生成列表日志
+        asyncCalleeProcessorService.generateCustomerInfo(calleesList,clientMp,robotic);
         for (CompanyVoiceRoboticCallees callees : calleesList) {
-            //根据配置随机生成电话号
-            if (phoneConfig != null && phoneConfig.getEnablePhoneConfig()) {//配置不为空并且开启了
-                List<CompanyVoiceRoboticCallees> roboticCallees = generatePhoneNumber(phoneConfig, callees);
-                if (!roboticCallees.isEmpty()) {
-                    batchToInsert.addAll(roboticCallees);
-                    if (batchToInsert.size() >= BATCH_SIZE) {
-                        flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic, workflow, nodeInfoVo);
-                        batchToInsert.clear();
-                    }
-                }
-            }
             CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
             companyVoiceRoboticBusiness.setRoboticId(robotic.getId());
             companyVoiceRoboticBusiness.setCalleeId(callees.getId());
@@ -1462,13 +1426,6 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             companyVoiceRoboticBusiness.setCreateTime(new Date());
             addList.add(companyVoiceRoboticBusiness);
         }
-
-        //处理剩余数据
-        if (!batchToInsert.isEmpty()) {
-            flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic, workflow, nodeInfoVo);
-            batchToInsert.clear();
-        }
-
         companyVoiceRoboticBusinessMapper.insertBatch(addList);
 
     }
@@ -1678,232 +1635,4 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return vo;
         }).collect(Collectors.toList());
     }
-
-    /**
-     * 根据配置生成手机号
-     *
-     * @param config  配置对象(包含是否启用、生成数量、起始位置、结束位置)
-     * @param callees 任务外呼电话对象
-     * @return 生成的手机号列表
-     */
-    public static List<CompanyVoiceRoboticCallees> generatePhoneNumber(CidPhoneConfig config, CompanyVoiceRoboticCallees callees) {
-        String basePhone = callees.getPhone();
-        if (basePhone == null || basePhone.length() != 11) {
-            return null;
-        }
-
-        int start = config.getStartIndex();
-        int end = config.getEndIndex();
-        int count = config.getGenerateCount();
-
-        // 校验索引范围
-        if (start < 1 || start > 11 || end < 1 || end > 11 || start > end) {
-            return null;
-        }
-
-        int startIdx = start;
-        int endIdx = end - 1;
-
-        char[] baseChars = basePhone.toCharArray();
-        //预加载随机
-        List<String> nameList = RandomNameGeneratorUtil.generateBatch(count);
-        Random random = new Random();
-        List<CompanyVoiceRoboticCallees> result = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
-            // 克隆基础数组
-            char[] newChars = baseChars.clone();
-            for (int j = startIdx; j <= endIdx; j++) {
-                // 生成0-9的随机数字字符
-                newChars[j] = (char) ('0' + random.nextInt(10));
-            }
-            String phone = String.valueOf(newChars);
-            roboticCallees.setPhone(phone);//电话
-            roboticCallees.setRoboticId(callees.getRoboticId());//任务ID
-            roboticCallees.setTaskFlow(callees.getTaskFlow());//任务流程
-            roboticCallees.setRunTaskFlow(callees.getRunTaskFlow());//已执行流程
-            roboticCallees.setIsWeCom(callees.getIsWeCom());
-            roboticCallees.setUserId(0L);//用户ID
-            roboticCallees.setUserName(nameList.get(i));//姓名
-            roboticCallees.setIsGenerate(1);
-            result.add(roboticCallees);
-        }
-        return result;
-    }
-
-    /**
-     * 生成外呼任务表
-     *
-     **/
-    public void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
-                                             Map<String, CompanyWxClient> clientMp
-            , CompanyVoiceRobotic robotic
-            , CompanyWorkflow workflow
-            , CompanyNodeInfoVo nodeInfoVo) {
-        if (!calleesList.isEmpty()) {
-            List<CompanyVoiceRoboticBusiness> addList = new LinkedList<>();
-            Date date = new Date();
-            for (CompanyVoiceRoboticCallees callees : calleesList) {
-                CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
-                companyVoiceRoboticBusiness.setRoboticId(callees.getRoboticId());
-                companyVoiceRoboticBusiness.setCalleeId(callees.getId());
-                companyVoiceRoboticBusiness.setWxClientId(clientMp.getOrDefault(callees.getRoboticId() + "-" + callees.getUserId(), new CompanyWxClient()).getId());
-                companyVoiceRoboticBusiness.setAddWxDone(0);
-                companyVoiceRoboticBusiness.setCallPhoneDone(0);
-                companyVoiceRoboticBusiness.setSendMsgDone(0);
-                companyVoiceRoboticBusiness.setCreateTime(date);
-                companyVoiceRoboticBusiness.setIsGenerate(1);
-                addList.add(companyVoiceRoboticBusiness);
-            }
-
-            //批量插入ai外呼业务对象
-            if (!addList.isEmpty()) {
-                flushGeneratedBusinessBatch(addList, robotic, workflow, nodeInfoVo);
-            }
-        }
-    }
-
-    /**
-     * 刷新并批量插入生成的被叫数据,并生成对应的业务对象
-     *
-     * @param batchToInsert 待插入的被叫列表
-     * @param clientMap     微信客户映射,用于关联客户ID
-     */
-    @Async("calleeTaskExecutor")
-    public void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
-                                           Map<String, CompanyWxClient> clientMap,
-                                           CompanyVoiceRobotic robotic,
-                                           CompanyWorkflow workflow,
-                                           CompanyNodeInfoVo nodeInfoVo) {
-        if (batchToInsert.isEmpty()) {
-            return;
-        }
-        int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
-        if (rows > 0) {
-            generateVoiceRoboticBusiness(batchToInsert, clientMap, robotic, workflow, nodeInfoVo);
-        }
-        batchToInsert.clear();
-    }
-
-    /**
-     * 批量插入业务对象,并创建工作流执行记录
-     */
-    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,CompanyVoiceRobotic robotic,CompanyWorkflow workflow,CompanyNodeInfoVo nodeInfoVo){
-        int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
-        if(rows > 0){
-            LocalDateTime now = LocalDateTime.now();
-            List<CompanyAiWorkflowExec> startExecList = new LinkedList<>();//第一节点
-            List<CompanyAiWorkflowExec> targetExecList = new LinkedList<>();//第二节点
-            //插入执行流程代码
-            for (CompanyVoiceRoboticBusiness business : batchToInsert){
-                // 第一个节点(开始节点)
-                CompanyAiWorkflowExec startExec = new CompanyAiWorkflowExec();
-                startExec.setWorkflowInstanceId(generateInstanceId());
-                startExec.setWorkflowId(robotic.getCompanyAiWorkflowId());
-                startExec.setCurrentNodeKey(workflow.getStartNodeKey());
-                startExec.setCurrentNodeType(NodeTypeEnum.START.getValue());
-                startExec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
-                startExec.setStatus(ExecutionStatusEnum.SUCCESS.getValue()); // 开始节点执行成功
-                startExec.setStartTime(now);
-
-                JSONObject variables = new JSONObject();
-                variables.put("roboticId", robotic.getId());
-                variables.put("businessId", business.getId());
-                variables.put("cidGroupNo", robotic.getCidGroupNo());
-                variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
-                variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
-                startExec.setVariables(variables.toJSONString());
-                startExec.setBusinessKey(business.getId().toString());
-                startExec.setStartNodeKey(workflow.getStartNodeKey());
-                startExec.setEndNodeKey(workflow.getEndNodeKey());
-                startExec.setCidGroupNo(robotic.getCidGroupNo());
-                startExec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
-                startExec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
-                startExec.setIsGenerate(1);
-                startExecList.add(startExec);
-
-                CompanyAiWorkflowExec targetExec = new CompanyAiWorkflowExec();
-                // 复制公共字段
-                targetExec.setWorkflowInstanceId(startExec.getWorkflowInstanceId());
-                targetExec.setWorkflowId(startExec.getWorkflowId());
-                targetExec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
-                targetExec.setCurrentNodeName(nodeInfoVo.getNodeName());
-                targetExec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
-                targetExec.setStatus(ExecutionStatusEnum.INTERRUPT.getValue());
-                targetExec.setStartTime(now);
-                targetExec.setVariables(variables.toJSONString());
-                targetExec.setBusinessKey(startExec.getBusinessKey());
-                targetExec.setStartNodeKey(startExec.getStartNodeKey());
-                targetExec.setEndNodeKey(startExec.getEndNodeKey());
-                targetExec.setCidGroupNo(startExec.getCidGroupNo());
-                targetExec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
-                targetExec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
-                targetExec.setIsGenerate(1);
-                targetExecList.add(targetExec);
-
-                if(targetExecList.size() >= BATCH_SIZE){
-                    generateVoiceRoboticCurrentExecLogs(targetExecList, startExecList);
-                }
-            }
-
-            if(!targetExecList.isEmpty()){
-                generateVoiceRoboticCurrentExecLogs(targetExecList, startExecList);
-            }
-        }
-        batchToInsert.clear();
-    }
-
-    public void generateVoiceRoboticCurrentExecLogs(List<CompanyAiWorkflowExec> workflowExecs, List<CompanyAiWorkflowExec> startExecList){
-        if (workflowExecs.isEmpty()) {
-            return;
-        }
-
-        int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
-        if(rows > 0){
-            workflowExecLogBatchInsert(startExecList);//第一节点
-            workflowExecs.stream().forEach(a->a.setStatus(ExecutionStatusEnum.FAILURE.getValue()));
-            workflowExecLogBatchInsert(workflowExecs);//第二节点
-        }
-        workflowExecs.clear();
-        startExecList.clear();
-    }
-
-    /**
-     * AI外呼流程执行记录对象批量插入
-     * **/
-    private void workflowExecLogBatchInsert(List<CompanyAiWorkflowExec> workflowExecs){
-        //插入日志记录表
-        Date date =new Date();
-        List<CompanyAiWorkflowExecLog> batch = new LinkedList<>();
-        workflowExecs.forEach(w->{
-            CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
-            execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
-            execLog.setNodeKey(w.getCurrentNodeKey());
-            execLog.setNodeName(w.getCurrentNodeName());
-            execLog.setNodeType(w.getCurrentNodeType());
-            execLog.setInputData(w.getVariables());
-            execLog.setStatus(w.getStatus());
-            execLog.setOutputData("null");
-            execLog.setStartTime(date);
-            execLog.setEndTime(date);
-            execLog.setIsGenerate(1);
-            batch.add(execLog);
-        });
-        if(!batch.isEmpty()){
-            companyAiWorkflowExecLogMapper.batchInsert(batch);
-            batch.clear();
-        }
-        workflowExecs.clear();
-    }
-
-
-    /**
-     * 生成工作流实例ID
-     */
-    private String generateInstanceId() {
-        return "wf_" + System.currentTimeMillis() + "_" +
-                UUID.randomUUID().toString().replace("-", "");
-    }
-
-
 }

+ 1 - 1
fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java

@@ -368,7 +368,7 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
         context.setVariables(inputVariables != null ? inputVariables : new HashMap<>());
         context.setStartTime(LocalDateTime.now());
         context.setCurrentTime(LocalDateTime.now());
-        context.setBusinessId(inputVariables.containsKey("businessId") ? inputVariables.get("businessId").toString() : null);
+        context.setBusinessId(inputVariables.containsKey("businessId") ? Long.parseLong(inputVariables.get("businessId").toString()) : null);
 
         return context;
     }

+ 48 - 11
fs-service/src/main/java/com/fs/company/service/impl/GeneralCustomerEntryServiceImpl.java

@@ -11,9 +11,11 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.mapper.CompanyVoiceRoboticMapper;
 import com.fs.company.param.EntryCustomerParam;
+import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyVoiceRoboticService;
 import com.fs.company.service.IGeneralCustomerEntryService;
 import com.fs.company.util.CryptoUtil;
@@ -39,6 +41,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
 import java.time.LocalTime;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
@@ -67,6 +70,8 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
     @Autowired
     private SysDictDataMapper sysDictDataMapper;
 
+    @Autowired
+    ICompanyConfigService companyConfigService;
     /**
      * 录入客户
      *
@@ -113,7 +118,7 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
         Map<String, Object> requestParam = new HashMap<>();
         List<CrmCustomerPropertyTemplate> crmCustomerPropertyTemplates = SpringUtils.getBean(ICrmCustomerPropertyTemplateService.class).getBaseMapper().selectList(new LambdaQueryWrapper<CrmCustomerPropertyTemplate>());
         List<SysDictData> sysDictData = sysDictDataMapper.selectDictDataByType(TRADE_TYPE);
-        ArrayList<Map<String, String>> tags = new ArrayList<>();//标签及提示词
+        ArrayList<Map<String, String>> tags = new ArrayList<>();//标签模板及提示词
         crmCustomerPropertyTemplates.forEach(o -> {
             Map<String, String> tag = MapUtil.convertToMap(new CrmCustomerAiAutoTagVo(String.valueOf(o.getId()), o.getName(), o.getAiHint()));
             tags.add(tag);
@@ -157,8 +162,8 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
         requestParam.put("userInfo", userInfo);
         requestParam.put("history", history);
         requestParam.put("tags", tags);
-        requestParam.put("tradeName", sysDictData.stream().map(SysDictData::getDictLabel));
-        requestParam.put("tradeType", sysDictData.stream().map(SysDictData::getDictValue));
+        requestParam.put("tradeName", sysDictData.stream().map(SysDictData::getDictLabel).collect(Collectors.toList()));
+        requestParam.put("tradeType", sysDictData.stream().map(SysDictData::getDictValue).collect(Collectors.toList()));
         requestParam.put("tagInfos", Collections.emptyList());
         requestParam.put("isRepository", "");
         requestParam.put("userContent", "");
@@ -210,11 +215,28 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
         }
         //客户数据解析,是否包含对话图 对话图解析标签&意向度标识 todo 庄旭组在研发此功能
         if (StringUtils.isNotBlank(data.getDialogue())) {
-            JSONObject jsonObject = analysisDialogue(data.getDialogue());
-            if (jsonObject != null) {
-                data.setIntention(jsonObject.getString("intention"));
-                data.setTags(jsonObject.getString("tags"));
+            try {
+                List<CrmCustomerAiTagVo> aiTags = getAiTags(data.getDialogue());
+                if(null != aiTags && !aiTags.isEmpty()){
+                    String tags = data.getTags()==null?"":data.getTags();
+                    StringBuilder sb = new StringBuilder(tags);
+                    aiTags.forEach(a->{
+                        sb.append(a.getName()).append(",");
+                    });
+                    if(sb.length() >0 &&  ',' == sb.charAt(sb.length()-1)){
+                        data.setTags(sb.substring(0,sb.length()-1));
+                    }
+                    data.setTags(sb.toString());
+                }
+                //todo 意向度分析
+            } catch (JsonProcessingException e) {
+                log.error("导入用户标签数据解析异常", e);
             }
+//            JSONObject jsonObject = analysisDialogue(data.getDialogue());
+//            if (jsonObject != null) {
+//                data.setIntention(jsonObject.getString("intention"));
+//                data.setTags(jsonObject.getString("tags"));
+//            }
         }
         //客户数据插入
         insertCrmCustomer(data);
@@ -239,10 +261,18 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
             log.error("手机号格式错误,{}", param.getMobile());
             return false;
         }
-        Long  customerId = crmCustomerMapper.selectCrmCustomerByCrmMobile(param.getMobile());
-        // todo 添加配置是否允许重复客户导入
-        if( null != customerId && true){
-            return false;
+        //根据配置项是否允许重复手机号客户录入
+        CompanyConfig config=companyConfigService.selectCompanyConfigByKey(param.getCompanyId(),"cId.config");
+        if (null != config) {
+            String configValue = config.getConfigValue();
+            JSONObject configObj = JSONObject.parseObject(configValue);
+            if(configObj.containsKey("allowRepeatCustomer") && null != configObj.getBoolean("allowRepeatCustomer") && !configObj.getBoolean("allowRepeatCustomer")){
+                Long  customerId = crmCustomerMapper.selectCrmCustomerByCrmMobileAndCompanyId(param.getCompanyId(),param.getMobile());
+                if( null != customerId && true){
+                    log.error("手机号重复数据,{}", param.getMobile());
+                    return false;
+                }
+            }
         }
         return true;
     }
@@ -265,6 +295,13 @@ public class GeneralCustomerEntryServiceImpl implements IGeneralCustomerEntrySer
     public void insertCrmCustomer(EntryCustomerParam data){
         CrmCustomer insertObj = new CrmCustomer();
         BeanUtils.copyProperties(data,insertObj);
+        if(StringUtils.isBlank(insertObj.getCustomerName())){
+            insertObj.setCustomerName("客户"+insertObj.getMobile().substring(7,11));
+        }
+        insertObj.setIsLine(1);
+        if(null == insertObj.getCreateTime()){
+            insertObj.setCreateTime(new Date());
+        }
         crmCustomerMapper.insertCrmCustomer(insertObj);
         data.setCustomerId(insertObj.getCustomerId());
     }

+ 3 - 3
fs-service/src/main/java/com/fs/company/util/PhoneNumberUtil.java

@@ -56,20 +56,20 @@ public class PhoneNumberUtil {
     public static boolean isValid(String mobile) {
         // 空值校验
         if (StringUtils.isBlank(mobile)) {
-            log.warn("手机号校验失败:手机号为空");
+            log.error("手机号校验失败:手机号为空");
             return false;
         }
         // 去除首尾空格
         String trimmedMobile = mobile.trim();
         // 长度校验(手机号必须为11位)
         if (trimmedMobile.length() != MOBILE_LENGTH) {
-            log.warn("手机号校验失败:手机号长度不正确,当前长度={}", trimmedMobile.length());
+            log.error("手机号校验失败:手机号长度不正确,当前长度={}", trimmedMobile.length());
             return false;
         }
         // 格式校验(使用正则表达式)
         boolean isValid = MOBILE_PATTERN.matcher(trimmedMobile).matches();
         if (!isValid) {
-            log.warn("手机号校验失败:手机号格式不正确,mobile={}", trimmedMobile);
+            log.error("手机号校验失败:手机号格式不正确,mobile={}", trimmedMobile);
         }
         return isValid;
     }

+ 14 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java

@@ -118,6 +118,20 @@ public interface FsCourseAnswerLogsMapper
             "</script>"})
     FsCourseAnswerLogs selectRightLogByCourseVideo(@Param("videoId") Long videoId,@Param("userId") Long userId,@Param("qwUserId") String qwUserId);
 
+    @Select({"<script> " +
+            "select * from fs_course_answer_logs where video_id = #{videoId} and user_id = #{userId} and is_right = 1 " +
+            "<if test = 'qwUserId !=null '> " +
+            "and qw_user_id = #{qwUserId} " +
+            "</if>" +
+            "<if test = 'periodId !=null '> " +
+            " and period_id = #{periodId} " +
+            "</if>" +
+            "limit 1 " +
+            "</script>"})
+    FsCourseAnswerLogs selectRightLogByCourseVideoWithPeriodId(@Param("videoId") Long videoId,
+                                                               @Param("userId") Long userId,
+                                                               @Param("qwUserId") String qwUserId,
+                                                               @Param("periodId") Long periodId);
 
     @Select({"<script> " +
             "select count(0) from fs_course_answer_logs where video_id = #{videoId} and user_id = #{userId} and is_right = 0 " +

+ 5 - 2
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
+import com.fs.course.param.newfs.FsUserCourseVideoRemainTimeParam;
 import com.fs.course.vo.*;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwSidebarStatsParam;
@@ -755,8 +756,8 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     List<FsSopMyCourseH5LinkVO> getSopCourseH5StudyListByQwExId(@Param("qwExternalId") Long qwExternalId);
 
-    @Select("select * from fs_course_watch_log where user_id=#{userId} and company_user_id=#{companyUserId} and course_id=#{courseId} and video_id=#{videoId} limit 1")
-    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(@Param("userId") Long userId,@Param("companyUserId") Long companyUserId,@Param("courseId") Integer courseId,@Param("videoId") Integer videoId);
+    @Select("select * from fs_course_watch_log where user_id=#{param.fsUserId} and company_user_id=#{param.companyUserId} and course_id=#{param.courseId} and video_id=#{param.videoId} and project=#{param.projectId} and period_id=#{param.periodId} limit 1")
+    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(@Param("param") FsUserCourseVideoRemainTimeParam param);
 
     /**
      * 查询视频时长(只返回duration字段)
@@ -819,4 +820,6 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      */
     List<com.fs.course.vo.CourseStatisticsUserDetailVO> selectCourseStatisticsUserDetailExportList(
             @Param("param") com.fs.course.param.CourseStatisticsUserDetailParam param);
+
+    FSActualCompletionVO selectActualCompletionList(@Param("periodId") Long periodId, @Param("videoId") Long videoId);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyUserMapper.java

@@ -104,4 +104,10 @@ public interface FsUserCompanyUserMapper extends BaseMapper<FsUserCompanyUser>{
      */
     int batchDeleteByIds(@Param("userIds") List<Long> userIds);
 
+    /**
+     * 根据userId查询最开始的数据
+     * @param userId
+     * @return
+     */
+    FsUserCompanyUser selectFsUserCompanyUserByUserId(@Param("userId") Long userId);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java

@@ -24,4 +24,12 @@ public class FsUserCourseVideoRemainTimeParam implements Serializable {
     @ApiModelProperty(value = "销售id")
     private Long companyUserId;
 
+    @NotNull(message = "项目id不能为空")
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @NotNull(message = "营期id不能为空")
+    @ApiModelProperty(value = "营期id")
+    private Long periodId;
+
 }

+ 0 - 1
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -172,7 +172,6 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
 
     List<FsCourseWatchLog> selectFsUserWatchLogByExtId(QwExternalContact qwExternalContact);
 
-    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId);
 
     /**
      * 查询课程小结详情总体数据

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -267,4 +267,9 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
      * @return list
      */
     List<OptionsVO> selectVideoOptionsByCourseId(Long courseId);
+
+    /**
+     * 获取销售易看课详情
+     */
+    ResponseResult<FsUserCourseVideoLinkDetailsVO> getXiaoShouYiCourseVideoDetails(FsUserCourseVideoLinkParam param);
 }

+ 21 - 20
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -1757,10 +1757,6 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectFsUserWatchLogByExtId(qwExternalContact);
     }
 
-    @Override
-    public FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId) {
-        return fsCourseWatchLogMapper.selectFsCourseWatchLogWithUCCV(userId, companyUserId, courseId, videoId);
-    }
 
     @Override
     public CourseStatisticsDetailVO getCourseStatisticsDetail(Long videoId, Long periodId) {
@@ -1811,23 +1807,28 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             }
         }
 
-        // 第2-n次观看数据:view_start不在首次点播窗口内的记录(窗口外=首次窗口前或窗口后)
+//        // 第2-n次观看数据:view_start不在首次点播窗口内的记录(窗口外=首次窗口前或窗口后)
+//        if (periodId != null && videoId != null) {
+//            Map<String, Object> repeatStats = fsCourseWatchLogMapper.selectRepeatPlaybackStats(videoId, periodId);
+//            if (repeatStats != null && !repeatStats.isEmpty()) {
+//                Long repeatWatch = getLongFromMap(repeatStats, "repeatWatchCount");
+//                Long repeat20 = getLongFromMap(repeatStats, "repeatWatch20MinCount");
+//                Long repeat30 = getLongFromMap(repeatStats, "repeatWatch30MinCount");
+//                vo.setRepeatWatchCount(repeatWatch != null ? repeatWatch : 0L);
+//                vo.setRepeatWatch20MinCount(repeat20 != null ? repeat20 : 0L);
+//                vo.setRepeatWatch30MinCount(repeat30 != null ? repeat30 : 0L);
+//                if (repeatWatch != null && repeatWatch > 0) {
+//                    vo.setRepeatCompleteRate20Min(BigDecimal.valueOf(repeat20 != null ? repeat20 : 0)
+//                            .divide(BigDecimal.valueOf(repeatWatch), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
+//                    vo.setRepeatCompleteRate30Min(BigDecimal.valueOf(repeat30 != null ? repeat30 : 0)
+//                            .divide(BigDecimal.valueOf(repeatWatch), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
+//                }
+//            }
+//        }
+
+        //实际看课数据
         if (periodId != null && videoId != null) {
-            Map<String, Object> repeatStats = fsCourseWatchLogMapper.selectRepeatPlaybackStats(videoId, periodId);
-            if (repeatStats != null && !repeatStats.isEmpty()) {
-                Long repeatWatch = getLongFromMap(repeatStats, "repeatWatchCount");
-                Long repeat20 = getLongFromMap(repeatStats, "repeatWatch20MinCount");
-                Long repeat30 = getLongFromMap(repeatStats, "repeatWatch30MinCount");
-                vo.setRepeatWatchCount(repeatWatch != null ? repeatWatch : 0L);
-                vo.setRepeatWatch20MinCount(repeat20 != null ? repeat20 : 0L);
-                vo.setRepeatWatch30MinCount(repeat30 != null ? repeat30 : 0L);
-                if (repeatWatch != null && repeatWatch > 0) {
-                    vo.setRepeatCompleteRate20Min(BigDecimal.valueOf(repeat20 != null ? repeat20 : 0)
-                            .divide(BigDecimal.valueOf(repeatWatch), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
-                    vo.setRepeatCompleteRate30Min(BigDecimal.valueOf(repeat30 != null ? repeat30 : 0)
-                            .divide(BigDecimal.valueOf(repeatWatch), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
-                }
-            }
+            vo.setFsActualCompletionVO(fsCourseWatchLogMapper.selectActualCompletionList(periodId,videoId));
         }
 
         // 订单数据:fs_store_order_scrm order_type=3,videoId+periodId 匹配,paid=1

+ 22 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1630,6 +1630,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
 
         FsCourseAnswerLogs rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideo(param.getVideoId(), param.getUserId(), param.getQwUserId());
+        if("泽林文化".equals(signProjectName)){
+            rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideoWithPeriodId(param.getVideoId(), param.getUserId(), param.getQwUserId(),param.getPeriodId());
+        }
         if (rightLog == null) {
             logger.error("未答题:{}", param.getUserId());
             return R.error("未答题");
@@ -4630,5 +4633,24 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             vo.setFsStoreProductScrms(new ArrayList<>());
         }
     }
+
+    /**
+     * 获取销售易看课详情
+     */
+    @Override
+    public ResponseResult<FsUserCourseVideoLinkDetailsVO> getXiaoShouYiCourseVideoDetails(FsUserCourseVideoLinkParam param) {
+        // 1、获取视频详情、问题详情
+        ResponseResult<FsUserCourseVideoDetailsVO> videoDetails = this.getVideoDetails(param.getVideoId());
+        FsUserCourseVideoDetailsVO courseVideoDetails = videoDetails.getData() != null ? videoDetails.getData() : null;
+
+        long duration = 0L;
+        int isFinish = 0;
+        FsUserCourseVideoLinkDetailsVO vo = new FsUserCourseVideoLinkDetailsVO();
+        vo.setCourseVideoDetails(courseVideoDetails);
+        vo.setIsFinish(isFinish);
+        vo.setPlayDuration(duration);
+
+        return ResponseResult.ok(vo);
+    }
 }
 

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/CourseStatisticsDetailVO.java

@@ -69,4 +69,7 @@ public class CourseStatisticsDetailVO implements Serializable {
     private Long redPacketUserCount;
     /** 单品销量统计 */
     private java.util.List<CourseProductSalesVO> productList;
+
+    /** 实际完播统计 */
+    private FSActualCompletionVO fsActualCompletionVO;
 }

+ 42 - 0
fs-service/src/main/java/com/fs/course/vo/FSActualCompletionVO.java

@@ -0,0 +1,42 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 实际完播统计VO
+ * **/
+@Data
+public class FSActualCompletionVO implements Serializable {
+    /**
+     * 实际到课人数
+     */
+    private Integer totalStudents;
+
+    /**
+     * 实际完课人数
+     */
+    private Integer completedCount;
+
+    /**
+     * 实际完课率(百分比)
+     */
+    private BigDecimal actualCompletionRate;
+
+    /**
+     * 人均看课时长(分钟)
+     */
+    private BigDecimal avgWatchDurationMinutes;
+
+    /**
+     * 人均完课时长(分钟)
+     */
+    private BigDecimal avgCompletedDuration;
+
+    /**
+     * 人均完课完播率(百分比)
+     */
+    private BigDecimal avgCompletionPlaybackRate;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmCustomerAnalyze.java

@@ -42,6 +42,10 @@ public class CrmCustomerAnalyze extends BaseEntity{
     @Excel(name = "流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险")
     private Long attritionLevel;
 
+    /** 流失风险等级提示 */
+    @Excel(name = "流失风险等级提示")
+    private String attritionLevelPrompt;
+
     /** 客户关注点 */
     @Excel(name = "客户关注点")
     private String customerFocusJson;

+ 12 - 7
fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerAnalyzeMapper.java

@@ -3,17 +3,18 @@ package com.fs.crm.mapper;
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.crm.domain.CrmCustomerAnalyze;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * 客户聊天记录分析Mapper接口
- * 
+ *
  * @author fs
  * @date 2026-03-24
  */
 public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>{
     /**
      * 查询客户聊天记录分析
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 客户聊天记录分析
      */
@@ -21,7 +22,7 @@ public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>
 
     /**
      * 查询客户聊天记录分析列表
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 客户聊天记录分析集合
      */
@@ -29,7 +30,7 @@ public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>
 
     /**
      * 新增客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -37,7 +38,7 @@ public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>
 
     /**
      * 修改客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -45,7 +46,7 @@ public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>
 
     /**
      * 删除客户聊天记录分析
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 结果
      */
@@ -53,9 +54,13 @@ public interface CrmCustomerAnalyzeMapper extends BaseMapper<CrmCustomerAnalyze>
 
     /**
      * 批量删除客户聊天记录分析
-     * 
+     *
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */
     int deleteCrmCustomerAnalyzeByIds(Long[] ids);
+
+    CrmCustomerAnalyze selectLatestOne(@Param("customerId") Long customerId);
+
+    int updateCustomerPortrait(@Param("customerId") Long customerId,@Param("customerPortrait") String customerPortrait);
 }

+ 33 - 2
fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerMapper.java

@@ -345,9 +345,24 @@ public interface CrmCustomerMapper extends BaseMapper<CrmCustomer> {
             "</script>"})
     List<CrmMyCustomerListQueryVO> selectCrmMyCustomerListQuery(@Param("maps") CrmMyCustomerListQueryParam param);
     @Select({"<script> " +
-            "select c.*,u.nick_name as company_user_nick_name,ccu.start_time as startTime  from  crm_customer c left join company_user u on u.user_id=c.receive_user_id left join crm_customer_user ccu on c.customer_user_id = ccu.customer_user_id " +
+            "select c.*,u.nick_name as company_user_nick_name,ccu.start_time as startTime,ca.attrition_level,ca.intention_degree  from  crm_customer c " +
+            "left join company_user u on u.user_id=c.receive_user_id " +
+            "left join crm_customer_user ccu on c.customer_user_id = ccu.customer_user_id " +
+            "LEFT JOIN LATERAL ( SELECT attrition_level, intention_degree FROM crm_customer_analyze WHERE customer_id = c.customer_id " +
+            "   ORDER BY create_time DESC " +
+            "   LIMIT 1 " +
+            ") ca ON TRUE " +
 //            "where is_line=0 " +
             "where 1=1 " +
+            "<if test = 'maps.attritionLevel != null and maps.attritionLevel != \"\" '> " +
+            "and ca.attrition_level = #{maps.attritionLevel} " +
+            "</if>" +
+            "<if test = 'maps.intentionDegreeGt != null'> " +
+            "and ca.intention_degree &gt;= #{maps.intentionDegreeGt} " +
+            "</if>" +
+            "<if test = 'maps.intentionDegreelt != null'> " +
+            "and ca.intention_degree &lt;= #{maps.intentionDegreelt} " +
+            "</if>" +
             "<if test = 'maps.companyId != null     '> " +
             "and c.company_id =#{maps.companyId} " +
             "</if>" +
@@ -427,7 +442,12 @@ public interface CrmCustomerMapper extends BaseMapper<CrmCustomer> {
             "</script>"})
     List<CrmCustomerListQueryVO> selectCrmCustomerListQuery(@Param("maps") CrmCustomerListQueryParam param);
     @Select({"<script> " +
-            "select c.*,u.nick_name as company_user_nick_name  from  crm_customer c left join company_user u on u.user_id=c.receive_user_id   " +
+            "select c.*,u.nick_name as company_user_nick_name,ca.attrition_level,ca.intention_degree  from  crm_customer c " +
+            "left join company_user u on u.user_id=c.receive_user_id " +
+            "LEFT JOIN LATERAL ( SELECT attrition_level, intention_degree FROM crm_customer_analyze WHERE customer_id = c.customer_id " +
+            "   ORDER BY create_time DESC " +
+            "   LIMIT 1 " +
+            ") ca ON TRUE " +
             "where is_line=1 " +
 //            " select  c.*  " +
 ////            "<if test = 'maps.isDuplicate != null '> " +
@@ -480,6 +500,15 @@ public interface CrmCustomerMapper extends BaseMapper<CrmCustomer> {
             "<if test = 'maps.endTime != null and maps.endTime != \"\" '> " +
             "and date_format(c.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
             "</if>" +
+            "<if test = 'maps.attritionLevel != null and maps.attritionLevel != \"\" '> " +
+            "and ca.attrition_level = #{maps.attritionLevel} " +
+            "</if>" +
+            "<if test = 'maps.intentionDegreeGt != null'> " +
+            "and ca.intention_degree &gt;= #{maps.intentionDegreeGt} " +
+            "</if>" +
+            "<if test = 'maps.intentionDegreelt != null'> " +
+            "and ca.intention_degree &lt;= #{maps.intentionDegreelt} " +
+            "</if>" +
             " order by c.customer_id desc "+
             "</script>"})
     List<CrmLineCustomerListQueryVO> selectCrmLineCustomerListQuery(@Param("maps") CrmLineCustomerListQueryParam param);
@@ -940,6 +969,8 @@ public interface CrmCustomerMapper extends BaseMapper<CrmCustomer> {
     @Select("select customer_id  from  crm_customer where mobile=#{remarkMobile} limit 1")
     Long selectCrmCustomerByCrmMobile(String remarkMobile);
 
+    @Select("select customer_id  from  crm_customer where company_id = #{companyId} and  mobile=#{remarkMobile} limit 1")
+    Long selectCrmCustomerByCrmMobileAndCompanyId(@Param("companyId") Long companyId, @Param("remarkMobile") String remarkMobile);
     /**
      *   根据userid和外部联系人id获取到客户详情
      */

+ 7 - 0
fs-service/src/main/java/com/fs/crm/param/CrmCustomerListQueryParam.java

@@ -103,5 +103,12 @@ public class CrmCustomerListQueryParam extends BaseQueryParam
     /** 结束时间 */
     private String endTime;
 
+    /** 流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险 */
+    private Long attritionLevel;
+    /** 意向度>= */
+    private Long intentionDegreeGt;
+    /** 意向度<= */
+    private Long intentionDegreelt;
+
 
 }

+ 7 - 0
fs-service/src/main/java/com/fs/crm/param/CrmLineCustomerListQueryParam.java

@@ -85,4 +85,11 @@ public class CrmLineCustomerListQueryParam extends BaseQueryParam
     @Excel(name = "标签" )
     private String tags;
 
+    /** 流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险 */
+    private Long attritionLevel;
+    /** 意向度>= */
+    private Long intentionDegreeGt;
+    /** 意向度<= */
+    private Long intentionDegreelt;
+
 }

+ 9 - 7
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerAnalyzeService.java

@@ -6,14 +6,14 @@ import com.fs.crm.domain.CrmCustomerAnalyze;
 
 /**
  * 客户聊天记录分析Service接口
- * 
+ *
  * @author fs
  * @date 2026-03-24
  */
 public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>{
     /**
      * 查询客户聊天记录分析
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 客户聊天记录分析
      */
@@ -21,7 +21,7 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
 
     /**
      * 查询客户聊天记录分析列表
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 客户聊天记录分析集合
      */
@@ -29,7 +29,7 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
 
     /**
      * 新增客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -37,7 +37,7 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
 
     /**
      * 修改客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -45,7 +45,7 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
 
     /**
      * 批量删除客户聊天记录分析
-     * 
+     *
      * @param ids 需要删除的客户聊天记录分析主键集合
      * @return 结果
      */
@@ -53,9 +53,11 @@ public interface ICrmCustomerAnalyzeService extends IService<CrmCustomerAnalyze>
 
     /**
      * 删除客户聊天记录分析信息
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 结果
      */
     int deleteCrmCustomerAnalyzeById(Long id);
+
+    void aiGeneratedCustomerPortrait(String customerId, String dataJson,String logId);
 }

+ 125 - 11
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerAnalyzeServiceImpl.java

@@ -1,25 +1,45 @@
 package com.fs.crm.service.impl;
 
-import java.util.List;
-import com.fs.common.utils.DateUtils;
+import cn.hutool.core.lang.TypeReference;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.stereotype.Service;
-import com.fs.crm.mapper.CrmCustomerAnalyzeMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.common.utils.DateUtils;
 import com.fs.crm.domain.CrmCustomerAnalyze;
+import com.fs.crm.mapper.CrmCustomerAnalyzeMapper;
 import com.fs.crm.service.ICrmCustomerAnalyzeService;
+import com.fs.crm.utils.CrmCustomerAiTagUtil;
+import com.fs.system.mapper.SysDictDataMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 客户聊天记录分析Service业务层处理
- * 
+ *
  * @author fs
  * @date 2026-03-24
  */
 @Service
 public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyzeMapper, CrmCustomerAnalyze> implements ICrmCustomerAnalyzeService {
 
+    @Autowired
+    private SysDictDataMapper sysDictDataMapper;
     /**
      * 查询客户聊天记录分析
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 客户聊天记录分析
      */
@@ -31,7 +51,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
     /**
      * 查询客户聊天记录分析列表
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 客户聊天记录分析
      */
@@ -43,7 +63,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
     /**
      * 新增客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -56,7 +76,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
     /**
      * 修改客户聊天记录分析
-     * 
+     *
      * @param crmCustomerAnalyze 客户聊天记录分析
      * @return 结果
      */
@@ -68,7 +88,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
     /**
      * 批量删除客户聊天记录分析
-     * 
+     *
      * @param ids 需要删除的客户聊天记录分析主键
      * @return 结果
      */
@@ -80,7 +100,7 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
 
     /**
      * 删除客户聊天记录分析信息
-     * 
+     *
      * @param id 客户聊天记录分析主键
      * @return 结果
      */
@@ -89,4 +109,98 @@ public class CrmCustomerAnalyzeServiceImpl extends ServiceImpl<CrmCustomerAnalyz
     {
         return baseMapper.deleteCrmCustomerAnalyzeById(id);
     }
+    private static final String AI_PORTRAIT = "crm_ai_portrait";
+    private static final ObjectMapper mapper = new ObjectMapper();
+    //ai获取客户画像
+    @Override
+    @Async
+    public void aiGeneratedCustomerPortrait(String customerId, String dataJson,String logId) {
+        Map<String, Object> stringObjectMap = buildRequestParam(Long.valueOf(customerId), dataJson);
+        R aiResponse = CrmCustomerAiTagUtil.callAiService(stringObjectMap, Long.valueOf(logId));
+
+        JSONObject root = JSON.parseObject(JSONUtil.toJsonStr(aiResponse));
+
+// 获取 data.responseData
+        JSONArray responseData = root.getJSONObject("data").getJSONArray("responseData");
+
+        JSONObject userInfo = null;
+
+// 遍历 responseData
+        for (int i = 0; i < responseData.size(); i++) {
+            JSONObject node = responseData.getJSONObject(i);
+            JSONArray historyPreview = node.getJSONArray("historyPreview");
+
+            if (historyPreview != null) {
+                for (int j = 0; j < historyPreview.size(); j++) {
+                    JSONObject historyItem = historyPreview.getJSONObject(j);
+
+                    // 找到 obj 为 "AI" 的项
+                    if ("AI".equals(historyItem.getString("obj"))) {
+                        String valueStr = historyItem.getString("value");
+                        JSONObject valueObj = JSON.parseObject(valueStr);
+                        userInfo = valueObj.getJSONObject("userInfo");
+                        break;
+                    }
+                }
+            }
+            if (userInfo != null) break;
+        }
+
+        if (userInfo != null) {
+            baseMapper.updateCustomerPortrait(Long.valueOf(customerId),userInfo.toString());
+        }
+    }
+    private Map<String, Object> buildRequestParam(Long customerId,
+                                                         String communication) {
+        Map<String, Object> requestParam = new HashMap<>();
+
+        // 获取各类数据
+        HashMap<String, Object> history = new HashMap<>();
+        history.put("history", communication);
+        Map<String, Object> userInfo = getUserInfo(customerId);
+        String likeRatio ="";
+        if (!userInfo.isEmpty()){
+            likeRatio = (String) userInfo.remove("likeRatio");
+        }
+        // 合并数据
+        requestParam.putAll(history);
+        requestParam.putAll(userInfo);
+
+        // 设置其他参数
+        requestParam.put("tagInfos", Collections.emptyList());
+        requestParam.put("isRepository", "");
+        requestParam.put("userContent", "");
+        requestParam.put("aiContent", "");
+        requestParam.put("likeRatio", likeRatio);
+        requestParam.put("modelType","客户画像");
+
+        return requestParam;
+    }
+    private Map<String, Object> getUserInfo(Long customerId) {
+        CrmCustomerAnalyze crmCustomerAnalyze = baseMapper.selectLatestOne(customerId);
+        if (ObjectUtil.isEmpty(crmCustomerAnalyze))throw new RuntimeException("客户信息不存在");
+        HashMap<String, String> userInfo = new HashMap<String, String>();
+//        userInfo.put("name", crmCustomerAnalyze.getCustomerName()==null?"" : crmCustomerAnalyze.getCustomerName());
+        List<SysDictData> portraits = sysDictDataMapper.selectDictDataByType(AI_PORTRAIT);
+        List<String> dictValue = portraits.stream().map(SysDictData::getDictValue).collect(Collectors.toList());
+        Map<String, String> portraitMap = portraits.stream().collect(Collectors.toMap(SysDictData::getDictValue, SysDictData::getDictLabel));
+
+        if (crmCustomerAnalyze.getCustomerPortraitJson() != null){
+            Map<String, String> portraitList = JSON.parseObject(
+                    crmCustomerAnalyze.getCustomerPortraitJson(),
+                    new TypeReference<Map<String, String>>() {}
+            );
+            portraitList.forEach((k, v)->{
+                if(ObjectUtil.isNotEmpty(portraitMap.get(k)))userInfo.put(portraitMap.get(k),v);
+            });
+
+        }else {
+            dictValue.forEach(o->{
+                userInfo.put(o, "");
+            });
+        }
+        HashMap<String, Object> result = new HashMap<>();
+        result.put("userInfo", userInfo);
+        return result;
+    }
 }

+ 15 - 9
fs-service/src/main/java/com/fs/crm/utils/CrmCustomerAiTagUtil.java

@@ -14,12 +14,15 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DictUtils;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.config.ai.AiHostProper;
 import com.fs.crm.domain.CrmCustomer;
+import com.fs.crm.domain.CrmCustomerAnalyze;
 import com.fs.crm.domain.CrmCustomerInfo;
 import com.fs.crm.domain.CrmCustomerPropertyTemplate;
 import com.fs.crm.dto.CrmCustomerAiAutoTagVo;
+import com.fs.crm.mapper.CrmCustomerAnalyzeMapper;
 import com.fs.crm.mapper.CrmCustomerMapper;
 import com.fs.crm.param.CrmCustomerAiTagParam;
 import com.fs.crm.service.ICrmCustomerPropertyTemplateService;
@@ -73,17 +76,18 @@ public class CrmCustomerAiTagUtil {
         // 4. 解析响应并保存
         List<CrmCustomerAiTagVo> results = parseAiResponse(aiResponse, customerId);
 
-        // 5. 异步保存到Redis,后续调用ai分析其他数据
-        saveToRedisAsync(customerId, aiResponse);
+        // 5. 异步保存到Redis,后续调用ai分析其他数据 //暂时只传聊天记录
+        saveToRedisAsync(customerId, (String) requestParam.get("history"),logId);
         return results;
     }
-    private static void saveToRedisAsync(Long customerId, R aiResponse) {
+    private static void saveToRedisAsync(Long customerId, String aiResponse, Long logId) {
         // 使用线程池异步保存,避免影响主流程
         CompletableFuture.runAsync(() -> {
             try {
                 Map<String, Object> dataMap = new HashMap<>();
                 dataMap.put("customerId", customerId);
                 dataMap.put("data", aiResponse);
+                dataMap.put("logId", logId);
                 dataMap.put("timestamp", System.currentTimeMillis());
 
                 RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean(RedisTemplate.class);
@@ -118,7 +122,7 @@ public class CrmCustomerAiTagUtil {
                 .map(tag -> buildTagVo(tag, customerId))
                 .collect(Collectors.toList());
     }
-    private static R callAiService(Map<String, Object> requestParam, Long logId) {
+    public static R callAiService(Map<String, Object> requestParam, Long logId) {
         try {
             String requestJson = mapper.writeValueAsString(requestParam);
 
@@ -169,6 +173,7 @@ public class CrmCustomerAiTagUtil {
         requestParam.put("userContent", "");
         requestParam.put("aiContent", "");
         requestParam.put("likeRatio", likeRatio);
+        requestParam.put("modelType","ai标签");
 
         return requestParam;
     }
@@ -339,8 +344,8 @@ public class CrmCustomerAiTagUtil {
             customerMapper.insertCrmCustomerInfo(crmCustomerInfo);
         }
         HashMap<String, String> userInfo = new HashMap<String, String>();
-        userInfo.put("name", crmCustomerInfo.getName());
-        userInfo.put("sex", crmCustomerInfo.getSex());
+        userInfo.put("name", crmCustomerInfo.getName()==null?"" : crmCustomerInfo.getName());
+        userInfo.put("sex", crmCustomerInfo.getSex()==null?"" : crmCustomerInfo.getSex().toString());
         userInfo.put("age", "");
         userInfo.put("city", "");
         userInfo.put("habits", "");
@@ -377,9 +382,10 @@ public class CrmCustomerAiTagUtil {
             }
         });
         if (!maps.isEmpty()){
-            CrmCustomerInfo crmCustomerInfo = new CrmCustomerInfo();
-            crmCustomerInfo.setCustomerId(Long.valueOf(customerId)).setAiChatRecord(JSONUtil.toJsonStr(maps));
-            SpringUtils.getBean(CrmCustomerMapper.class).updateCrmCustomerInfo(crmCustomerInfo);
+            CrmCustomerAnalyze crmCustomerInfo = new CrmCustomerAnalyze();
+            crmCustomerInfo.setCustomerId(Long.valueOf(customerId));
+            crmCustomerInfo.setAiChatRecord(JSONUtil.toJsonStr(maps));
+            SpringUtils.getBean(CrmCustomerAnalyzeMapper.class).insertCrmCustomerAnalyze(crmCustomerInfo);
         }
 
         return result;

+ 7 - 0
fs-service/src/main/java/com/fs/crm/vo/CrmCustomerListQueryVO.java

@@ -113,4 +113,11 @@ public class CrmCustomerListQueryVO implements Serializable
     /** 认领开始时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date startTime;
+
+    /** 流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险 */
+    @Excel(name = "流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险")
+    private Long attritionLevel;
+    /** 意向度 */
+    @Excel(name = "意向度")
+    private Long intentionDegree;
 }

+ 7 - 0
fs-service/src/main/java/com/fs/crm/vo/CrmLineCustomerListQueryVO.java

@@ -106,5 +106,12 @@ public class CrmLineCustomerListQueryVO implements Serializable
     /** 非重客户Id(重客户最早录入手机号码的客户id) */
     private Long dCustomerId;
 
+    /** 流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险 */
+    @Excel(name = "流失风险等级 0:无风险;1:低风险;2:中风险;3:高风险")
+    private Long attritionLevel;
+    /** 意向度 */
+    @Excel(name = "意向度")
+    private Long intentionDegree;
+
 
 }

+ 78 - 0
fs-service/src/main/java/com/fs/his/domain/FsHealthPulse.java

@@ -0,0 +1,78 @@
+package com.fs.his.domain;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 脉诊对象 fs_health_pulse
+ *
+ * @author fs
+ * @date 2025-10-11
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsHealthPulse extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 用户名 */
+    @Excel(name = "用户名")
+    private String name;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private Long sex;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Long age;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Long status;
+
+    /** 左手结果 */
+    @Excel(name = "左手结果")
+    private String leftHandResult;
+
+    /** 右手结果 */
+    @Excel(name = "右手结果")
+    private String rightHandResult;
+
+    /** 脉诊总结 */
+    @Excel(name = "脉诊总结")
+    private String pulseSummary;
+
+    /** 脉诊建议 */
+    @Excel(name = "脉诊建议")
+    private String pulseSuggestions;
+
+    /** 脉诊设备测量ID */
+    @Excel(name = "脉诊设备测量ID")
+    private String measureId;
+
+    /**左手尺寸 */
+    @TableField(exist = false)
+    private String leftHandSize;
+
+     /**右手尺寸 */
+    @TableField(exist = false)
+    private String rightHandSize;
+
+    /**脉诊设备检测结果*/
+    @TableField(exist = false)
+    private Object pulseEquipmentResult;
+
+    /** url */
+    @Excel(name = "url")
+    private String pulseUrl;
+}

+ 83 - 0
fs-service/src/main/java/com/fs/his/domain/FsHealthSurface.java

@@ -0,0 +1,83 @@
+package com.fs.his.domain;
+
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 面诊对象 fs_health_surface
+ *
+ * @author fs
+ * @date 2025-10-10
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsHealthSurface extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 用户名 */
+    @Excel(name = "用户名")
+    private String name;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private Integer sex;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Integer age;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    /** 面诊图片 */
+    @Excel(name = "面诊图片")
+    private String surfaceUrl;
+
+    /** 肤色的结果 */
+    @Excel(name = "肤色的结果")
+    private String complexionResult;
+
+    /** 肤色结果类型 */
+    @Excel(name = "肤色结果类型")
+    private String complexionTypes;
+
+    /** 区域色斑问题 */
+    @Excel(name = "区域色斑问题")
+    private String spotProblems;
+
+    /** 浮肿问题 */
+    @Excel(name = "浮肿问题")
+    private String swellingProblems;
+
+    /** 光泽度结果 */
+    @Excel(name = "光泽度结果")
+    private String glowResult;
+
+    /** 面诊总结 */
+    @Excel(name = "面诊总结")
+    private String diagnosisSummary;
+
+    /** 健康建议 */
+    @Excel(name = "健康建议")
+    private String healthSuggestions;
+
+    /** 时间 */
+    @Excel(name = "时间")
+    private Date createTime;
+
+    /** 备注 */
+    @Excel(name = "备注")
+    private String remark;
+}

+ 84 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserAdditionalData.java

@@ -0,0 +1,84 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 设备注册用户额外参数对象 fs_user_additional_data
+ *
+ * @author fs
+ * @date 2025-10-17
+ */
+@Data
+public class FsUserAdditionalData {
+
+    /** $column.columnComment */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户名 */
+    @Excel(name = "用户名")
+    private String username;
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    private Long fsUserId;
+
+    /** 电话 */
+    @Excel(name = "电话")
+    private String phone;
+
+    /** 身高 */
+    @Excel(name = "身高")
+    private String height;
+
+    /** 体重 */
+    @Excel(name = "体重")
+    private String weight;
+
+    /** 年龄-出生年月 */
+    @Excel(name = "年龄-出生年月")
+    private String age;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private Integer sex;
+
+    /** 既往病史 */
+    @Excel(name = "既往病史")
+    private String previousMedicalHistory;
+
+    /** 创建时间 **/
+    private Date createTime;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    /** 备注 **/
+    private String remark;
+
+    /** 脉诊设备返回H5 **/
+    private String pulseUrl;
+
+    /** 过敏史 */
+    private String historyOfAllergies;
+
+    /** 生活习惯 */
+    private String habit;
+
+    @TableField(exist = false)
+    private List<Object> habitList;
+
+    /** 身份证号码 **/
+    private String idCard;
+
+    private String consultationRecord;
+
+}

+ 74 - 0
fs-service/src/main/java/com/fs/his/mapper/FsHealthPulseMapper.java

@@ -0,0 +1,74 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsHealthPulse;
+import com.fs.his.param.FsHealthPulseListUParam;
+import com.fs.his.vo.FsHealthPulseListVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 脉诊Mapper接口
+ *
+ * @author fs
+ * @date 2025-10-11
+ */
+public interface FsHealthPulseMapper extends BaseMapper<FsHealthPulse>{
+    /**
+     * 查询脉诊
+     *
+     * @param id 脉诊主键
+     * @return 脉诊
+     */
+    FsHealthPulse selectFsHealthPulseById(Long id);
+
+    /**
+     * 查询脉诊列表
+     *
+     * @param fsHealthPulse 脉诊
+     * @return 脉诊集合
+     */
+    List<FsHealthPulse> selectFsHealthPulseList(FsHealthPulse fsHealthPulse);
+
+    /**
+     * 新增脉诊
+     *
+     * @param fsHealthPulse 脉诊
+     * @return 结果
+     */
+    int insertFsHealthPulse(FsHealthPulse fsHealthPulse);
+
+    /**
+     * 修改脉诊
+     *
+     * @param fsHealthPulse 脉诊
+     * @return 结果
+     */
+    int updateFsHealthPulse(FsHealthPulse fsHealthPulse);
+
+    /**
+     * 删除脉诊
+     *
+     * @param id 脉诊主键
+     * @return 结果
+     */
+    int deleteFsHealthPulseById(Long id);
+
+    /**
+     * 批量删除脉诊
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsHealthPulseByIds(Long[] ids);
+
+    @Select("SELECT * from fs_health_pulse where user_id=#{userId} and status=0 order by create_time desc limit 1")
+    FsHealthPulse queryUserLatestHealthPulse(@Param("userId") Long userId);
+
+    List<FsHealthPulseListVO> selectFsHealthPulseListVO(FsHealthPulseListUParam param);
+
+    List<String> selectListByDateAndUserId(@Param("startDate") String startDate,@Param("endDate") String endDate,@Param("userId") Long userId);
+
+}

+ 77 - 0
fs-service/src/main/java/com/fs/his/mapper/FsHealthSurfaceMapper.java

@@ -0,0 +1,77 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsHealthSurface;
+import com.fs.his.param.FsHealthSurfaceListUParam;
+import com.fs.his.vo.FsHealthSurfaceListVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+
+/**
+ * 面诊Mapper接口
+ *
+ * @author fs
+ * @date 2025-10-10
+ */
+public interface FsHealthSurfaceMapper extends BaseMapper<FsHealthSurface>{
+    /**
+     * 查询面诊
+     *
+     * @param id 面诊主键
+     * @return 面诊
+     */
+    FsHealthSurface selectFsHealthSurfaceById(Long id);
+
+    /**
+     * 查询面诊列表
+     *
+     * @param fsHealthSurface 面诊
+     * @return 面诊集合
+     */
+    List<FsHealthSurface> selectFsHealthSurfaceList(FsHealthSurface fsHealthSurface);
+
+    /**
+     * 新增面诊
+     *
+     * @param fsHealthSurface 面诊
+     * @return 结果
+     */
+    int insertFsHealthSurface(FsHealthSurface fsHealthSurface);
+
+    /**
+     * 修改面诊
+     *
+     * @param fsHealthSurface 面诊
+     * @return 结果
+     */
+    int updateFsHealthSurface(FsHealthSurface fsHealthSurface);
+
+    /**
+     * 删除面诊
+     *
+     * @param id 面诊主键
+     * @return 结果
+     */
+    int deleteFsHealthSurfaceById(Long id);
+
+    /**
+     * 批量删除面诊
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsHealthSurfaceByIds(Long[] ids);
+
+    @Select("SELECT * from fs_health_surface where user_id=#{userId} and status=0 order by create_time desc limit 1")
+    FsHealthSurface queryUserLatestHealthSurface(@Param("userId") Long userId);
+
+    List<FsHealthSurfaceListVO> selectFsHealthSurfaceListVO(FsHealthSurfaceListUParam param);
+
+    List<String> selectListByDateAndUserId(@Param("startDate") String startDate,@Param("endDate") String endDate,@Param("userId") Long userId);
+
+
+
+}

+ 68 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserAdditionalDataMapper.java

@@ -0,0 +1,68 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUserAdditionalData;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+
+/**
+ * 设备注册用户额外参数Mapper接口
+ *
+ * @author fs
+ * @date 2025-10-17
+ */
+public interface FsUserAdditionalDataMapper extends BaseMapper<FsUserAdditionalData>{
+    /**
+     * 查询设备注册用户额外参数
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 设备注册用户额外参数
+     */
+    FsUserAdditionalData selectFsUserAdditionalDataById(Long id);
+
+    /**
+     * 查询设备注册用户额外参数列表
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 设备注册用户额外参数集合
+     */
+    List<FsUserAdditionalData> selectFsUserAdditionalDataList(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 新增设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    int insertFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 修改设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    int updateFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 删除设备注册用户额外参数
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 结果
+     */
+    int deleteFsUserAdditionalDataById(Long id);
+
+    /**
+     * 批量删除设备注册用户额外参数
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserAdditionalDataByIds(Long[] ids);
+
+    @Select("select * from fs_user_additional_data where status=0 and fs_user_id=#{fsUserId}")
+    FsUserAdditionalData selectUserByUserId(@Param("fsUserId") Long fsUserId);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.MerchantAppConfig;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * 商户应用配置Mapper接口
@@ -74,4 +75,13 @@ public interface MerchantAppConfigMapper extends BaseMapper<MerchantAppConfig>{
      * @return 结果
      */
     int deleteMerchantAppConfigByIds(Long[] ids);
+
+    /**
+     * 根据appId和支付类型查询商户信息
+     *
+     * @param appId
+     * @param payType
+     * @return
+     */
+    MerchantAppConfig selectMerchantAppConfigByAppId(@Param("appId") String appId, @Param("payType") String payType);
 }

+ 16 - 0
fs-service/src/main/java/com/fs/his/param/FsHealthPulseListUParam.java

@@ -0,0 +1,16 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FsHealthPulseListUParam extends BaseParam implements Serializable {
+    /** id */
+    private Long id;
+
+
+    private Long userId;
+
+    private String createDay;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/his/param/FsHealthSurfaceListUParam.java

@@ -0,0 +1,16 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FsHealthSurfaceListUParam extends BaseParam implements Serializable {
+    /** id */
+    private Long id;
+
+
+    private Long userId;
+
+    private String createDay;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/param/FsHealthTongueListUParam.java

@@ -12,4 +12,6 @@ public class FsHealthTongueListUParam extends BaseParam implements Serializable
     /** 状态 */
     @Excel(name = "状态")
     private Long userId;
+
+    private String createDay;
 }

+ 65 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserAdditionalDataService.java

@@ -0,0 +1,65 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserAdditionalData;
+
+import java.util.List;
+
+/**
+ * 设备注册用户额外参数Service接口
+ *
+ * @author fs
+ * @date 2025-10-17
+ */
+public interface IFsUserAdditionalDataService extends IService<FsUserAdditionalData>{
+    /**
+     * 查询设备注册用户额外参数
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 设备注册用户额外参数
+     */
+    FsUserAdditionalData selectFsUserAdditionalDataById(Long id);
+
+    /**
+     * 查询设备注册用户额外参数列表
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 设备注册用户额外参数集合
+     */
+    List<FsUserAdditionalData> selectFsUserAdditionalDataList(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 新增设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    int insertFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 修改设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    int updateFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData);
+
+    /**
+     * 批量删除设备注册用户额外参数
+     *
+     * @param ids 需要删除的设备注册用户额外参数主键集合
+     * @return 结果
+     */
+    int deleteFsUserAdditionalDataByIds(Long[] ids);
+
+    /**
+     * 删除设备注册用户额外参数信息
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 结果
+     */
+    int deleteFsUserAdditionalDataById(Long id);
+
+    FsUserAdditionalData selectUserByUserId(Long fsUserId);
+
+}

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -1602,7 +1602,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         storePayment.setStoreId(payOrderParam.getStoreId());
         storePayment.setUserId(user.getUserId());
         storePayment.setBusinessId(payOrderParam.getOrderId().toString());
-
+        storePayment.setAppId(payOrderParam.getAppId());
         // 设置openId(如果是微信支付)
         if (isWechatPayment(payOrderParam.getPaymentMethod())) {
             storePayment.setOpenId(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));

+ 98 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserAdditionalDataServiceImpl.java

@@ -0,0 +1,98 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsUserAdditionalData;
+import com.fs.his.mapper.FsUserAdditionalDataMapper;
+import com.fs.his.service.IFsUserAdditionalDataService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 设备注册用户额外参数Service业务层处理
+ *
+ * @author fs
+ * @date 2025-10-17
+ */
+@Service
+public class FsUserAdditionalDataServiceImpl extends ServiceImpl<FsUserAdditionalDataMapper, FsUserAdditionalData> implements IFsUserAdditionalDataService {
+
+    /**
+     * 查询设备注册用户额外参数
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 设备注册用户额外参数
+     */
+    @Override
+    public FsUserAdditionalData selectFsUserAdditionalDataById(Long id)
+    {
+        return baseMapper.selectFsUserAdditionalDataById(id);
+    }
+
+    /**
+     * 查询设备注册用户额外参数列表
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 设备注册用户额外参数
+     */
+    @Override
+    public List<FsUserAdditionalData> selectFsUserAdditionalDataList(FsUserAdditionalData fsUserAdditionalData)
+    {
+        return baseMapper.selectFsUserAdditionalDataList(fsUserAdditionalData);
+    }
+
+    /**
+     * 新增设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData)
+    {
+        fsUserAdditionalData.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserAdditionalData(fsUserAdditionalData);
+    }
+
+    /**
+     * 修改设备注册用户额外参数
+     *
+     * @param fsUserAdditionalData 设备注册用户额外参数
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserAdditionalData(FsUserAdditionalData fsUserAdditionalData)
+    {
+        return baseMapper.updateFsUserAdditionalData(fsUserAdditionalData);
+    }
+
+    /**
+     * 批量删除设备注册用户额外参数
+     *
+     * @param ids 需要删除的设备注册用户额外参数主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserAdditionalDataByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsUserAdditionalDataByIds(ids);
+    }
+
+    /**
+     * 删除设备注册用户额外参数信息
+     *
+     * @param id 设备注册用户额外参数主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserAdditionalDataById(Long id)
+    {
+        return baseMapper.deleteFsUserAdditionalDataById(id);
+    }
+
+    @Override
+    public FsUserAdditionalData selectUserByUserId(Long fsUserId) {
+        return baseMapper.selectUserByUserId(fsUserId);
+    }
+}

+ 69 - 0
fs-service/src/main/java/com/fs/his/vo/FsHealthPulseListVO.java

@@ -0,0 +1,69 @@
+package com.fs.his.vo;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 脉诊对象 fs_health_pulse
+ *
+ * @author fs
+ * @date 2025-10-11
+ */
+@Data
+public class FsHealthPulseListVO extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 用户名 */
+    @Excel(name = "用户名")
+    private String name;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private Long sex;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Long age;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Long status;
+
+    /** 左手结果 */
+    @Excel(name = "左手结果")
+    private String leftHandResult;
+
+    /** 右手结果 */
+    @Excel(name = "右手结果")
+    private String rightHandResult;
+
+    /** 脉诊总结 */
+    @Excel(name = "脉诊总结")
+    private String pulseSummary;
+
+    /** 脉诊建议 */
+    @Excel(name = "脉诊建议")
+    private String pulseSuggestions;
+
+    /** 脉诊设备测量ID */
+    @Excel(name = "脉诊设备测量ID")
+    private String measureId;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** url */
+    @Excel(name = "url")
+    private String pulseUrl;
+}

+ 83 - 0
fs-service/src/main/java/com/fs/his/vo/FsHealthSurfaceListVO.java

@@ -0,0 +1,83 @@
+package com.fs.his.vo;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 面诊对象 fs_health_surface
+ *
+ * @author fs
+ * @date 2025-10-10
+ */
+@Data
+public class FsHealthSurfaceListVO extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 用户名 */
+    @Excel(name = "用户名")
+    private String name;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private Integer sex;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Integer age;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    /** 面诊图片 */
+    @Excel(name = "面诊图片")
+    private String surfaceUrl;
+
+    /** 肤色的结果 */
+    @Excel(name = "肤色的结果")
+    private String complexionResult;
+
+    /** 肤色结果类型 */
+    @Excel(name = "肤色结果类型")
+    private String complexionTypes;
+
+    /** 区域色斑问题 */
+    @Excel(name = "区域色斑问题")
+    private String spotProblems;
+
+    /** 浮肿问题 */
+    @Excel(name = "浮肿问题")
+    private String swellingProblems;
+
+    /** 光泽度结果 */
+    @Excel(name = "光泽度结果")
+    private String glowResult;
+
+    /** 面诊总结 */
+    @Excel(name = "面诊总结")
+    private String diagnosisSummary;
+
+    /** 健康建议 */
+    @Excel(name = "健康建议")
+    private String healthSuggestions;
+
+    /** 时间 */
+    @Excel(name = "时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 备注 */
+    @Excel(name = "备注")
+    private String remark;
+}

+ 16 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -129,8 +129,13 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
+            "            <if test=\"maps.realName != null and  maps.realName !=''\">\n" +
+            "                and o.real_name like CONCAT('%',#{maps.realName},'%')\n" +
+            "            </if>"+
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
-            "and (o.company_id is null or o.order_type = 2 ) " +
+//            "and (o.company_id is null or o.order_type = 2 ) " +
+            "              and (o.company_id is null\n" +
+            "                or o.order_type = 2 or o.order_type = 3)"+
             "</if>" +
             "<if test = 'maps.notHealth != null and maps.notHealth !=  \"\"  '> " +
             "and o.company_id is not null " +
@@ -172,10 +177,19 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.scheduleId != null    '> " +
             "and o.schedule_id =#{maps.scheduleId} " +
             "</if>" +
+            "            <if test=\"maps.deliveryPayStatus != null  \">\n" +
+            "                and o.delivery_pay_status =#{maps.deliveryPayStatus}\n" +
+            "            </if>"+
+            "            <if test=\"maps.deliveryStatus != null     \">\n" +
+            "                and o.delivery_status =#{maps.deliveryStatus}\n" +
+            "            </if>"+
+            "           <if test=\"maps.productName != null and  maps.productName !=  '' \">\n" +
+            "                and psps.product_name like concat('%', #{maps.productName}, '%')\n" +
+            "            </if>"+
             "<if test = 'maps.isAudit != null'> " +
             "and o.is_audit = #{maps.isAudit} " +
             "</if>" +
-            " order by o.id desc limit 50000"+
+            "GROUP BY  o.id order by o.id desc limit 50000"+
             "</script>"})
     List<FsStoreOrderItemExportVO> selectFsStoreOrderItemListExportVO(@Param("maps")FsStoreOrderParam fsStoreOrder);
 

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java

@@ -293,7 +293,7 @@ public interface FsStorePaymentScrmMapper
             "</script>"})
     List<FsStorePaymentVO> selectFsMyStorePaymentListQueryVO(@Param("maps") FsStorePaymentParam fsStorePayment);
 
-    @Select("select * from fs_store_payment_scrm where business_type=2 and order_id=#{orderId} and status=1   ")
+    @Select("select * from fs_store_payment_scrm where (business_type=2 or business_type=8) and order_id=#{orderId} and status=1   ")
     List<FsStorePaymentScrm> selectFsStorePaymentByOrderId(Long orderId);
 
 

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductUserEndCategoryMapper.java

@@ -24,4 +24,7 @@ public interface FsStoreProductUserEndCategoryMapper {
 
     /** 关联表内全部去重商品ID(不按分类,配合 PageHelper 用于「全部」) */
     List<Long> selectDistinctProductIds(@Param("keyword") String keyword);
+
+    /** 按区域位置查询商品ID:1=金刚区 2=瀑布区,配合 keyword 筛选,支持 storeId */
+    List<Long> selectDistinctProductIdsByPosition(@Param("id") Long id,@Param("storeId") Long storeId, @Param("position") Integer position, @Param("keyword") String keyword);
 }

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreUserEndCategoryScrmService.java

@@ -26,8 +26,8 @@ public interface IFsStoreUserEndCategoryScrmService {
     /** 按用户端分类ID分页查询关联商品(去重商品ID分页,再查商品简表+标签并组装) */
     List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId, String keyword);
 
-    /** 首页商品列表:id 为空查全部(分页商品ID后查简表+标签),id 不为空按用户端分类查;返回 list+total */
-    java.util.Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize);
+    /** 首页商品列表:id 为空查全部(分页商品ID后查简表+标签),id 不为空按用户端分类查;position=1金刚区/2瀑布区 时按区域查全部;返回 list+total */
+    java.util.Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize, Long storeId, Integer position);
 
     int insert(FsStoreUserEndCategoryScrm entity);
 

+ 63 - 14
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -29,6 +29,7 @@ import com.fs.erp.dto.ErpRefundUpdateRequest;
 import com.fs.erp.mapper.FsJstAftersalePushMapper;
 import com.fs.erp.mapper.FsJstAftersalePushScrmMapper;
 import com.fs.erp.service.IErpOrderService;
+import com.fs.his.config.AppConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.*;
 import com.fs.his.enums.FsStoreAfterSalesStatusEnum;
@@ -82,6 +83,7 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.sql.Timestamp;
 import java.text.DecimalFormat;
 import java.text.ParseException;
@@ -285,7 +287,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     @Transactional
     public R applyForAfterSales(long userId, FsStoreAfterSalesParam storeAfterSalesParam) {
         logger.info("申请退款请求信息:"+JSONUtil.toJsonStr(storeAfterSalesParam));
-        
+
         // 查询配置:是否删除历史售后数据
         try {
             String deleteAfterSalesConfig = configService.selectConfigByKey("delete_after_sales");
@@ -309,7 +311,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         } catch (Exception e) {
             logger.error("查询或更新历史售后数据失败", e);
         }
-        
+
         FsStoreOrderScrm order=orderService.selectFsStoreOrderByOrderCode(storeAfterSalesParam.getOrderCode());
         Integer orderStatus = order.getStatus();
         if(!order.getUserId().equals(userId)){
@@ -860,6 +862,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
             if(payments!=null){
 
                 for(FsStorePaymentScrm payment:payments){
+
                     if (StringUtils.isBlank(payment.getAppId())) {
                         throw new IllegalArgumentException("appId不能为空");
                     }
@@ -871,10 +874,17 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
                     if (merchantConfigId == null || merchantConfigId <= 0) {
                         throw new CustomException("小程序没有配置商户信息");
                     }
-                    MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
-                    FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
+                    String payType = payment.getPayMode();
+                    if ("wxApp".equals(payment.getPayMode())){
+                        payType = "wx";
+                    }
 
                     if (payment.getPayMode()==null||payment.getPayMode().equals("wx")){
+
+                        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigByAppId(payment.getAppId(),payType);
+                        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
                         WxPayConfig payConfig = new WxPayConfig();
                         payConfig.setAppId(fsCoursePlaySourceConfig.getAppid());
                         payConfig.setMchId(fsPayConfig.getWxMchId());
@@ -908,35 +918,74 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
                             return R.error("退款请求失败"+e.getErrCodeDes());
                         }
                     }else if (payment.getPayMode()!=null&&payment.getPayMode().equals("hf")){
+//
+//                        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+//                        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
+                        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigByAppId(payment.getAppId(),payType);
+                        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
+//                        String huifuId="";
+//                        FsHfpayConfigMapper fsHfpayConfigMapper = SpringUtils.getBean(FsHfpayConfigMapper.class);
+//
+//                        if (payment.getAppId() != null) {
+//                            FsHfpayConfig fsHfpayConfig = fsHfpayConfigMapper.selectByAppId(payment.getAppId());
+//                            if (fsHfpayConfig == null){
+//                                huifuId = fsPayConfig.getHuifuId();
+//                            }else {
+//                                huifuId = fsHfpayConfig.getHuifuId();
+//                            }
+//                        } else {
+//                            if (("益善缘".equals(cloudHostProper.getCompanyName()))) {
+//
+//                                FsHfpayConfig fsPayConfig2 = fsHfpayConfigMapper.selectByAppId("wx0d1a3dd485268521");
+//                                huifuId = fsPayConfig2.getHuifuId();
+//                            }else {
+//                                huifuId=fsPayConfig.getHuifuId();
+//                            }
+//                        }
+
                         String huifuId="";
-                        FsHfpayConfigMapper fsHfpayConfigMapper = SpringUtils.getBean(FsHfpayConfigMapper.class);
                         if (payment.getAppId() != null) {
-                            FsHfpayConfig fsHfpayConfig = fsHfpayConfigMapper.selectByAppId(payment.getAppId());
-                            if (fsHfpayConfig == null){
+                            if (merchantAppConfig == null){
                                 huifuId = fsPayConfig.getHuifuId();
                             }else {
-                                huifuId = fsHfpayConfig.getHuifuId();
+                                huifuId = merchantAppConfig.getMerchantId();
                             }
                         } else {
                             if (("益善缘".equals(cloudHostProper.getCompanyName()))) {
 
-                                FsHfpayConfig fsPayConfig2 = fsHfpayConfigMapper.selectByAppId("wx0d1a3dd485268521");
-                                huifuId = fsPayConfig2.getHuifuId();
+                                huifuId = merchantAppConfig.getMerchantId();
                             }else {
                                 huifuId=fsPayConfig.getHuifuId();
                             }
                         }
+
                         V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest();
                         DecimalFormat df = new DecimalFormat("0.00");
                         request.setOrgHfSeqId(payment.getTradeNo());
                         request.setHuifuId(huifuId);
-                        request.setOrdAmt(df.format(refundAmount));
+
+//                        request.setOrdAmt(df.format(refundAmount));
+//                        request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+//                        request.setReqSeqId("refund-"+payment.getPayCode());
+//                        Map<String, Object> extendInfoMap = new HashMap<>();
+//                        extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
+//                        request.setAppId(payment.getAppId());
+
+                        Map<String, Object> extendInfoMap = new HashMap<>();
+                        if (order.getPayType().equals("99")){
+                            request.setOrdAmt(payment.getPayMoney().setScale(2, RoundingMode.DOWN).toString());
+                            extendInfoMap.put("org_req_seq_id", "store-"+payment.getPayCode());
+                        }else {
+                            request.setOrdAmt(payment.getPayMoney().toString());
+                            extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
+                            request.setAppId(payment.getAppId());
+                        }
+
                         request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                         request.setReqSeqId("refund-"+payment.getPayCode());
-                        Map<String, Object> extendInfoMap = new HashMap<>();
-                        extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
                         request.setExtendInfo(extendInfoMap);
-                        request.setAppId(payment.getAppId());
                         HuiFuRefundResult refund = huiFuService.refund(request);
                         logger.info("退款:"+refund);
                         if((refund.getResp_code().equals("00000000")||refund.getResp_code().equals("00000100"))&&(refund.getTrans_stat().equals("S")||refund.getTrans_stat().equals("P"))){

+ 102 - 17
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -44,14 +44,17 @@ import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
+import com.fs.course.domain.FsUserCompanyUser;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
 import com.fs.course.dto.OrderOpenIdTransDTO;
 import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
+import com.fs.course.mapper.FsUserCompanyUserMapper;
 import com.fs.erp.domain.*;
 import com.fs.erp.dto.*;
 import com.fs.erp.dto.df.*;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
+import com.fs.his.config.AppConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.*;
 import com.fs.his.dto.FsPrescribeUsageDTO;
@@ -105,6 +108,7 @@ import com.fs.hisStore.constants.StoreConstants;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.enums.*;
 import com.fs.hisStore.service.*;
+import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysDictTypeService;
 import com.fs.wx.miniapp.config.WxMaProperties;
@@ -125,6 +129,7 @@ import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.google.common.base.Joiner;
+import com.google.gson.Gson;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -151,6 +156,7 @@ import org.redisson.api.RedissonClient;
 import javax.annotation.PostConstruct;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.nio.charset.Charset;
 import java.sql.Timestamp;
 import java.text.ParseException;
@@ -201,6 +207,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsStoreCartScrmMapper cartMapper;
 
+
+    @Autowired
+    private FsUserCompanyUserMapper fsUserCompanyUserMapper;
+
     @Autowired
     private FsUserAddressScrmMapper userAddressMapper;
 
@@ -867,12 +877,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Override
     @Transactional
     public R createOrder(long userId, FsStoreOrderCreateParam param) {
+
+        FsUserCompanyUser fsUserCompanyUser = fsUserCompanyUserMapper.selectFsUserCompanyUserByUserId(userId);
+        if (ObjectUtil.isNotEmpty(fsUserCompanyUser)){
+            param.setCompanyId(fsUserCompanyUser.getCompanyId());
+            param.setCompanyUserId(fsUserCompanyUser.getCompanyUserId());
+        }
+
         if (!CloudHostUtils.hasCloudHostName("鹤颜堂")){
             log.error("进入到数据");
             if (ObjectUtil.isEmpty(param.getAddressId())){
                 return R.error("地址不能为空!");
             }
         }
+
         FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
         BeanUtils.copyProperties(param, computedParam);
         //计算金额
@@ -1022,17 +1040,19 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             }
 
             //付款方式
-            if (param.getPayType().equals("1")) {
+            if (param.getPayType().equals("1") || param.getPayType().equals("99")) {
                 //全款支付
                 storeOrder.setStatus(0);
                 if("广州郑多燕".equals(cloudHostProper.getCompanyName())){
                     BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + param.getCreateOrderKey());
                     storeOrder.setPayDelivery(amount != null ? amount : BigDecimal.ZERO);
                 }
-            } else if (param.getPayType().equals("2")) {
+            }
+            else if (param.getPayType().equals("2")) {
                 //物流代收
                 storeOrder.setStatus(1);
-            } else if (param.getPayType().equals("3")) {
+            }
+            else if (param.getPayType().equals("3")) {
                 //货到付款
                 BigDecimal amount = param.getAmount();  //货到付款 自定义代收金额
                 if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
@@ -1680,6 +1700,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Transactional
     public R createPackageOrder(long uid, FsStorePackageOrderCreateParam param) {
         //计算金额
+        FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
         String packageId = redisCache.getCacheObject("orderKey:" + param.getOrderKey());
         if (packageId != null) {
             FsStoreProductPackageScrm storeProductPackage = productPackageService.selectFsStoreProductPackageById(param.getPackageId());
@@ -1717,6 +1738,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             String joinCartIds = null;
             if(carts == null || carts.isEmpty()){
                 carts = new ArrayList<>();
+                List<FsStoreCartQueryVO> cartQueryVOS = new LinkedList<>();
                 for (StorePackageProductDTO goods : goodsList) {
                     FsStoreProductAttrValueScrm attrValue = attrValueService.selectFsStoreProductAttrValueById(goods.getId());
                     if (attrValue != null && attrValue.getProductId() != null) {
@@ -1734,12 +1756,26 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                             vo.setGroupBarCode(attrValue.getGroupBarCode());
                             vo.setPrice(product.getPrice());
                             vo.setProductType(product.getProductType());
+                            vo.setTempId(product.getTempId().longValue());
                             carts.add(vo);
 
                         }
                     }
                     cartService.checkProductStock(attrValue.getProductId(), attrValue.getId());
                 }
+                //校验运费
+                if ("北京卓美".equals(companyName)) {
+                    try {
+                        BigDecimal payPostage =getPayPostage(carts,address);
+                        storeOrder.setPayPostage(payPostage);
+                    } catch (ServiceException e) {
+                        // 捕获运费模板检查异常,直接返回错误
+                        if ("偏远地区暂不可购买".equals(e.getMessage())) {
+                            return R.error("偏远地区暂不可购买");
+                        }
+                        throw e;
+                    }
+                }
             } else {
                 // 如果cartIds不为空,则表示是套餐制单购买,非套餐制单是没有购物车信息的。
                 List<Long> cartIds = carts.stream().map(FsStoreCartQueryVO::getId).collect(Collectors.toList());
@@ -1751,7 +1787,6 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             Boolean isIntegral = false;
             //组合数据
             CompanyUser user = companyUserService.selectCompanyUserById(param.getCompanyUserId());
-            FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
             storeOrder.setCartId(joinCartIds);
             //修改默认仓库
 
@@ -1784,6 +1819,14 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     }
                 }
             }
+
+            //如果运费存在加入运费金额
+            if("北京卓美".equals(companyName)){
+                if(storeOrder.getPayPostage().compareTo(BigDecimal.ZERO) == 1){
+                    totalMoney = totalMoney.add(storeOrder.getPayPostage());
+                }
+            }
+
             storeOrder.setCompanyUserId(param.getCompanyUserId());
             storeOrder.setUserId(uid);
             storeOrder.setOrderCode(orderSn);
@@ -2537,7 +2580,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             //将钱退还给用户
             List<FsStorePaymentScrm> payments = paymentService.selectFsStorePaymentByOrderId(order.getId());
             if (payments != null) {
+
                 for (FsStorePaymentScrm payment : payments) {
+
                     if (StringUtils.isBlank(payment.getAppId())) {
                         throw new IllegalArgumentException("appId不能为空");
                     }
@@ -2549,10 +2594,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     if (merchantConfigId == null || merchantConfigId <= 0) {
                         throw new CustomException("小程序没有配置商户信息");
                     }
-                    MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
-                    FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
+                    String payType = payment.getPayMode();
+                    if ("wxApp".equals(payment.getPayMode())){
+                        payType = "wx";
+                    }
+
+//                    MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+//                    FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
 
                     if (payment.getPayMode() == null || payment.getPayMode().equals("wx")) {
+
+                        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigByAppId(payment.getAppId(),payType);
+                        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
                         WxPayConfig payConfig = new WxPayConfig();
                         payConfig.setAppId(payment.getAppId());
                         payConfig.setMchId(fsPayConfig.getWxMchId());
@@ -2585,6 +2640,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                             return R.error("退款请求失败" + e.getErrCodeDes());
                         }
                     } else if (payment.getPayMode() != null && payment.getPayMode().equals("hf")) {
+
+                        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigByAppId(payment.getAppId(),payType);
+                        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+
                         String huifuId="";
                         FsHfpayConfigMapper fsHfpayConfigMapper = SpringUtils.getBean(FsHfpayConfigMapper.class);
                         if (payment.getAppId() != null) {
@@ -2608,13 +2667,28 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                         V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest();
                         request.setOrgHfSeqId(payment.getTradeNo());
                         request.setHuifuId(huifuId);
-                        request.setOrdAmt(payment.getPayMoney().toString());
+
+//                        request.setOrdAmt(payment.getPayMoney().toString());
+//                        request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+//                        request.setReqSeqId("refund-" + payment.getPayCode());
+//                        Map<String, Object> extendInfoMap = new HashMap<>();
+//                        extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
+//                        request.setAppId(payment.getAppId());
+//
+                        Map<String, Object> extendInfoMap = new HashMap<>();
+                        if (order.getPayType().equals("99")){
+                            request.setOrdAmt(payment.getPayMoney().setScale(2, RoundingMode.DOWN).toString());
+                            extendInfoMap.put("org_req_seq_id", "store-"+payment.getPayCode());
+                        }else {
+                            request.setOrdAmt(payment.getPayMoney().toString());
+                            extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
+                            request.setAppId(payment.getAppId());
+                        }
+
                         request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                         request.setReqSeqId("refund-" + payment.getPayCode());
-                        Map<String, Object> extendInfoMap = new HashMap<>();
-                        extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
                         request.setExtendInfo(extendInfoMap);
-                        request.setAppId(payment.getAppId());
+
                         HuiFuRefundResult refund = huiFuService.refund(request);
                         logger.info("退款:" + refund);
                         if ((refund.getResp_code().equals("00000000") || refund.getResp_code().equals("00000100")) && (refund.getTrans_stat().equals("S") || refund.getTrans_stat().equals("P"))) {
@@ -5027,17 +5101,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             else{
                 String config=configService.selectConfigByKey("his.store");
                 com.fs.store.config.StoreConfig storeConfig= JSONUtil.toBean(config, com.fs.store.config.StoreConfig.class);
-                if(param.getPayType().equals(1)){
-                    order.setPayType("1");
+                if(param.getPayType().equals(1)||param.getPayType().equals(99)||param.getPayType().equals(5)){
+
+                    order.setPayType(String.valueOf(param.getPayType()));
                     order.setPayMoney(order.getPayPrice());
                     if(!"广州郑多燕".equals(cloudHostProper.getCompanyName())){
                         order.setPayDelivery(BigDecimal.ZERO);
                     }
-//                    else {
-//                        // 郑多燕单独设置支付类型
-//                        order.setPayType(order.getPayPrice().compareTo(order.getTotalPrice()) == 0 ?"1":"5");
-//                        logger.info("修改价格--------实际支付金额和总金额:{},{},", order.getPayPrice(), order.getTotalPrice());
-//                    }
                 }
                 else if(param.getPayType().equals(2)){
                     // 物流代收
@@ -5994,6 +6064,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
 
         PayOrderParam payOrderParam = buildPayOrderParam(paymentMethod, order);
+        //加前端传来的appid
+        payOrderParam.setAppId(param.getAppId());
+
         return storePaymentService.processPaymentScrm(payOrderParam);
     }
 
@@ -6105,4 +6178,16 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     public FsStoreOrderAmountScrmStatsVo selectFsStoreOrderAmountScrmStats(FsStoreOrderAmountScrmStatsQueryDto queryDto) {
         return fsStoreOrderMapper.selectFsStoreOrderAmountScrmStats(queryDto);
     }
+
+    private BigDecimal getPayPostage(List<FsStoreCartQueryVO> carts, FsUserAddressScrm
+            userAddress){
+        FsStoreOrderPriceDTO priceGroup = this.getOrderPriceGroup(carts, userAddress);
+        BigDecimal payPostage = priceGroup.getStorePostage();
+        BigDecimal badCode = BigDecimal.valueOf(-1);
+        // 检查运费计算结果,如果是 -1 表示偏远地区不可购买
+        if (payPostage.compareTo(badCode) == 0) {
+            throw new ServiceException("偏远地区暂不可购买");
+        }
+        return payPostage;
+    }
 }

+ 1 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -1228,7 +1228,6 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         storePayment.setStoreId(payOrderParam.getStoreId());
         storePayment.setUserId(user.getUserId());
         storePayment.setBusinessId(payOrderParam.getOrderId().toString());
-
         // 设置openId(如果是微信支付)
         if (isWechatPayment(payOrderParam.getPaymentMethod())) {
             storePayment.setOpenId(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));
@@ -1438,7 +1437,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
 
         FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
         storePayment.setStatus(0);
-        storePayment.setAppId(payConfig.getAppId());
+        storePayment.setAppId(payOrderParam.getAppId());
         storePayment.setOrderId(payOrderParam.getOrderId());
         storePayment.setPayMode(payConfig.getType());
         storePayment.setBusinessCode(payOrderParam.getOrderCode());

+ 20 - 5
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java

@@ -57,6 +57,9 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
         if (categoryId == null) return new ArrayList<>();
         List<Long> productIds = productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(categoryId, keyword);
         if (productIds == null || productIds.isEmpty()) return new ArrayList<>();
+        long total = (productIds instanceof Page) ? ((Page<?>) productIds).getTotal() : productIds.size();
+        int pageNum = (productIds instanceof Page) ? ((Page<?>) productIds).getPageNum() : 1;
+        int pageSize = (productIds instanceof Page) ? ((Page<?>) productIds).getPageSize() : 10;
         List<FsStoreProductScrm> products = fsStoreProductScrmMapper.getStoreProductInProductIds(productIds);
         Map<Long, FsStoreProductScrm> productMap = products.stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, p -> p, (a, b) -> a));
         List<FsStoreProductTagNameVO> tagNames = productTagRelationMapper.selectProductTagNamesByProductIds(productIds);
@@ -87,19 +90,31 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
             result.add(vo);
         }
-        return result;
+        // 包装为 Page 以携带正确的 total,供 getDataTable 使用
+        Page<FsStoreUserEndCategoryProductVO> pageResult = new Page<>(pageNum, pageSize);
+        pageResult.setTotal(total);
+        pageResult.addAll(result);
+        return pageResult;
     }
 
     @Override
-    public Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize) {
+    public Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize, Long storeId, Integer position) {
         Map<String, Object> out = new HashMap<>();
         out.put("list", new ArrayList<FsStoreUserEndCategoryProductVO>());
         out.put("total", 0L);
         if (pageNum == null || pageSize == null || pageSize <= 0) return out;
         PageHelper.startPage(pageNum, pageSize);
-        List<Long> productIds = (id != null && id != 0L)
-                ? productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(id, keyword)
-                : productUserEndCategoryMapper.selectDistinctProductIds(keyword);
+        List<Long> productIds;
+        if (position == null) {
+            position = 1;
+        }
+        if (position == 1 || position == 2) {
+            productIds = productUserEndCategoryMapper.selectDistinctProductIdsByPosition(id,storeId, position, keyword);
+        } else if (id != null && id != 0L) {
+            productIds = productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(id, keyword);
+        } else {
+            productIds = productUserEndCategoryMapper.selectDistinctProductIds(keyword);
+        }
         long total = productIds instanceof Page ? ((Page<?>) productIds).getTotal() : (productIds != null ? productIds.size() : 0);
         if (productIds == null || productIds.isEmpty()) {
             out.put("total", total);

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java

@@ -1,5 +1,6 @@
 package com.fs.hisStore.vo;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
@@ -58,6 +59,10 @@ public class FsStoreOrderVO implements Serializable
     @Excel(name = "收货人姓名")
     private String realName;
 
+    @TableField(exist = false)
+    @Excel(name = "用户昵称")
+    private String userNickName;
+
     /** 用户电话 */
     @Excel(name = "收货人电话")
     private String userPhone;
@@ -262,6 +267,7 @@ public class FsStoreOrderVO implements Serializable
 
     //小程序名称
     private String miniProgramName;
+    private String appId;
 
 
     //erp推送号码

+ 0 - 83
fs-service/src/main/java/com/fs/watch/domain/FsMonitorDataType.java

@@ -1,83 +0,0 @@
-package com.fs.watch.domain;
-
-import com.fs.common.annotation.Excel;
-import com.fs.common.core.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-/**
- * app-健康监测数据类型对象 fs_monitor_data_type
- *
- * @author fs
- * @date 2024-08-26
- */
-public class FsMonitorDataType extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-
-    /** id */
-    private String id;
-
-    /** 数据类型 */
-    @Excel(name = "数据类型")
-    private String type;
-
-    /** 小标题 */
-    @Excel(name = "小标题")
-    private String title;
-
-    /** 图标 */
-    @Excel(name = "图标")
-    private String icon;
-
-    public void setId(String id)
-    {
-        this.id = id;
-    }
-
-    public String getId()
-    {
-        return id;
-    }
-    public void setType(String type)
-    {
-        this.type = type;
-    }
-
-    public String getType()
-    {
-        return type;
-    }
-    public void setTitle(String title)
-    {
-        this.title = title;
-    }
-
-    public String getTitle()
-    {
-        return title;
-    }
-    public void setIcon(String icon)
-    {
-        this.icon = icon;
-    }
-
-    public String getIcon()
-    {
-        return icon;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-            .append("id", getId())
-            .append("type", getType())
-            .append("title", getTitle())
-            .append("icon", getIcon())
-            .append("createTime", getCreateTime())
-            .append("createBy", getCreateBy())
-            .append("updateTime", getUpdateTime())
-            .append("updateBy", getUpdateBy())
-            .toString();
-    }
-}

+ 96 - 0
fs-service/src/main/java/com/fs/watch/domain/WatchAudioMsgLog.java

@@ -0,0 +1,96 @@
+package com.fs.watch.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+* 语音聊天记录
+* @TableName watch_audio_msg_log
+*/
+public class WatchAudioMsgLog implements Serializable {
+
+    /**
+    * 主键id
+    */
+    private UUID id;
+    /**
+    * 设备编号
+    */
+    @ApiModelProperty("设备编号")
+    private String deviceId;
+
+    @ApiModelProperty("文件地址")
+    private String fileUrl;
+
+    @ApiModelProperty("0:发送 1:接收")
+    private Integer type;
+
+    @ApiModelProperty("结果 0:失败 1:成功")
+    private Integer status;
+
+    @ApiModelProperty("返回信息")
+    private String msg;
+
+    @ApiModelProperty("创建时间")
+    private String createTime;
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+
+}

+ 0 - 108
fs-service/src/main/java/com/fs/watch/domain/WatchDeviceBeginnerGuide.java

@@ -1,108 +0,0 @@
-package com.fs.watch.domain;
-
-import com.fs.common.annotation.Excel;
-import com.fs.common.core.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-/**
- * 新手引导对象 device_beginner_uide
- *
- * @author fs
- * @date 2024-09-06
- */
-public class WatchDeviceBeginnerGuide extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-
-    /** 主键id */
-    private Long id;
-
-    /** 标题 */
-    @Excel(name = "标题")
-    private String title;
-
-    /** 链接 */
-    @Excel(name = "链接")
-    private String url;
-
-    /** 资料类型 1:文章 2:视频 */
-    @Excel(name = "资料类型 1:文章 2:视频")
-    private Long type;
-
-    /** 是否已删除 */
-    @Excel(name = "是否已删除")
-    private Long isDel;
-
-    public String getBelong() {
-        return belong;
-    }
-
-    public void setBelong(String belong) {
-        this.belong = belong;
-    }
-
-    private String belong;
-
-    public void setId(Long id)
-    {
-        this.id = id;
-    }
-
-    public Long getId()
-    {
-        return id;
-    }
-    public void setTitle(String title)
-    {
-        this.title = title;
-    }
-
-    public String getTitle()
-    {
-        return title;
-    }
-    public void setUrl(String url)
-    {
-        this.url = url;
-    }
-
-    public String getUrl()
-    {
-        return url;
-    }
-    public void setType(Long type)
-    {
-        this.type = type;
-    }
-
-    public Long getType()
-    {
-        return type;
-    }
-    public void setIsDel(Long isDel)
-    {
-        this.isDel = isDel;
-    }
-
-    public Long getIsDel()
-    {
-        return isDel;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-            .append("id", getId())
-            .append("title", getTitle())
-            .append("url", getUrl())
-            .append("type", getType())
-            .append("createBy", getCreateBy())
-            .append("createTime", getCreateTime())
-            .append("updateBy", getUpdateBy())
-            .append("updateTime", getUpdateTime())
-            .append("isDel", getIsDel())
-            .append("belong", getBelong())
-            .toString();
-    }
-}

+ 56 - 0
fs-service/src/main/java/com/fs/watch/domain/WatchDeviceWeek.java

@@ -0,0 +1,56 @@
+package com.fs.watch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+* 每周健康周报汇总
+* @TableName watch_device_week
+*/
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("watch_device_week") // 确保表名正确
+public class WatchDeviceWeek extends BaseEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+    * 设备编号
+    */
+    @ApiModelProperty("设备编号")
+    private String deviceId;
+    /**
+    * 异常数据
+    */
+    @ApiModelProperty("异常数据")
+    private String listJson;
+    /**
+    * 标题
+    */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+    * 详细描述
+    */
+    @ApiModelProperty("详细描述")
+    private String desc;
+    /**
+    * 评分
+    */
+    @ApiModelProperty("评分")
+    private Integer score;
+    /**
+    *
+    */
+    private String aiSuggestion;
+
+    private Long userId;
+
+
+
+}

+ 89 - 0
fs-service/src/main/java/com/fs/watch/domain/WatchSosCallLogs.java

@@ -0,0 +1,89 @@
+package com.fs.watch.domain;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * sos报警日志(WatchSosCallLogs)实体类
+ *
+ * @author makejava
+ * @since 2024-12-24 14:30:26
+ */
+public class WatchSosCallLogs implements Serializable {
+    private static final long serialVersionUID = 656916219606367008L;
+/**
+     * 主键id
+     */
+    private UUID id;
+/**
+     * 设备编号
+     */
+    private String deviceId;
+/**
+     * 报警时间
+     */
+    private String alarmTime;
+/**
+     * sos时的定位 纬度
+     */
+    private String lat;
+/**
+     * sos时的定位 经度
+     */
+    private String lon;
+/**
+     * 通话日志json
+     */
+    private String callLogs;
+
+
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getAlarmTime() {
+        return alarmTime;
+    }
+
+    public void setAlarmTime(String alarmTime) {
+        this.alarmTime = alarmTime;
+    }
+
+    public String getLat() {
+        return lat;
+    }
+
+    public void setLat(String lat) {
+        this.lat = lat;
+    }
+
+    public String getLon() {
+        return lon;
+    }
+
+    public void setLon(String lon) {
+        this.lon = lon;
+    }
+
+    public String getCallLogs() {
+        return callLogs;
+    }
+
+    public void setCallLogs(String callLogs) {
+        this.callLogs = callLogs;
+    }
+
+}
+

+ 4 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserHealthReportVo.java

@@ -10,4 +10,8 @@ public class AppFsUserHealthReportVo {
     private String title; //标题
     private String desc; //详细描述
     private Integer score;//评分
+    private String aiSuggestion;
+    private Integer sessionId;
+    private Integer roleId = 140;
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserHealthVo.java

@@ -20,5 +20,9 @@ public class AppFsUserHealthVo{
 
     //舌诊日期
     private List<String> tongueDateList;
+    //面诊日期
+    private List<String> pulseDateList;
+    //脉诊日期
+    private List<String> surfaceDateList;
 
 }

+ 4 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/AppFsUserVo.java

@@ -119,4 +119,8 @@ public class AppFsUserVo {
     /** 微信公众号OPENID */
     @Excel(name = "微信公众号OPENID")
     private String mpOpenId;
+    /** 既往病史 */
+    @Excel(name = "既往病史")
+    private String previousMedicalHistory;
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/AppWatchSleepDataVo.java

@@ -2,6 +2,8 @@ package com.fs.watch.domain.vo;
 
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 public class AppWatchSleepDataVo extends WatchSleepDataVo {
     /**
@@ -39,4 +41,6 @@ public class AppWatchSleepDataVo extends WatchSleepDataVo {
      * 呼吸质量 得分(百分制)
      */
     private Integer breathScore;
+
+    private List<WatchSleepDataVo> watchSleepDataVoList;
 }

+ 69 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/WatchDeviceInfoAndIotExportVO.java

@@ -0,0 +1,69 @@
+package com.fs.watch.domain.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2025/11/18 下午1:44)
+ */
+
+@Data
+public class WatchDeviceInfoAndIotExportVO {
+
+
+
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    @Excel(name = "设备编号")
+    private String deviceId;
+
+
+    @Excel(name = "ICCID")
+    private String iccid;
+
+    //物联网卡运营商类型:1=移动 2=联通 3=电信
+    private Integer operatorType;
+
+    @Excel(name = "运营商")
+    private String operatorTypeName;
+
+    //当前状态 5=已激活 6=已停用
+    private Integer status;
+
+    //当前状态 5=已激活 6=已停用
+    @Excel(name = "状态")
+    private String statusName;
+
+    //当月总流量,单位MB
+    @Excel(name = "当月总流量")
+    private Integer totalFlowSize;
+
+    //当月已使用流量,单位MB
+    @Excel(name = "当月已使用流量")
+    private Integer usedFlowSize;
+
+    //当前套餐名称
+    @Excel(name = "当前套餐")
+    private String currentPlanName;
+
+    //套餐开始时间
+    @Excel(name = "套餐开始时间")
+    private String currentPlanStartTime;
+
+    //套餐失效时间
+    @Excel(name = "套餐失效时间")
+    private String currentPlanExpireTime;
+
+    //下期套餐名称
+    @Excel(name = "下期套餐")
+    private String nextPlanName;
+
+    //实名状态 0 未认证  1 已认证 2人工审核中
+    private Integer certified;
+
+    //实名状态 0 未认证  1 已认证 2人工审核中
+    @Excel(name = "实名状态")
+    private String certifiedName;
+}

+ 0 - 9
fs-service/src/main/java/com/fs/watch/domain/vo/WatchDeviceInfoAndUserIdVo.java

@@ -1,9 +0,0 @@
-package com.fs.watch.domain.vo;
-
-import com.fs.watch.domain.WatchDeviceInfo;
-import lombok.Data;
-
-@Data
-public class WatchDeviceInfoAndUserIdVo extends WatchDeviceInfo {
-    private Long userId;
-}

+ 2 - 0
fs-service/src/main/java/com/fs/watch/domain/vo/WatchSleepDataVo.java

@@ -15,6 +15,8 @@ public class WatchSleepDataVo {
      */
     private String deviceId;
 
+    private String date; //时间
+
     /**
      * 开始时间
      */

+ 2 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchAlarmDataMapper.java

@@ -36,4 +36,6 @@ public interface WatchAlarmDataMapper{
     Integer setAppStatusByDeviceId(@Param("deviceId")String deviceId);
 
     Boolean deleteByDeviceId(@Param("deviceId")String deviceId);
+
+    Integer setLocation(@Param("data")WatchAlarmData watchAlarmData);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchAudioMsgLogMapper.java

@@ -0,0 +1,12 @@
+package com.fs.watch.mapper;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.watch.domain.WatchAudioMsgLog;
+import org.apache.ibatis.annotations.Param;
+
+@DataSource(DataSourceType.CLICKHOUSE)
+public interface WatchAudioMsgLogMapper {
+
+    void insert(@Param("data") WatchAudioMsgLog data);
+}

+ 4 - 8
fs-service/src/main/java/com/fs/watch/mapper/WatchBloodPressureDataMapper.java

@@ -14,34 +14,30 @@ import java.util.Map;
 * @createDate 2024-10-15 11:06:50
 * @Entity watch.domain.WatchBloodPressureData
 */
+@DataSource(DataSourceType.CLICKHOUSE)
 public interface WatchBloodPressureDataMapper{
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     void insert(@Param("data") WatchBloodPressureData data);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     Integer queryByCreateTime(@Param("createTime") String createTime);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     List<?> queryByDateAndDeviceId(@Param("date") String date, @Param("deviceId") String deviceId);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     List<WatchBloodPressureData> queryBpByDate(@Param("startTime") String startTime, @Param("endTime") String endTime,
                                                @Param("deviceId") String deviceId, @Param("status") Integer status);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
+
     List<Map<String, Object>> countBpByDate(@Param("startTime") String startTime, @Param("endTime") String endTime, @Param("deviceId") String deviceId);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
+    Map<String, Integer> countMaxAndMinBpByDate(@Param("startTime") String startTime, @Param("endTime") String endTime, @Param("deviceId") String deviceId);
+
     WatchBloodPressureData getLatest(@Param("deviceId") String deviceId);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     int countBpPageByDate(@Param("startTime") String startTime,
                           @Param("endTime") String endTime,
                           @Param("deviceId") String deviceId,
                           @Param("status") Integer status);
 
-    @DataSource(DataSourceType.CLICKHOUSE)
     List<WatchBloodPressureData> queryBgPageByDate(@Param("startTime") String startTime,
                                                    @Param("endTime") String endTime,
                                                    @Param("deviceId") String deviceId,

+ 2 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceInfoClicMapper.java

@@ -32,5 +32,7 @@ public interface WatchDeviceInfoClicMapper {
     List<WatchDeviceInfoClic> selectDeviceInfoList(WatchDeviceInfoClicParam param);
 
     WatchDeviceInfoClic selectOneByIcc(@Param("iccid")String iccid);
+
+    List<WatchDeviceInfoClic> selectListByIccList(@Param("iccidList") List<String> iccidList);
 }
 

+ 4 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceInfoMapper.java

@@ -69,4 +69,8 @@ public interface WatchDeviceInfoMapper{
     List<WatchDeviceInfo> selectListByUserOrFamilyUser(@Param("userId") Long userId);
 
     void removeUserId(WatchDeviceInfo data);
+
+    WatchDeviceInfo selectByUserId(String userId);
+
+    List<WatchDeviceInfoVo> selectCompanyNameByList(@Param("deviceIds") List<String> deviceIds);
 }

+ 68 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchDeviceWeekMapper.java

@@ -0,0 +1,68 @@
+package com.fs.watch.mapper;
+
+import com.fs.watch.domain.WatchDeviceWeek;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+* @author Administrator
+* @description 针对表【watch_device_week(每周健康周报汇总)】的数据库操作Mapper
+* @createDate 2025-10-11 13:22:54
+* @Entity generator.domain.WatchDeviceWeek
+*/
+public interface WatchDeviceWeekMapper {
+
+    /**
+     * 查询每周健康周报汇总
+     *
+     * @param id 每周健康周报汇总主键
+     * @return 每周健康周报汇总
+     */
+    WatchDeviceWeek selectWatchDeviceWeekById(Long id);
+
+    /**
+     * 查询每周健康周报汇总列表
+     *
+     * @param watchDeviceWeek 每周健康周报汇总
+     * @return 每周健康周报汇总集合
+     */
+    List<WatchDeviceWeek> selectWatchDeviceWeekList(WatchDeviceWeek watchDeviceWeek);
+
+    /**
+     * 新增每周健康周报汇总
+     *
+     * @param watchDeviceWeek 每周健康周报汇总
+     * @return 结果
+     */
+    int insertWatchDeviceWeek(WatchDeviceWeek watchDeviceWeek);
+
+    /**
+     * 修改每周健康周报汇总
+     *
+     * @param watchDeviceWeek 每周健康周报汇总
+     * @return 结果
+     */
+    int updateWatchDeviceWeek(WatchDeviceWeek watchDeviceWeek);
+
+    /**
+     * 删除每周健康周报汇总
+     *
+     * @param id 每周健康周报汇总主键
+     * @return 结果
+     */
+    int deleteWatchDeviceWeekById(Long id);
+
+    /**
+     * 批量删除每周健康周报汇总
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteWatchDeviceWeekByIds(Long[] ids);
+
+    WatchDeviceWeek getByDeviceIdAndTime(@Param("deviceId") String deviceId,@Param("createTime") Date createTime);
+
+    WatchDeviceWeek getByUserIdAndTime(@Param("userId") Long userId,@Param("createTime") Date createTime);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchDoctorMapper.java

@@ -65,4 +65,6 @@ public interface WatchDoctorMapper extends BaseMapper<WatchDoctor>{
     void insertBatchWatchUserCompany(@Param("list") ArrayList<WatchDoctor> dList);
 
     void deleteBatch(@Param("deviceId")Long deviceId,@Param("list") List<String> subDoctorList);
+
+    WatchDoctor selectDoctorByDeviceId(Long deviceId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchHeartRateDataMapper.java

@@ -38,5 +38,7 @@ public interface WatchHeartRateDataMapper{
 
     List<Map<String, Object>> countBpByDate(@Param("startTime")String startTime, @Param("endTime") String endTime, @Param("deviceId")String deviceId);
 
+    Map<String, Integer> countMaxAndAvgBpByDate(@Param("startTime")String startTime, @Param("endTime") String endTime, @Param("deviceId")String deviceId);
+
     List<WatchHeartRateData> queryByMonth(@Param("deviceId")String deviceId, @Param("monthStr")String monthStr);
 }

+ 36 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchSosCallLogsMapper.java

@@ -0,0 +1,36 @@
+package com.fs.watch.mapper;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.watch.domain.WatchSosCallLogs;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * sos报警日志(WatchSosCallLogs)表数据库访问层
+ *
+ * @author makejava
+ * @since 2024-12-24 14:30:26
+ */
+@DataSource(DataSourceType.CLICKHOUSE)
+public interface WatchSosCallLogsMapper {
+
+
+    /**
+     * 新增数据
+     *
+     * @param watchSosCallLogs 实例对象
+     * @return 影响行数
+     */
+    void insert(@Param("data")WatchSosCallLogs watchSosCallLogs);
+
+    Integer queryByAlarmTime(@Param("alarmTime")String alarmTime);
+
+    WatchSosCallLogs getByDeviceIdAndTime(@Param("deviceId") String deviceId, @Param("alarmTime") String alarmTime);
+
+    WatchSosCallLogs getByDeviceIdAndTimeOne(@Param("deviceId") String deviceId, @Param("alarmTime") long alarmTime);
+
+    void batchInsertData(@Param("list") List<WatchSosCallLogs> list);
+}
+

+ 2 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchSpo2DataMapper.java

@@ -40,4 +40,6 @@ public interface WatchSpo2DataMapper {
                                             @Param("num") Integer num,
                                             @Param("size") Integer size
     );
+
+    Float countAvgBpByDate(@Param("startTime") String startTime, @Param("endTime") String endTime, @Param("deviceId") String deviceId);
 }

+ 15 - 0
fs-service/src/main/java/com/fs/watch/param/DeviceSendParam.java

@@ -0,0 +1,15 @@
+package com.fs.watch.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class DeviceSendParam {
+    @NotNull
+    private String deviceId;
+    @NotNull
+    private String fileUrl;
+    @NotNull
+    private String sendUserName;
+}

+ 0 - 1
fs-service/src/main/java/com/fs/watch/param/WatchChartQueryParam.java

@@ -1,6 +1,5 @@
 package com.fs.watch.param;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 

+ 15 - 0
fs-service/src/main/java/com/fs/watch/param/WatchDeviceInfoQueryParam.java

@@ -22,6 +22,16 @@ public class WatchDeviceInfoQueryParam extends BaseQueryParam{
      */
     private Long doctorId;
 
+    /**
+     * 绑定用户id
+     */
+    private String userId;
+
+    /**
+     * 绑定家人用户id
+     */
+    private String familyUserId;
+
     /**
      * 所属医生名字
      */
@@ -59,4 +69,9 @@ public class WatchDeviceInfoQueryParam extends BaseQueryParam{
      */
     private String watchUserName;
 
+    /**
+     * 健康状态
+     */
+    private Integer isNormal;
+
 }

+ 8 - 0
fs-service/src/main/java/com/fs/watch/service/DeviceSetUpService.java

@@ -3,6 +3,7 @@ package com.fs.watch.service;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.watch.domain.*;
 import com.fs.watch.domain.vo.AlarmClockVos;
+import com.fs.watch.param.DeviceSendParam;
 
 public interface DeviceSetUpService {
     Boolean userinfo(UserInfo userInfo);
@@ -72,4 +73,11 @@ public interface DeviceSetUpService {
     Boolean temperatureUnit (String deviceNumber,Integer unit);
 
     DeviceUserInfo getUserinfo(String deviceId,Long userId,Integer selectType);
+
+    /**
+     * 发送语音给腕表
+     * @param param
+     * @return
+     */
+    Boolean sendMp3(DeviceSendParam param);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/watch/service/WatchAudioMsgLogService.java

@@ -0,0 +1,7 @@
+package com.fs.watch.service;
+
+import com.fs.watch.domain.WatchAudioMsgLog;
+
+public interface WatchAudioMsgLogService {
+    Boolean insert(WatchAudioMsgLog data);
+}

+ 1 - 1
fs-service/src/main/java/com/fs/watch/service/WatchBasicInfoService.java

@@ -46,5 +46,5 @@ public interface WatchBasicInfoService{
 
     List<GnssVo> queryGnssByDate(String starTime, String endTime, String deviceId);
 
-    AppFsUserHealthReportVo getAppFsUserHealthReportVo(Date startTime, Date endTime, String deviceId);
+    AppFsUserHealthReportVo getAppFsUserHealthReportVo(Date startTime, Date endTime, String deviceId,Long watchUserId,Boolean isFamily,Integer isRefresh);
 }

+ 0 - 24
fs-service/src/main/java/com/fs/watch/service/WatchDeviceBeginnerGuideService.java

@@ -1,24 +0,0 @@
-package com.fs.watch.service;
-
-import com.fs.watch.domain.WatchDeviceBeginnerGuide;
-
-import java.util.List;
-
-public interface WatchDeviceBeginnerGuideService {
-    /**
-     * 查询新手引导
-     *
-     * @param id 新手引导主键
-     * @return 新手引导
-     */
-    public WatchDeviceBeginnerGuide selectDeviceBeginnerGuideById(Long id);
-
-    /**
-     * 查询新手引导列表
-     *
-     * @param deviceBeginnerGuide 新手引导
-     * @return 新手引导集合
-     */
-    public List<WatchDeviceBeginnerGuide> selectDeviceBeginnerGuideList(WatchDeviceBeginnerGuide deviceBeginnerGuide);
-
-}

+ 3 - 0
fs-service/src/main/java/com/fs/watch/service/WatchDeviceInfoClicService.java

@@ -2,6 +2,7 @@ package com.fs.watch.service;
 
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.watch.domain.WatchDeviceInfoClic;
+import com.fs.watch.domain.vo.WatchDeviceInfoAndIotExportVO;
 import com.fs.watch.param.DeviceIdAndIotInfoQueryParam;
 import com.fs.watch.param.WatchDeviceInfoClicParam;
 
@@ -25,4 +26,6 @@ public interface WatchDeviceInfoClicService {
     TableDataInfo getDeviceIdAndIotInfo(DeviceIdAndIotInfoQueryParam param);
 
     List<WatchDeviceInfoClic> selectWatchDeviceInfoClicList(WatchDeviceInfoClicParam param);
+
+    List<WatchDeviceInfoAndIotExportVO> exportDeviceIdAndIotInfo(DeviceIdAndIotInfoQueryParam param);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/watch/service/WatchDeviceInfoService.java

@@ -62,4 +62,6 @@ public interface WatchDeviceInfoService{
     void bindCompany(CompanyUserUser map);
 
     void removeUserId(String deviceId);
+
+    List<WatchDeviceInfoVo> selectCompanyNameByList(List<String> deviceIds);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/watch/service/WatchDeviceWeekService.java

@@ -0,0 +1,11 @@
+package com.fs.watch.service;
+
+
+/**
+* @author Administrator
+* @description 针对表【watch_device_week(每周健康周报汇总)】的数据库操作Service
+* @createDate 2025-10-11 13:22:54
+*/
+public interface WatchDeviceWeekService {
+
+}

+ 33 - 0
fs-service/src/main/java/com/fs/watch/service/WatchSosCallLogsService.java

@@ -0,0 +1,33 @@
+package com.fs.watch.service;
+
+
+import com.fs.watch.domain.WatchSosCallLogs;
+
+import java.util.List;
+
+/**
+ * sos报警日志(WatchSosCallLogs)表服务接口
+ *
+ * @author makejava
+ * @since 2024-12-24 14:30:26
+ */
+public interface WatchSosCallLogsService {
+
+
+    /**
+     * 新增数据
+     *
+     * @param watchSosCallLogs 实例对象
+     * @return 实例对象
+     */
+    Boolean insert(WatchSosCallLogs watchSosCallLogs);
+
+    /**
+     * 批量添加
+     * @param list
+     * @return
+     */
+    Boolean batchInsertData(List<WatchSosCallLogs> list);
+
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/watch/service/impl/DeviceSetUpServiceImpl.java

@@ -11,8 +11,13 @@ import com.fs.his.utils.PhoneUtil;
 import com.fs.watch.domain.*;
 import com.fs.watch.domain.vo.AlarmClockObjectVo;
 import com.fs.watch.domain.vo.AlarmClockVos;
+import com.fs.watch.mapper.WatchAudioMsgLogMapper;
+import com.fs.watch.param.DeviceSendParam;
 import com.fs.watch.service.*;
+import com.fs.watch.utils.AudioConvertUtil;
+import com.fs.watch.utils.MD5Utils;
 import com.fs.watch.utils.MyHttpUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -26,6 +31,7 @@ import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 @Service
+@Slf4j
 public class DeviceSetUpServiceImpl implements DeviceSetUpService {
     private MyHttpUtils httpUtils = new MyHttpUtils();
     @Autowired
@@ -38,6 +44,8 @@ public class DeviceSetUpServiceImpl implements DeviceSetUpService {
     private WatchSendMsgSetService watchSendMsgSetService;
     @Autowired
     private WatchSendMsgLogService watchSendMsgLogService;
+    @Autowired
+    private WatchAudioMsgLogMapper watchAudioMsgLogMapper;
 
     /**
      * 下发用户设置到设备
@@ -1271,9 +1279,64 @@ public class DeviceSetUpServiceImpl implements DeviceSetUpService {
                 } else {
                     userInfo.setPhone(PhoneUtil.decryptPhone(phone));
                 }
+                //2025-8-8新增需求手机号码脱敏显示
+                if(StringUtils.isNotBlank(userInfo.getPhone())){
+                    userInfo.setPhone(userInfo.getPhone().replaceAll("(\\d{3})\\d{8}", "$1********"));
+                }
             }
             return userInfo;
         }
         return null;
     }
+
+    @Override
+    public Boolean sendMp3(DeviceSendParam param) {
+        Date nowDate = DateUtils.getNowDate();
+        String fileUrl = param.getFileUrl();
+        String sendUserName = param.getSendUserName();
+        String deviceId = param.getDeviceId();
+        try {
+            fileUrl = AudioConvertUtil.convertUploadAndClean(
+                    fileUrl,
+                    deviceId,
+                    DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss",nowDate),
+                    "/tmp/audio/"
+            );
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        JSONObject fileInfo = MD5Utils.getMd5AndSizeFromUrl(fileUrl);
+        Map<String, Object> map = new HashMap<>();
+        map.put("type",3);
+        map.put("device_id",deviceId);
+        map.put("url",fileUrl);
+        map.put("md5",fileInfo.getString("md5"));
+        map.put("size",Long.valueOf(fileInfo.getString("size")));
+        map.put("sender",sendUserName);
+        //取时间的 时:分
+        map.put("send_time", DateUtils.parseDateToStr("HH:mm",nowDate));
+        Map response = httpUtils.getPost("/entservice2/file/download", map);
+        WatchAudioMsgLog watchAudioMsgLog = new WatchAudioMsgLog();
+        watchAudioMsgLog.setDeviceId(deviceId);
+        watchAudioMsgLog.setFileUrl(param.getFileUrl());
+        watchAudioMsgLog.setType(0);
+        watchAudioMsgLog.setCreateTime(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss",nowDate));
+        watchAudioMsgLog.setStatus(0);
+        watchAudioMsgLog.setMsg(JSON.toJSONString(response));
+        if (response != null){
+            String rrpcCode = response.get("RrpcCode").toString();
+            if ("SUCCESS".equals(rrpcCode) || "TIMEOUT".equals(rrpcCode)){
+                watchAudioMsgLog.setStatus(1);
+            }
+
+        }
+        //记录发送记录
+        try {
+            watchAudioMsgLogMapper.insert(watchAudioMsgLog);
+        } catch (Exception e) {
+            log.error("插入 watchAudioMsgLog 失败,参数: {}", watchAudioMsgLog, e); // 关键:打印完整异常
+            throw new RuntimeException(e);
+        }
+        return watchAudioMsgLog.getStatus()==1?true:false;
+    }
 }

Some files were not shown because too many files changed in this diff