Browse Source

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

caoliqin 1 month ago
parent
commit
9a32ded8ad
100 changed files with 5609 additions and 383 deletions
  1. 197 0
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 6 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java
  3. 3 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  4. 61 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  5. 142 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  6. 47 0
      fs-admin/src/main/java/com/fs/his/task/FsCourseTask.java
  7. 9 2
      fs-admin/src/main/java/com/fs/his/task/Task.java
  8. 64 0
      fs-admin/src/main/java/com/fs/qw/FsCourseTask.java
  9. 1 1
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  10. 97 0
      fs-admin/src/main/java/com/fs/transfer/CustomerTransferApprovalController.java
  11. 2 7
      fs-admin/src/main/resources/application.yml
  12. 1 1
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  13. 115 19
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  14. 240 0
      fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java
  15. 4 12
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  16. 153 0
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseAnswerLogsController.java
  17. 262 0
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  18. 8 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  19. 53 19
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsInfoController.java
  20. 138 0
      fs-company/src/main/java/com/fs/company/controller/qw/qw/QwQwWorkTaskController.java
  21. 104 0
      fs-company/src/main/java/com/fs/transfer/CustomerTransferApprovalController.java
  22. 64 0
      fs-company/src/main/java/com/fs/user/FsUserAdminController.java
  23. 0 6
      fs-company/web/WEB-INF/web.xml
  24. 1 1
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  25. 33 9
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  26. 200 87
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  27. 0 3
      fs-qwhook-sop/src/main/java/com/fs/app/redis/RedisKeyExpirationListener.java
  28. 15 0
      fs-service/pom.xml
  29. 22 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyCacheService.java
  30. 9 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyTagCacheService.java
  31. 32 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyUserCacheService.java
  32. 36 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyTagCacheServiceImpl.java
  33. 59 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyUserCacheServiceImpl.java
  34. 46 0
      fs-service/src/main/java/com/fs/company/cache/impl/ICompanyCacheServiceImpl.java
  35. 15 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  36. 10 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  37. 42 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  38. 4 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseDomainName.java
  39. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseLinkMapper.java
  40. 13 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  41. 50 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  42. 79 0
      fs-service/src/main/java/com/fs/course/mapper/HyWatchLogMapper.java
  43. 19 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java
  44. 8 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  45. 7 0
      fs-service/src/main/java/com/fs/course/service/cache/IFsUserCourseVideoCacheService.java
  46. 29 0
      fs-service/src/main/java/com/fs/course/service/cache/impl/FsUserCourseVideoCacheServiceImpl.java
  47. 165 4
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  48. 52 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  49. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListVO.java
  50. 2 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogTaskVO.java
  51. 21 2
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  52. 20 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java
  53. 20 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  54. 4 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java
  55. 12 0
      fs-service/src/main/java/com/fs/his/service/IFsUserService.java
  56. 10 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java
  57. 113 1
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  58. 19 0
      fs-service/src/main/java/com/fs/qw/cache/IQwExternalContactCacheService.java
  59. 5 0
      fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
  60. 49 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwExternalContactCacheServiceImpl.java
  61. 34 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java
  62. 99 0
      fs-service/src/main/java/com/fs/qw/domain/CustomerTransferApproval.java
  63. 61 0
      fs-service/src/main/java/com/fs/qw/domain/HyWorkTask.java
  64. 2 0
      fs-service/src/main/java/com/fs/qw/domain/QwGroupChatUser.java
  65. 31 0
      fs-service/src/main/java/com/fs/qw/dto/FsUserTransferParamDTO.java
  66. 62 0
      fs-service/src/main/java/com/fs/qw/mapper/CustomerTransferApprovalMapper.java
  67. 119 0
      fs-service/src/main/java/com/fs/qw/mapper/HyWorkTaskMapper.java
  68. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  69. 3 1
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  70. 2 2
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  71. 40 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  72. 19 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  73. 3 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactVOTime.java
  74. 19 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  75. 23 0
      fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java
  76. 15 11
      fs-service/src/main/java/com/fs/qw/param/SopExternalContactInfo.java
  77. 62 0
      fs-service/src/main/java/com/fs/qw/service/ICustomerTransferApprovalService.java
  78. 112 0
      fs-service/src/main/java/com/fs/qw/service/IHyWorkTaskService.java
  79. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  80. 6 2
      fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java
  81. 4 0
      fs-service/src/main/java/com/fs/qw/service/IQwWorkTaskService.java
  82. 266 0
      fs-service/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java
  83. 589 0
      fs-service/src/main/java/com/fs/qw/service/impl/HyWorkTaskServiceImpl.java
  84. 21 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  85. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagServiceImpl.java
  86. 199 12
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  87. 75 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java
  88. 1 1
      fs-service/src/main/java/com/fs/qw/vo/QwSopRuleTimeVO.java
  89. 26 0
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogAllStatisticsListVO.java
  90. 37 22
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogStatisticsListVO.java
  91. 22 0
      fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java
  92. 24 3
      fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskListVO.java
  93. 30 0
      fs-service/src/main/java/com/fs/qw/vo/TransferCustomDTO.java
  94. 1 1
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  95. 2 0
      fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java
  96. 8 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  97. 5 2
      fs-service/src/main/java/com/fs/sop/service/IQwSopLogsService.java
  98. 5 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  99. 511 127
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  100. 53 16
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

+ 197 - 0
fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -0,0 +1,197 @@
+package com.fs.api.controller;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.statis.StatisticsRedisConstant;
+import com.fs.statis.dto.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.fs.statis.StatisticsRedisConstant.*;
+
+/**
+ * 首页-统计
+ */
+@RestController
+@RequestMapping("/index/statistics")
+public class IndexStatisticsController {
+    @Autowired
+    private RedisCache redisCache;
+    /**
+     * 分析概览
+     */
+    @PostMapping("/analysisPreview")
+    public R analysisPreview(@RequestBody AnalysisPreviewQueryDTO param){
+        AnalysisPreviewDTO analysisPreviewDTO = null;
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+
+        if(userType == null) {
+            userType = 0;
+        }
+        analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType));
+
+        return R.ok().put("data",analysisPreviewDTO);
+    }
+
+
+    /**
+     * 消费余额
+     */
+    @GetMapping("/rechargeComsumption")
+    public R rechargeComsumption(){
+        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE);
+
+        return R.ok().put("data", consumptionBalanceDataDTO);
+    }
+
+    /**
+     * 获取统计流量
+     * @return
+     */
+    @GetMapping("/trafficLog")
+    public R getTrafficLog(){
+        TrafficLogDTO trafficLogDTO = redisCache.getCacheObject(DATA_OVERVIEW_TRAFFIC_LOG);
+        return R.ok().put("data",trafficLogDTO);
+    }
+
+    /**
+     * 观看趋势
+     */
+    @PostMapping("/watchEndPlayTrend")
+    public R watchEndPlayTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        String key = String.format("%s:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType);
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 经销商会员观看
+     */
+    @PostMapping("/deaMemberTopTen")
+    public R deaMemberTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType));
+        if(deaMemberTopTenDTOS == null){
+            deaMemberTopTenDTOS = new ArrayList<>();
+        }
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 奖励金额top10
+     */
+    @PostMapping("/rewardMoneyTopTen")
+    public R rewardMoneyTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer dataType = param.getDataType();
+        Integer userType = param.getUserType();
+
+        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType));
+        return R.ok().put("data", rewardMoneyTopTenDTOS);
+    }
+
+    /**
+     * 答题红包金额趋势图
+     */
+    @PostMapping("/rewardMoneyTrend")
+    public R rewardMoneyTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType));
+        return R.ok().put("data", rewardMoneyTrendDTOS);
+    }
+
+    /**
+     * 课程观看top10
+     */
+    @PostMapping("/watchCourseTopTen")
+    public R watchCourseTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        String sort = param.getSort();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+
+        List<CourseStatsDTO> courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort));
+        return R.ok().put("data", courseStatsDTOS);
+    }
+
+    /**
+     * 数据概览
+     */
+    @GetMapping("/dealerAggregated")
+    public R dealerAggregated(){
+        DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED);
+
+        return R.ok().put("data",dealerAggregatedDTO);
+    }
+
+    /**
+     * 短信余额
+     */
+    @GetMapping("/smsBalance")
+    public R smsBalance(){
+        Long smsBalance = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE);
+
+        return R.ok().put("data", smsBalance);
+    }
+
+
+    /**
+     * 授权信息
+     */
+    @GetMapping("/authorizationInfo")
+    public R authorizationInfo(){
+        AuthorizationInfoDTO authorizationInfoDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO);
+
+        return R.ok().put("data", authorizationInfoDTO);
+    }
+
+
+    /**
+     * 当月订单数统计
+     * @return
+     */
+    @GetMapping("/thisMonthOrderCount")
+    public R thisMonthOrderCount(){
+        R result = redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT);
+        return result;
+    }
+
+    /**
+     * 当月收益统计
+     * @return
+     */
+
+    @GetMapping("/thisMonthRecvCount")
+    public R thisMonthRecvCount(){
+        R result = redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_RECV_COUNT);
+        return result;
+    }
+}

+ 6 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java

@@ -109,7 +109,12 @@ public class CompanyUserController extends BaseController
         List<CompanyUser> list = companyUserService.selectCompanyUserList(map);
         List<CompanyUser> list = companyUserService.selectCompanyUserList(map);
         return R.ok().put("data",list);
         return R.ok().put("data",list);
     }
     }
-
+    @GetMapping("/getAllUserListLimit")
+    public R getAllUserListLimit(@RequestParam(required = false) Long companyId,
+                                 @RequestParam(required = false) String keywords){
+        List<CompanyUser> list = companyUserService.getAllUserListLimit(companyId,keywords);
+        return R.ok().put("data", list);
+    }
     @GetMapping("/getUserListByDeptId")
     @GetMapping("/getUserListByDeptId")
     public R getUserListByDeptId(CompanyUser user)
     public R getUserListByDeptId(CompanyUser user)
     {
     {

+ 3 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java

@@ -1,10 +1,13 @@
 package com.fs.course.controller;
 package com.fs.course.controller;
 
 
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
+import com.fs.common.exception.CustomException;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.GetMapping;

+ 61 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -1,9 +1,16 @@
 package com.fs.course.controller;
 package com.fs.course.controller;
 
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.exception.CustomException;
 import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogListParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
+import com.fs.qw.service.IQwWatchLogService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -36,6 +43,8 @@ public class FsCourseWatchLogController extends BaseController
     @Autowired
     @Autowired
     private IFsCourseWatchLogService fsCourseWatchLogService;
     private IFsCourseWatchLogService fsCourseWatchLogService;
 
 
+    @Autowired
+    private IQwWatchLogService qwWatchLogService;
     /**
     /**
      * 查询短链课程看课记录列表
      * 查询短链课程看课记录列表
      */
      */
@@ -48,6 +57,58 @@ public class FsCourseWatchLogController extends BaseController
         return getDataTable(list);
         return getDataTable(list);
     }
     }
 
 
+    @GetMapping("/qwWatchLogAllStatisticsList")
+    public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        logger.info("会员课程数据汇总 参数: {}",param);
+
+        if(param.getCompanyId() == null){
+            throw new CustomException("必须选择公司!");
+        }
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        return qwWatchLogService.selectQwWatchLogAllStatisticsListVONew(param);
+    }
+    @GetMapping("/qwWatchLogStatisticsList")
+    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        if(param.getPageNum() == null){
+            param.setPageNum(1L);
+        }
+        if(param.getPageSize() == null){
+            param.setPageSize(10L);
+        }
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        if(param.getCompanyId() == null){
+            throw new CustomException("必须选择公司!");
+        }
+        return qwWatchLogService.selectQwWatchLogStatisticsListVONew(param);
+    }
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @GetMapping("/statisticsList")
+    public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
+    {
+        // 如果看指定用户就不用设置公司
+        if(param.getCompanyId() == null){
+            if(param.getUserId() == null) {
+                throw new CustomException("查看公司或者用户必填!");
+            }
+        }
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONew(param);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONewCount(param));
+        return rspData;
+    }
+
     /**
     /**
      * 导出短链课程看课记录列表
      * 导出短链课程看课记录列表
      */
      */

+ 142 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -0,0 +1,142 @@
+package com.fs.course.controller.qw;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.param.FsCourseWatchLogListParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseWatchLogListVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
+import com.fs.qw.service.IQwWatchLogService;
+import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 短链课程看课记录Controller
+ *
+ * @author fs
+ * @date 2024-10-24
+ */
+@RestController
+@RequestMapping("/qw/course/courseWatchLog")
+public class QwFsCourseWatchLogController extends BaseController
+{
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private IQwWatchLogService qwWatchLogService;
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseWatchLogListParam param)
+    {
+        startPage();
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/qwWatchLogAllStatisticsList")
+    public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        logger.info("企微课程数据汇总 参数:{}",param);
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @GetMapping("/statisticsList")
+    public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        return getDataTable(list);
+    }
+    @GetMapping("/qwWatchLogStatisticsList")
+    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        if(param.getCompanyId() == null){
+            throw new CustomException("必须选择公司!");
+        }
+        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+    }
+    /**
+     * 导出短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseWatchLogListParam param)
+    {
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
+        return util.exportExcel(list, "短链课程看课记录数据");
+    }
+
+    /**
+     * 获取短链课程看课记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(fsCourseWatchLogService.selectFsCourseWatchLogByLogId(logId));
+    }
+
+    /**
+     * 新增短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:add')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.insertFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 修改短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:edit')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.updateFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 删除短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:remove')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(fsCourseWatchLogService.deleteFsCourseWatchLogByLogIds(logIds));
+    }
+}

+ 47 - 0
fs-admin/src/main/java/com/fs/his/task/FsCourseTask.java

@@ -0,0 +1,47 @@
+package com.fs.his.task;
+
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.qw.service.IHyWorkTaskService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 后台统计相关 定时任务
+ */
+@Slf4j
+@Service("fsCourseTask")
+public class FsCourseTask {
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IHyWorkTaskService hyWorkTaskService;
+    /**
+     * 添加会员观看日志
+     * @throws Exception
+     */
+    public void addHyWatchLog() throws Exception
+    {
+        fsCourseWatchLogService.addCourseWatchLogDayNew();
+    }
+
+    /**
+     * 删除过期数据
+     * @throws Exception
+     */
+    public void hyWorkTask4() throws Exception
+    {
+        // 更新已完课的催课看板
+        hyWorkTaskService.delHyWorkTaskByOver();
+    }
+
+    /**
+     * 会员查看催课面板 获取看课中断和待看的先导课
+     * @throws Exception
+     */
+    public void hyWorkTask(){
+
+        hyWorkTaskService.hyWorkTask();
+    }
+
+}

+ 9 - 2
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -29,6 +29,7 @@ import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.ITencentCloudCosService;
 import com.fs.crm.param.SmsSendParam;
 import com.fs.crm.param.SmsSendParam;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.domain.ErpOrderQuery;
@@ -210,13 +211,19 @@ public class Task {
     @Autowired
     @Autowired
     private IFsCourseWatchLogService fsCourseWatchLogService;
     private IFsCourseWatchLogService fsCourseWatchLogService;
 
 
-    public void tt() throws Exception
+    @Autowired
+    ITencentCloudCosService tencentCloudCosService;
+    public void videoTranscode() throws Exception
     {
     {
 
 
-
+        tencentCloudCosService.videoTranscode();
     }
     }
 
 
+    public void updateUrl() throws Exception
+    {
 
 
+        tencentCloudCosService.updateUrl();
+    }
     public void addPrescribeImg() throws Exception
     public void addPrescribeImg() throws Exception
     {
     {
        List<Long> ids= fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();
        List<Long> ids= fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();

+ 64 - 0
fs-admin/src/main/java/com/fs/qw/FsCourseTask.java

@@ -0,0 +1,64 @@
+package com.fs.qw;
+
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.qw.service.IQwWorkTaskService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 后台统计相关 定时任务
+ */
+@Slf4j
+@Service("qwFsCourseTask")
+public class FsCourseTask {
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IQwWorkTaskService qwWorkTaskService;
+
+    /**
+     * 添加企微观看日志
+     * @throws Exception
+     */
+    public void addQwWatchLog() throws Exception
+    {
+        fsCourseWatchLogService.addCourseWatchLogDay();
+    }
+
+    /**
+     * 企微任务定时更新
+     * @throws Exception
+     */
+    public void qwWorkTask1() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByCourse4();
+        qwWorkTaskService.addQwWorkByCourseLastTime();
+    }
+    /**
+     * 企微待看课和先导课
+     * @throws Exception
+     */
+    public void qwWorkTask2() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByCourse();
+        qwWorkTaskService.addQwWorkByFirstCourse();
+    }
+    /**
+     * 用户大小转
+     * @throws Exception
+     */
+    public void qwWorkTask3() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByConversionDay();
+    }
+    /**
+     * 删除过期数据
+     * @throws Exception
+     */
+    public void qwWorkTask4() throws Exception
+    {
+        qwWorkTaskService.delQwWorkTaskByOver();
+    }
+
+}

+ 1 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -128,7 +128,7 @@ public class QwSopTempController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
+        if(qwSopTemp.getSendType() == 11){
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         }
         return toAjax(i);
         return toAjax(i);

+ 97 - 0
fs-admin/src/main/java/com/fs/transfer/CustomerTransferApprovalController.java

@@ -0,0 +1,97 @@
+package com.fs.transfer;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.CustomerTransferApproval;
+import com.fs.qw.service.ICustomerTransferApprovalService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 客户转移审批Controller
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+@RestController
+@RequestMapping("/system/approval")
+public class CustomerTransferApprovalController extends BaseController
+{
+    @Autowired
+    private ICustomerTransferApprovalService customerTransferApprovalService;
+
+    /**
+     * 查询客户转移审批列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CustomerTransferApproval customerTransferApproval)
+    {
+        startPage();
+        List<CustomerTransferApproval> list = customerTransferApprovalService.selectCustomerTransferApprovalList(customerTransferApproval);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出客户转移审批列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:export')")
+    @Log(title = "客户转移审批", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CustomerTransferApproval customerTransferApproval)
+    {
+        List<CustomerTransferApproval> list = customerTransferApprovalService.selectCustomerTransferApprovalList(customerTransferApproval);
+        ExcelUtil<CustomerTransferApproval> util = new ExcelUtil<CustomerTransferApproval>(CustomerTransferApproval.class);
+        return util.exportExcel(list, "approval");
+    }
+
+    /**
+     * 获取客户转移审批详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(customerTransferApprovalService.selectCustomerTransferApprovalById(id));
+    }
+
+    /**
+     * 新增客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:add')")
+    @Log(title = "客户转移审批", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CustomerTransferApproval customerTransferApproval)
+    {
+        return toAjax(customerTransferApprovalService.insertCustomerTransferApproval(customerTransferApproval));
+    }
+
+    /**
+     * 修改客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:edit')")
+    @Log(title = "客户转移审批", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CustomerTransferApproval customerTransferApproval)
+    {
+        return toAjax(customerTransferApprovalService.updateCustomerTransferApproval(customerTransferApproval));
+    }
+
+    /**
+     * 删除客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:remove')")
+    @Log(title = "客户转移审批", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(customerTransferApprovalService.deleteCustomerTransferApprovalByIds(ids));
+    }
+}

+ 2 - 7
fs-admin/src/main/resources/application.yml

@@ -5,10 +5,5 @@ server:
 spring:
 spring:
   profiles:
   profiles:
     active: dev
     active: dev
-    include: common,config-dev
-#    active: druid-hcl
-#    include: common,config-druid-hcl
-#    active: druid-hdt
-#    include: common,config-druid-hdt
-#    active: druid-sxjz
-#    include: common,config-druid-sxjz
+    include: common,config-myhk
+

+ 1 - 1
fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java

@@ -122,7 +122,7 @@ public class SysUser extends BaseEntity
 
 
     public static boolean isAdmin(Long userId)
     public static boolean isAdmin(Long userId)
     {
     {
-        return userId != null && (1L == userId || 145L == userId);
+        return userId != null && (1L == userId);
     }
     }
 
 
     public Long getDeptId()
     public Long getDeptId()

+ 115 - 19
fs-common/src/main/java/com/fs/common/utils/DateUtils.java

@@ -3,13 +3,16 @@ package com.fs.common.utils;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ManagementFactory;
 import java.text.ParseException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
-import java.util.*;
-
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.Calendar;
+import java.util.Date;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
 
 /**
 /**
  * 时间工具类
  * 时间工具类
- * 
+ *
 
 
  */
  */
 public class DateUtils extends org.apache.commons.lang3.time.DateUtils
 public class DateUtils extends org.apache.commons.lang3.time.DateUtils
@@ -23,15 +26,16 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
     public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
 
 
     public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
     public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
-    
+
     private static String[] parsePatterns = {
     private static String[] parsePatterns = {
-            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", 
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
             "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
             "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
 
     /**
     /**
      * 获取当前Date型日期
      * 获取当前Date型日期
-     * 
+     *
      * @return Date() 当前日期
      * @return Date() 当前日期
      */
      */
     public static Date getNowDate()
     public static Date getNowDate()
@@ -41,7 +45,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
 
 
     /**
     /**
      * 获取当前日期, 默认格式为yyyy-MM-dd
      * 获取当前日期, 默认格式为yyyy-MM-dd
-     * 
+     *
      * @return String
      * @return String
      */
      */
     public static String getDate()
     public static String getDate()
@@ -122,7 +126,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
             return null;
             return null;
         }
         }
     }
     }
-    
+
     /**
     /**
      * 获取服务器启动时间
      * 获取服务器启动时间
      */
      */
@@ -153,20 +157,112 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         // long sec = diff % nd % nh % nm / ns;
         // long sec = diff % nd % nh % nm / ns;
         return day + "天" + hour + "小时" + min + "分钟";
         return day + "天" + hour + "小时" + min + "分钟";
     }
     }
+    public static int getAge(Date birthDay) {
+        Calendar cal = Calendar.getInstance();
+        //出生日期晚于当前时间,无法计算
+        if (cal.before(birthDay)) {
+            throw new IllegalArgumentException(
+                    "The birthDay is before Now.It's unbelievable!");
+        }
+        //当前年份
+        int yearNow = cal.get(Calendar.YEAR);
+        //当前月份
+        int monthNow = cal.get(Calendar.MONTH);
+        //当前日期
+        int dayOfMonthNow = cal.get(Calendar.DAY_OF_MONTH);
+        cal.setTime(birthDay);
+        int yearBirth = cal.get(Calendar.YEAR);
+        int monthBirth = cal.get(Calendar.MONTH);
+        int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
+        //计算整岁数
+        int age = yearNow - yearBirth;
+        if (monthNow <= monthBirth) {
+            if (monthNow == monthBirth) {
+                if (dayOfMonthNow < dayOfMonthBirth) {
+                    //当前日期在生日之前,年龄减一
+                    age--;
+                }
+            } else {
+                //当前月份在生日之前,年龄减一
+                age--;
 
 
+            }
+        }
+        return age;
+    }
+    /**
+     * 计算给定日期与当前日期相差的天数 (基于日历日期)。
+     * 例如:昨天 23:59 和 今天 00:01 相差 1 天。
+     *
+     * @param createTime 起始时间 (java.util.Date)
+     * @return 相差的天数 (long)。如果 createTime 为 null,返回 0。
+     *         如果 createTime 在当前时间之后,结果为负数。
+     */
+    public static long getDaysDifferenceFromNow(Date createTime) {
+        if (createTime == null) {
+            return 0; // 或者根据业务抛出异常或返回特定值
+        }
+        // 1. 将 java.util.Date 转换为 Instant (时间线上的一个点)
+        Instant createInstant = createTime.toInstant();
+        // 2. 获取当前时间的 Instant
+        Instant nowInstant = Instant.now();
+        // 3. 指定时区 (非常重要!否则会使用系统默认时区,可能导致不一致)
+        //    建议使用一个明确的时区,例如服务器所在时区或业务标准时区
+        ZoneId zoneId = ZoneId.systemDefault(); // 或者 ZoneId.of("Asia/Shanghai");
+        // 4. 将 Instant 转换为 LocalDate (只包含年月日,忽略时分秒)
+        LocalDate createDate = createInstant.atZone(zoneId).toLocalDate();
+        LocalDate nowDate = nowInstant.atZone(zoneId).toLocalDate(); // 使用相同的时区
+        // 5. 使用 ChronoUnit.DAYS.between 计算两个 LocalDate 之间的天数差
+        long daysBetween = ChronoUnit.DAYS.between(createDate, nowDate);
+        return daysBetween;
+    }
 
 
-    public static List<String> getDayListOfMonth(Date date) {
-        List<String> list = new ArrayList();
-        Calendar aCalendar = Calendar.getInstance(Locale.CHINA);
-        aCalendar.setTime(date);
-        int year = aCalendar.get(Calendar.YEAR);//年份
-        int month = aCalendar.get(Calendar.MONTH) + 1;//月份
-        int day = aCalendar.getActualMaximum(Calendar.DATE);
-        for (int i = 1; i <= day; i++) {
-            String aDate = String.valueOf(year) + "-" + month + "-" + i;
-            list.add(aDate);
+    /**
+     * 获取指定日期当天的开始时间字符串 (格式: yyyy-MM-dd 00:00:00)
+     *
+     * @param date 输入的 Date 对象
+     * @return 当天开始时间的字符串表示,如果输入为 null 则返回 null
+     */
+    public static String getStartOfDayString(Date date) {
+        // 使用 Objects.requireNonNull(date, "输入日期不能为 null"); 如果希望在输入为 null 时抛出异常
+        if (date == null) {
+            return null;
         }
         }
-        return  list;
+        // 1. 将 Date 转换为更现代的 LocalDateTime (考虑时区)
+        //    使用系统默认时区。如果需要特定时区,请替换 ZoneId.systemDefault()
+        //    例如:ZoneId.of("Asia/Shanghai")
+        LocalDateTime localDateTime = date.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDateTime();
+        // 2. 获取该日期的开始时间 (00:00:00)
+        LocalDateTime startOfDay = localDateTime.toLocalDate().atStartOfDay();
+        // 3. 格式化为目标字符串
+        return startOfDay.format(OUTPUT_FORMATTER);
     }
     }
+    /**
+     * 获取指定日期当天的结束时间字符串 (格式: yyyy-MM-dd 23:59:59)
+     *
+     * @param date 输入的 Date 对象
+     * @return 当天结束时间的字符串表示,如果输入为 null 则返回 null
+     */
+    public static String getEndOfDayString(Date date) {
+        if (date == null) {
+            return null;
+        }
+        // 1. 将 Date 转换为 LocalDateTime (考虑时区)
+        LocalDateTime localDateTime = date.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDateTime();
+        // 2. 获取该日期的 LocalDate 部分
+        LocalDate localDate = localDateTime.toLocalDate();
+        // 3. 创建当天的结束时间 (23:59:59)
+        LocalDateTime endOfDay = LocalDateTime.of(localDate, LocalTime.of(23, 59, 59));
+        // 注意: 如果需要的是一天的最后一纳秒 (23:59:59.999999999),可以使用:
+        // LocalDateTime endOfDay = LocalDateTime.of(localDate, LocalTime.MAX);
+        // 但根据 "23:59:59" 的要求,明确指定秒更符合。
+        // 4. 格式化为目标字符串
+        return endOfDay.format(OUTPUT_FORMATTER);
+    }
+
 
 
 }
 }

+ 240 - 0
fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java

@@ -0,0 +1,240 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.statis.StatisticsRedisConstant;
+import com.fs.statis.dto.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.fs.statis.StatisticsRedisConstant.*;
+
+/**
+ * 首页-统计
+ */
+@RestController
+@RequestMapping("/index/statistics")
+public class IndexStatisticsController {
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 分析概览
+     */
+    @PostMapping("/analysisPreview")
+    public R analysisPreview(@RequestBody AnalysisPreviewQueryDTO param){
+        AnalysisPreviewDTO analysisPreviewDTO = null;
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+
+        if(userType == null) {
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,param.getCompanyId()));
+
+        return R.ok().put("data",analysisPreviewDTO);
+    }
+
+
+    /**
+     * 消费余额
+     */
+    @GetMapping("/rechargeComsumption")
+    public R rechargeComsumption(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE,companyId));
+        return R.ok().put("data", consumptionBalanceDataDTO);
+    }
+
+    /**
+     * 获取统计流量
+     * @return
+     */
+    @GetMapping("/trafficLog")
+    public R getTrafficLog(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        TrafficLogDTO trafficLogDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_TRAFFIC_LOG,companyId));
+        return R.ok().put("data",trafficLogDTO);
+    }
+
+    /**
+     * 观看趋势
+     */
+    @PostMapping("/watchEndPlayTrend")
+    public R watchEndPlayTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 经销商会员观看
+     */
+    @PostMapping("/deaMemberTopTen")
+    public R deaMemberTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,param.getCompanyId()));
+        if(deaMemberTopTenDTOS == null){
+            deaMemberTopTenDTOS = new ArrayList<>();
+        }
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 奖励金额top10
+     */
+    @PostMapping("/rewardMoneyTopTen")
+    public R rewardMoneyTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer dataType = param.getDataType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId()));
+        return R.ok().put("data", rewardMoneyTopTenDTOS);
+    }
+
+    /**
+     * 答题红包金额趋势图
+     */
+    @PostMapping("/rewardMoneyTrend")
+    public R rewardMoneyTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId()));
+        return R.ok().put("data", rewardMoneyTrendDTOS);
+    }
+
+    /**
+     * 课程观看top10
+     */
+    @PostMapping("/watchCourseTopTen")
+    public R watchCourseTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        String sort = param.getSort();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<CourseStatsDTO> courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
+        return R.ok().put("data", courseStatsDTOS);
+    }
+
+    /**
+     * 数据概览
+     */
+    @GetMapping("/dealerAggregated")
+    public R dealerAggregated(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED,companyId));
+
+        return R.ok().put("data",dealerAggregatedDTO);
+    }
+
+    /**
+     * 短信余额
+     */
+    @GetMapping("/smsBalance")
+    public R smsBalance(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        Long smsBalance = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE,companyId));
+
+        return R.ok().put("data", smsBalance);
+    }
+
+
+    /**
+     * 授权信息
+     */
+    @GetMapping("/authorizationInfo")
+    public R authorizationInfo(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        AuthorizationInfoDTO authorizationInfoDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO,companyId));
+
+        return R.ok().put("data", authorizationInfoDTO);
+    }
+
+
+    /**
+     * 当月订单数统计
+     * @return
+     */
+    @GetMapping("/thisMonthOrderCount")
+    public R thisMonthOrderCount(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        R result = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT,companyId));
+        return result;
+    }
+
+    /**
+     * 当月收益统计
+     * @return
+     */
+    @GetMapping("/thisMonthRecvCount")
+    public R thisMonthRecvCount(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        R result = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.THIS_MONTH_RECV_COUNT,companyId));
+        return result;
+    }
+}

+ 4 - 12
fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java

@@ -88,54 +88,46 @@ public class FsCourseWatchLogController extends BaseController
     @GetMapping("/qwWatchLogStatisticsList")
     @GetMapping("/qwWatchLogStatisticsList")
     public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
     public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
     {
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         if (param.getSTime()==null||param.getETime()==null){
         if (param.getSTime()==null||param.getETime()==null){
             return getDataTable(new ArrayList<>());
             return getDataTable(new ArrayList<>());
         }
         }
-        List<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogStatisticsListVONew(param);
     }
     }
     @GetMapping("/myQwWatchLogStatisticsList")
     @GetMapping("/myQwWatchLogStatisticsList")
     public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
     public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
     {
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
         if (param.getSTime()==null||param.getETime()==null){
         if (param.getSTime()==null||param.getETime()==null){
             return getDataTable(new ArrayList<>());
             return getDataTable(new ArrayList<>());
         }
         }
-        List<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogStatisticsListVONew(param);
     }
     }
 
 
 
 
     @GetMapping("/qwWatchLogAllStatisticsList")
     @GetMapping("/qwWatchLogAllStatisticsList")
     public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     {
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         if (param.getSTime()==null||param.getETime()==null){
         if (param.getSTime()==null||param.getETime()==null){
             return getDataTable(new ArrayList<>());
             return getDataTable(new ArrayList<>());
         }
         }
-        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogAllStatisticsListVONew(param);
     }
     }
     @GetMapping("/myQwWatchLogAllStatisticsList")
     @GetMapping("/myQwWatchLogAllStatisticsList")
     public TableDataInfo myQwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     public TableDataInfo myQwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     {
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
         if (param.getSTime()==null||param.getETime()==null){
         if (param.getSTime()==null||param.getETime()==null){
             return getDataTable(new ArrayList<>());
             return getDataTable(new ArrayList<>());
         }
         }
-        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogAllStatisticsListVONew(param);
     }
     }
     @GetMapping("/watchLogStatistics")
     @GetMapping("/watchLogStatistics")
     public TableDataInfo watchLogStatistics(FsCourseOverParam param)
     public TableDataInfo watchLogStatistics(FsCourseOverParam param)

+ 153 - 0
fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseAnswerLogsController.java

@@ -0,0 +1,153 @@
+package com.fs.company.controller.course.qw;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.param.FsCourseAnswerLogsParam;
+import com.fs.course.service.IFsCourseAnswerLogsService;
+import com.fs.course.vo.FsCourseAnswerLogsListVO;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static com.fs.his.utils.PhoneUtil.encryptPhone;
+
+/**
+ * 答题日志Controller
+ *
+ * @author fs
+ * @date 2024-10-26
+ */
+@RestController
+@RequestMapping("/qw/course/courseAnswerLog")
+public class FsQwCourseAnswerLogsController extends BaseController
+{
+    @Autowired
+    private IFsCourseAnswerLogsService fsCourseAnswerLogsService;
+
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 查询答题日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseAnswerLogsParam param)
+    {
+        startPage();
+        if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
+            param.setPhone(encryptPhone(param.getPhoneMk()));
+        }
+
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询答题日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:myList')")
+    @GetMapping("/myList")
+    public TableDataInfo myList(FsCourseAnswerLogsParam param)
+    {
+        startPage();
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+
+        if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
+            param.setPhone(encryptPhone(param.getPhoneMk()));
+        }
+
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        return getDataTable(list);
+    }
+    /**
+     * 导出答题日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:export')")
+    @Log(title = "答题日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseAnswerLogsParam param)
+    {
+
+        if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
+            param.setPhone(encryptPhone(param.getPhoneMk()));
+        }
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        ExcelUtil<FsCourseAnswerLogsListVO> util = new ExcelUtil<FsCourseAnswerLogsListVO>(FsCourseAnswerLogsListVO.class);
+        return util.exportExcel(list, "答题日志数据");
+    }
+
+    /**
+     * 导出答题日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:myExport')")
+    @Log(title = "答题日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(FsCourseAnswerLogsParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+
+        if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
+            param.setPhone(encryptPhone(param.getPhoneMk()));
+        }
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        ExcelUtil<FsCourseAnswerLogsListVO> util = new ExcelUtil<FsCourseAnswerLogsListVO>(FsCourseAnswerLogsListVO.class);
+        return util.exportExcel(list, "答题日志数据");
+    }
+
+//    /**
+//     * 获取答题日志详细信息
+//     */
+//    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:query')")
+//    @GetMapping(value = "/{logId}")
+//    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+//    {
+//        return AjaxResult.success(fsCourseAnswerLogsService.selectFsCourseAnswerLogsByLogId(logId));
+//    }
+
+//    /**
+//     * 新增答题日志
+//     */
+//    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:add')")
+//    @Log(title = "答题日志", businessType = BusinessType.INSERT)
+//    @PostMapping
+//    public AjaxResult add(@RequestBody FsCourseAnswerLogs fsCourseAnswerLogs)
+//    {
+//        return toAjax(fsCourseAnswerLogsService.insertFsCourseAnswerLogs(fsCourseAnswerLogs));
+//    }
+//
+//    /**
+//     * 修改答题日志
+//     */
+//    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:edit')")
+//    @Log(title = "答题日志", businessType = BusinessType.UPDATE)
+//    @PutMapping
+//    public AjaxResult edit(@RequestBody FsCourseAnswerLogs fsCourseAnswerLogs)
+//    {
+//        return toAjax(fsCourseAnswerLogsService.updateFsCourseAnswerLogs(fsCourseAnswerLogs));
+//    }
+//
+//    /**
+//     * 删除答题日志
+//     */
+//    @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:remove')")
+//    @Log(title = "答题日志", businessType = BusinessType.DELETE)
+//	@DeleteMapping("/{logIds}")
+//    public AjaxResult remove(@PathVariable Long[] logIds)
+//    {
+//        return toAjax(fsCourseAnswerLogsService.deleteFsCourseAnswerLogsByLogIds(logIds));
+//    }
+}

+ 262 - 0
fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java

@@ -0,0 +1,262 @@
+package com.fs.company.controller.course.qw;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.param.FsCourseOverParam;
+import com.fs.course.param.FsCourseUserStatisticsListParam;
+import com.fs.course.param.FsCourseWatchLogListParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseOverVO;
+import com.fs.course.vo.FsCourseUserStatisticsListVO;
+import com.fs.course.vo.FsCourseWatchLogListVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
+import com.fs.qw.service.IQwWatchLogService;
+import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
+import com.fs.sop.mapper.SopUserLogsMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 短链课程看课记录Controller
+ *
+ * @author fs
+ * @date 2024-10-24
+ */
+@RestController
+@RequestMapping("/qw/course/courseWatchLog")
+public class FsQwCourseWatchLogController extends BaseController
+{
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+    @Autowired
+    private IQwWatchLogService qwWatchLogService;
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseWatchLogListParam param)
+    {
+
+//        if (param.getScheduleStartTime() != null && param.getScheduleEndTime() != null){
+//            List<String> sopUserLogsVOS = sopUserLogsMapper.selectSopUserLogsByDate(param.getScheduleStartTime(), param.getScheduleEndTime());
+//            param.setSopIds(sopUserLogsVOS);
+//            if (sopUserLogsVOS==null||sopUserLogsVOS.size()==0){
+//                return getDataTable(new ArrayList<>());
+//            }
+//        }
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @GetMapping("/statisticsList")
+    public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/qwWatchLogStatisticsList")
+    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+    }
+    @GetMapping("/myQwWatchLogStatisticsList")
+    public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        param.setCompanyUserId(loginUser.getUser().getUserId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+    }
+
+
+    @GetMapping("/qwWatchLogAllStatisticsList")
+    public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
+        return getDataTable(list);
+    }
+    @GetMapping("/myQwWatchLogAllStatisticsList")
+    public TableDataInfo myQwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        param.setCompanyUserId(loginUser.getUser().getUserId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
+        return getDataTable(list);
+    }
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
+    @GetMapping("/watchLogStatisticsExport")
+    public AjaxResult watchLogStatisticsExport(FsCourseOverParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return AjaxResult.error("请选择时间");
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        ExcelUtil<FsCourseOverVO> util = new ExcelUtil<FsCourseOverVO>(FsCourseOverVO.class);
+        return util.exportExcel(list, "完课数据");
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:userStatisticsList')")
+    @GetMapping("/userStatisticsList")
+    public TableDataInfo userStatisticsList(FsCourseUserStatisticsListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseUserStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseUserStatisticsListVO(param);
+        return getDataTable(list);
+    }
+
+
+
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:myList')")
+    @GetMapping("/myList")
+    public TableDataInfo myList(FsCourseWatchLogListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyUserId( loginUser.getUser().getUserId());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseWatchLogListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
+        return util.exportExcel(list, "短链课程看课记录数据");
+    }
+
+    /**
+     * 导出短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:myExport')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(FsCourseWatchLogListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
+        return util.exportExcel(list, "短链课程看课记录数据");
+    }
+    /**
+     * 获取短链课程看课记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(fsCourseWatchLogService.selectFsCourseWatchLogByLogId(logId));
+    }
+
+    /**
+     * 新增短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:add')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.insertFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 修改短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:edit')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.updateFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 删除短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:remove')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(fsCourseWatchLogService.deleteFsCourseWatchLogByLogIds(logIds));
+    }
+}

+ 8 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -135,11 +135,12 @@ public class QwSopTempController extends BaseController
         qwSopTemp.setCompanyId(loginUser.getCompany().getCompanyId());
         qwSopTemp.setCompanyId(loginUser.getCompany().getCompanyId());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
+        if(qwSopTemp.getSendType() == 11){
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         }
         return toAjax(i);
         return toAjax(i);
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "sop模板update", businessType = BusinessType.UPDATE)
     @Log(title = "sop模板update", businessType = BusinessType.UPDATE)
     @PostMapping("/update")
     @PostMapping("/update")
@@ -147,12 +148,14 @@ public class QwSopTempController extends BaseController
         int update = qwSopTempService.update(qwSopTemp);
         int update = qwSopTempService.update(qwSopTemp);
         return toAjax(update);
         return toAjax(update);
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "addOrUpdateSop模板规则", businessType = BusinessType.UPDATE)
     @Log(title = "addOrUpdateSop模板规则", businessType = BusinessType.UPDATE)
     @PostMapping("/addOrUpdateSetting")
     @PostMapping("/addOrUpdateSetting")
     public AjaxResult addOrUpdateSetting(@RequestBody QwSopTempDay day){
     public AjaxResult addOrUpdateSetting(@RequestBody QwSopTempDay day){
         return AjaxResult.success(qwSopTempService.addOrUpdateSetting(day));
         return AjaxResult.success(qwSopTempService.addOrUpdateSetting(day));
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "sop模板规则delRules", businessType = BusinessType.DELETE)
     @Log(title = "sop模板规则delRules", businessType = BusinessType.DELETE)
     @GetMapping("/delRules")
     @GetMapping("/delRules")
@@ -165,18 +168,21 @@ public class QwSopTempController extends BaseController
     public AjaxResult selectRulesInfo(Long id){
     public AjaxResult selectRulesInfo(Long id){
         return AjaxResult.success(qwSopTempService.selectRulesInfo(id));
         return AjaxResult.success(qwSopTempService.selectRulesInfo(id));
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PostMapping("/copyTemplate")
     @PostMapping("/copyTemplate")
     public AjaxResult copyTemplate(@RequestBody QwSopTemp qwSopTemp){
     public AjaxResult copyTemplate(@RequestBody QwSopTemp qwSopTemp){
         qwSopTempService.copyTemplate(qwSopTemp);
         qwSopTempService.copyTemplate(qwSopTemp);
         return toAjax(1);
         return toAjax(1);
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PostMapping("/sortDay")
     @PostMapping("/sortDay")
     public AjaxResult sortDay(@RequestBody List<SortDayVo> list){
     public AjaxResult sortDay(@RequestBody List<SortDayVo> list){
         qwSopTempService.sortDay(list);
         qwSopTempService.sortDay(list);
         return toAjax(1);
         return toAjax(1);
     }
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @GetMapping("/dayList")
     @GetMapping("/dayList")
     public AjaxResult dayList(String id){
     public AjaxResult dayList(String id){
@@ -187,6 +193,7 @@ public class QwSopTempController extends BaseController
     public AjaxResult redList(String id){
     public AjaxResult redList(String id){
         return AjaxResult.success(qwSopTempService.redList(id));
         return AjaxResult.success(qwSopTempService.redList(id));
     }
     }
+
     @PostMapping("/updateRedPackage")
     @PostMapping("/updateRedPackage")
     public AjaxResult updateRedPackage(@RequestBody UpdateRedVo data){
     public AjaxResult updateRedPackage(@RequestBody UpdateRedVo data){
         qwSopTempService.updateRedPackage(data.getList());
         qwSopTempService.updateRedPackage(data.getList());

+ 53 - 19
fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsInfoController.java

@@ -1,6 +1,7 @@
 package com.fs.company.controller.qw;
 package com.fs.company.controller.qw;
 
 
 
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
@@ -8,8 +9,11 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.qw.domain.QwGroupChatUser;
 import com.fs.qw.domain.QwGroupChatUser;
+import com.fs.qw.domain.QwTag;
+import com.fs.qw.mapper.QwTagMapper;
 import com.fs.qw.param.QwExternalContactVOTime;
 import com.fs.qw.param.QwExternalContactVOTime;
 import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.param.SopExternalContactInfo;
 import com.fs.qw.param.SopExternalContactInfo;
@@ -54,6 +58,8 @@ public class SopUserLogsInfoController extends BaseController
     @Autowired
     @Autowired
     private IQwTagService iQwTagService;
     private IQwTagService iQwTagService;
     @Autowired
     @Autowired
+    private QwTagMapper qwTagMapper;
+    @Autowired
     private IQwGroupChatUserService qwGroupChatUserService;
     private IQwGroupChatUserService qwGroupChatUserService;
 
 
     private static final Gson GSON = new Gson();
     private static final Gson GSON = new Gson();
@@ -66,45 +72,72 @@ public class SopUserLogsInfoController extends BaseController
     public TableDataInfo list(SopUserLogsInfo sopUserLogsInfo) {
     public TableDataInfo list(SopUserLogsInfo sopUserLogsInfo) {
         startPage();
         startPage();
         if (sopUserLogsInfo.getFilterMode() == 1) {
         if (sopUserLogsInfo.getFilterMode() == 1) {
+            startPage();
             List<SopUserLogsInfo> list = sopUserLogsInfoService.selectSopUserLogsInfoList(sopUserLogsInfo);
             List<SopUserLogsInfo> list = sopUserLogsInfoService.selectSopUserLogsInfoList(sopUserLogsInfo);
 
 
             if (!list.isEmpty()) {
             if (!list.isEmpty()) {
+                // 使用并行流提取externalId
+                List<Long> externalIdList = list.parallelStream()
+                        .map(SopUserLogsInfo::getExternalId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
 
 
-                List<Long> externalIdList = list.stream()
-                        .map(SopUserLogsInfo::getExternalId) // 提取 externalId
-                        .filter(Objects::nonNull) // 过滤掉 null 值,防止 NullPointerException
-                        .collect(Collectors.toList()); // 收集到 List
-
-                List<QwExternalContactVOTime> qwExternalContactVOTimes = iQwExternalContactService.selectQwExternalContactListVOByIds(externalIdList);
-
+                List<QwExternalContactVOTime> qwExternalContactVOTimes =
+                        iQwExternalContactService.selectQwExternalContactListVOByIds(externalIdList);
 
 
-                // 先将 qwExternalContactVOTimes 转换为 Map,key 为 id,value 为 ExternalContactInfo(包含 createTime 和 tagIds)
+                // 构建联系人信息映射
                 Map<Long, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                 Map<Long, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                         .collect(Collectors.toMap(
                         .collect(Collectors.toMap(
                                 QwExternalContactVOTime::getId,
                                 QwExternalContactVOTime::getId,
                                 item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIds(), item.getRemark())
                                 item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIds(), item.getRemark())
                         ));
                         ));
-
-                // 遍历 list,赋值 inComTime 和 tagIds
+                List<String> tagIds = qwExternalContactVOTimes.stream().map(QwExternalContactVOTime::getTagIds).filter(StringUtils::isNotEmpty).flatMap(e -> JSON.parseArray(e, String.class).stream()).collect(Collectors.toList());
+                if(!tagIds.isEmpty()){
+                    List<QwTag> tagList = qwTagMapper.selectQwTagListByTagIdsNew(tagIds);
+                    Map<String, QwTag> tagMap = PubFun.listToMapByGroupObject(tagList, QwTag::getTagId);
+                    qwExternalContactVOTimes.forEach(e -> {
+                        List<String> tagId = JSON.parseArray(e.getTagIds(), String.class);
+                        List<String> tagNameList = tagId.stream().filter(tagMap::containsKey).map(t -> tagMap.get(t).getName()).filter(StringUtils::isNotEmpty).collect(Collectors.toList());
+                        e.setTagIdsName(tagNameList);
+                    });
+                }
+                // 设置联系信息
                 list.forEach(item -> {
                 list.forEach(item -> {
-                    SopExternalContactInfo info = externalContactInfoMap.getOrDefault(item.getExternalId(), new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
+                    SopExternalContactInfo info = externalContactInfoMap.getOrDefault(
+                            item.getExternalId(),
+                            new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
                     item.setInComTime(info.getCreateTime());
                     item.setInComTime(info.getCreateTime());
                     item.setTagIds(info.getTagIds());
                     item.setTagIds(info.getTagIds());
                     item.setRemark(info.getRemark());
                     item.setRemark(info.getRemark());
                 });
                 });
-
             }
             }
 
 
-            if ((sopUserLogsInfo.getTagIds() != null && !sopUserLogsInfo.getTagIds().isEmpty())
-                    || !StringUtil.strIsNullOrEmpty(sopUserLogsInfo.getRemark())) {
+            // 优化过滤条件
+            boolean isRemarkEmpty = StringUtil.strIsNullOrEmpty(sopUserLogsInfo.getRemark());
+            Predicate<SopUserLogsInfo> tagFilter = item ->
+                    sopUserLogsInfo.getTagIds() == null ||
+                            sopUserLogsInfo.getTagIds().isEmpty() ||
+                            item.getTagIds().contains(sopUserLogsInfo.getTagIds());
+
+            Predicate<SopUserLogsInfo> remarkFilter = item ->
+                    isRemarkEmpty ||
+                            item.getRemark().contains(sopUserLogsInfo.getRemark());
+
+            if (sopUserLogsInfo.getTagIds() != null || !isRemarkEmpty) {
                 list = list.stream()
                 list = list.stream()
-                        .filter(item ->
-                                (sopUserLogsInfo.getTagIds() == null || sopUserLogsInfo.getTagIds().isEmpty() || item.getTagIds().contains(sopUserLogsInfo.getTagIds()))
-                                        && (StringUtil.strIsNullOrEmpty(sopUserLogsInfo.getRemark()) || item.getRemark().contains(sopUserLogsInfo.getRemark()))
-                        )
+                        .filter(tagFilter.and(remarkFilter))
                         .collect(Collectors.toList());
                         .collect(Collectors.toList());
             }
             }
 
 
+            // 处理标签名称
+            list.parallelStream().forEach(item -> {
+                if (item.getTagIds() != null && !item.getTagIds().equals("[]") && !item.getTagIds().equals("无标签")) {
+                    List<String> tagIds = GSON.fromJson(item.getTagIds(), new TypeToken<List<String>>() {}.getType());
+                    QwTagSearchParam param = new QwTagSearchParam();
+                    param.setTagIds(tagIds);
+                    item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
+                }
+            });
             return getDataTable(list);
             return getDataTable(list);
         } else {
         } else {
             List<QwGroupChatUser> list = qwGroupChatUserService.selectByChatId(sopUserLogsInfo);
             List<QwGroupChatUser> list = qwGroupChatUserService.selectByChatId(sopUserLogsInfo);
@@ -125,7 +158,7 @@ public class SopUserLogsInfoController extends BaseController
                 Map<String, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                 Map<String, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                         .collect(Collectors.toMap(
                         .collect(Collectors.toMap(
                                 QwExternalContactVOTime::getExternalUserId,
                                 QwExternalContactVOTime::getExternalUserId,
-                                item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIds(), item.getRemark())
+                                item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIdsName(), item.getTagIds(), item.getRemark())
                         ));
                         ));
 
 
                 // 遍历 list,赋值 inComTime 和 tagIds
                 // 遍历 list,赋值 inComTime 和 tagIds
@@ -133,6 +166,7 @@ public class SopUserLogsInfoController extends BaseController
                     SopExternalContactInfo info = externalContactInfoMap.getOrDefault(item.getUserId(), new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
                     SopExternalContactInfo info = externalContactInfoMap.getOrDefault(item.getUserId(), new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
                     item.setInComTime(info.getCreateTime());
                     item.setInComTime(info.getCreateTime());
                     item.setTagIds(info.getTagIds());
                     item.setTagIds(info.getTagIds());
+                    item.setTagNames(info.getTagNames());
                     item.setRemark(info.getRemark());
                     item.setRemark(info.getRemark());
                 });
                 });
 
 

+ 138 - 0
fs-company/src/main/java/com/fs/company/controller/qw/qw/QwQwWorkTaskController.java

@@ -0,0 +1,138 @@
+package com.fs.company.controller.qw.qw;
+
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
+import com.fs.qw.vo.QwWorkTaskListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 企微任务看板Controller
+ *
+ * @author fs
+ * @date 2025-03-25
+ */
+@RestController
+@RequestMapping("/qw/qw/QwWorkTask")
+public class QwQwWorkTaskController extends BaseController
+{
+    @Autowired
+    private IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    /**
+     * 查询企微任务看板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(ObjectUtils.isNull(qwWorkTask.getCompanyUserId())) {
+            qwWorkTask.setCompanyUserId(loginUser.getUser().getUserId());
+        }
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (qwWorkTask.getSTime()==null||qwWorkTask.getETime()==null){
+            return new TableDataInfo();
+        }
+        List<QwWorkTaskAllListVO> list = qwWorkTaskService.selectQwWorkTaskAllListVO(qwWorkTask);
+
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 导出企微任务看板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:export')")
+    @Log(title = "企微任务看板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwWorkTask qwWorkTask)
+    {
+        List<QwWorkTask> list = qwWorkTaskService.selectQwWorkTaskList(qwWorkTask);
+        ExcelUtil<QwWorkTask> util = new ExcelUtil<QwWorkTask>(QwWorkTask.class);
+        return util.exportExcel(list, "企微任务看板数据");
+    }
+
+    /**
+     * 获取企微任务看板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwWorkTaskService.selectQwWorkTaskById(id));
+    }
+
+    /**
+     * 新增企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:add')")
+    @Log(title = "企微任务看板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwWorkTask qwWorkTask)
+    {
+        return toAjax(qwWorkTaskService.insertQwWorkTask(qwWorkTask));
+    }
+
+    /**
+     * 修改企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:edit')")
+    @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwWorkTask qwWorkTask)
+    {
+        QwWorkTask task = new QwWorkTask();
+        task.setId(qwWorkTask.getId());
+        task.setRemark(qwWorkTask.getRemark());
+        task.setStatus(1);
+        task.setUpdateTime(new Date());
+        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
+    }
+
+    /**
+     * 删除企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qw:QwWorkTask:remove')")
+    @Log(title = "企微任务看板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwWorkTaskService.deleteQwWorkTaskByIds(ids));
+    }
+}

+ 104 - 0
fs-company/src/main/java/com/fs/transfer/CustomerTransferApprovalController.java

@@ -0,0 +1,104 @@
+package com.fs.transfer;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.CustomerTransferApproval;
+import com.fs.qw.service.ICustomerTransferApprovalService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 客户转移审批Controller
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+@RestController
+@RequestMapping("/system/approval")
+public class CustomerTransferApprovalController extends BaseController
+{
+    @Autowired
+    private ICustomerTransferApprovalService customerTransferApprovalService;
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询客户转移审批列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CustomerTransferApproval customerTransferApproval)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        customerTransferApproval.setInitiatorUserId(loginUser.getUser().getUserId());
+        List<CustomerTransferApproval> list = customerTransferApprovalService.selectCustomerTransferApprovalList(customerTransferApproval);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出客户转移审批列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:export')")
+    @Log(title = "客户转移审批", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CustomerTransferApproval customerTransferApproval)
+    {
+        List<CustomerTransferApproval> list = customerTransferApprovalService.selectCustomerTransferApprovalList(customerTransferApproval);
+        ExcelUtil<CustomerTransferApproval> util = new ExcelUtil<CustomerTransferApproval>(CustomerTransferApproval.class);
+        return util.exportExcel(list, "approval");
+    }
+
+    /**
+     * 获取客户转移审批详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(customerTransferApprovalService.selectCustomerTransferApprovalById(id));
+    }
+
+    /**
+     * 新增客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:add')")
+    @Log(title = "客户转移审批", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CustomerTransferApproval customerTransferApproval)
+    {
+        return toAjax(customerTransferApprovalService.insertCustomerTransferApproval(customerTransferApproval));
+    }
+
+    /**
+     * 修改客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:edit')")
+    @Log(title = "客户转移审批", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CustomerTransferApproval customerTransferApproval)
+    {
+        return toAjax(customerTransferApprovalService.updateCustomerTransferApproval(customerTransferApproval));
+    }
+
+    /**
+     * 删除客户转移审批
+     */
+    @PreAuthorize("@ss.hasPermi('system:approval:remove')")
+    @Log(title = "客户转移审批", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(customerTransferApprovalService.deleteCustomerTransferApprovalByIds(ids));
+    }
+}

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

@@ -0,0 +1,64 @@
+package com.fs.user;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.service.IFsUserService;
+import com.fs.store.param.h5.FsUserPageListParam;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Api(tags = "会员管理接口")
+@RestController
+@RequestMapping("/user/fsUser")
+@AllArgsConstructor
+public class FsUserAdminController extends BaseController {
+
+    @Autowired
+    private IFsUserService fsUserService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @PreAuthorize("@ss.hasPermi('user:fsUser:list')")
+    @PostMapping("/list")
+    @ApiOperation("会员列表(与移动端使用的相同查询)")
+    public TableDataInfo pageList(@RequestBody FsUserPageListParam param) {
+//        startPage();
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        param.setCompanyUserId(String.valueOf(loginUser.getUser().getUserId()));
+
+        if(param.getCompanyUserId() == null) {
+            throw new IllegalArgumentException("当前销售不存在!");
+        }
+        return fsUserService.selectFsUserPageListNew(param);
+    }
+
+    @PostMapping("/auditUser")
+    @ApiOperation("审核用户(移除小黑屋)")
+    public R auditUser(@RequestBody String[] userIds) {
+        Boolean r = fsUserService.disabledUser(userIds, true);
+        if (r) {
+            return R.ok();
+        }
+        return R.error();
+    }
+
+
+}

+ 0 - 6
fs-company/web/WEB-INF/web.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
-         version="4.0">
-</web-app>

+ 1 - 1
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -172,7 +172,7 @@ public class CommonController {
 
 
     @GetMapping("/testSop3")
     @GetMapping("/testSop3")
     public R testSop3(String date) throws Exception {
     public R testSop3(String date) throws Exception {
-        qwSopLogsService.createCorpMassSending(date);
+//        qwSopLogsService.createCorpMassSending(date);
 //        QwGetGroupmsgSendParam qwGetGroupmsgSendParam = new QwGetGroupmsgSendParam();
 //        QwGetGroupmsgSendParam qwGetGroupmsgSendParam = new QwGetGroupmsgSendParam();
 //        qwGetGroupmsgSendParam.setMsgid("msg7tWFCgAAjJC-HqurNKsOJif5oUHQiA");
 //        qwGetGroupmsgSendParam.setMsgid("msg7tWFCgAAjJC-HqurNKsOJif5oUHQiA");
 //        qwGetGroupmsgSendParam.setUserid("ZhangZhanYue");
 //        qwGetGroupmsgSendParam.setUserid("ZhangZhanYue");

+ 33 - 9
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -117,25 +117,48 @@ public class qwTask {
         sopLogsTaskChatService.createAiChatSopLogs(today);
         sopLogsTaskChatService.createAiChatSopLogs(today);
     }
     }
 
 
+//    /**
+//    * 定时 发送 通过调用 企业微信接口 发送的 SOP 群发消息
+//    */
+//    @Scheduled(cron = "0 15 0 * * ?")
+//    public void SendQwApiSopLogTimer(){
+//        log.info("zyp \n【企微官方接口群发开始】");
+////        qwSopLogsService.checkQwSopLogs();
+//        LocalDate localDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0).toLocalDate();
+//        String date = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//
+//        qwSopLogsService.createCorpMassSending(date);
+//    }
+//
+//    /**
+//    * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果
+//    */
+//    @Scheduled(cron = "0 0 8 * * ?")
+//    public void GetQwApiSopLogResultTimer(){
+//        qwSopLogsService.qwSopLogsResult();
+//    }
+
     /**
     /**
-    * 定时 发送 通过调用 企业微信接口 发送的 SOP 群发消息
-    */
-    @Scheduled(cron = "0 15 0 * * ?")
-    public void SendQwApiSopLogTimer(){
+     * 定时 发送 通过调用 企业微信接口 发送的 SOP 群发消息(新版-安装营期发)
+     */
+    @Scheduled(cron = "0 20 0 * * ?")
+    public void SendQwApiSopLogTimerNew(){
+
         log.info("zyp \n【企微官方接口群发开始】");
         log.info("zyp \n【企微官方接口群发开始】");
 //        qwSopLogsService.checkQwSopLogs();
 //        qwSopLogsService.checkQwSopLogs();
         LocalDate localDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0).toLocalDate();
         LocalDate localDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0).toLocalDate();
         String date = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
         String date = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
 
 
-        qwSopLogsService.createCorpMassSending(date);
+        qwSopLogsService.createCorpMassSendingByUserLogs(date);
     }
     }
 
 
+
     /**
     /**
-    * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果
-    */
+     * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果(新版-安装营期发)
+     */
     @Scheduled(cron = "0 0 8 * * ?")
     @Scheduled(cron = "0 0 8 * * ?")
-    public void GetQwApiSopLogResultTimer(){
-        qwSopLogsService.qwSopLogsResult();
+    public void GetQwApiSopLogResultTimerNew(){
+        qwSopLogsService.qwSopLogsResultNew();
     }
     }
 
 
     /**
     /**
@@ -206,6 +229,7 @@ public class qwTask {
         }
         }
     }
     }
 
 
+
     // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
     // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
     private void processAndInsertQwSopLogs(List<QwSopLogsDoSendListTVO> logsByJsApiNotExtId) {
     private void processAndInsertQwSopLogs(List<QwSopLogsDoSendListTVO> logsByJsApiNotExtId) {
         // 定义批量插入的大小
         // 定义批量插入的大小

+ 200 - 87
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -17,12 +17,10 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsCourseLinkService;
-import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwGroupChat;
-import com.fs.qw.domain.QwGroupChatUser;
-import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwGroupChatService;
 import com.fs.qw.service.IQwGroupChatService;
 import com.fs.qw.service.IQwGroupChatUserService;
 import com.fs.qw.service.IQwGroupChatUserService;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
@@ -163,6 +161,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     @Autowired
     private ICompanyUserService companyUserService;
     private ICompanyUserService companyUserService;
 
 
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
 
 
     @PostConstruct
     @PostConstruct
     public void init() {
     public void init() {
@@ -301,45 +302,48 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
             groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
         }
         }
 
 
-//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
-//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
-        // 查询销售二级域名
-        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
-            String[] userKey = s.getUserId().split("\\|");
-            if (userKey.length < 3) {
-                return null;
-            }
-            return Long.parseLong(userKey[1]);
-        }).filter(Objects::nonNull).collect(Collectors.toSet());
-        List<CompanyUser> companyUserList;
-        if (ids.isEmpty()) {
-            companyUserList = new ArrayList<>();
-        } else {
-            companyUserList = companyUserService.selectCompanyUserByIds(ids);
-        }
-
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
-                .peek(s -> {
-                    String[] userKey = s.getUserId().split("\\|");
-                    if (userKey.length < 3) {
-                        return;
-                    }
-
-                    // 销售ID
-                    Long companyUserId = Long.parseLong(userKey[1]);
-                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
-                    if (Objects.nonNull(companyUser)) {
-                        if (StringUtils.isNotBlank(companyUser.getDomain())) {
-                            s.setDomain(companyUser.getDomain().trim());
-                        } else {
-                            s.setDomain(config.getRealLinkDomainName().trim());
-                        }
-                    } else {
-                        s.setDomain(config.getRealLinkDomainName().trim());
-                    }
-                })
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
 
 
+
+        // 查询销售二级域名
+//        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
+//            String[] userKey = s.getUserId().split("\\|");
+//            if (userKey.length < 3) {
+//                return null;
+//            }
+//            return Long.parseLong(userKey[1]);
+//        }).filter(Objects::nonNull).collect(Collectors.toSet());
+//
+//        List<CompanyUser> companyUserList;
+//        if (ids.isEmpty()) {
+//            companyUserList = new ArrayList<>();
+//        } else {
+//            companyUserList = companyUserService.selectCompanyUserByIds(ids);
+//        }
+//
+//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
+//                .peek(s -> {
+//                    String[] userKey = s.getUserId().split("\\|");
+//                    if (userKey.length < 3) {
+//                        return;
+//                    }
+//
+//                    // 销售ID
+//                    Long companyUserId = Long.parseLong(userKey[1]);
+//                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
+//                    if (Objects.nonNull(companyUser)) {
+//                        if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
+//                            s.setDomain(companyUser.getDomain().trim());
+//                        } else {
+//                            s.setDomain(config.getRealLinkDomainName().trim());
+//                        }
+//                    } else {
+//                        s.setDomain(config.getRealLinkDomainName().trim());
+//                    }
+//                })
+//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
+
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
 
 
         CountDownLatch sopGroupLatch = new CountDownLatch(sopLogsGroupedById.size());
         CountDownLatch sopGroupLatch = new CountDownLatch(sopLogsGroupedById.size());
@@ -347,7 +351,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config);
         }
         }
 
 
         // 等待所有 SOP 分组处理完成
         // 等待所有 SOP 分组处理完成
@@ -367,9 +371,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             maxAttempts = 3,
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
             backoff = @Backoff(delay = 2000)
     )
     )
-    public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
+    public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
+                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config) {
         try {
         try {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap);
+            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } finally {
         } finally {
@@ -378,7 +383,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
     }
 
 
 
 
-    private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) throws Exception {
+    private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
+            QwGroupChat> groupChatMap,CourseConfig config) throws Exception {
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
 
 
         if (ruleTimeVO == null) {
         if (ruleTimeVO == null) {
@@ -392,6 +398,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             log.error("SOP ID {} 模板不存在,相关日志已清除。", sopId);
             log.error("SOP ID {} 模板不存在,相关日志已清除。", sopId);
             return;
             return;
         }
         }
+
         ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
         ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
         ruleTimeVO.setTempGap(qwSopTemp.getGap());
         ruleTimeVO.setTempGap(qwSopTemp.getGap());
 
 
@@ -410,9 +417,16 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             return;
             return;
         }
         }
 
 
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(ruleTimeVO.getCorpId());
+
+        if (qwCompany == null ) {
+            log.error("SOP ID {} 的 公司信息为空 为空,跳过处理。", sopId);
+            return ;
+        }
+
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         for (SopUserLogsVo logVo : userLogsVos) {
         for (SopUserLogsVo logVo : userLogsVos) {
-            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap);
+            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config);
         }
         }
 
 
         // 等待所有用户日志处理完成
         // 等待所有用户日志处理完成
@@ -431,9 +445,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             maxAttempts = 3,
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
             backoff = @Backoff(delay = 2000)
     )
     )
-    public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings, CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
+    public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
+                                    CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
+                                    String miniAppId,CourseConfig config) {
         try {
         try {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap);
+            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } finally {
         } finally {
@@ -442,8 +458,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
     }
 
 
 
 
-    private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
+    private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
+                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
         try {
         try {
+
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
             LocalDate currentDate = currentTime.toLocalDate();
             LocalDate currentDate = currentTime.toLocalDate();
 
 
@@ -472,14 +490,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 return;
                 return;
             }
             }
 
 
-            String[] userKey = logVo.getUserId().split("\\|");
-            if (userKey.length < 3) {
-                log.error("用户 ID {} 格式不正确,跳过处理。", logVo.getUserId());
-                return;
-            }
-            String qwUserId = userKey[0].trim();
-            String companyUserId = userKey[1].trim();
-            String companyId = userKey[2].trim();
+//            String[] userKey = logVo.getUserId().split("\\|");
+//            if (userKey.length < 3) {
+//                log.error("用户 ID {} 格式不正确,跳过处理。", logVo.getUserId());
+//                return;
+//            }
+
+//            String qwUserId = userKey[0].trim();
+//            String companyUserId = userKey[1].trim();
+//            String companyId = userKey[2].trim();
 
 
 
 
             //获取企业微信员工的称呼//从redis里或者从库里取
             //获取企业微信员工的称呼//从redis里或者从库里取
@@ -489,6 +508,26 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 return;
                 return;
             }
             }
 
 
+            String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
+            String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
+            String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
+
+            if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
+                log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
+                return;
+            }
+
+            CompanyUser companyUser = companyUserService.selectCompanyUserByIdForRedis(Long.valueOf(companyUserId));
+            if (Objects.nonNull(companyUser)) {
+                if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
+                    logVo.setDomain(companyUser.getDomain().trim());
+                } else {
+                    logVo.setDomain(config.getRealLinkDomainName().trim());
+                }
+            } else {
+                logVo.setDomain(config.getRealLinkDomainName().trim());
+            }
+
             //寻找时间
             //寻找时间
 //            LocalDateTime currentTime = LocalDateTime.of(2024, 12, 25,23 , 40);
 //            LocalDateTime currentTime = LocalDateTime.of(2024, 12, 25,23 , 40);
 
 
@@ -582,20 +621,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             }
                             }
                         }
                         }
 
 
-                        // 获取fsUserId
-                        Set<Long> externalIds = sopUserLogsInfos.stream().map(SopUserLogsInfo::getExternalId).collect(Collectors.toSet());
-                        if (!externalIds.isEmpty()) {
-                            List<QwExternalContact> externalContactList = qwExternalContactService.list(Wrappers.<QwExternalContact>lambdaQuery().in(QwExternalContact::getId, externalIds));
-                            sopUserLogsInfos.forEach(s -> {
-                                QwExternalContact qwExternalContact = externalContactList.stream().filter(e -> Objects.equals(s.getExternalId(), e.getId())).findFirst().orElse(null);
-                                if (Objects.nonNull(qwExternalContact)) {
-                                    s.setFsUserId(qwExternalContact.getFsUserId());
-                                }
-                            });
-                        }
+                        // 获取fsUserId TODO
+//                        Set<Long> externalIds = sopUserLogsInfos.stream().map(SopUserLogsInfo::getExternalId).collect(Collectors.toSet());
+//                        if (!externalIds.isEmpty()) {
+//                            List<QwExternalContact> externalContactList = qwExternalContactService.list(Wrappers.<QwExternalContact>lambdaQuery().in(QwExternalContact::getId, externalIds));
+//                            sopUserLogsInfos.forEach(s -> {
+//                                QwExternalContact qwExternalContact = externalContactList.stream().filter(e -> Objects.equals(s.getExternalId(), e.getId())).findFirst().orElse(null);
+//                                if (Objects.nonNull(qwExternalContact)) {
+//                                    s.setFsUserId(qwExternalContact.getFsUserId());
+//                                }
+//                            });
+//                        }
 
 
 
 
-                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId, companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(), groupChatMap);
+                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
+                                companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
+                                groupChatMap, miniAppId);
 
 
                     }
                     }
                 } catch (Exception e) {
                 } catch (Exception e) {
@@ -637,7 +678,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     //消息处理
     //消息处理
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
-                                   String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName, Map<String, QwGroupChat> groupChatMap) {
+                                   String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
+                                   Map<String, QwGroupChat> groupChatMap,String miniAppId) {
         String formattedSendTime = sendTime.toInstant()
         String formattedSendTime = sendTime.toInstant()
                 .atZone(ZoneId.systemDefault())
                 .atZone(ZoneId.systemDefault())
                 .format(DATE_TIME_FORMATTER);
                 .format(DATE_TIME_FORMATTER);
@@ -674,7 +716,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     log.error("群聊创建看课记录失败!", e);
                     log.error("群聊创建看课记录失败!", e);
                 }
                 }
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true);
+                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true, miniAppId);
             } else {
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
                     groupChat.getChatUserList().forEach(user -> {
@@ -682,7 +724,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         ruleTimeVO.setRemark("客户群催课");
                         ruleTimeVO.setRemark("客户群催课");
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName, null, false);
+                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName, null, false, miniAppId);
                     });
                     });
                 }
                 }
             }
             }
@@ -695,7 +737,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     Long fsUserId = contactId.getFsUserId();
                     Long fsUserId = contactId.getFsUserId();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId);
                 } catch (Exception e) {
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
                 }
@@ -745,7 +787,23 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         sopLogs.setCorpId(logVo.getCorpId());
         sopLogs.setCorpId(logVo.getCorpId());
         sopLogs.setLogType(ruleTimeVO.getType());
         sopLogs.setLogType(ruleTimeVO.getType());
 
 
-        sopLogs.setSendType(isOfficial == 1 ? 1 : ruleTimeVO.getSendType());
+        if (isOfficial == 1) {
+
+            if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
+                sopLogs.setSendType(2);
+                sopLogs.setRemark("未绑定小程序用户,单链补发");
+                //时间设置成固定7点
+                LocalDateTime dateTime = LocalDateTime.parse(formattedSendTime, DATE_TIME_FORMATTER);
+                sopLogs.setSendTime(OUTPUT_FORMATTER.format(dateTime));
+            }else {
+                sopLogs.setSendType(1);
+            }
+
+        }else if (isOfficial==0){
+            sopLogs.setSendType(ruleTimeVO.getSendType() == 1 ? 2 : ruleTimeVO.getSendType());
+        }else{
+            sopLogs.setSendType(ruleTimeVO.getSendType());
+        }
 
 
 
 
         sopLogs.setSendStatus(3L);
         sopLogs.setSendStatus(3L);
@@ -754,11 +812,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         String[] userKey = logVo.getUserId().split("\\|");
         String[] userKey = logVo.getUserId().split("\\|");
         sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
         sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
         sopLogs.setSopId(logVo.getSopId());
         sopLogs.setSopId(logVo.getSopId());
-
+        sopLogs.setSort(Integer.valueOf(logVo.getStartTime().replaceAll("-","")));
         sopLogs.setExternalUserId(externalContactId);
         sopLogs.setExternalUserId(externalContactId);
         sopLogs.setExternalId(externalId);
         sopLogs.setExternalId(externalId);
         sopLogs.setExternalUserName(externalUserName);
         sopLogs.setExternalUserName(externalUserName);
         sopLogs.setFsUserId(fsUserId);
         sopLogs.setFsUserId(fsUserId);
+        sopLogs.setUserLogsId(logVo.getId());
 
 
         return sopLogs;
         return sopLogs;
     }
     }
@@ -766,14 +825,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       Long videoId, int type, String qwUserId,
                                       Long videoId, int type, String qwUserId,
-                                      String companyUserId, String companyId, String externalId,String welcomeText,String qwUserName, Long fsUserId, boolean isGroupChat) {
+                                      String companyUserId, String companyId, String externalId,String welcomeText,
+                                      String qwUserName, Long fsUserId, boolean isGroupChat,String miniAppId) {
         switch (type) {
         switch (type) {
             case 1:
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
                 handleNormalMessage(sopLogs, content,companyUserId);
                 break;
                 break;
             case 2:
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId, isGroupChat);
+                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId, isGroupChat, miniAppId);
                 break;
                 break;
             case 3:
             case 3:
                 handleOrderMessage(sopLogs, content);
                 handleOrderMessage(sopLogs, content);
@@ -805,7 +865,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      Long videoId, String qwUserId, String companyUserId,
                                      Long videoId, String qwUserId, String companyUserId,
-                                     String companyId, String externalId,String welcomeText,String qwUserName, Long fsUserId, boolean isGroupChat) {
+                                     String companyId, String externalId,String welcomeText,String qwUserName,
+                                     Long fsUserId, boolean isGroupChat,String miniAppId) {
         // 深拷贝 Content 对象,避免使用 JSON
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
         if (clonedContent == null) {
@@ -816,6 +877,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //
 //
 //        Integer courseType = clonedContent.getCourseType();
 //        Integer courseType = clonedContent.getCourseType();
 
 
+        String isOfficial = clonedContent.getIsOfficial();
+
         List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
         List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
         if (settings == null || settings.isEmpty()) {
         if (settings == null || settings.isEmpty()) {
             log.error("Cloned content settings are empty, skipping.");
             log.error("Cloned content settings are empty, skipping.");
@@ -848,7 +911,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         } else {
                         } else {
                             addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
                             addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
                             link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
                             link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
-                                    qwUserId, companyUserId, companyId, externalId, fsUserId);
+                                    qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
                         }
                         }
 
 
                         if (StringUtils.isNotEmpty(link)) {
                         if (StringUtils.isNotEmpty(link)) {
@@ -859,7 +922,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 if (currentValue == null) {
                                 if (currentValue == null) {
                                     setting.setValue(link);
                                     setting.setValue(link);
                                 } else {
                                 } else {
-//                                    setting.setValue(currentValue + "\n" + sortLink);
                                     setting.setValue(currentValue
                                     setting.setValue(currentValue
                                             .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                             .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                             + "\n" + link);
                                             + "\n" + link);
@@ -882,12 +944,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
                     addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
 
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
-                            qwUserId, companyUserId, companyId, externalId);
+                            qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
+
+                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                        setting.setMiniprogramAppid(miniAppId);
+                    }else {
+                        log.error("公司的小程序id为空:采用了前端传的固定值"+sopLogs.getSopId());
+                    }
 
 
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
 
 
                     try {
                     try {
-                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
+                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl())?"https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png":setting.getMiniprogramPicUrl());
                     } catch (Exception e) {
                     } catch (Exception e) {
                         log.error("赋值-小程序封面地址失败-" + e);
                         log.error("赋值-小程序封面地址失败-" + e);
                     }
                     }
@@ -933,7 +1001,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private String generateShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
     private String generateShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                      Long courseId, Long videoId, String qwUserId,
                                      Long courseId, Long videoId, String qwUserId,
-                                     String companyUserId, String companyId, String externalId, Long fsUserId) {
+                                     String companyUserId, String companyId, String externalId,String isOfficial, Long fsUserId) {
         // 获取缓存的配置
         // 获取缓存的配置
         CourseConfig config;
         CourseConfig config;
         synchronized(configLock) {
         synchronized(configLock) {
@@ -954,7 +1022,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCorpId(logVo.getCorpId());
         link.setCorpId(logVo.getCorpId());
         link.setCourseId(courseId.longValue());
         link.setCourseId(courseId.longValue());
         link.setQwExternalId(Long.parseLong(externalId));
         link.setQwExternalId(Long.parseLong(externalId));
-        link.setLinkType(0); //正常链接
+
+        if (StringUtil.strIsNullOrEmpty(isOfficial)){
+            link.setLinkType(0);
+        }else {
+            if (isOfficial.equals("1")) {
+                if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
+                    link.setLinkType(0);
+                }else {
+                    link.setLinkType(5);
+                }
+            }else if (isOfficial.equals("0")){
+                link.setLinkType(0);
+            }else{
+                link.setLinkType(0);
+            }
+        }
 
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         FsCourseRealLink courseMap = new FsCourseRealLink();
         courseMap.setCompanyId(link.getCompanyId());
         courseMap.setCompanyId(link.getCompanyId());
@@ -964,9 +1047,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         courseMap.setCorpId(link.getCorpId());
         courseMap.setCorpId(link.getCorpId());
         courseMap.setCourseId(link.getCourseId());
         courseMap.setCourseId(link.getCourseId());
         courseMap.setQwExternalId(link.getQwExternalId());
         courseMap.setQwExternalId(link.getQwExternalId());
-        courseMap.setLinkType(0);
         courseMap.setFsUserId(fsUserId);
         courseMap.setFsUserId(fsUserId);
 
 
+        if (StringUtil.strIsNullOrEmpty(isOfficial)){
+            courseMap.setLinkType(0);
+        }else {
+            if (isOfficial.equals("1")) {
+                if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
+                    courseMap.setLinkType(0);
+                }else {
+                    courseMap.setLinkType(5);
+                }
+            }else if (isOfficial.equals("0")){
+                courseMap.setLinkType(0);
+            }else{
+                courseMap.setLinkType(0);
+            }
+        }
+
+
         String courseJson = JSON.toJSONString(courseMap);
         String courseJson = JSON.toJSONString(courseMap);
         String realLinkFull = REAL_LINK_PREFIX + courseJson;
         String realLinkFull = REAL_LINK_PREFIX + courseJson;
         link.setRealLink(realLinkFull);
         link.setRealLink(realLinkFull);
@@ -1112,7 +1211,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                      Long courseId, Long videoId, String qwUserId,
                                      Long courseId, Long videoId, String qwUserId,
-                                     String companyUserId, String companyId, String externalId) {
+                                     String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId) {
         // 获取缓存的配置
         // 获取缓存的配置
         CourseConfig config;
         CourseConfig config;
         synchronized(configLock) {
         synchronized(configLock) {
@@ -1137,7 +1236,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCorpId(logVo.getCorpId());
         link.setCorpId(logVo.getCorpId());
         link.setCourseId(courseId.longValue());
         link.setCourseId(courseId.longValue());
         link.setQwExternalId(Long.parseLong(externalId));
         link.setQwExternalId(Long.parseLong(externalId));
-        link.setLinkType(3); //正常链接
+
+        if (StringUtil.strIsNullOrEmpty(isOfficial)){
+            link.setLinkType(3);
+        }else {
+            if (isOfficial.equals("1")) {
+                if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
+                    link.setLinkType(3);
+                }else {
+                    link.setLinkType(5);
+                }
+            }else if (isOfficial.equals("0")){
+                link.setLinkType(3);
+            }else{
+                link.setLinkType(3);
+            }
+        }
 
 
         String randomString = generateRandomStringWithLock();
         String randomString = generateRandomStringWithLock();
         if (StringUtil.strIsNullOrEmpty(randomString)){
         if (StringUtil.strIsNullOrEmpty(randomString)){
@@ -1153,7 +1267,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
         String courseJson = JSON.toJSONString(courseMap);
         String courseJson = JSON.toJSONString(courseMap);
         String realLinkFull = miniappRealLink + courseJson;
         String realLinkFull = miniappRealLink + courseJson;
-//        String realLinkFull = config.getMiniprogramPage() + courseJson;
         link.setRealLink(realLinkFull);
         link.setRealLink(realLinkFull);
 
 
 
 

+ 0 - 3
fs-qwhook-sop/src/main/java/com/fs/app/redis/RedisKeyExpirationListener.java

@@ -41,9 +41,6 @@ public class RedisKeyExpirationListener extends KeyExpirationEventMessageListene
     @Autowired
     @Autowired
     private ConfigUtil configUtil;
     private ConfigUtil configUtil;
 
 
-    @Value("${hook.path}")
-    private String hookPath;
-
     @Autowired
     @Autowired
     private StringRedisTemplate redisTemplate;  // 使用 RedisTemplate 进行分布式锁
     private StringRedisTemplate redisTemplate;  // 使用 RedisTemplate 进行分布式锁
 
 

+ 15 - 0
fs-service/pom.xml

@@ -253,6 +253,21 @@
             <version>0.2.16</version>
             <version>0.2.16</version>
         </dependency>
         </dependency>
 
 
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+            <version>${org.mapstruct.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
+            <version>${org.mapstruct.version}</version>
+        </dependency>
+
         <dependency>
         <dependency>
             <groupId>org.mapstruct</groupId>
             <groupId>org.mapstruct</groupId>
             <artifactId>mapstruct</artifactId>
             <artifactId>mapstruct</artifactId>

+ 22 - 0
fs-service/src/main/java/com/fs/company/cache/ICompanyCacheService.java

@@ -0,0 +1,22 @@
+package com.fs.company.cache;
+
+
+import com.fs.company.domain.Company;
+
+public interface ICompanyCacheService {
+    /**
+     * 查询企业
+     *
+     * @param companyId 企业ID
+     * @return 企业
+     */
+    public Company selectCompanyById(Long companyId);
+
+    /**
+     * 查询企业名称
+     *
+     * @param companyId 企业ID
+     * @return 企业名称
+     */
+    String selectCompanyNameById(Long companyId);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/cache/ICompanyTagCacheService.java

@@ -0,0 +1,9 @@
+package com.fs.company.cache;
+
+import java.util.Map;
+
+public interface ICompanyTagCacheService {
+    String findUserTagByUserId(Long userId);
+
+    Map<Long, String> queryAllTagMap();
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/cache/ICompanyUserCacheService.java

@@ -0,0 +1,32 @@
+package com.fs.company.cache;
+
+import com.fs.company.domain.CompanyUser;
+
+import java.util.Set;
+
+;
+
+/**
+ * 物业公司管理员信息Service接口
+ *
+ * @author fs
+ * @date 2021-05-25
+ */
+public interface ICompanyUserCacheService {
+    /**
+     * 查询物业公司管理员信息
+     *
+     * @param userId 物业公司管理员信息ID
+     * @return 物业公司管理员信息
+     */
+    public CompanyUser selectCompanyUserById(Long userId);
+    public String selectCompanyUserNameUserById(Long userId);
+
+    /**
+     * 查询当前用户所有的下级销售
+     * @param companyUserId
+     * @return String
+     */
+    public Set<Long> selectUserAllCompanyUserId(Long companyUserId);
+
+}

+ 36 - 0
fs-service/src/main/java/com/fs/company/cache/impl/CompanyTagCacheServiceImpl.java

@@ -0,0 +1,36 @@
+package com.fs.company.cache.impl;
+
+import com.fs.company.cache.ICompanyTagCacheService;
+import com.fs.company.service.ICompanyTagService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class CompanyTagCacheServiceImpl implements ICompanyTagCacheService {
+
+    @Autowired
+    private ICompanyTagService companyTagService;
+    private static final Cache<Long,String> COMPANY_TAG_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(5, TimeUnit.MINUTES)
+            .build();
+
+    private static final Cache<Long,Map<Long, String>> COMPANY_USER_TAG_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(5, TimeUnit.MINUTES)
+            .build();
+    @Override
+    public String findUserTagByUserId(Long key) {
+        return COMPANY_TAG_CACHE.get(key,e-> companyTagService.findUserTagByUserId(key));
+    }
+
+    @Override
+    public Map<Long, String> queryAllTagMap() {
+        return COMPANY_USER_TAG_CACHE.get(0L, e-> companyTagService.queryAllTagMap());
+    }
+}

+ 59 - 0
fs-service/src/main/java/com/fs/company/cache/impl/CompanyUserCacheServiceImpl.java

@@ -0,0 +1,59 @@
+package com.fs.company.cache.impl;
+
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class CompanyUserCacheServiceImpl implements ICompanyUserCacheService {
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    private static final Cache<Long, CompanyUser> USER_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+
+    private static final Cache<Long,Set<Long>> COMPANY_USER_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(5, TimeUnit.MINUTES)
+            .build();
+
+    private static final Cache<Long,String> COMPANY_USER_NAME_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(5, TimeUnit.MINUTES)
+            .build();
+
+
+    @Override
+    public CompanyUser selectCompanyUserById(Long userId) {
+        return USER_CACHE.get(userId,e-> companyUserService.selectCompanyUserByUserId(userId));
+    }
+
+    @Override
+    public String selectCompanyUserNameUserById(Long userId) {
+        return COMPANY_USER_NAME_CACHE.get(userId,e-> companyUserService.selectCompanyUserNameUserById(userId));
+    }
+
+    @Override
+    public Set<Long> selectUserAllCompanyUserId(Long companyUserId) {
+        return COMPANY_USER_CACHE.get(companyUserId,e->{
+            List<Long> longs = companyUserService.selectUserAllCompanyUserId(companyUserId);
+            Set<Long> set = new HashSet<>(longs);
+            set.add(companyUserId);
+            return set;
+        });
+    }
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/cache/impl/ICompanyCacheServiceImpl.java

@@ -0,0 +1,46 @@
+package com.fs.company.cache.impl;
+
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class ICompanyCacheServiceImpl implements ICompanyCacheService {
+    @Autowired
+    private ICompanyService companyService;
+
+    private static final Cache<Long, Company> COMPANY_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+    private static final Cache<Long, String> COMPANY_NAME_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(7, TimeUnit.DAYS)
+            .build();
+    @Override
+    public Company selectCompanyById(Long companyId) {
+        return COMPANY_CACHE.get(companyId,e-> companyService.selectCompanyById(companyId));
+    }
+
+    @Override
+    public String selectCompanyNameById(Long companyId) {
+        if(companyId == null){
+            return "未找到";
+        }
+        return COMPANY_NAME_CACHE.get(companyId, e-> {
+            Company company = companyService.selectCompanyById(companyId);
+            if(company == null){
+                return "";
+            }
+            return String.format("%s_%s",company.getCompanyName(),company.getCompanyId());
+        });
+    }
+}

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

@@ -233,4 +233,19 @@ public interface CompanyUserMapper
     String selectDomainByUserId(Long userId);
     String selectDomainByUserId(Long userId);
 
 
     List<CompanyUser> selectAllCompanyUserAndSelf(@Param("userId") Long userId);
     List<CompanyUser> selectAllCompanyUserAndSelf(@Param("userId") Long userId);
+
+    @Select("select * from company_user where company_id=#{companyId} and del_flag=0")
+    List<CompanyUser> selectCompanyUserByCompanyId(Long companyId);
+
+    @Select("select * from company_user where user_id=#{userId}")
+    CompanyUser selectCompanyUserByUserId(Long userId);
+
+    @Select("select concat(nick_name,'_',user_name) from company_user where user_id=${userId} limit 1")
+    String selectCompanyUserNameUserById(Long userId);
+
+    @Select("select distinct user_id from company_user where parent_id=${companyUserId}")
+    List<Long> selectUserAllCompanyUserId(@Param("companyUserId") Long companyUserId);
+
+    List<CompanyUser> getAllUserListLimit(@Param("companyId") Long companyId,
+                                          @Param("keywords") String keywords);
 }
 }

+ 10 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -32,6 +32,8 @@ public interface ICompanyUserService {
      */
      */
     public CompanyUser selectCompanyUserById(Long userId);
     public CompanyUser selectCompanyUserById(Long userId);
 
 
+    public CompanyUser selectCompanyUserByIdForRedis(Long userId);
+
     /**
     /**
      * 查询物业公司管理员信息列表
      * 查询物业公司管理员信息列表
      *
      *
@@ -134,4 +136,12 @@ public interface ICompanyUserService {
      * @return  list
      * @return  list
      */
      */
     List<CompanyUser> selectCompanyUserByIds(Set<Long> ids);
     List<CompanyUser> selectCompanyUserByIds(Set<Long> ids);
+
+    CompanyUser selectCompanyUserByUserId(Long userId);
+
+    String selectCompanyUserNameUserById(Long userId);
+
+    List<Long> selectUserAllCompanyUserId(Long companyUserId);
+
+    List<CompanyUser> getAllUserListLimit(Long companyId, String keywords);
 }
 }

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

@@ -1,7 +1,9 @@
 package com.fs.company.service.impl;
 package com.fs.company.service.impl;
 
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.domain.*;
@@ -18,6 +20,7 @@ import com.fs.his.vo.CitysAreaVO;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import com.fs.wxUser.domain.CompanyWxUser;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
@@ -26,6 +29,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
 import java.util.*;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -57,6 +61,10 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
     @Autowired
     @Autowired
     public IFsCityService iFsCityService;
     public IFsCityService iFsCityService;
+
+    @Autowired
+    private RedisCache redisCache;
+
     /**
     /**
      * 查询物业公司管理员信息
      * 查询物业公司管理员信息
      *
      *
@@ -69,6 +77,20 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         return companyUserMapper.selectCompanyUserById(userId);
         return companyUserMapper.selectCompanyUserById(userId);
     }
     }
 
 
+    @Override
+    public CompanyUser selectCompanyUserByIdForRedis(Long userId) {
+
+        String key =(String)redisCache.getCacheObject("companyUserInfo:"+userId);
+        if (!StringUtil.strIsNullOrEmpty(key)){
+            return JSON.parseObject(key, CompanyUser.class);
+        }
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(userId);
+
+        redisCache.setCacheObject("companyUserInfo:"+userId ,JSON.toJSONString(companyUser),45, TimeUnit.MINUTES);
+
+        return companyUser;
+    }
+
     /**
     /**
      * 查询物业公司管理员信息列表
      * 查询物业公司管理员信息列表
      *
      *
@@ -401,4 +423,24 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<CompanyUser> selectCompanyUserByIds(Set<Long> ids) {
     public List<CompanyUser> selectCompanyUserByIds(Set<Long> ids) {
         return companyUserMapper.selectCompanyUserByIds(ids);
         return companyUserMapper.selectCompanyUserByIds(ids);
     }
     }
+
+    @Override
+    public CompanyUser selectCompanyUserByUserId(Long userId) {
+        return companyUserMapper.selectCompanyUserByUserId(userId);
+    }
+
+    @Override
+    public String selectCompanyUserNameUserById(Long userId) {
+        return companyUserMapper.selectCompanyUserNameUserById(userId);
+    }
+
+    @Override
+    public List<Long> selectUserAllCompanyUserId(Long companyUserId) {
+        return companyUserMapper.selectUserAllCompanyUserId(companyUserId);
+    }
+
+    @Override
+    public List<CompanyUser> getAllUserListLimit(Long companyId, String keywords) {
+        return companyUserMapper.getAllUserListLimit(companyId,keywords);
+    }
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseDomainName.java

@@ -22,6 +22,10 @@ public class FsCourseDomainName extends BaseEntity
     @Excel(name = "域名")
     @Excel(name = "域名")
     private String domainName;
     private String domainName;
 
 
+    private Long companyId;
+
+    private Long companyUserId;
+
     /** 状态 0 停用  1正常 */
     /** 状态 0 停用  1正常 */
     @Excel(name = "状态 0 停用  1正常")
     @Excel(name = "状态 0 停用  1正常")
     private Long status;
     private Long status;

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

@@ -22,6 +22,12 @@ public interface FsCourseLinkMapper
      */
      */
     public FsCourseLink selectFsCourseLinkByLinkId(Long linkId);
     public FsCourseLink selectFsCourseLinkByLinkId(Long linkId);
 
 
+
+    @Select("select * from fs_course_link where qw_user_id = #{qwUserId} and video_id = #{videoId} and qw_external_id = #{qwExternalId} " +
+            "and link_type=5 order by create_time desc limit 1")
+    public FsCourseLink selectExpireLinkByQwExternalId(@Param("qwUserId") String qwUserId ,@Param("videoId") Long videoId,@Param("qwExternalId") Long  qwExternalId);
+
+
     /**
     /**
      * 查询短链列表
      * 查询短链列表
      *
      *

+ 13 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -85,4 +85,17 @@ public interface FsCourseTrafficLogMapper
     FsCourseTrafficLog selectFsCourseTrafficLogByuuId(@Param("uuId") String uuId);
     FsCourseTrafficLog selectFsCourseTrafficLogByuuId(@Param("uuId") String uuId);
 
 
     void insertOrUpdateTrafficLog(FsCourseTrafficLog trafficLog);
     void insertOrUpdateTrafficLog(FsCourseTrafficLog trafficLog);
+
+    Long getTodayTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    Long getYesterdayTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    Long getMonthTrafficLogCompanyId(@Param("companyId") Long companyId);
+
+    Long getTodayTrafficLog();
+
+    Long getYesterdayTrafficLog();
+
+    Long getMonthTrafficLog();
+
 }
 }

+ 50 - 1
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Update;
 
 
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.NotNull;
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -196,7 +197,7 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "LEFT JOIN qw_user qu on qu.id=o.qw_user_id\n" +
             "LEFT JOIN qw_user qu on qu.id=o.qw_user_id\n" +
             "LEFT JOIN fs_user_course_video v on v.video_id=o.video_id \n" +
             "LEFT JOIN fs_user_course_video v on v.video_id=o.video_id \n" +
             "LEFT JOIN fs_user_course uc on uc.course_id=v.course_id\n" +
             "LEFT JOIN fs_user_course uc on uc.course_id=v.course_id\n" +
-            "where o.company_id=#{companyId}  " +
+            "where o.company_id=#{companyId} AND send_type=2 " +
             "<if test= 'sTime != null '> " +
             "<if test= 'sTime != null '> " +
             "       and DATE(o.create_time) &gt;= DATE(#{sTime})\n" +
             "       and DATE(o.create_time) &gt;= DATE(#{sTime})\n" +
             "</if>\n" +
             "</if>\n" +
@@ -270,4 +271,52 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     FsCourseWatchLog getWatchCourseVideoByFsUser(@Param("userId") Long userId, @Param("videoId") Long videoId, @Param("companyUserId") Long companyUserId);
     FsCourseWatchLog getWatchCourseVideoByFsUser(@Param("userId") Long userId, @Param("videoId") Long videoId, @Param("companyUserId") Long companyUserId);
 
 
     FsCourseWatchLog getWatchLogByFsUser(@Param("videoId") Long videoId, @Param("fsUserId") Long fsUserId, @Param("companyUserId") Long companyUserId);
     FsCourseWatchLog getWatchLogByFsUser(@Param("videoId") Long videoId, @Param("fsUserId") Long fsUserId, @Param("companyUserId") Long companyUserId);
+
+    List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVONew(FsCourseWatchLogStatisticsListParam param);
+
+    long selectFsCourseWatchLogStatisticsListVONewCount(FsCourseWatchLogStatisticsListParam param);
+    @Select("WITH date_series AS (\n" +
+            "  SELECT DATE_SUB(CURRENT_DATE(), INTERVAL 6-n DAY) AS report_date\n" +
+            "  FROM (\n" +
+            "    SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 \n" +
+            "    UNION SELECT 4 UNION SELECT 5 UNION SELECT 6\n" +
+            "  ) days\n" +
+            "  ORDER BY n\n" +
+            "),\n" +
+            "daily_data AS (\n" +
+            "  SELECT \n" +
+            "    DATE(create_time) AS log_date,\n" +
+            "    log_type,\n" +
+            "    ROW_NUMBER() OVER (PARTITION BY DATE(create_time) ORDER BY create_time DESC) AS rn\n" +
+            "  FROM fs_course_watch_log\n" +
+            "  WHERE qw_external_contact_id = #{extId}\n" +
+            "    AND create_time >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)\n" +
+            ")\n" +
+            "SELECT \n" +
+            "  IFNULL(dd.log_type, 0) AS log_type\n" +
+            "FROM date_series ds\n" +
+            "LEFT JOIN (\n" +
+            "  SELECT log_date, log_type FROM daily_data WHERE rn = 1\n" +
+            ") dd ON ds.report_date = dd.log_date\n" +
+            "ORDER BY ds.report_date ASC  ")
+    List<Integer> selectFsCourseWatchLog7DayByExtId(Long extId);
+
+    @Select("select l.user_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id FROM fs_course_watch_log l LEFT JOIN fs_user ext ON ext.user_id =l.user_id  where l.sop_id=#{SopId} and  date(l.create_time)= CURDATE() and l.log_type =4 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 )")
+    List<FsCourseWatchLogTaskVO> selectFsCourseWatchLogByDaySopIdFsUser4(@Param("SopId")String SopId);
+
+    @Select("select l.qw_external_contact_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id,ext.last_watch_time FROM fs_course_watch_log l LEFT JOIN qw_external_contact ext ON ext.id =l.qw_external_contact_id  where l.sop_id=#{SopId} and  date(l.create_time)= CURDATE() and l.log_type =3 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 ) and ext.last_watch_time < #{lastTime} and ext.last_watch_time !=0  ")
+    List<FsCourseWatchLogTaskVO> selectFsCourseWatchLogByDaySopId3LastTime(@Param("SopId")String SopId,@Param("lastTime")Integer lastTime);
+
+    @Select("SELECT l.qw_external_contact_id,l.project,l.video_id,l.course_id,l.log_type,l.qw_user_id,l.create_time lineTime,l.company_user_id as company_user_id,l.user_id as user_id,l.company_id as company_id FROM fs_course_watch_log l" +
+            " WHERE l.send_type=1 AND DATE(l.create_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY) and l.video_id =#{videoId}")
+    List<FsQwCourseWatchLogVO> selectFsCourseBeforeMonthWatchLogByVideoId(Long videoId);
+
+    @Select("SELECT min(create_time) FROM fs_course_watch_log WHERE user_id=#{userId} limit 1")
+    Date queryFirstWatchDateLogByVideoId(Long userId);
+
+    @Select("select  * from fs_course_watch_log where user_id=#{userId} " +
+            "and video_id=#{videoId} and qw_user_id=#{qwUserId} limit 1 " )
+    FsCourseWatchLog selectFsCourseWatchLogByCourseSopIdAndVideoId(@Param("userId") Long userId,
+                                                                   @Param("videoId") Long videoId,
+                                                                   @Param("qwUserId") String qwUserId);
 }
 }

+ 79 - 0
fs-service/src/main/java/com/fs/course/mapper/HyWatchLogMapper.java

@@ -0,0 +1,79 @@
+package com.fs.course.mapper;
+
+import com.fs.course.vo.HyWatchLog;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 企微看课表数据库访问接口
+ */
+@Mapper
+public interface HyWatchLogMapper {
+
+    /**
+     * 新增记录
+     *
+     * @param hyWatchLog 记录对象
+     * @return 影响行数
+     */
+    @Insert("INSERT INTO hy_watch_log (ext_id, qw_user_id, status, day, project, create_time, line_time, fs_user_id, company_id, company_user_id, course_id, video_id) " +
+            "VALUES (#{extId}, #{qwUserId}, #{status}, #{day}, #{project}, #{createTime}, #{lineTime}, #{fsUserId}, #{companyId}, #{companyUserId}, #{courseId}, #{videoId})")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insert(HyWatchLog hyWatchLog);
+
+    /**
+     * 根据ID查询记录
+     *
+     * @param id 主键ID
+     * @return 记录对象,如果不存在则返回 null
+     */
+    @Select("SELECT id, ext_id, qw_user_id, status, day, project, create_time, line_time, fs_user_id, company_id, company_user_id, course_id, video_id " +
+            "FROM hy_watch_log WHERE id = #{id}")
+    HyWatchLog selectById(@Param("id") Long id);
+
+    /**
+     * 根据ID更新记录
+     * 注意:这里是全量更新,会更新所有字段。如果需要部分更新,请自行调整 SQL。
+     *
+     * @param hyWatchLog 包含更新信息的记录对象 (ID 必须存在)
+     * @return 影响行数
+     */
+    @Update("UPDATE hy_watch_log SET " +
+            "ext_id = #{extId}, " +
+            "qw_user_id = #{qwUserId}, " +
+            "status = #{status}, " +
+            "day = #{day}, " +
+            "project = #{project}, " +
+            "create_time = #{createTime}, " +
+            "line_time = #{lineTime}, " +
+            "fs_user_id = #{fsUserId}, " +
+            "company_id = #{companyId}, " +
+            "company_user_id = #{companyUserId}, " +
+            "course_id = #{courseId}, " +
+            "video_id = #{videoId} " +
+            "WHERE id = #{id}")
+    int updateById(HyWatchLog hyWatchLog);
+
+    /**
+     * 根据外部联系人ID查询记录列表
+     *
+     * @param extId 外部联系人ID
+     * @return 记录列表
+     */
+    @Select("SELECT id, ext_id, qw_user_id, status, day, project, create_time, line_time, fs_user_id, company_id, company_user_id, course_id, video_id " +
+            "FROM hy_watch_log WHERE ext_id = #{extId}")
+    List<HyWatchLog> selectByExtId(@Param("extId") Long extId);
+
+    /**
+     * 根据状态查询记录列表
+     *
+     * @param status 状态码
+     * @return 记录列表
+     */
+    @Select("SELECT id, ext_id, qw_user_id, status, day, project, create_time, line_time, fs_user_id, company_id, company_user_id, course_id, video_id " +
+            "FROM hy_watch_log WHERE status = #{status}")
+    List<HyWatchLog> selectByStatus(@Param("status") Integer status);
+
+    void insertHyWatchLogBatch(@Param("hyWatchLogs") List<HyWatchLog> hyWatchLogs);
+}

+ 19 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java

@@ -1,6 +1,7 @@
 package com.fs.course.param;
 package com.fs.course.param;
 
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.utils.DateUtils;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.util.Date;
 import java.util.Date;
@@ -10,6 +11,8 @@ import java.util.List;
 public class FsCourseWatchLogStatisticsListParam {
 public class FsCourseWatchLogStatisticsListParam {
 
 
     private Long companyId;
     private Long companyId;
+    private Long companyUserId;
+    private Long userId;
 
 
     private String nickName;
     private String nickName;
 
 
@@ -21,4 +24,20 @@ public class FsCourseWatchLogStatisticsListParam {
     private Date eTime;
     private Date eTime;
     @JsonFormat(pattern = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date sTime;
     private Date sTime;
+
+    private String startDate;
+    private String endDate;
+
+    public String getStartDate() {
+        return DateUtils.getStartOfDayString(sTime);
+    }
+
+    public String getEndDate() {
+        return DateUtils.getEndOfDayString(eTime);
+    }
+
+    private Long project;
+
+    private Long pageNum;
+    private Long pageSize;
 }
 }

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

@@ -103,4 +103,12 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     void addCourseWatchLogDay2();
     void addCourseWatchLogDay2();
 
 
     FsCourseWatchLog getWatchCourseVideoH5(Long videoId,String qwUserId,Long externalId);
     FsCourseWatchLog getWatchCourseVideoH5(Long videoId,String qwUserId,Long externalId);
+
+    List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVONew(FsCourseWatchLogStatisticsListParam param);
+
+    long selectFsCourseWatchLogStatisticsListVONewCount(FsCourseWatchLogStatisticsListParam param);
+
+    void addCourseWatchLogDayNew();
+
+
 }
 }

+ 7 - 0
fs-service/src/main/java/com/fs/course/service/cache/IFsUserCourseVideoCacheService.java

@@ -0,0 +1,7 @@
+package com.fs.course.service.cache;
+
+import com.fs.course.domain.FsUserCourseVideo;
+
+public interface IFsUserCourseVideoCacheService {
+    public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/course/service/cache/impl/FsUserCourseVideoCacheServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.course.service.cache.impl;
+
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@AllArgsConstructor
+@Service
+@Slf4j
+public class FsUserCourseVideoCacheServiceImpl implements IFsUserCourseVideoCacheService {
+
+    private final IFsUserCourseVideoService fsUserCourseVideoService;
+    private static final Cache<Long, FsUserCourseVideo> VIDEO_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+
+    @Override
+    public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId) {
+        return VIDEO_CACHE.get(videoId,e-> fsUserCourseVideoService.selectFsUserCourseVideoByVideoId(videoId));
+    }
+}

+ 165 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -5,25 +5,31 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyUser;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideo;
-import com.fs.course.mapper.FsCourseFinishTempMapper;
-import com.fs.course.mapper.FsCourseWatchLogMapper;
-import com.fs.course.mapper.FsUserCourseMapper;
-import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.*;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.*;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.FsSysConfig;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.Bean.MsgBean;
+import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwUser;
@@ -39,11 +45,15 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.IQwSopLogsService;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
+import com.hc.openapi.tool.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
 import java.time.Duration;
 import java.time.Duration;
@@ -87,6 +97,31 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     @Autowired
     private ConfigUtil configUtil;
     private ConfigUtil configUtil;
 
 
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+
+    @Autowired
+    private IFsUserCourseCacheService fsUserCourseCacheService;
+
+    @Autowired
+    private IFsUserCourseVideoCacheService fsUserCourseVideoCacheService;
+
+    @Autowired
+    private IFsUserCacheService fsUserCacheService;
+
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+
+    @Autowired
+    private IQwUserCacheService qwUserCacheService;
+
+    @Autowired
+    private IFsUserService fsUserService;
+
+    @Autowired
+    private HyWatchLogMapper hyWatchLogMapper;
+
     /**
     /**
      * 查询短链课程看课记录
      * 查询短链课程看课记录
      *
      *
@@ -182,6 +217,132 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId,qwUserId,externalId);
         return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId,qwUserId,externalId);
     }
     }
 
 
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVONew(FsCourseWatchLogStatisticsListParam param) {
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVONew(param);
+        for (FsCourseWatchLogStatisticsListVO item : list) {
+            // 项目名
+            if(ObjectUtils.isNotNull(item.getProject())){
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    item.setProjectName(sysCourseProject);
+                }
+            }
+            // 课程名
+            if(ObjectUtils.isNotNull(item.getCourseId())) {
+                String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(item.getCourseId());
+                if(ObjectUtils.isNotNull(courseName)){
+                    item.setCourseName(courseName);
+                }
+            }
+            // 小节名
+            if(ObjectUtils.isNotNull(item.getVideoId())) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
+                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                    item.setVideoName(fsUserCourseVideo.getTitle());
+                }
+            }
+            // 用户名
+            if(ObjectUtils.isNotNull(item.getUserId())) {
+                FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
+                if(ObjectUtils.isNotNull(fsUser)){
+                    item.setUserName(String.format("%s_%d",fsUser.getNickName(),fsUser.getUserId()));
+                }
+            }
+            // 销售名
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())) {
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setCompanyUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public long selectFsCourseWatchLogStatisticsListVONewCount(FsCourseWatchLogStatisticsListParam param) {
+        return fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVONewCount(param);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void addCourseWatchLogDayNew() {
+
+        List<FsUserCourse> courses = fsUserCourseMapper.selectFsUserCourseAllCourse();
+        for (FsUserCourse course : courses) {
+            Long project = course.getProject();
+            List<FsUserCourseVideo> fsUserCourseVideos = fsUserCourseVideoMapper.selectVideoByCourseId(course.getCourseId());
+            for (FsUserCourseVideo fsUserCourseVideo : fsUserCourseVideos) {
+                try {
+                    ArrayList<HyWatchLog> QwWatchLogs = new ArrayList<>();
+                    List<FsQwCourseWatchLogVO> watchLogs = fsCourseWatchLogMapper.selectFsCourseBeforeMonthWatchLogByVideoId(fsUserCourseVideo.getVideoId());
+
+                    for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
+
+                        // 获取进线时间
+                        FsUser fsUser = fsUserService.selectFsUserById(fsCourseWatchLog.getUserId());
+                        if (fsUser!=null){
+                            fsCourseWatchLog.setLineTime(fsUser.getCreateTime());
+                        }
+                        // 查询首次观看时间
+                        Date date = fsCourseWatchLogMapper.queryFirstWatchDateLogByVideoId(fsCourseWatchLog.getUserId());
+                        if (date!=null){
+                            fsCourseWatchLog.setFirstTime(date);
+                        }
+
+                        Date firstTime = fsCourseWatchLog.getFirstTime();
+                        Long day=1L;
+                        if (fsCourseWatchLog.getLineTime()==null){
+                            continue;
+                        }
+                        //不是第一次 看课程
+                        if (firstTime!=null){
+                            LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                            LocalDate currentDate = LocalDate.now();
+                            day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
+                        }else {
+                            //是先导课
+                            if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
+                                // 如果存在第一次记录就跳过
+                                int count = qwWatchLogMapper.selectQwWatchLogIsFirstByUserId(fsCourseWatchLog.getUserId());
+                                if (count>0){
+                                    continue;
+                                }
+                                day=0L;
+                                //不是先导课
+                            }
+                        }
+                        HyWatchLog qwWatchLog = new HyWatchLog();
+                        qwWatchLog.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                        qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
+                        if(fsCourseWatchLog.getQwUserId() != null) {
+                            qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
+                        }
+                        qwWatchLog.setDay(day);
+                        qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
+                        qwWatchLog.setProject(project);
+                        qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
+                        qwWatchLog.setCompanyId(fsCourseWatchLog.getCompanyId());
+                        qwWatchLog.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                        qwWatchLog.setFsUserId(fsCourseWatchLog.getUserId());
+                        qwWatchLog.setCourseId(fsCourseWatchLog.getCourseId());
+                        qwWatchLog.setVideoId(fsCourseWatchLog.getVideoId());
+                        QwWatchLogs.add(qwWatchLog);
+                    }
+                    if (!QwWatchLogs.isEmpty()){
+                        hyWatchLogMapper.insertHyWatchLogBatch(QwWatchLogs);
+                    }
+                }catch (Exception e){
+                    log.error("看课记录add异常:{}",course.getCourseId(),e);
+                    throw new RuntimeException(e);
+                }
+
+
+            }
+        }
+    }
+
     @Override
     @Override
     public FsCourseWatchLog getWatchCourseLogVideoBySop(Long videoId,String qwUserId,Long externalId )
     public FsCourseWatchLog getWatchCourseLogVideoBySop(Long videoId,String qwUserId,Long externalId )
     {
     {

+ 52 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -42,6 +42,7 @@ import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -86,6 +87,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private FsCourseQuestionBankMapper courseQuestionBankMapper;
     private FsCourseQuestionBankMapper courseQuestionBankMapper;
     @Autowired
     @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
     private FsCourseWatchLogMapper courseWatchLogMapper;
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+    @Autowired
+    private FsCourseLinkMapper fsCourseLinkMapper;
+
 
 
     @Autowired
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     private QwExternalContactMapper qwExternalContactMapper;
@@ -161,7 +167,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         FsUserCourseVideo courseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
         FsUserCourseVideo courseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
 
 
         BeanCopyUtils.copy(courseVideo,fsUserCourseVideoQVO);
         BeanCopyUtils.copy(courseVideo,fsUserCourseVideoQVO);
-
+        if (courseVideo.getRedPacketMoney()!=null){
+            fsUserCourseVideoQVO.setRedPacketMoney(courseVideo.getRedPacketMoney().toString());
+        }
         if (StringUtils.isNotEmpty(courseVideo.getQuestionBankId())){
         if (StringUtils.isNotEmpty(courseVideo.getQuestionBankId())){
             List<FsCourseQuestionBank> fsCourseQuestionBanks = courseQuestionBankMapper.selectFsCourseQuestionBankByIdVO(courseVideo.getQuestionBankId().split(","));
             List<FsCourseQuestionBank> fsCourseQuestionBanks = courseQuestionBankMapper.selectFsCourseQuestionBankByIdVO(courseVideo.getQuestionBankId().split(","));
             fsUserCourseVideoQVO.setQuestionBankList(fsCourseQuestionBanks);
             fsUserCourseVideoQVO.setQuestionBankList(fsCourseQuestionBanks);
@@ -427,13 +435,31 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             return R.error("链接过期");
             return R.error("链接过期");
         }
         }
 
 
+        Long qwExternalId = param.getQwExternalId();
 
 
+        FsCourseWatchLog log=new FsCourseWatchLog();
 
 
-        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(param.getQwExternalId(), param.getVideoId(),param.getQwUserId());
-        if (log==null ){
-            return addCustomerService(param.getQwUserId(),msg);
+        //如果是官方的则查真正的外部联系人id
+        if (param.getLinkType()!=null&&param.getLinkType()==5) {
+            log = courseWatchLogMapper.selectFsCourseWatchLogByCourseSopIdAndVideoId(param.getUserId(), param.getVideoId(), param.getQwUserId());
+            if (log == null) {
+                return addCustomerService(param.getQwUserId(), msg);
+            }else {
+                qwExternalId=log.getQwExternalContactId();
+            }
+
+        }else {
+            log = courseWatchLogMapper.getWatchCourseVideoByExt(qwExternalId, param.getVideoId(),param.getQwUserId());
+            if (log==null ){
+                return addCustomerService(param.getQwUserId(),msg);
+            }
         }
         }
 
 
+//        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(param.getQwExternalId(), param.getVideoId(),param.getQwUserId());
+//        if (log==null ){
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+
         //查询是否有添加客服
         //查询是否有添加客服
         QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(param.getQwExternalId());
         QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(param.getQwExternalId());
 
 
@@ -463,6 +489,16 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             }
             }
             log.setUpdateTime(new Date());
             log.setUpdateTime(new Date());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
             courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
+
+
+            if (param.getLinkType()!=null&&param.getLinkType()==5){
+                FsCourseLink fsCourseLink = fsCourseLinkMapper.selectExpireLinkByQwExternalId(param.getQwUserId(), param.getVideoId(), qwExternalId);
+                return R.error(566,"官方群发通用链接").put("courseLink",fsCourseLink);
+            }
+
             return R.ok();
             return R.ok();
 
 
         }else {
         }else {
@@ -471,6 +507,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             contact.setId(param.getQwExternalId());
             contact.setId(param.getQwExternalId());
             contact.setFsUserId(param.getUserId());
             contact.setFsUserId(param.getUserId());
             qwExternalContactMapper.updateQwExternalContact(contact);
             qwExternalContactMapper.updateQwExternalContact(contact);
+
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
+
             FsUser user = new FsUser();
             FsUser user = new FsUser();
             user.setUserId(param.getUserId());
             user.setUserId(param.getUserId());
             user.setIsAddQw(1);
             user.setIsAddQw(1);
@@ -490,6 +529,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
 
             log.setUpdateTime(new Date());
             log.setUpdateTime(new Date());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
             courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+            if (param.getLinkType()!=null&&param.getLinkType()==5){
+                FsCourseLink fsCourseLink = fsCourseLinkMapper.selectExpireLinkByQwExternalId(param.getQwUserId(), param.getVideoId(), qwExternalId);
+                return R.error(566,"官方群发通用链接").put("courseLink",fsCourseLink);
+            }
+
             return R.ok();
             return R.ok();
         }
         }
     }
     }
@@ -656,14 +701,17 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         if (param.getLinkType() != null && param.getLinkType() == 1) {
         if (param.getLinkType() != null && param.getLinkType() == 1) {
             FsCourseRedPacketLog packetLog = redPacketLogMapper.selectFsCourseRedPacketLogByTemporary(param.getVideoId(), param.getUserId());
             FsCourseRedPacketLog packetLog = redPacketLogMapper.selectFsCourseRedPacketLogByTemporary(param.getVideoId(), param.getUserId());
             if (packetLog != null) {
             if (packetLog != null) {
+                System.out.println("奖励已发放1");
                 return R.error("奖励已发放");
                 return R.error("奖励已发放");
             }
             }
         } else {
         } else {
             log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
             log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
             if (log == null) {
             if (log == null) {
+                System.out.println("无记录");
                 return R.error("无记录");
                 return R.error("无记录");
             }
             }
             if (log.getRewardType() != null) {
             if (log.getRewardType() != null) {
+                System.out.println("奖励已发放2");
                 return R.error("奖励已发放");
                 return R.error("奖励已发放");
             }
             }
         }
         }

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListVO.java

@@ -10,9 +10,15 @@ import java.util.Date;
 public class FsCourseWatchLogStatisticsListVO {
 public class FsCourseWatchLogStatisticsListVO {
 
 
 
 
+    private Integer project;
+    @Excel(name = "项目名称")
+    private String projectName;
+
+    private Long courseId;
     @Excel(name = "课程名称")
     @Excel(name = "课程名称")
     private String courseName;
     private String courseName;
 
 
+    private Long videoId;
     @Excel(name = "小节名称")
     @Excel(name = "小节名称")
     private String videoName;
     private String videoName;
 
 
@@ -27,7 +33,15 @@ public class FsCourseWatchLogStatisticsListVO {
     @Excel(name = "企业微信员工名称")
     @Excel(name = "企业微信员工名称")
     private String qwUserName;
     private String qwUserName;
 
 
+    private Long userId;
+    @Excel(name = "员工名称")
+    private String userName;
+
     @Excel(name = "创建时间")
     @Excel(name = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date createTime;
     private Date createTime;
+
+    private Long companyUserId;
+    @Excel(name = "销售名称")
+    private String companyUserName;
 }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogTaskVO.java

@@ -17,4 +17,6 @@ public class FsCourseWatchLogTaskVO {
     private Integer level;
     private Integer level;
 
 
     private Long qwUserId;
     private Long qwUserId;
+
+    private Long userId;
 }
 }

+ 21 - 2
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -29,6 +29,11 @@ public class FsUser extends BaseEntity
 
 
     /** 用户id */
     /** 用户id */
     private Long userId;
     private Long userId;
+    private String username;
+    private String password;
+    private String realName;
+    private Long birthday;
+    private String idCard;
 
 
     /** 用户昵称 */
     /** 用户昵称 */
     @Excel(name = "用户昵称")
     @Excel(name = "用户昵称")
@@ -37,10 +42,13 @@ public class FsUser extends BaseEntity
     /** 用户头像 */
     /** 用户头像 */
     @Excel(name = "用户头像")
     @Excel(name = "用户头像")
     private String avatar;
     private String avatar;
+    private String remark;
 
 
     /** 手机号码 */
     /** 手机号码 */
     @Excel(name = "手机号码")
     @Excel(name = "手机号码")
     private String phone;
     private String phone;
+    private BigDecimal nowMoney;
+    private BigDecimal brokeragePrice;
 
 
     /** 用户积分 */
     /** 用户积分 */
     @Excel(name = "用户积分")
     @Excel(name = "用户积分")
@@ -53,11 +61,17 @@ public class FsUser extends BaseEntity
     /** 推广上级用户ID */
     /** 推广上级用户ID */
     @Excel(name = "推广上级用户ID")
     @Excel(name = "推广上级用户ID")
     private String tuiUserId;
     private String tuiUserId;
+    private Date spreadTime;
 
 
     /** 推广员关联时间 */
     /** 推广员关联时间 */
     @JsonFormat(pattern = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "推广员关联时间", width = 30, dateFormat = "yyyy-MM-dd")
     @Excel(name = "推广员关联时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date tuiTime;
     private Date tuiTime;
+    private String userType;
+    private Integer isPromoter;
+    private Long payCount;
+    private Long spreadCount;
+    private String addres;
 
 
     /** 下级人数 */
     /** 下级人数 */
     @Excel(name = "下级人数")
     @Excel(name = "下级人数")
@@ -85,7 +99,12 @@ public class FsUser extends BaseEntity
 
 
     private Long companyId;
     private Long companyId;
     private Long companyUserId;
     private Long companyUserId;
-
+    private String companyUserName;
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "推线日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date registerDate;
+    @Excel(name = "推线编码")
+    private String registerCode;
     /** 最后一次登录ip */
     /** 最后一次登录ip */
     @Excel(name = "最后一次登录ip")
     @Excel(name = "最后一次登录ip")
     private String lastIp;
     private String lastIp;
@@ -100,7 +119,6 @@ public class FsUser extends BaseEntity
 
 
     private Integer integralStatus;
     private Integer integralStatus;
 
 
-    private String password;
     private Integer isBuy;
     private Integer isBuy;
 
 
     private String jpushId;
     private String jpushId;
@@ -126,6 +144,7 @@ public class FsUser extends BaseEntity
     private String source;//app来源
     private String source;//app来源
 
 
     private Integer isAddQw;//是否添加企微客服
     private Integer isAddQw;//是否添加企微客服
+    private Integer isShow;//是否展示购买以及订单状态
 
 
     private Long parentId; //邀请人id
     private Long parentId; //邀请人id
 
 

+ 20 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java

@@ -227,4 +227,24 @@ public interface FsStoreProductMapper
     FsStoreProductAttrVO selectFsStoreProductAttrVOByProdId(Long productId);
     FsStoreProductAttrVO selectFsStoreProductAttrVOByProdId(Long productId);
 
 
     FsStoreProduct selectFsStoreProductById(Long productId);
     FsStoreProduct selectFsStoreProductById(Long productId);
+    @Select({"<script> " +
+            "select count(1) from fs_store_product  " +
+            "where 1=1 " +
+            "<if test = 'type != null and  type ==1  '> " +
+            "and  DATE_FORMAT(create_time, '%Y-%m-%d')  = DATE_FORMAT(NOW(), '%Y-%m-%d') " +
+            "</if>" +
+            "<if test = 'companyIds != null and companyIds != \"\" '> " +
+            "and find_in_set(#{companyIds}, company_ids) " +
+            "</if>" +
+            "</script>"})
+    Long selectFsStoreProductCompanyCount(@Param("type") int type,@Param("companyIds") Long companyIds);
+
+    @Select({"<script> " +
+            "select count(1) from fs_store_product  " +
+            "where 1=1 " +
+            "<if test = 'type != null and  type ==1  '> " +
+            "and  DATE_FORMAT(create_time, '%Y-%m-%d')  = DATE_FORMAT(NOW(), '%Y-%m-%d') " +
+            "</if>" +
+            "</script>"})
+    Long selectFsStoreProductCount(int type);
 }
 }

+ 20 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -8,6 +8,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserVO;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -249,4 +250,23 @@ public interface FsUserMapper
     int batchUpdateFsUserByIds(@Param("ids") String[] ids, @Param("status") Integer status);
     int batchUpdateFsUserByIds(@Param("ids") String[] ids, @Param("status") Integer status);
 
 
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
+
+    void transferCompanyUser(FsUserTransferParamDTO param);
+
+    List<FsUserPageListVO> selectFsUserPageListNew(FsUserPageListParam param);
+
+    Long selectFsUserPageListCount(FsUserPageListParam param);
+
+    @Select({"<script> " +
+            "select count(1) from fs_user  " +
+            "where 1=1 " +
+            "<if test = 'type != null and  type ==1  '> " +
+            "and  DATE_FORMAT(create_time, '%Y-%m-%d')  = DATE_FORMAT(NOW(), '%Y-%m-%d') " +
+            "</if>" +
+            "<if test = 'companyId != null  '> " +
+            "and  company_id=#{companyId} " +
+            "</if>" +
+            "</script>"})
+    Long selectFsUserCount(@Param("type") int type,@Param("companyId") Long companyId);
+
 }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java

@@ -90,4 +90,8 @@ public interface IFsStoreProductService
     List<FsStoreProductAttrValueVO> selectFsStoreProductAttrValueListVO(FsProductAttrValueParam param);
     List<FsStoreProductAttrValueVO> selectFsStoreProductAttrValueListVO(FsProductAttrValueParam param);
 
 
     FsStoreProduct selectFsStoreProductById(Long productId);
     FsStoreProduct selectFsStoreProductById(Long productId);
+
+    Long selectFsStoreProductCount(int type, Long companyId);
+    Long selectFsStoreProductCount(int type);
+
 }
 }

+ 12 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserService.java

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.List;
 
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.course.param.newfs.FsUserCourseBeMemberParam;
 import com.fs.course.param.newfs.FsUserCourseBeMemberParam;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.his.domain.FsUserAddress;
@@ -12,6 +13,7 @@ import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
 import com.fs.his.vo.UserVo;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -124,4 +126,14 @@ public interface IFsUserService
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
 
 
     Boolean disabledUser(String[] ids, boolean status);
     Boolean disabledUser(String[] ids, boolean status);
+
+    FsUser selectFsUserById(Long userId);
+
+    void transfer(FsUserTransferParamDTO transferParam);
+
+    TableDataInfo selectFsUserPageListNew(FsUserPageListParam param);
+
+    Long selectFsUserCount(FsUserPageListParam param);
+    Long selectFsUserCount(int type,Long companyId);
+
 }
 }

+ 10 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java

@@ -773,5 +773,15 @@ public class FsStoreProductServiceImpl implements IFsStoreProductService
         return fsStoreProductMapper.selectFsStoreProductById(productId);
         return fsStoreProductMapper.selectFsStoreProductById(productId);
     }
     }
 
 
+    @Override
+    public Long selectFsStoreProductCount(int type, Long companyId) {
+        return fsStoreProductMapper.selectFsStoreProductCompanyCount(type,companyId);
+    }
+
+    @Override
+    public Long selectFsStoreProductCount(int type) {
+        return fsStoreProductMapper.selectFsStoreProductCount(type);
+    }
+
 
 
 }
 }

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

@@ -8,9 +8,14 @@ import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.cache.ICompanyTagCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.CompanyTag;
 import com.fs.company.domain.CompanyTag;
 import com.fs.company.domain.CompanyTagUser;
 import com.fs.company.domain.CompanyTagUser;
 import com.fs.company.mapper.CompanyTagMapper;
 import com.fs.company.mapper.CompanyTagMapper;
@@ -23,19 +28,24 @@ import com.fs.his.config.IntegralConfig;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.mapper.*;
 import com.fs.his.mapper.*;
-import com.fs.his.param.FsStoreOrderParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
 import com.fs.his.vo.UserVo;
+import com.fs.qw.cache.IQwExternalContactCacheService;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.qw.vo.QwFsUserVO;
+import com.fs.store.domain.FsUserCourseCount;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.param.h5.FsUserPageListParam;
+import com.fs.store.service.cache.IFsUserCourseCountCacheService;
 import com.fs.store.vo.h5.FsUserPageListVO;
 import com.fs.store.vo.h5.FsUserPageListVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.watch.domain.WatchUser;
 import com.fs.watch.domain.WatchUser;
 import com.fs.watch.service.WatchUserService;
 import com.fs.watch.service.WatchUserService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.http.util.Asserts;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
@@ -43,6 +53,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserService;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
@@ -79,6 +90,18 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     @Autowired
     private FsUserCompanyUserMapper fsUserCompanyUserMapper;
     private FsUserCompanyUserMapper fsUserCompanyUserMapper;
 
 
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private IFsUserCourseCountCacheService fsUserCourseCountCacheService;
+
+    @Autowired
+    private ICompanyTagCacheService companyTagCacheService;
+
+    @Autowired
+    private IQwExternalContactCacheService qwExternalContactCacheService;
+
 
 
     /**
     /**
      * 查询用户
      * 查询用户
@@ -503,4 +526,93 @@ public class FsUserServiceImpl implements IFsUserService
         return result;
         return result;
     }
     }
 
 
+    @Override
+    public FsUser selectFsUserById(Long userId) {
+        return fsUserMapper.selectFsUserById(userId);
+    }
+
+    @Override
+    @Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
+    public void transfer(FsUserTransferParamDTO param) {
+        Asserts.check(ObjectUtils.isNotNull(param.getSourceCompanyUserId()), "来源用户不能为空!");
+        Asserts.check(ObjectUtils.isNotNull(param.getTargetCompanyUserId()), "转移用户不能为空!");
+        Asserts.check(CollectionUtils.isNotEmpty(param.getUserIds()), "要转移的客户列表不能为空!");
+
+        fsUserMapper.transferCompanyUser(param);
+    }
+
+    @Override
+    public TableDataInfo selectFsUserPageListNew(FsUserPageListParam param) {
+        // 找出下级销售
+        String companyUserId = param.getCompanyUserId();
+        if(companyUserId != null) {
+            Long companyUser = Long.parseLong(companyUserId);
+            Set<Long> userIds = companyUserCacheService.selectUserAllCompanyUserId(companyUser);
+            param.setCompanyUserIds(userIds);
+        }
+
+        List<FsUserPageListVO> fsUserPageListVOS = fsUserMapper.selectFsUserPageListNew(param);
+        for (FsUserPageListVO item : fsUserPageListVOS) {
+            if(item.getCompanyUserId() != null) {
+                String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
+                if(companyUserName != null) {
+                    item.setCompanyUserNickName(companyUserName);
+                }
+            }
+            if(item.getUserId() != null) {
+                FsUserCourseCount byUserId = fsUserCourseCountCacheService.findByUserId(item.getUserId());
+                if(byUserId != null) {
+                    item.setWatchCourseCount(byUserId.getWatchCourseCount());
+                    item.setMissCourseCount(byUserId.getMissCourseCount());
+                    item.setMissCourseStatus(byUserId.getMissCourseStatus());
+                    if(StringUtils.isNotEmpty(byUserId.getPartCourseCount())){
+                        item.setPartCourseCount(Long.valueOf(byUserId.getPartCourseCount()));
+                    }
+                    item.setCourseCountStatus(byUserId.getStatus());
+                    item.setStopWatchDays(byUserId.getStopWatchDays());
+                    item.setCompleteWatchDate(byUserId.getCompleteWatchDate());
+                }
+
+                String userTagByUserId = companyTagCacheService.findUserTagByUserId(item.getUserId());
+                if(StringUtils.isNotEmpty(userTagByUserId)) {
+                    String[] split = userTagByUserId.split(",");
+                    Map<Long, String> tagMap = companyTagCacheService.queryAllTagMap();
+                    Set<String> tagNames = new HashSet<>();
+                    for (String tag : split) {
+                        if(tag != null) {
+                            Long tagL = Long.parseLong(tag);
+                            String tagName = tagMap.get(tagL);
+                            tagNames.add(tagName);
+                        }
+                    }
+                    item.setTagIds(userTagByUserId);
+                    item.setTag(String.join(",",tagNames));
+                }
+
+                // 是否宠粉
+                Integer isRepeat = qwExternalContactCacheService.selectQwIsRepeat(item.getUserId());
+                if(isRepeat != null) {
+                    item.setIsRepeat(isRepeat);
+                }
+            }
+        }
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(fsUserPageListVOS);
+
+        rspData.setTotal(this.selectFsUserCount(param));
+        return rspData;
+    }
+    @Override
+    public Long selectFsUserCount(FsUserPageListParam param) {
+        return fsUserMapper.selectFsUserPageListCount(param);
+    }
+
+    @Override
+    public Long selectFsUserCount(int type, Long companyId) {
+        return fsUserMapper.selectFsUserCount(type,companyId);
+    }
+
 }
 }

+ 19 - 0
fs-service/src/main/java/com/fs/qw/cache/IQwExternalContactCacheService.java

@@ -0,0 +1,19 @@
+package com.fs.qw.cache;
+
+public interface IQwExternalContactCacheService {
+    /**
+     * 查询企业微信客户
+     *
+     * @param id 企业微信客户主键
+     * @return 企业微信客户
+     */
+    String selectQwExternalContactById(Long id);
+
+    /**
+     * 查询是否宠粉
+     *
+     * @param id 企业微信客户主键
+     * @return 企业微信客户
+     */
+    Integer selectQwIsRepeat(Long id);
+}

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

@@ -0,0 +1,5 @@
+package com.fs.qw.cache;
+
+public interface IQwUserCacheService {
+    String queryQwUserNameByUserId(String userId);
+}

+ 49 - 0
fs-service/src/main/java/com/fs/qw/cache/impl/QwExternalContactCacheServiceImpl.java

@@ -0,0 +1,49 @@
+package com.fs.qw.cache.impl;
+
+import com.fs.qw.cache.IQwExternalContactCacheService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.service.IQwExternalContactService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class QwExternalContactCacheServiceImpl implements IQwExternalContactCacheService {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+    /**
+     * 企微用户名昵称缓存类
+     */
+    private static final Cache<Long, String> QW_EXTERNAL_CONTACT_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(12, TimeUnit.HOURS)
+            .build();
+
+    private static final Cache<Long,Integer> QW_REPEAT_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+
+
+    @Override
+    public String selectQwExternalContactById(Long id) {
+        return QW_EXTERNAL_CONTACT_CACHE.get(id,e->{
+            QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(id);
+            if(qwExternalContact != null) {
+                return qwExternalContact.getName();
+            }
+            return "-";
+        });
+    }
+
+    @Override
+    public Integer selectQwIsRepeat(Long id) {
+        return QW_REPEAT_CACHE.get(id,e->{
+            return qwExternalContactService.selectQwIsRepeat(id);
+        });
+    }
+}

+ 34 - 0
fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java

@@ -0,0 +1,34 @@
+package com.fs.qw.cache.impl;
+
+import com.fs.qw.cache.IQwUserCacheService;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwUserService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class QwUserCacheServiceImpl implements IQwUserCacheService {
+    @Autowired
+    private IQwUserService qwUserService;
+    /**
+     * 企微用户名昵称缓存类
+     */
+    private static final Cache<String, String> QW_USER_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(12, TimeUnit.HOURS)
+            .build();
+    @Override
+    public String queryQwUserNameByUserId(String userId) {
+        return QW_USER_CACHE.get(userId,e-> {
+            QwUser qwUser = qwUserService.selectQwUserByQwUserId(userId);
+            if(qwUser != null) {
+                return String.format("%s_%s",qwUser.getQwUserId(),qwUser.getQwUserName());
+            }
+            return "-";
+        });
+    }
+}

+ 99 - 0
fs-service/src/main/java/com/fs/qw/domain/CustomerTransferApproval.java

@@ -0,0 +1,99 @@
+package com.fs.qw.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.qw.vo.TransferCustomDTO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 客户转移审批对象 customer_transfer_approval
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CustomerTransferApproval extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 唯一主键 ID */
+    private Long id;
+
+    /** 企业 ID */
+    @Excel(name = "企业 ID")
+    private String corpId;
+    private String companyName;
+
+    /** 转移类型: 1=离职继承, 2=在职转接 */
+    @Excel(name = "转移类型: 1=离职继承, 2=在职转接")
+    private Integer transferType;
+    private String transferTypeText;
+
+    /** 客户 ID 列表 (JSON 数组字符串或逗号分隔) */
+    @Excel(name = "客户 ID 列表 (JSON 数组字符串或逗号分隔)")
+    private String customerIds;
+    private List<TransferCustomDTO> customerList;
+
+    /** 原负责人用户 ID (离职继承时为离职人员ID,在职转接时为转出人员ID) */
+    @Excel(name = "原负责人用户 ID (离职继承时为离职人员ID,在职转接时为转出人员ID)")
+    private Long originalUserId;
+    private String originalUserName;
+
+    /** 目标接收销售用户 ID */
+    @Excel(name = "目标接收销售用户 ID")
+    private Long targetUserId;
+    private String targetUserName;
+
+    /** 发起此转移请求的用户 ID */
+    @Excel(name = "发起此转移请求的用户 ID")
+    private Long initiatorUserId;
+    private String initiatorUserName;
+
+    /**
+     * 转移前数据
+     */
+    private String transferBefore;
+
+    /** 转移提示内容/原因 */
+    @Excel(name = "转移提示内容/原因")
+    private String content;
+
+    /** 审批状态: 0=待审批, 1=审批通过, 2=审批驳回, 3=已撤销 */
+    @Excel(name = "审批状态: 0=待审批, 1=审批通过, 2=审批驳回, 3=已撤销")
+    private Integer approvalStatus;
+    private String approvalStatusText;
+
+    /** 审批人用户 ID */
+    @Excel(name = "审批人用户 ID")
+    private Long approverUserId;
+
+    /** 审批意见/备注 */
+    @Excel(name = "审批意见/备注")
+    private String approvalRemark;
+
+    /** 审批处理时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "审批处理时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date processedAt;
+
+    /** 记录创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "记录创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdAt;
+
+    /** 记录最后更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "记录最后更新时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date updatedAt;
+
+}

+ 61 - 0
fs-service/src/main/java/com/fs/qw/domain/HyWorkTask.java

@@ -0,0 +1,61 @@
+package com.fs.qw.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 个微任务看板对象 hy_work_task
+ *
+ * @author fs
+ * @date 2025-03-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HyWorkTask extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 外部联系人id */
+    @Excel(name = "外部联系人id")
+    private Long extId;
+
+    /** 企微用户id */
+    @Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    private Long userId;
+
+    /** 类别 1先导 2 课程 3 大小转 4 转人工 */
+    @Excel(name = "类别 1先导 2 课程 3 大小转 4 转人工")
+    private Integer type;
+
+    /** 状态 0 待处理 1 已处理 3 过期 */
+    @Excel(name = "状态 0 待处理 1 已处理 3 过期")
+    private Integer status;
+
+    /** 分值 */
+    @Excel(name = "分值")
+    private Integer score;
+
+    /** sopid */
+    @Excel(name = "sopid")
+    private String sopId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long companyUserId;
+
+    private String title;
+
+    /**
+     * 营期id
+     */
+    private String userLogsId;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/qw/domain/QwGroupChatUser.java

@@ -94,6 +94,8 @@ public class QwGroupChatUser extends BaseEntity
     @TableField(exist = false)
     @TableField(exist = false)
     private String tagIds;
     private String tagIds;
     @TableField(exist = false)
     @TableField(exist = false)
+    private List<String> tagNames;
+    @TableField(exist = false)
     private String fsUserId;
     private String fsUserId;
     @TableField(exist = false)
     @TableField(exist = false)
     private List<GroupUserExternalVo> userList;
     private List<GroupUserExternalVo> userList;

+ 31 - 0
fs-service/src/main/java/com/fs/qw/dto/FsUserTransferParamDTO.java

@@ -0,0 +1,31 @@
+package com.fs.qw.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 客户转移dto
+ */
+@Data
+public class FsUserTransferParamDTO implements Serializable {
+
+    /**
+     * 来源销售id
+     */
+    private Long sourceCompanyUserId;
+    /**
+     * 目标销售id
+     */
+    private Long targetCompanyUserId;
+    /**
+     * 客户id
+     */
+    private List<Long> userIds;
+
+    /**
+     * 转移提示内容/原因
+     */
+    private String content;
+}

+ 62 - 0
fs-service/src/main/java/com/fs/qw/mapper/CustomerTransferApprovalMapper.java

@@ -0,0 +1,62 @@
+package com.fs.qw.mapper;
+
+import com.fs.qw.domain.CustomerTransferApproval;
+
+import java.util.List;
+
+/**
+ * 客户转移审批Mapper接口
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+public interface CustomerTransferApprovalMapper
+{
+    /**
+     * 查询客户转移审批
+     *
+     * @param id 客户转移审批ID
+     * @return 客户转移审批
+     */
+    public CustomerTransferApproval selectCustomerTransferApprovalById(Long id);
+
+    /**
+     * 查询客户转移审批列表
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 客户转移审批集合
+     */
+    public List<CustomerTransferApproval> selectCustomerTransferApprovalList(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 新增客户转移审批
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 结果
+     */
+    public int insertCustomerTransferApproval(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 修改客户转移审批
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 结果
+     */
+    public int updateCustomerTransferApproval(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 删除客户转移审批
+     *
+     * @param id 客户转移审批ID
+     * @return 结果
+     */
+    public int deleteCustomerTransferApprovalById(Long id);
+
+    /**
+     * 批量删除客户转移审批
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteCustomerTransferApprovalByIds(Long[] ids);
+}

+ 119 - 0
fs-service/src/main/java/com/fs/qw/mapper/HyWorkTaskMapper.java

@@ -0,0 +1,119 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.qw.domain.HyWorkTask;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskListVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 企微任务看板Mapper接口
+ *
+ * @author fs
+ * @date 2025-03-18
+ */
+public interface HyWorkTaskMapper extends BaseMapper<HyWorkTask>{
+    /**
+     * 查询企微任务看板
+     *
+     * @param id 企微任务看板主键
+     * @return 企微任务看板
+     */
+    HyWorkTask selectHyWorkTaskById(Long id);
+
+    /**
+     * 查询企微任务看板列表
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 企微任务看板集合
+     */
+    List<HyWorkTask> selectHyWorkTaskList(HyWorkTask qwWorkTask);
+
+    /**
+     * 新增企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    int insertHyWorkTask(HyWorkTask qwWorkTask);
+
+    /**
+     * 修改企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    int updateHyWorkTask(HyWorkTask qwWorkTask);
+
+    /**
+     * 删除企微任务看板
+     *
+     * @param id 企微任务看板主键
+     * @return 结果
+     */
+    int deleteHyWorkTaskById(Long id);
+
+    /**
+     * 批量删除企微任务看板
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteHyWorkTaskByIds(Long[] ids);
+
+    void insertQwWorkTaskBatch(@Param("qwWorkTasks")List<HyWorkTask> qwWorkTasks);
+
+    List<QwWorkTaskListVO> selectHyWorkTaskListVONew(QwWorkTaskListParam qwWorkTask);
+
+    @Select("select ext_id from hy_work_task where type=2 and DATE(create_time) = CURDATE() ")
+    List<Long> selectHyWorkTaskByType();
+
+    @Select("select id,ext_id from hy_work_task where type=2 and status=0 and DATE(create_time) = CURDATE() ")
+    List<QwWorkTask> selectHyWorkTaskByTypeStatus();
+    @Update({
+            "<script>",
+            "UPDATE hy_work_task",
+            "SET status = 2",
+            "WHERE id IN",
+            "<foreach item='id' collection='overIds' open='(' separator=',' close=')'>",
+            "   #{id}",
+            "</foreach>",
+            "</script>"
+    })
+    void updateHyWorkTaskStatus(@Param("overIds")List<Long> overIds);
+
+    @Select("select id,ext_id from hy_work_task where type=2 and status=1 and DATE(create_time) = CURDATE() ")
+    List<QwWorkTask> selectHyWorkTaskByTypeStatus1();
+    @Update({
+            "<script>",
+            "UPDATE hy_work_task",
+            "SET status = 3",
+            "WHERE id IN",
+            "<foreach item='id' collection='overIds' open='(' separator=',' close=')'>",
+            "   #{id}",
+            "</foreach>",
+            "</script>"
+    })
+    void updateHyWorkTaskStatus1(List<Long> overIds1);
+
+    /**
+     * 查询会员任务看板
+     * @param params 参数
+     * @return list
+     */
+    List<HyWorkTask> selectHyWorkTaskListByMap(@Param("params") Map<String, Object> params);
+
+    Long selectHyWorkTaskListVONewCount(QwWorkTaskListParam qwWorkTask);
+
+    void hyWorkTask();
+
+    List<FsCourseWatchLog> hyWorkTaskGetInterruptedAndFirst();
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -360,4 +360,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(@Param("ids") List<String> ids);
     List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(@Param("ids") List<String> ids);
 
 
     List<GroupUserExternalVo> selectByGroupUser(@Param("ids") List<String> ids);
     List<GroupUserExternalVo> selectByGroupUser(@Param("ids") List<String> ids);
+
+    @Select("select is_repeat from qw_external_contact where user_id=${userId} limit 1")
+    Integer selectQwIsRepeat(Long id);
 }
 }

+ 3 - 1
fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java

@@ -89,5 +89,7 @@ public interface QwTagMapper
             "#{tagId}" +
             "#{tagId}" +
             "</foreach>" +
             "</foreach>" +
             "</script>")
             "</script>")
-    List<String> selectQwTagListByTagIds(@Param("date") QwTagSearchParam param);
+    List<String> selectQwTagListNameByTagIds(@Param("date") QwTagSearchParam param);
+
+    List<QwTag> selectQwTagListByTagIdsNew(@Param("tagIds") List<String> tagIds);
 }
 }

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

@@ -44,7 +44,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
 
     public List<QwUser> batchGetQwUser(@Param("list") List<QwUserKeyDTO> qwUserId);
     public List<QwUser> batchGetQwUser(@Param("list") List<QwUserKeyDTO> qwUserId);
 
 
-    @Select("select welcome_text,qw_user_name from qw_user where id = #{id}")
+    @Select("select company_user_id,company_id,welcome_text,qw_user_name from qw_user where id = #{id}")
     public QwUser selectQwUserByIdByWeComeText(@Param("id") Long id);
     public QwUser selectQwUserByIdByWeComeText(@Param("id") Long id);
 
 
     @Select("select * from qw_user where qw_user_id = #{qwUserId} and corp_id = #{corpId} ")
     @Select("select * from qw_user where qw_user_id = #{qwUserId} and corp_id = #{corpId} ")
@@ -246,7 +246,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     @Select("select app_key from qw_user where qw_user_id=#{qwUserId} and corp_id =#{corpId} limit 1")
     @Select("select app_key from qw_user where qw_user_id=#{qwUserId} and corp_id =#{corpId} limit 1")
     String selectQwUserByQwUserIdAndCorId(@Param("qwUserId")String qwUserId,@Param("corpId") String corpId);
     String selectQwUserByQwUserIdAndCorId(@Param("qwUserId")String qwUserId,@Param("corpId") String corpId);
 
 
-    @Select("UPDATE qw_user SET company_user_id = NULL,status=0 where company_user_id=#{companyUserId}")
+    @Select("UPDATE qw_user SET company_user_id = NULL,company_id = NULL,status=0 where company_user_id=#{companyUserId}")
     void updateUserByUserId(Long companyUserId);
     void updateUserByUserId(Long companyUserId);
 
 
     @Select("UPDATE qw_user SET company_user_id = NULL,status=0 where id=#{id} ")
     @Select("UPDATE qw_user SET company_user_id = NULL,status=0 where id=#{id} ")

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

@@ -195,4 +195,44 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "    DATE(qec.create_time) "+
             "    DATE(qec.create_time) "+
             "</script>"})
             "</script>"})
     List<QwWatchLogAllStatisticsListVO> selectQwExtCountByDayAndLine(QwWatchLogStatisticsListParam param);
     List<QwWatchLogAllStatisticsListVO> selectQwExtCountByDayAndLine(QwWatchLogStatisticsListParam param);
+
+    List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVONew(@Param("companyUserIds") List<Long> userIds,
+                                                                               @Param("sDate") String sDate, @Param("eDate") String eDate,
+                                                                               @Param("project") Long project,
+                                                                               @Param("courseId") Long courseId,
+                                                                               @Param("videoId") Long videoId);
+
+    Long selectQwWatchLogAllStatisticsListVONewCount(@Param("companyUserIds") List<Long> userIds,
+                                                     @Param("sDate") String sTime,
+                                                     @Param("eDate") String eTime,
+                                                     @Param("project") Long project,
+                                                     @Param("courseId") Long courseId,
+                                                     @Param("videoId") Long videoId);
+
+    List<QwWatchLogStatisticsListVO> selectQwWatchLogByCompanyUserId(
+            @Param("companyUserIds") List<Long> companyUserIds,
+            @Param("sTime") String sTime,
+            @Param("dTime") String dTime,
+            @Param("project") Long project,
+            @Param("courseId") Long courseId,
+            @Param("videoId") Long videoId,
+            @Param("pageNum") Long pageNum,
+            @Param("pageSize") Long pageSize
+    );
+
+    Long selectQwWatchLogByCompanyUserIdCount(
+            @Param("companyUserIds") List<Long> companyUserIds,
+            @Param("sTime") String sTime,
+            @Param("dTime") String dTime,
+            @Param("project") Long project,
+            @Param("courseId") Long courseId,
+            @Param("videoId") Long videoId
+    );
+
+    Long selectQwExtCountByDayAndCount(QwWatchLogStatisticsListParam param);
+
+
+    @Select("SELECT count(1) from qw_watch_log where fs_user_id=#{userId} and `day`=0")
+    int selectQwWatchLogIsFirstByUserId(Long userId);
+
 }
 }

+ 19 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java

@@ -3,6 +3,7 @@ package com.fs.qw.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Select;
@@ -98,4 +99,22 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
             "</script>"
             "</script>"
     })
     })
     void updateQwWorkTaskStatus(@Param("overIds")List<Long> overIds);
     void updateQwWorkTaskStatus(@Param("overIds")List<Long> overIds);
+
+    @Select({"<script> " +
+            "select t.qw_user_id,qw.qw_user_name,ANY_VALUE(cu.nick_name) companyUserName,DATE(t.create_time) createTime,\n" +
+            "\t\tSUM(CASE WHEN t.status = 0 THEN 1 ELSE 0 END) AS status0,\n" +
+            "    SUM(CASE WHEN t.status = 1 THEN 1 ELSE 0 END) AS status1,\n" +
+            "    SUM(CASE WHEN t.status = 2 THEN 1 ELSE 0 END) AS status2,\n" +
+            "    SUM(CASE WHEN t.status = 3 THEN 1 ELSE 0 END) AS status3\n" +
+            "\t\tfrom qw_work_task t  LEFT JOIN qw_user qw ON qw.id = t.qw_user_id LEFT JOIN company_user cu on cu.user_id=t.company_user_id  where  t.company_id=#{companyId} " +
+            "    <if test=\"companyUserId != null \"> and t.company_user_id = #{companyUserId}</if>\n" +
+            "    <if test=\"companyUserName != null and companyUserName != ''\"> and cu.nick_name = #{companyUserName}</if>\n" +
+            "    <if test=\"qwUserName != null and qwUserName != ''\"> and qw.qw_user_name = #{qwUserName}</if>\n" +
+            " " +
+            "    <if test=\"sTime != null \">  and DATE(t.create_time) &gt;= DATE(#{sTime})</if>\n" +
+            "    <if test=\"eTime != null \">  and DATE(t.create_time) &lt;= DATE(#{eTime})</if>\n" +
+            " " +
+            " GROUP BY t.qw_user_id,DATE(t.create_time) "+
+            "</script>"})
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactVOTime.java

@@ -3,12 +3,15 @@ package com.fs.qw.param;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.List;
+
 @Data
 @Data
 public class QwExternalContactVOTime {
 public class QwExternalContactVOTime {
 
 
     private Long id;
     private Long id;
 
 
     private String tagIds;
     private String tagIds;
+    private List<String> tagIdsName;
 
 
     private String remark;
     private String remark;
 
 

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

@@ -1,6 +1,7 @@
 package com.fs.qw.param;
 package com.fs.qw.param;
 
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.utils.DateUtils;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.util.Date;
 import java.util.Date;
@@ -11,10 +12,28 @@ public class QwWatchLogStatisticsListParam {
     private Date eTime;
     private Date eTime;
     @JsonFormat(pattern = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date sTime;
     private Date sTime;
+
+    private String startDate;
+    private String endDate;
+
+    public String getStartDate() {
+        return DateUtils.getStartOfDayString(sTime);
+    }
+
+    public String getEndDate() {
+        return DateUtils.getEndOfDayString(eTime);
+    }
+
     private String nickName;
     private String nickName;
     private String corpId;
     private String corpId;
     private Long companyId;
     private Long companyId;
     private Long companyUserId;
     private Long companyUserId;
     private Long id;
     private Long id;
     private String ids;
     private String ids;
+    private Long project;
+    private Long courseId;
+    private Long videoId;
+
+    private Long pageNum;
+    private Long pageSize;
 }
 }

+ 23 - 0
fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java

@@ -1,8 +1,12 @@
 package com.fs.qw.param;
 package com.fs.qw.param;
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.Date;
+
 @Data
 @Data
 public class QwWorkTaskListParam {
 public class QwWorkTaskListParam {
     private Long id;
     private Long id;
@@ -40,4 +44,23 @@ public class QwWorkTaskListParam {
     private Long companyUserId;
     private Long companyUserId;
 
 
     private String title;
     private String title;
+
+    private String companyUserName;
+
+
+
+    private Long deptId;
+
+    private String qwUserName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date eTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date sTime;
+
+    private Long pageNum;
+    private Long pageSize;
+
+
 }
 }

+ 15 - 11
fs-service/src/main/java/com/fs/qw/param/SopExternalContactInfo.java

@@ -1,26 +1,30 @@
 package com.fs.qw.param;
 package com.fs.qw.param;
 
 
 
 
+import lombok.Data;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@Data
 public class SopExternalContactInfo {
 public class SopExternalContactInfo {
     private String createTime;
     private String createTime;
     private String tagIds;
     private String tagIds;
+    private List<String> tagNames;
     private String remark;
     private String remark;
 
 
-    public SopExternalContactInfo(String createTime, String tagIds,String remark) {
+    public SopExternalContactInfo(String createTime, List<String> tagNames, String tagIds,String remark) {
         this.createTime = createTime != null ? createTime : "无进线时间";
         this.createTime = createTime != null ? createTime : "无进线时间";
+        this.tagNames = (tagNames != null && !tagNames.isEmpty()) ? tagNames : Collections.singletonList("无标签");
         this.tagIds = (tagIds != null && !tagIds.equals("[]")) ? tagIds : "无标签";
         this.tagIds = (tagIds != null && !tagIds.equals("[]")) ? tagIds : "无标签";
         this.remark = remark != null ? remark : "无备注";
         this.remark = remark != null ? remark : "无备注";
     }
     }
-
-    public String getCreateTime() {
-        return createTime;
-    }
-
-    public String getTagIds() {
-        return tagIds;
+    public SopExternalContactInfo(String createTime, String tagIds,String remark) {
+        this.createTime = createTime != null ? createTime : "无进线时间";
+        this.tagNames = Collections.singletonList("无标签");
+        this.tagIds = (tagIds != null && !tagIds.equals("[]")) ? tagIds : "无标签";
+        this.remark = remark != null ? remark : "无备注";
     }
     }
 
 
-    public String getRemark() {
-        return remark;
-    }
 }
 }

+ 62 - 0
fs-service/src/main/java/com/fs/qw/service/ICustomerTransferApprovalService.java

@@ -0,0 +1,62 @@
+package com.fs.qw.service;
+
+import com.fs.qw.domain.CustomerTransferApproval;
+
+import java.util.List;
+
+/**
+ * 客户转移审批Service接口
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+public interface ICustomerTransferApprovalService
+{
+    /**
+     * 查询客户转移审批
+     *
+     * @param id 客户转移审批ID
+     * @return 客户转移审批
+     */
+    public CustomerTransferApproval selectCustomerTransferApprovalById(Long id);
+
+    /**
+     * 查询客户转移审批列表
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 客户转移审批集合
+     */
+    public List<CustomerTransferApproval> selectCustomerTransferApprovalList(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 新增客户转移审批
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 结果
+     */
+    public int insertCustomerTransferApproval(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 修改客户转移审批
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 结果
+     */
+    public int updateCustomerTransferApproval(CustomerTransferApproval customerTransferApproval);
+
+    /**
+     * 批量删除客户转移审批
+     *
+     * @param ids 需要删除的客户转移审批ID
+     * @return 结果
+     */
+    public int deleteCustomerTransferApprovalByIds(Long[] ids);
+
+    /**
+     * 删除客户转移审批信息
+     *
+     * @param id 客户转移审批ID
+     * @return 结果
+     */
+    public int deleteCustomerTransferApprovalById(Long id);
+}

+ 112 - 0
fs-service/src/main/java/com/fs/qw/service/IHyWorkTaskService.java

@@ -0,0 +1,112 @@
+package com.fs.qw.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.HyWorkTask;
+import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskListVO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 企微任务看板Service接口
+ *
+ * @author fs
+ * @date 2025-03-18
+ */
+public interface IHyWorkTaskService extends IService<HyWorkTask>{
+    /**
+     * 查询企微任务看板
+     *
+     * @param id 企微任务看板主键
+     * @return 企微任务看板
+     */
+    HyWorkTask selectHyWorkTaskById(Long id);
+
+    /**
+     * 查询企微任务看板列表
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 企微任务看板集合
+     */
+    List<HyWorkTask> selectHyWorkTaskList(HyWorkTask qwWorkTask);
+
+    /**
+     * 新增企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    int insertHyWorkTask(HyWorkTask qwWorkTask);
+
+    /**
+     * 修改企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    int updateHyWorkTask(HyWorkTask qwWorkTask);
+
+    /**
+     * 批量删除企微任务看板
+     *
+     * @param ids 需要删除的企微任务看板主键集合
+     * @return 结果
+     */
+    int deleteHyWorkTaskByIds(Long[] ids);
+
+    /**
+     * 删除企微任务看板信息
+     *
+     * @param id 企微任务看板主键
+     * @return 结果
+     */
+    int deleteHyWorkTaskById(Long id);
+    /**
+     * 根据用户首次上课情况,添加相应的企微任务。
+     * 用于处理首次课程触发的任务创建逻辑。
+     */
+    void addHyWorkByFirstCourse();
+
+    /**
+     * 根据用户上课情况(通用逻辑),添加相应的企微任务。
+     * 用于处理一般课程事件触发的任务创建逻辑。
+     */
+    void addHyWorkByCourse();
+
+    /**
+     * 根据转化日(或特定业务日期节点)情况,添加相应的企微任务。
+     * 用于处理基于特定日期或转化阶段的任务创建逻辑。
+     */
+    void  addHyWorkByConversionDay();
+
+    List<QwWorkTaskListVO> selectHyWorkTaskListVONew(QwWorkTaskListParam qwWorkTask);
+    Long selectHyWorkTaskListVONewCount(QwWorkTaskListParam qwWorkTask);
+
+    /**
+     * 根据特定课程逻辑(可能与课程编号4或第四阶段相关)添加企微任务。
+     * 用于处理与特定课程标识(如'4')相关的任务创建逻辑。
+     */
+    void addHyWorkByCourse4();
+
+    /**
+     * 删除已完成、过期或不再需要的企微任务。
+     * 用于定期清理或处理生命周期结束的任务。
+     */
+    void delHyWorkTaskByOver();
+    /**
+     * 根据用户最后一次上课时间或课程结束时间情况,添加相应的企微任务。
+     * 用于处理基于课程结束或最后活动时间的任务创建逻辑。
+     */
+    void addHyWorkByCourseLastTime();
+
+    /**
+     * 查询企微任务看板
+     * @param params 参数
+     * @return list
+     */
+    List<HyWorkTask> selectHyWorkTaskListByMap(Map<String, Object> params);
+
+    void hyWorkTask();
+
+}

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

@@ -165,4 +165,6 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
     void synchronizeQwExternalContactTask();
     void synchronizeQwExternalContactTask();
 
 
     List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> externalIdList);
     List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> externalIdList);
+
+    Integer selectQwIsRepeat(Long id);
 }
 }

+ 6 - 2
fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java

@@ -1,6 +1,7 @@
 package com.fs.qw.service;
 package com.fs.qw.service;
 
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
@@ -63,7 +64,10 @@ public interface IQwWatchLogService extends IService<QwWatchLog>{
      */
      */
     int deleteQwWatchLogById(Long id);
     int deleteQwWatchLogById(Long id);
 
 
-    List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param);
-
+    TableDataInfo selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param);
     List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVO(QwWatchLogStatisticsListParam param);
     List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVO(QwWatchLogStatisticsListParam param);
+
+    TableDataInfo selectQwWatchLogAllStatisticsListVONew(QwWatchLogStatisticsListParam param);
+
+    TableDataInfo selectQwWatchLogStatisticsListVONew(QwWatchLogStatisticsListParam param);
 }
 }

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

@@ -3,6 +3,7 @@ package com.fs.qw.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 
 
 import java.util.List;
 import java.util.List;
@@ -75,4 +76,7 @@ public interface IQwWorkTaskService extends IService<QwWorkTask>{
 
 
     void delQwWorkTaskByOver();
     void delQwWorkTaskByOver();
 
 
+    void addQwWorkByCourseLastTime();
+
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }
 }

+ 266 - 0
fs-service/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java

@@ -0,0 +1,266 @@
+package com.fs.qw.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.qw.domain.CustomerTransferApproval;
+import com.fs.qw.dto.FsUserTransferParamDTO;
+import com.fs.qw.mapper.CustomerTransferApprovalMapper;
+import com.fs.qw.service.ICustomerTransferApprovalService;
+import com.fs.qw.vo.TransferCustomDTO;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.hc.openapi.tool.util.StringUtils;
+import org.apache.http.util.Asserts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 客户转移审批Service业务层处理
+ *
+ * @author fs
+ * @date 2025-04-01
+ */
+@Service
+@EnableAspectJAutoProxy(proxyTargetClass = true,exposeProxy = true)
+public class CustomerTransferApprovalServiceImpl implements ICustomerTransferApprovalService
+{
+    @Autowired
+    private CustomerTransferApprovalMapper customerTransferApprovalMapper;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+
+    @Autowired
+    private IFsUserCacheService fsUserCacheService;
+
+    @Autowired
+    private IFsUserService fsUserService;
+
+    /**
+     * 查询客户转移审批
+     *
+     * @param id 客户转移审批ID
+     * @return 客户转移审批
+     */
+    @Override
+    public CustomerTransferApproval selectCustomerTransferApprovalById(Long id)
+    {
+        CustomerTransferApproval item = customerTransferApprovalMapper.selectCustomerTransferApprovalById(id);
+
+        if(ObjectUtils.isNotNull(item.getCorpId())){
+            Company company = companyCacheService.selectCompanyById(Long.valueOf(item.getCorpId()));
+            if(ObjectUtils.isNotNull(company)){
+                item.setCompanyName(String.format("%s_%s",company.getCompanyName(),company.getCompanyId()));
+            }
+        }
+        if(ObjectUtils.isNotNull(item.getTransferType())){
+            String transferType = DictUtils.getDictLabel("transfer_type", String.valueOf(item.getTransferType()));
+            if(ObjectUtils.isNotEmpty(transferType)){
+                item.setTransferTypeText(transferType);
+            }
+        }
+        if(ObjectUtils.isNotNull(item.getApprovalStatus())){
+            String approvalStatus = DictUtils.getDictLabel("transfer_approval_status", String.valueOf(item.getApprovalStatus()));
+            if(ObjectUtils.isNotEmpty(approvalStatus)){
+                item.setApprovalStatusText(approvalStatus);
+            }
+        }
+
+        if(ObjectUtils.isNotNull(item.getOriginalUserId())){
+            CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getOriginalUserId());
+            if(ObjectUtils.isNotNull(companyUser)){
+                item.setOriginalUserName(String.format("%s_%d",companyUser.getUserName(),companyUser.getUserId()));
+            }
+        }
+
+        // 目标接收销售用户
+        if(ObjectUtils.isNotNull(item.getTargetUserId())){
+            CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getTargetUserId());
+            if(ObjectUtils.isNotNull(companyUser)){
+                item.setTargetUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+            }
+        }
+
+        // 发起此转移请求的用户
+        if(ObjectUtils.isNotNull(item.getInitiatorUserId())){
+            CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getInitiatorUserId());
+            if(ObjectUtils.isNotNull(companyUser)){
+                item.setInitiatorUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+            }
+        }
+
+        if(StringUtils.isBlank(item.getTransferBefore()) && StringUtils.isNotBlank(item.getCustomerIds())){
+            List<Long> customerIds = JSON.parseArray(item.getCustomerIds(), Long.class);
+            List<TransferCustomDTO> customerList = getCustomerList(customerIds, item);
+            item.setCustomerList(customerList);
+        } else {
+            List<TransferCustomDTO> customerList = JSON.parseArray(item.getTransferBefore(), TransferCustomDTO.class);
+            item.setCustomerList(customerList);
+        }
+        return item;
+    }
+
+    private List<TransferCustomDTO> getCustomerList(List<Long> customerIds, CustomerTransferApproval item) {
+        List<TransferCustomDTO> customerList = new ArrayList<>();
+
+        for (Long customerId : customerIds) {
+
+            FsUser fsUser = fsUserCacheService.selectFsUserById(customerId);
+            if(ObjectUtils.isNotNull(fsUser)){
+                String companyUserName = "无";
+                if(ObjectUtils.isNotNull(fsUser.getCompanyUserId())){
+                    CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(fsUser.getCompanyUserId());
+                    companyUserName = String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId());
+                }
+
+                customerList.add(TransferCustomDTO.builder()
+                        .userName(String.format("%s_%d", fsUser.getNickName(), fsUser.getUserId()))
+                        .userId(fsUser.getUserId())
+                        .beforeCompanyUserName(companyUserName)
+                        .beforeCompanyUserId(fsUser.getCompanyUserId())
+                        .afterCompanyUserName(item.getTargetUserName())
+                        .afterCompanyUserId(item.getTargetUserId())
+                        .build());
+            }
+        }
+        return customerList;
+    }
+
+    /**
+     * 查询客户转移审批列表
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 客户转移审批
+     */
+    @Override
+    public List<CustomerTransferApproval> selectCustomerTransferApprovalList(CustomerTransferApproval customerTransferApproval)
+    {
+        List<CustomerTransferApproval> list = customerTransferApprovalMapper.selectCustomerTransferApprovalList(customerTransferApproval);
+        for (CustomerTransferApproval item : list) {
+            if(ObjectUtils.isNotNull(item.getCorpId())){
+                Company company = companyCacheService.selectCompanyById(Long.valueOf(item.getCorpId()));
+                if(ObjectUtils.isNotNull(company)){
+                    item.setCompanyName(String.format("%s_%s",company.getCompanyName(),company.getCompanyId()));
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getTransferType())){
+                String transferType = DictUtils.getDictLabel("transfer_type", String.valueOf(item.getTransferType()));
+                if(ObjectUtils.isNotEmpty(transferType)){
+                    item.setTransferTypeText(transferType);
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getApprovalStatus())){
+                String approvalStatus = DictUtils.getDictLabel("transfer_approval_status", String.valueOf(item.getApprovalStatus()));
+                if(ObjectUtils.isNotEmpty(approvalStatus)){
+                    item.setApprovalStatusText(approvalStatus);
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getOriginalUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getOriginalUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setOriginalUserName(String.format("%s_%d",companyUser.getUserName(),companyUser.getUserId()));
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getTargetUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getTargetUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setTargetUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getInitiatorUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getInitiatorUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setInitiatorUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 新增客户转移审批
+     *
+     * @param customerTransferApproval 客户转移审批
+     * @return 结果
+     */
+    @Override
+    public int insertCustomerTransferApproval(CustomerTransferApproval customerTransferApproval)
+    {
+        return customerTransferApprovalMapper.insertCustomerTransferApproval(customerTransferApproval);
+    }
+
+    /**
+     * 修改客户转移审批
+     *
+     * @param item 客户转移审批
+     * @return 结果
+     */
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
+    public int updateCustomerTransferApproval(CustomerTransferApproval item)
+    {
+        item.setProcessedAt(new Date());
+//        审批状态: 0=待审批, 1=审批通过, 2=审批驳回, 3=已撤销
+        // 如果审批通过 进行转移
+        if(ObjectUtil.equal(1,item.getApprovalStatus())){
+            FsUserTransferParamDTO transferParam = new FsUserTransferParamDTO();
+            transferParam.setContent(item.getContent());
+            transferParam.setTargetCompanyUserId(item.getTargetUserId());
+
+            Asserts.check(StringUtils.isNotBlank(item.getCustomerIds()),"转移客户不能为空!");
+            List<Long> customerIds = JSON.parseArray(item.getCustomerIds(), Long.class);
+            transferParam.setUserIds(customerIds);
+            transferParam.setSourceCompanyUserId(item.getOriginalUserId());
+
+            fsUserService.transfer(transferParam);
+        }
+        List<Long> customerIds = JSON.parseArray(item.getCustomerIds(), Long.class);
+        List<TransferCustomDTO> customerList = getCustomerList(customerIds, item);
+        item.setTransferBefore(JSON.toJSONString(customerList));
+        return customerTransferApprovalMapper.updateCustomerTransferApproval(item);
+    }
+
+    /**
+     * 批量删除客户转移审批
+     *
+     * @param ids 需要删除的客户转移审批ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCustomerTransferApprovalByIds(Long[] ids)
+    {
+        return customerTransferApprovalMapper.deleteCustomerTransferApprovalByIds(ids);
+    }
+
+    /**
+     * 删除客户转移审批信息
+     *
+     * @param id 客户转移审批ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCustomerTransferApprovalById(Long id)
+    {
+        return customerTransferApprovalMapper.deleteCustomerTransferApprovalById(id);
+    }
+}

+ 589 - 0
fs-service/src/main/java/com/fs/qw/service/impl/HyWorkTaskServiceImpl.java

@@ -0,0 +1,589 @@
+package com.fs.qw.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.vo.FsCourseWatchLogTaskVO;
+import com.fs.course.vo.FsQwCourseWatchLogVO;
+import com.fs.qw.domain.HyWorkTask;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.mapper.HyWorkTaskMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.service.IHyWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskListVO;
+import com.fs.sop.domain.QwSop;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.hc.openapi.tool.util.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 企微任务看板Service业务层处理
+ *
+ * @author fs
+ * @date 2025-03-18
+ */
+@Service
+@Slf4j
+public class HyWorkTaskServiceImpl extends ServiceImpl<HyWorkTaskMapper, HyWorkTask> implements IHyWorkTaskService {
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    private QwSopMapper qwSopMapper;
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+    @Autowired
+    private HyWorkTaskMapper hyWorkTaskMapper;
+
+    /**
+     * 查询企微任务看板
+     *
+     * @param id 企微任务看板主键
+     * @return 企微任务看板
+     */
+    @Override
+    public HyWorkTask selectHyWorkTaskById(Long id)
+    {
+        return baseMapper.selectHyWorkTaskById(id);
+    }
+
+    /**
+     * 查询企微任务看板列表
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 企微任务看板
+     */
+    @Override
+    public List<HyWorkTask> selectHyWorkTaskList(HyWorkTask qwWorkTask)
+    {
+        return baseMapper.selectHyWorkTaskList(qwWorkTask);
+    }
+
+    /**
+     * 新增企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    @Override
+    public int insertHyWorkTask(HyWorkTask qwWorkTask)
+    {
+        qwWorkTask.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertHyWorkTask(qwWorkTask);
+    }
+
+    /**
+     * 修改企微任务看板
+     *
+     * @param qwWorkTask 企微任务看板
+     * @return 结果
+     */
+    @Override
+    public int updateHyWorkTask(HyWorkTask qwWorkTask)
+    {
+        qwWorkTask.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateHyWorkTask(qwWorkTask);
+    }
+
+    /**
+     * 批量删除企微任务看板
+     *
+     * @param ids 需要删除的企微任务看板主键
+     * @return 结果
+     */
+    @Override
+    public int deleteHyWorkTaskByIds(Long[] ids)
+    {
+        return baseMapper.deleteHyWorkTaskByIds(ids);
+    }
+
+    /**
+     * 删除企微任务看板信息
+     *
+     * @param id 企微任务看板主键
+     * @return 结果
+     */
+    @Override
+    public int deleteHyWorkTaskById(Long id)
+    {
+        return baseMapper.deleteHyWorkTaskById(id);
+    }
+
+    @Override
+    public void addHyWorkByFirstCourse() {
+            List<FsQwCourseWatchLogVO> fsQwCourseWatchLogVOS = fsCourseWatchLogMapper.selectFsCourseWatchLogByVideoId();
+        ArrayList<HyWorkTask> qwWorkTasks = new ArrayList<>();
+        for (FsQwCourseWatchLogVO vo : fsQwCourseWatchLogVOS) {
+                HyWorkTask qwWorkTask = new HyWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(vo.getQwExternalContactId());
+                qwWorkTask.setCompanyId(vo.getCompanyId());
+                qwWorkTask.setCompanyUserId(vo.getCompanyUserId());
+                qwWorkTask.setQwUserId(Long.parseLong(vo.getQwUserId()));
+                qwWorkTask.setType(1);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle(vo.getLogType()==3?"先导课待看课":"先导课完课");
+                qwWorkTask.setScore(vo.getLogType()==3?4:3);
+                qwWorkTasks.add(qwWorkTask);
+            }
+        if (!qwWorkTasks.isEmpty()) {
+            hyWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+        }
+
+    }
+    Integer getHyWorkCourseScore(Integer type, Integer day,Integer leve){
+
+            switch (day){
+                case 1:
+                   return type == 4 ? 12 : 11;
+                case 2:
+                case 3:
+                    return type == 4 ? 9 : 8;
+                case 4:
+                case 5:
+                case 6:
+                case 7:
+                    return type == 4 ? 3 : 2;
+                default:
+
+                    return leve==null?0: leve==1?5:leve==2?3:leve==3?1:0;
+            }
+
+
+    }
+
+    @Override
+    public void addHyWorkByCourse() {
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
+        LocalDate today = LocalDate.now();
+        List<Long> extIds = hyWorkTaskMapper.selectHyWorkTaskByType();
+        Set<Long> extIdSet = new HashSet<>(extIds);
+        // 获取出执行sop的记录数
+        for (QwSop qwSop : qwSops) {
+            if (qwSop.getCourseDay()==null){
+                continue;
+            }
+            Integer courseDay=qwSop.getCourseDay()-1;
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectSopUserLogsInfoBySopId(qwSop.getId());
+            if (qwSopLogs==null || qwSopLogs.isEmpty()) {
+                continue;
+            }
+            // 每次sop的观看记录
+            List<FsCourseWatchLogTaskVO> fsCourseWatchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByDaySopId3(qwSop.getId());
+            if (fsCourseWatchLogs==null || fsCourseWatchLogs.isEmpty()) {
+                continue;
+            }
+            List<HyWorkTask> qwWorkTasks = new ArrayList<>();
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                Map<Long, FsCourseWatchLogTaskVO> map = fsCourseWatchLogs.stream()
+                        .collect(Collectors.toMap(FsCourseWatchLogTaskVO::getQwExternalContactId, data -> data,(oldValue, newValue) -> newValue ));
+                FsCourseWatchLogTaskVO fsCourseWatchLog = map.get(qwSopLog.getExternalId());
+                if (fsCourseWatchLog == null) {
+                    continue;
+                }
+                if (extIdSet.contains(fsCourseWatchLog.getQwExternalContactId())) {
+                    continue;
+                }
+                String createTime = qwSopLog.getCreateTime();
+                LocalDate createDate = LocalDate.parse(createTime.substring(0, 10), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                // 计算两个日期之间的天数差
+                Integer day = (Math.toIntExact(ChronoUnit.DAYS.between(createDate, today))) + 1 - courseDay;
+                Integer score = getHyWorkCourseScore(fsCourseWatchLog.getLogType(), day,fsCourseWatchLog.getLevel());
+                if (score==0){
+                    continue;
+                }
+                HyWorkTask qwWorkTask = new HyWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                qwWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+                qwWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                qwWorkTask.setSopId(qwSop.getId());
+                qwWorkTask.setQwUserId(fsCourseWatchLog.getQwUserId());
+                qwWorkTask.setType(2);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle("第"+day+"天"+"待看课");
+                qwWorkTask.setScore(score);
+                qwWorkTasks.add(qwWorkTask);
+            }
+            if (!qwWorkTasks.isEmpty()){
+                hyWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+            }
+
+        }
+    }
+
+
+
+    @Override
+    public void addHyWorkByConversionDay() {
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRatingNotNull();
+        Map<Integer, Integer> minMap = new HashMap<>();
+        // level->分值映射
+        minMap.put(1, 10);
+        minMap.put(2, 6);
+        minMap.put(3, 2);
+        Map<Integer, Integer> MaxMap = new HashMap<>();
+        // level->分值映射
+        MaxMap.put(1, 25);
+        MaxMap.put(2, 15);
+        MaxMap.put(3, 5);
+        for (QwSop qwSop : qwSops) {
+            Integer min= qwSop.getMinConversionDay();
+            Integer max = qwSop.getMaxConversionDay();
+            LocalDate today = LocalDate.now();
+            addHyWorkTask(today, min, qwSop, "用户小转",minMap);
+            addHyWorkTask(today, max, qwSop, "用户大转",MaxMap);
+        }
+    }
+
+    @Override
+    public List<QwWorkTaskListVO> selectHyWorkTaskListVONew(QwWorkTaskListParam qwWorkTask) {
+        List<QwWorkTaskListVO> list = hyWorkTaskMapper.selectHyWorkTaskListVONew(qwWorkTask);
+        for (QwWorkTaskListVO item : list) {
+            if(ObjectUtils.isNotNull(item.getCompanyId())){
+                Company company = companyCacheService.selectCompanyById(item.getCompanyId());
+                if(ObjectUtils.isNotNull(company)){
+                    item.setCompanyName(String.format("%s_%d",company.getCompanyName(),company.getCompanyId()));
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setCompanyUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getType())){
+                String kbBusinessType = DictUtils.getDictLabel("sys_qw_work_task_type", String.valueOf(item.getType()));
+                if(StringUtils.isNotBlank(kbBusinessType)){
+                    item.setTypeText(kbBusinessType);
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getStatus())){
+                String kbProcessingStatus = DictUtils.getDictLabel("sys_qw_work_task_status", String.valueOf(item.getStatus()));
+                if(StringUtils.isNotBlank(kbProcessingStatus)){
+                    item.setStatusText(kbProcessingStatus);
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public Long selectHyWorkTaskListVONewCount(QwWorkTaskListParam qwWorkTask) {
+        return hyWorkTaskMapper.selectHyWorkTaskListVONewCount(qwWorkTask);
+    }
+
+    @Override
+    public void addHyWorkByCourse4() {
+        // 获取sop模板
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
+        // 课程
+        List<Long> extIds = hyWorkTaskMapper.selectHyWorkTaskByType();
+        Set<Long> extIdSet = new HashSet<>(extIds);
+        LocalDate today = LocalDate.now();
+        for (QwSop qwSop : qwSops) {
+            if (qwSop.getCourseDay()==null){
+                continue;
+            }
+            Integer courseDay=qwSop.getCourseDay()-1;
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectFsUserIdSopUserLogsInfoBySopId(qwSop.getId());
+            if (qwSopLogs==null || qwSopLogs.isEmpty()) {
+                continue;
+            }
+            List<FsCourseWatchLogTaskVO> fsCourseWatchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByDaySopIdFsUser4(qwSop.getId());
+            if (fsCourseWatchLogs==null || fsCourseWatchLogs.isEmpty()) {
+                continue;
+            }
+            List<HyWorkTask> qwWorkTasks = new ArrayList<>();
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                Map<Long, FsCourseWatchLogTaskVO> map = fsCourseWatchLogs.stream()
+                        .collect(Collectors.toMap(FsCourseWatchLogTaskVO::getUserId, data -> data,(oldValue, newValue) -> newValue ));
+                FsCourseWatchLogTaskVO fsCourseWatchLog = map.get(qwSopLog.getFsUserId());
+                if (fsCourseWatchLog == null) {
+                    continue;
+                }
+                if (extIdSet.contains(fsCourseWatchLog.getQwExternalContactId())) {
+                    continue;
+                }
+                String createTime = qwSopLog.getCreateTime();
+                LocalDate createDate = LocalDate.parse(createTime.substring(0, 10), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                Integer day = (Math.toIntExact(ChronoUnit.DAYS.between(createDate, today))) + 1 - courseDay;
+                if (day>7){
+                    continue;
+                }
+                Integer score = getHyWorkCourseScore(fsCourseWatchLog.getLogType(), day,fsCourseWatchLog.getLevel());
+                if (score==0){
+                    continue;
+                }
+                HyWorkTask qwWorkTask = new HyWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                qwWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+                qwWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                qwWorkTask.setSopId(qwSop.getId());
+                qwWorkTask.setQwUserId(fsCourseWatchLog.getQwUserId());
+                qwWorkTask.setType(2);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle("第"+day+"天"+"看课中断");
+                qwWorkTask.setScore(score);
+                qwWorkTasks.add(qwWorkTask);
+            }
+            if (!qwWorkTasks.isEmpty()){
+                hyWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+            }
+
+        }
+    }
+
+    @Override
+    public void delHyWorkTaskByOver() {
+        List<Long> longs = fsCourseWatchLogMapper.selectFsCourseWatchLogByFinish();
+
+        List<QwWorkTask> qwWorkTasks = hyWorkTaskMapper.selectHyWorkTaskByTypeStatus();
+
+        Set<Long> targetIds = new HashSet<>(longs);
+
+        List<Long> overIds = qwWorkTasks.stream()
+                .map(QwWorkTask::getId)
+                .filter(targetIds::contains)
+                .collect(Collectors.toList());
+        if (overIds.isEmpty()){
+            return;
+        }
+        hyWorkTaskMapper.updateHyWorkTaskStatus(overIds);
+
+    }
+
+    @Override
+    public void addHyWorkByCourseLastTime() {
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
+        List<Long> extIds = hyWorkTaskMapper.selectHyWorkTaskByType();
+        SimpleDateFormat sdf = new SimpleDateFormat("HHmm"); // 24小时制,如 1100
+        String timeStr = sdf.format(new Date());
+        int lastTime = Integer.parseInt(timeStr);
+        Set<Long> extIdSet = new HashSet<>(extIds);
+        LocalDate today = LocalDate.now();
+        for (QwSop qwSop : qwSops) {
+            if (qwSop.getCourseDay()==null){
+                continue;
+            }
+            Integer courseDay=qwSop.getCourseDay()-1;
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectSopUserLogsInfoBySopId(qwSop.getId());
+            if (qwSopLogs==null || qwSopLogs.isEmpty()) {
+                continue;
+            }
+
+            List<FsCourseWatchLogTaskVO> fsCourseWatchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByDaySopId3LastTime(qwSop.getId(),lastTime);
+            if (fsCourseWatchLogs==null || fsCourseWatchLogs.isEmpty()) {
+                continue;
+            }
+            List<HyWorkTask> qwWorkTasks = new ArrayList<>();
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                Map<Long, FsCourseWatchLogTaskVO> map = fsCourseWatchLogs.stream()
+                        .collect(Collectors.toMap(FsCourseWatchLogTaskVO::getQwExternalContactId, data -> data,(oldValue, newValue) -> newValue ));
+                FsCourseWatchLogTaskVO fsCourseWatchLog = map.get(qwSopLog.getExternalId());
+
+                if (fsCourseWatchLog == null) {
+                    continue;
+                }
+                if (extIdSet.contains(fsCourseWatchLog.getQwExternalContactId())) {
+                    continue;
+                }
+                String createTime = qwSopLog.getCreateTime();
+                LocalDate createDate = LocalDate.parse(createTime.substring(0, 10), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+                Integer day = (Math.toIntExact(ChronoUnit.DAYS.between(createDate, today))) + 1 - courseDay;
+                if (day<=7){
+                    continue;
+                }
+                Integer score = getHyWorkCourseScore(fsCourseWatchLog.getLogType(), day,fsCourseWatchLog.getLevel());
+                if (score==0){
+                    continue;
+                }
+                HyWorkTask qwWorkTask = new HyWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                qwWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+                qwWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                qwWorkTask.setSopId(qwSop.getId());
+                qwWorkTask.setQwUserId(fsCourseWatchLog.getQwUserId());
+                qwWorkTask.setType(2);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle("第"+day+"天"+"待看课");
+                qwWorkTask.setScore(score);
+                qwWorkTasks.add(qwWorkTask);
+
+            }
+            if (!qwWorkTasks.isEmpty()){
+
+                hyWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+            }
+
+        }
+    }
+
+    /**
+     * 查询企微任务看板
+     * @param params 参数
+     * @return list
+     */
+    @Override
+    public List<HyWorkTask> selectHyWorkTaskListByMap(Map<String, Object> params) {
+        return hyWorkTaskMapper.selectHyWorkTaskListByMap(params);
+    }
+
+    // 定义批处理大小常量
+    private static final int BATCH_SIZE = 1000;
+
+    @Override
+    public void hyWorkTask() {
+
+        // 获取看课中断和待看的先导课
+        List<FsCourseWatchLog> fsCourseWatchLogs = hyWorkTaskMapper.hyWorkTaskGetInterruptedAndFirst();
+        if(CollectionUtils.isEmpty(fsCourseWatchLogs)){
+            log.info("[获取看课中断和待看的先导课] 没有找到数据,已经跳过!");
+            return;
+        }
+        List<HyWorkTask> batchList = new ArrayList<>(BATCH_SIZE);
+        int totalProcessed = 0;
+        int batchCount = 0;
+        for (FsCourseWatchLog fsCourseWatchLog : fsCourseWatchLogs) {
+            HyWorkTask hyWorkTask = new HyWorkTask();
+            hyWorkTask.setCreateTime(DateUtils.getNowDate());
+            hyWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+            hyWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+            hyWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+            hyWorkTask.setUserId(fsCourseWatchLog.getUserId());
+            if(fsCourseWatchLog.getPeriodId() != null) {
+                hyWorkTask.setUserLogsId(String.valueOf(fsCourseWatchLog.getPeriodId()));
+            }
+            hyWorkTask.setStatus(0);
+            hyWorkTask.setUserId(fsCourseWatchLog.getUserId());
+
+            // 看课中断
+            if(fsCourseWatchLog.getLogType()==4){
+                hyWorkTask.setType(2);
+                Date createTime = fsCourseWatchLog.getCreateTime();
+                long day = DateUtils.getDaysDifferenceFromNow(createTime);
+                hyWorkTask.setTitle("第"+day+"天"+"看课中断");
+            } else {
+                hyWorkTask.setType(1);
+                hyWorkTask.setTitle(fsCourseWatchLog.getLogType()==3?"先导课待看课":"先导课完课");
+            }
+            hyWorkTask.setScore(fsCourseWatchLog.getLogType()==3?4:3);
+
+            batchList.add(hyWorkTask);
+            totalProcessed++;
+
+            if (batchList.size() >= BATCH_SIZE) {
+                batchCount++;
+                log.info("处理到第 {} 条数据,开始插入第 {} 批 ({} 条)...", totalProcessed, batchCount, batchList.size());
+                try {
+                    hyWorkTaskMapper.insertQwWorkTaskBatch(batchList);
+                    log.info("第 {} 批插入成功.", batchCount);
+                } catch (Exception e) {
+                    log.error("第 {} 批插入时发生错误: {}", batchCount, e.getMessage(), e);
+                }
+                batchList.clear();
+            }
+        }
+        if (!batchList.isEmpty()) {
+            batchCount++;
+            log.info("处理完成,开始插入最后一批 ({} 条)...", batchList.size());
+            try {
+                hyWorkTaskMapper.insertQwWorkTaskBatch(batchList);
+                log.info("最后一批插入成功.");
+            } catch (Exception e) {
+                log.error("最后一批插入时发生错误: {}", e.getMessage(), e);
+            }
+            batchList.clear();
+        }
+        log.info("hyWorkTask 任务执行完毕,共处理 {} 条数据,分 {} 批插入.", totalProcessed, batchCount > 0 ? batchCount : (totalProcessed > 0 ? 1 : 0));
+
+    }
+
+    /**
+     * 根据SOP执行日志和特定条件,为符合要求的外部联系人添加企业微信工作任务。
+     * <p>
+     * 此方法仅在传入的 `day` 参数大于7时执行。
+     * 它会查询指定 `QwSop` 在过去 `day` 天内的用户执行日志 (`SopUserLogsInfo`)。
+     * 遍历日志,获取关联的外部联系人 (`QwExternalContact`)。
+     * 对联系人进行筛选:必须存在,且其级别 (`level`) 不能为 null、0 或 4。
+     * 对于通过筛选的联系人,调用内部的 `insertQwWorkTask` 方法来创建任务。
+     * </p>
+     *
+     * @param today   当前日期,用于计算查询日志的起始日期。
+     * @param day     回溯的天数。只有当 `day` 大于 7 时,才会执行添加任务的逻辑。
+     * @param qwSop   企业微信SOP(标准操作流程)对象,包含需要查询日志的SOP ID。
+     * @param title   要创建的企业微信工作任务的标题。
+     * @param map     一个映射表,键可能是外部联系人的级别 (`level`),值可能是传递给 `insertQwWorkTask` 的参数(例如优先级或特定配置)。
+     *                如果联系人级别在map中不存在,则使用默认值0。
+     * @author xdd
+     * @version 1.0
+     * @since yyyy-MM-dd // 建议替换为实际的编写或修改日期
+     */
+    private void addHyWorkTask(LocalDate today, Integer day, QwSop qwSop, String title,Map<Integer, Integer> map) {
+        if (day>7){
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+            String minDay = today.minusDays(day).format(formatter);
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectDayBySopId(qwSop.getId(), minDay);
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(qwSopLog.getExternalId());
+                if (qwExternalContact==null){
+                    continue;
+                }
+                if (qwExternalContact.getLevel()==null||qwExternalContact.getLevel()==4||qwExternalContact.getLevel()==0){
+                    continue;
+                }
+                System.out.println(qwExternalContact.getId()+"ok");
+
+
+                insertHyWorkTask(qwSopLog.getId(),qwExternalContact,3,title,map.getOrDefault(qwExternalContact.getLevel(), 0));
+            }
+        }
+    }
+
+
+    private void insertHyWorkTask(String sopId,QwExternalContact qwExternalContact, Integer type, String title, Integer score) {
+        HyWorkTask qwWorkTask = new HyWorkTask();
+        qwWorkTask.setCreateTime(DateUtils.getNowDate());
+        qwWorkTask.setExtId(qwExternalContact.getId());
+        qwWorkTask.setCompanyId(qwExternalContact.getCompanyId());
+        qwWorkTask.setCompanyUserId(qwExternalContact.getCompanyUserId());
+        qwWorkTask.setQwUserId(qwExternalContact.getQwUserId());
+        qwWorkTask.setSopId(sopId);
+        qwWorkTask.setType(type);
+        qwWorkTask.setStatus(0);
+        qwWorkTask.setTitle(title);
+        qwWorkTask.setScore(score);
+        baseMapper.insertHyWorkTask(qwWorkTask);
+    }
+
+
+}

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

@@ -8,6 +8,7 @@ import com.fs.ad.enums.AdUploadType;
 import com.fs.ad.service.IAdHtmlClickLogService;
 import com.fs.ad.service.IAdHtmlClickLogService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseConfig;
@@ -173,6 +174,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     @Autowired
     private RedisCache redisCache;
     private RedisCache redisCache;
 
 
+    @Autowired
+    private QwTagMapper qwTagMapper;
+
     @Autowired
     @Autowired
     private QwExternalContactServiceImpl qwExternalContactService;
     private QwExternalContactServiceImpl qwExternalContactService;
 
 
@@ -290,7 +294,23 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
 
     @Override
     @Override
     public List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> ids) {
     public List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> ids) {
-        return qwExternalContactMapper.selectQwExternalContactListVOByUserIds(ids);
+        List<QwExternalContactVOTime> list = qwExternalContactMapper.selectQwExternalContactListVOByUserIds(ids);
+        List<String> tagIds = list.stream().map(QwExternalContactVOTime::getTagIds).filter(StringUtils::isNotEmpty).flatMap(e -> JSON.parseArray(e, String.class).stream()).collect(Collectors.toList());
+        if(!tagIds.isEmpty()){
+            List<QwTag> tagList = qwTagMapper.selectQwTagListByTagIdsNew(tagIds);
+            Map<String, QwTag> tagMap = PubFun.listToMapByGroupObject(tagList, QwTag::getTagId);
+            list.forEach(e -> {
+                List<String> tagId = JSON.parseArray(e.getTagIds(), String.class);
+                List<String> tagNameList = tagId.stream().filter(tagMap::containsKey).map(t -> tagMap.get(t).getName()).filter(StringUtils::isNotEmpty).collect(Collectors.toList());
+                e.setTagIdsName(tagNameList);
+            });
+        }
+        return list;
+    }
+
+    @Override
+    public Integer selectQwIsRepeat(Long id) {
+        return qwExternalContactMapper.selectQwIsRepeat(id);
     }
     }
 
 
     /**
     /**

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

@@ -193,6 +193,6 @@ public class QwTagServiceImpl implements IQwTagService
 
 
     @Override
     @Override
     public List<String> selectQwTagListByTagIds(QwTagSearchParam param) {
     public List<String> selectQwTagListByTagIds(QwTagSearchParam param) {
-        return qwTagMapper.selectQwTagListByTagIds(param);
+        return qwTagMapper.selectQwTagListNameByTagIds(param);
     }
     }
 }
 }

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

@@ -1,8 +1,21 @@
 package com.fs.qw.service.impl;
 package com.fs.qw.service.impl;
 
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
+import com.fs.his.domain.FsUser;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
@@ -11,13 +24,15 @@ import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.fs.store.service.cache.IFsUserCourseCacheService;
+import com.hc.openapi.tool.util.StringUtils;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * 企微看课Service业务层处理
  * 企微看课Service业务层处理
@@ -35,6 +50,22 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
     private QwExternalContactMapper qwExternalContactMapper;
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
     @Autowired
     private CompanyUserMapper companyUserMapper;
     private CompanyUserMapper companyUserMapper;
+
+    @Autowired
+    private IFsUserCourseCacheService fsUserCourseCacheService;
+
+    @Autowired
+    private IFsUserCourseVideoCacheService fsUserCourseVideoCacheService;
+
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+    @Autowired
+    private IFsUserCacheService fsUserCacheService;
+
+
     /**
     /**
      * 查询企微看课
      * 查询企微看课
      *
      *
@@ -109,13 +140,18 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
     }
     }
 
 
     @Override
     @Override
-    public List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param) {
+    public TableDataInfo selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param) {
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(new ArrayList<>());
+        rspData.setTotal(0L);
 
 
         Date sTime = param.getSTime();
         Date sTime = param.getSTime();
         Date eTime = param.getETime();
         Date eTime = param.getETime();
         List<Date> datesBetween = getDatesBetween(sTime, eTime);
         List<Date> datesBetween = getDatesBetween(sTime, eTime);
         if (datesBetween.size() > 7) {
         if (datesBetween.size() > 7) {
-            return new ArrayList<>();
+            return rspData;
         }
         }
         if (param.getCompanyUserId()!=null){
         if (param.getCompanyUserId()!=null){
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
@@ -131,11 +167,10 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             vo.setFirstOver(stat.getFirstOver());
             vo.setFirstOver(stat.getFirstOver());
         }
         }
 
 
-
-
-
-
-        return vos;
+        Long total = qwWatchLogMapper.selectQwExtCountByDayAndCount(param);
+        rspData.setRows(vos);
+        rspData.setTotal(total);
+        return rspData;
     }
     }
 
 
     @Override
     @Override
@@ -161,10 +196,162 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             list.add(stat);
             list.add(stat);
         }
         }
 
 
+        return list;
+    }
 
 
+    @Override
+    public TableDataInfo selectQwWatchLogAllStatisticsListVONew(QwWatchLogStatisticsListParam param) {
+        // 获取当前公司下的所有销售
+        List<Long> userIds;
+        if(param.getCompanyUserId()  == null){
+            List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserByCompanyId(param.getCompanyId());
+            if(CollectionUtils.isEmpty(companyUsers)){
+                throw new CustomException("该公司下面没有任何销售!");
+            }
+            userIds = companyUsers.stream()
+                    .map(CompanyUser::getUserId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        } else {
+            userIds = new ArrayList<>();
+            userIds.add(param.getCompanyUserId());
+        }
 
 
+        List<QwWatchLogAllStatisticsListVO> list = new ArrayList<>();
 
 
-        return list;
+        List<QwWatchLogAllStatisticsListVO> vos = qwWatchLogMapper
+                .selectQwWatchLogAllStatisticsListVONew(userIds, param.getStartDate(), param.getEndDate(),
+                        param.getProject(),param.getCourseId(),param.getVideoId()
+                );
+        for (QwWatchLogAllStatisticsListVO item : vos) {
+            if(ObjectUtils.isNotNull(item.getProject())){
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    item.setProjectName(sysCourseProject);
+                }
+            }
+            // 课程名
+            if(ObjectUtils.isNotNull(item.getCourseId())) {
+                FsUserCourse course = fsUserCourseCacheService.selectFsUserCourseByCourseId(item.getCourseId());
+                if(ObjectUtils.isNotNull(course)){
+                    item.setCourseName(course.getCourseName());
+                }
+            }
+            // 小节名
+            if(ObjectUtils.isNotNull(item.getVideoId())) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
+                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                    item.setVideoName(fsUserCourseVideo.getTitle());
+                }
+            }
+            // 销售名称
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(companyUser != null) {
+                    item.setQwUserName(companyUser.getUserName());
+                }
+            }
+            // 会员名称
+            if(ObjectUtils.isNotNull(item.getFsUserId())) {
+                FsUser fsUser = fsUserCacheService.selectFsUserById(item.getFsUserId());
+                if(fsUser != null) {
+                    item.setFsUserName(String.format("%d_%s",fsUser.getUserId(),fsUser.getUsername()));
+                }
+            }
+
+            list.add(item);
+        }
+
+        Long total = qwWatchLogMapper
+                .selectQwWatchLogAllStatisticsListVONewCount(userIds, param.getStartDate(), param.getEndDate(),
+                        param.getProject(),param.getCourseId(),param.getVideoId()
+                );
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(total);
+
+        return rspData;
+    }
+
+    @Override
+    public TableDataInfo selectQwWatchLogStatisticsListVONew(QwWatchLogStatisticsListParam param) {
+        List<Long> userIds = null;
+        // 获取当前公司下的所有销售
+        if(param.getCompanyUserId()  == null){
+            List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserByCompanyId(param.getCompanyId());
+            if(CollectionUtils.isEmpty(companyUsers)){
+                throw new CustomException("该公司下面没有任何销售!");
+            }
+            userIds = companyUsers.stream()
+                    .map(CompanyUser::getUserId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        } else {
+            userIds = new ArrayList<>();
+            userIds.add(param.getCompanyUserId());
+        }
+
+
+        List<QwWatchLogStatisticsListVO> list = new ArrayList<>();
+
+        // 统计销售下面的所有记录
+        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper
+                .selectQwWatchLogByCompanyUserId(userIds
+                        , param.getStartDate(),param.getEndDate(),param.getProject(),param.getCourseId(),param.getVideoId(),param.getPageNum(),param.getPageSize());
+
+        for (QwWatchLogStatisticsListVO item : vos) {
+            Company company = companyCacheService.selectCompanyById(item.getCompanyId());
+            if(ObjectUtils.isNotNull(company)){
+                item.setCompanyUserName(company.getCompanyName());
+                item.setCreateTime(company.getCreateTime());
+            }
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())) {
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(companyUser != null) {
+                    item.setCompanyUserName(String.format("%d_%s",companyUser.getUserId(),companyUser.getUserName()));
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getProject())){
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    item.setProjectName(sysCourseProject);
+                }
+            }
+            // 课程名
+            if(ObjectUtils.isNotNull(item.getCourseId())) {
+                FsUserCourse course = fsUserCourseCacheService.selectFsUserCourseByCourseId(item.getCourseId());
+                if(ObjectUtils.isNotNull(course)){
+                    item.setCourseName(course.getCourseName());
+                }
+            }
+            // 小节名
+            if(ObjectUtils.isNotNull(item.getVideoId())) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
+                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                    item.setVideoName(fsUserCourseVideo.getTitle());
+                }
+            }
+            list.add(item);
+        }
+
+        // 获取总记录数
+        Long total = qwWatchLogMapper
+                .selectQwWatchLogByCompanyUserIdCount(userIds
+                        , param.getStartDate(), param.getEndDate(), param.getProject(), param.getCourseId(), param.getVideoId());
+
+
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(total);
+
+        return rspData;
     }
     }
 
 
     public List<Date> getDatesBetween(Date sTime, Date eTime) {
     public List<Date> getDatesBetween(Date sTime, Date eTime) {

+ 75 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java

@@ -12,6 +12,7 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -20,6 +21,7 @@ import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.ChronoUnit;
@@ -319,6 +321,79 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
 
 
     }
     }
 
 
+    @Override
+    public void addQwWorkByCourseLastTime() {
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
+        List<Long> extIds = qwWorkTaskMapper.selectQwWorkTaskByType();
+        SimpleDateFormat sdf = new SimpleDateFormat("HHmm"); // 24小时制,如 1100
+        String timeStr = sdf.format(new Date());
+        int lastTime = Integer.parseInt(timeStr);
+        Set<Long> extIdSet = new HashSet<>(extIds);
+        LocalDate today = LocalDate.now();
+        for (QwSop qwSop : qwSops) {
+            if (qwSop.getCourseDay()==null){
+                continue;
+            }
+            Integer courseDay=qwSop.getCourseDay()-1;
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectSopUserLogsInfoBySopId(qwSop.getId());
+            if (qwSopLogs==null || qwSopLogs.isEmpty()) {
+                continue;
+            }
+
+            List<FsCourseWatchLogTaskVO> fsCourseWatchLogs = courseWatchLogMapper.selectFsCourseWatchLogByDaySopId3LastTime(qwSop.getId(),lastTime);
+            if (fsCourseWatchLogs==null || fsCourseWatchLogs.isEmpty()) {
+                continue;
+            }
+            List<QwWorkTask> qwWorkTasks = new ArrayList<>();
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                Map<Long, FsCourseWatchLogTaskVO> map = fsCourseWatchLogs.stream()
+                        .collect(Collectors.toMap(FsCourseWatchLogTaskVO::getQwExternalContactId, data -> data,(oldValue, newValue) -> newValue ));
+                FsCourseWatchLogTaskVO fsCourseWatchLog = map.get(qwSopLog.getExternalId());
+
+                if (fsCourseWatchLog == null) {
+                    continue;
+                }
+                if (extIdSet.contains(fsCourseWatchLog.getQwExternalContactId())) {
+                    continue;
+                }
+                String createTime = qwSopLog.getCreateTime();
+                LocalDate createDate = LocalDate.parse(createTime.substring(0, 10), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+                Integer day = (Math.toIntExact(ChronoUnit.DAYS.between(createDate, today))) + 1 - courseDay;
+                if (day<=7){
+                    continue;
+                }
+                Integer score = getQwWorkCourseScore(fsCourseWatchLog.getLogType(), day,fsCourseWatchLog.getLevel());
+                if (score==0){
+                    continue;
+                }
+                QwWorkTask qwWorkTask = new QwWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                qwWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+                qwWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                qwWorkTask.setSopId(qwSop.getId());
+                qwWorkTask.setQwUserId(fsCourseWatchLog.getQwUserId());
+                qwWorkTask.setType(2);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle("第"+day+"天"+"待看课");
+                qwWorkTask.setScore(score);
+                qwWorkTasks.add(qwWorkTask);
+
+            }
+            if (!qwWorkTasks.isEmpty()){
+
+                qwWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+            }
+
+        }
+    }
+
+    @Override
+    public List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask) {
+        return qwWorkTaskMapper.selectQwWorkTaskAllListVO(qwWorkTask);
+    }
+
     private void addQwWorkTask(LocalDate today, Integer day, QwSop qwSop, String title,Map<Integer, Integer> map) {
     private void addQwWorkTask(LocalDate today, Integer day, QwSop qwSop, String title,Map<Integer, Integer> map) {
         if (day>7){
         if (day>7){
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

+ 1 - 1
fs-service/src/main/java/com/fs/qw/vo/QwSopRuleTimeVO.java

@@ -36,7 +36,7 @@ public class QwSopRuleTimeVO extends BaseEntity {
     private Long companyId;
     private Long companyId;
 
 
     /**
     /**
-     发送类型 1定时接口发送 2 Ai接口发送  3完课  4AI对话 5一键群发 6一键群发app
+      发送类型  1企微发送 2 Ai接口发送  3:完课发送  4:AI对话 5一键群发 6客户群群发 7欢迎语补发 8AI 9清除草稿 10发送草稿 11 课程模板类型 12 一键群发APP
      **/
      **/
     private Integer sendType;
     private Integer sendType;
 
 

+ 26 - 0
fs-service/src/main/java/com/fs/qw/vo/QwWatchLogAllStatisticsListVO.java

@@ -78,5 +78,31 @@ public class QwWatchLogAllStatisticsListVO {
     Long d29Over;
     Long d29Over;
     Long d30Online;
     Long d30Online;
     Long d30Over;
     Long d30Over;
+    /**
+     * 项目
+     */
+    private Long project;
+    private String projectName;
+
+    /**
+     * 课程
+     */
+    private Long courseId;
+    private String courseName;
+
+    /**
+     * 小节
+     */
+    private Long videoId;
+    private String videoName;
+
+    private Long companyId;
+
+    private Long companyUserId;
+
+    private Long fsUserId;
+
+    private String fsUserName;
+
 
 
 }
 }

+ 37 - 22
fs-service/src/main/java/com/fs/qw/vo/QwWatchLogStatisticsListVO.java

@@ -7,27 +7,42 @@ import java.util.Date;
 
 
 @Data
 @Data
 public class QwWatchLogStatisticsListVO {
 public class QwWatchLogStatisticsListVO {
-    Long id;
-    String qwUserName;
+    private Long id;
+    private String qwUserName;
+    private String companyUserName;
+    private Long companyUserId;
+    private Long companyId;
     @JsonFormat(pattern = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
-    Date createTime;
-
-    Long line;//进线数
-
-    Long firstOnline;//先导课上线
-
-    Long firstOver;//先导课完课
-
-    Long d1Online;//首日上线
-
-    Long d1Over;//首日完课
-
-    Long sign;//综合报名数
-    Long interact;//互动数
-    Long a;
-    Long b;
-    Long c;
-    Long d;
-    Long los;//流失数
-    Long del;//删除数
+    private Date createTime;
+    /**
+     * 项目
+     */
+    private Long project;
+    private String projectName;
+
+    /**
+     * 课程
+     */
+    private Long courseId;
+    private String courseName;
+
+    /**
+     * 小节
+     */
+    private Long videoId;
+    private String videoName;
+
+    private Long line;//进线数
+    private Long firstOnline;//先导课上线
+    private Long firstOver;//先导课完课
+    private Long d1Online;//首日上线
+    private Long d1Over;//首日完课
+    private Long sign;//综合报名数
+    private Long interact;//互动数
+    private Long a;
+    private Long b;
+    private Long c;
+    private Long d;
+    private Long los;//流失数
+    private Long del;//删除数
 }
 }

+ 22 - 0
fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java

@@ -0,0 +1,22 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+@Data
+public class QwWorkTaskAllListVO {
+    /** 外部联系人id */
+
+    private String qwUserName;
+
+    private String companyUserName;
+
+    private Integer status0;
+
+    private Integer status1;
+
+    private Integer status2;
+
+    private Integer status3;
+
+    private String createTime;
+}

+ 24 - 3
fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskListVO.java

@@ -1,8 +1,14 @@
 package com.fs.qw.vo;
 package com.fs.qw.vo;
 
 
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.Date;
+import java.util.List;
+
 @Data
 @Data
 public class QwWorkTaskListVO {
 public class QwWorkTaskListVO {
     private Long id;
     private Long id;
@@ -21,10 +27,12 @@ public class QwWorkTaskListVO {
     /** 类别 1先导 2 课程 3 大小转 4 转人工 */
     /** 类别 1先导 2 课程 3 大小转 4 转人工 */
     @Excel(name = "类别 1先导 2 课程 3 大小转 4 转人工")
     @Excel(name = "类别 1先导 2 课程 3 大小转 4 转人工")
     private Integer type;
     private Integer type;
+    private String typeText;
 
 
     /** 状态 0 待处理 1 已处理 3 过期 */
     /** 状态 0 待处理 1 已处理 3 过期 */
     @Excel(name = "状态 0 待处理 1 已处理 3 过期")
     @Excel(name = "状态 0 待处理 1 已处理 3 过期")
     private Integer status;
     private Integer status;
+    private String statusText;
 
 
     /** 分值 */
     /** 分值 */
     @Excel(name = "分值")
     @Excel(name = "分值")
@@ -35,12 +43,25 @@ public class QwWorkTaskListVO {
     private String sopId;
     private String sopId;
 
 
     /** 公司id */
     /** 公司id */
-    @Excel(name = "公司id")
     private Long companyId;
     private Long companyId;
+    @Excel(name = "公司名称")
+    private String companyName;
 
 
     /** 用户id */
     /** 用户id */
-    @Excel(name = "用户id")
     private Long companyUserId;
     private Long companyUserId;
-
+    /**
+     * 销售名称
+     */
+    @Excel(name = "销售名称")
+    private String companyUserName;
     private String title;
     private String title;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    List<Integer> logs;
 }
 }

+ 30 - 0
fs-service/src/main/java/com/fs/qw/vo/TransferCustomDTO.java

@@ -0,0 +1,30 @@
+package com.fs.qw.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class TransferCustomDTO implements Serializable {
+    /**
+     * 用户名
+     */
+    private String userName;
+    private Long userId;
+    /**
+     * 转移前销售
+     */
+    private String beforeCompanyUserName;
+    private Long beforeCompanyUserId;
+    /**
+     * 转移后销售
+     */
+    private String afterCompanyUserName;
+    private Long afterCompanyUserId;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -92,7 +92,7 @@ public class QwSop implements Serializable
     */
     */
     private Integer isRating;
     private Integer isRating;
 
 
-    private String courseDay;
+    private Integer courseDay;
 
 
 
 
     // 是否固定营期
     // 是否固定营期

+ 2 - 0
fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java

@@ -48,5 +48,7 @@ public class SopUserLogsInfo implements Serializable {
     private Integer filterMode;
     private Integer filterMode;
     @TableField(exist = false)
     @TableField(exist = false)
     private String chatId;
     private String chatId;
+    @TableField(exist = false)
+    private List<String> tagNames;
 
 
 }
 }

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

@@ -187,15 +187,23 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     @Update("update sop_user_logs_info set fs_user_id=#{fsUserId} where external_id =#{externalId}")
     @Update("update sop_user_logs_info set fs_user_id=#{fsUserId} where external_id =#{externalId}")
     int updateQwExternalContactChangeUserId(@Param("externalId") Long externalId,@Param("fsUserId") Long fsUserId);
     int updateQwExternalContactChangeUserId(@Param("externalId") Long externalId,@Param("fsUserId") Long fsUserId);
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     @Select("SELECT external_id  FROM `sop_user_logs_info` where sop_id = #{sopId}  and Date(create_time) = Date(#{minDay})")
     @Select("SELECT external_id  FROM `sop_user_logs_info` where sop_id = #{sopId}  and Date(create_time) = Date(#{minDay})")
     List<SopUserLogsInfo> selectDayBySopId(@Param("sopId")String sopId, @Param("minDay")String minDay);
     List<SopUserLogsInfo> selectDayBySopId(@Param("sopId")String sopId, @Param("minDay")String minDay);
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     @Select("SELECT external_id,create_time  FROM sop_user_logs_info where sop_id = #{sopId} ")
     @Select("SELECT external_id,create_time  FROM sop_user_logs_info where sop_id = #{sopId} ")
     List<SopUserLogsInfo> selectSopUserLogsInfoBySopId(@Param("sopId")String sopId);
     List<SopUserLogsInfo> selectSopUserLogsInfoBySopId(@Param("sopId")String sopId);
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     @Select("SELECT id  FROM sop_user_logs_info where external_id = #{extId} ")
     @Select("SELECT id  FROM sop_user_logs_info where external_id = #{extId} ")
     List<String> selectSopUserLogsInfoByExtId(@Param("extId")Long extId );
     List<String> selectSopUserLogsInfoByExtId(@Param("extId")Long extId );
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     void updateSopUserLogsInfoFsUserIdById(@Param("data")List<String> list,  @Param("userId") Long userId);
     void updateSopUserLogsInfoFsUserIdById(@Param("data")List<String> list,  @Param("userId") Long userId);
+
+    @DataSource(DataSourceType.SOP)
+    @Select("SELECT create_time,fs_user_id  FROM sop_user_logs_info where sop_id = #{sopId} ")
+    List<SopUserLogsInfo> selectFsUserIdSopUserLogsInfoBySopId(@Param("sopId")String sopId);
 }
 }

+ 5 - 2
fs-service/src/main/java/com/fs/sop/service/IQwSopLogsService.java

@@ -92,13 +92,16 @@ public interface IQwSopLogsService
      * 创建企业群发
      * 创建企业群发
      */
      */
     public void createCorpMassSending(String date);
     public void createCorpMassSending(String date);
-
+    /**
+     *  创建企业群发(按照营期发)
+     */
+    public void createCorpMassSendingByUserLogs(String date);
 
 
     /**
     /**
      *  检索执行符合条件的定时任务的结果回调(企业微信)
      *  检索执行符合条件的定时任务的结果回调(企业微信)
      */
      */
     public void qwSopLogsResult();
     public void qwSopLogsResult();
-
+    public void qwSopLogsResultNew() throws InterruptedException;
 
 
     public int updateQwSopLogsByWatchLogType(String id,String remark);
     public int updateQwSopLogsByWatchLogType(String id,String remark);
 
 

+ 5 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -21,6 +21,11 @@ public interface ISopUserLogsInfoService {
      */
      */
     void update(SopUserLogsInfo info);
     void update(SopUserLogsInfo info);
 
 
+    /**
+     * 修改
+     */
+    void updateSopUserInfoByExternalId(Long qwExternalId,Long userId);
+
     /**
     /**
      * 根据ID查询记录
      * 根据ID查询记录
      * @param id 主键ID
      * @param id 主键ID

+ 511 - 127
fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java

@@ -47,10 +47,8 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.*;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -527,12 +525,139 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                 qwSopLogs.size(), (endTime - startTime));
                 qwSopLogs.size(), (endTime - startTime));
     }
     }
 
 
+    @Override
+    public void qwSopLogsResultNew() {
+
+        logger.info("开始执行企业微信群发消息结果查询任务");
+        long startTime = System.currentTimeMillis();
+
+        List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectSopLogsByCreateCorpMassSendResult();
+        if (qwSopLogsList.isEmpty()) {
+            logger.info("没有需要查询结果的群发消息记录");
+            return;
+        }
+
+
+        Map<String, List<QwSopLogs>> grouped = qwSopLogsList.stream().collect(
+                Collectors.groupingBy(log -> log.getQwUserid() + "|" + log.getCorpId() + "|" + log.getMsgId())
+        );
+        for (Map.Entry<String, List<QwSopLogs>> entry : grouped.entrySet()) {
+            String key = entry.getKey();
+            List<QwSopLogs> corpLogs = entry.getValue();
+
+            String[] keys = key.split("\\|");
+            String qwUserid = keys[0];
+            String corpId = keys[1];
+            String msgID = keys[2];
+
+            QwGetGroupmsgSendParam param = new QwGetGroupmsgSendParam();
+            param.setMsgid(msgID);
+            param.setUserid(qwUserid);
+            param.setLimit(1000);
+
+            fetchAndProcessAllPages(param, corpId, corpLogs, msgID);
+
+        }
+
+        long endTime = System.currentTimeMillis();
+        logger.info("企业微信群发消息结果查询任务完成,处理记录总数: {},总耗时: {}ms",
+                qwSopLogsList.size(), (endTime - startTime));
+
+    }
+
+    private void fetchAndProcessAllPages(QwGetGroupmsgSendParam param, String corpId, List<QwSopLogs> logs, String msgId) {
+        String nextCursor = null;
+
+        do {
+            param.setCursor(nextCursor);
+            QwGroupmsgSendResult result = qwApiService.getGroupmsgSendResult(param, corpId);
+
+            if (result == null) {
+                logger.error("接口调用失败: {}", param);
+                return;
+            }
+
+            if (result.getErrCode() == 45033) {
+                try {
+                    Thread.sleep(2000 + new Random().nextInt(1000));
+                    result = qwApiService.getGroupmsgSendResult(param, corpId);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    logger.error("线程中断", e);
+                    return;
+                }
+            }
+
+            if (result.getErrCode() != 0) {
+                logger.error("查询失败: {}, errCode: {}, errMsg: {}", param, result.getErrCode(), result.getErrMsg());
+                return;
+            }
+
+            processPageResult(result, logs, corpId, msgId);
+            nextCursor = result.getNextCursor();
+
+        } while (nextCursor != null && !nextCursor.isEmpty());
+    }
+
+
+    private void processPageResult(QwGroupmsgSendResult result, List<QwSopLogs> logs, String corpId, String msgId) {
+        Map<String, SendItemResult> sendMap = result.getSendList().stream()
+                .collect(Collectors.toMap(
+                        r -> r.getUserId() + "_" + r.getExternalUserId() + "_" + corpId + "_" + msgId,
+                        Function.identity(),
+                        (a, b) -> a  // 如果重复,保留第一个
+                ));
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String now = LocalDateTime.now().format(formatter);
+
+        // 只处理匹配得上的记录
+        List<QwSopLogs> matchedLogs = new ArrayList<>();
+
+        for (QwSopLogs log : logs) {
+            String logKey = log.getQwUserid() + "_" + log.getExternalUserId() + "_" + log.getCorpId() + "_" + msgId;
+            SendItemResult matched = sendMap.get(logKey);
+
+            if (matched != null) {
+
+                switch (matched.getStatus()) {
+                    case 0:
+                        log.setSendStatus(5L);
+                        log.setRemark("员工未发送,已作废");
+                        break;
+                    case 1:
+                        log.setSendStatus(1L);
+                        log.setReceivingStatus(1L);
+                        break;
+                    case 2:
+                    case 3:
+                        log.setSendType(2);
+                        log.setSendStatus(3L);
+                        log.setRemark("客户无法接收,补发");
+                        log.setReceivingStatus(0L);
+                        log.setSendTime(now);
+                        log.setSort(30000001);
+                        break;
+                    default:
+                        break;
+                }
+
+                matchedLogs.add(log);
+            }
+        }
+
+        if (!matchedLogs.isEmpty()) {
+            batchUpdateDatabase(matchedLogs);
+        }
+    }
+
+
     /**
     /**
      * 批量更新数据库
      * 批量更新数据库
      * @param updateList 需要更新的记录列表
      * @param updateList 需要更新的记录列表
      */
      */
     private void batchUpdateDatabase(List<QwSopLogs> updateList) {
     private void batchUpdateDatabase(List<QwSopLogs> updateList) {
-        int updateBatchSize = 1000;
+        int updateBatchSize = 500;
         for (int i = 0; i < updateList.size(); i += updateBatchSize) {
         for (int i = 0; i < updateList.size(); i += updateBatchSize) {
             int endIndex = Math.min(i + updateBatchSize, updateList.size());
             int endIndex = Math.min(i + updateBatchSize, updateList.size());
             List<QwSopLogs> batch = updateList.subList(i, endIndex);
             List<QwSopLogs> batch = updateList.subList(i, endIndex);
@@ -545,7 +670,6 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
         }
         }
     }
     }
 
 
-
     @Override
     @Override
     public int updateQwSopLogsByWatchLogType(String id,String remark) {
     public int updateQwSopLogsByWatchLogType(String id,String remark) {
         return qwSopLogsMapper.updateQwSopLogsByWatchLogType(id,remark);
         return qwSopLogsMapper.updateQwSopLogsByWatchLogType(id,remark);
@@ -586,6 +710,13 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             List<QwSopLogsDoSendListTVO> logsByJsApi = qwSopLogsMapper.getQwSopLogsByJsApiAll(param);
             List<QwSopLogsDoSendListTVO> logsByJsApi = qwSopLogsMapper.getQwSopLogsByJsApiAll(param);
 
 
 
 
+            // 优先返回 sendType == 8 的第一条记录
+            List<QwSopLogsDoSendListTVO> result = logsByJsApi.stream()
+                    .filter(log -> log.getSendType() == 8)
+                    .findFirst()
+                    .map(Collections::singletonList) // 单元素不可变 List
+                    .orElse(logsByJsApi); // 如果没有匹配项,返回原列表
+
             // 查询员工信息的id
             // 查询员工信息的id
             QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
             QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
 
 
@@ -600,119 +731,126 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             contactHParam.setCorpId(param.getCorpId().trim());
             contactHParam.setCorpId(param.getCorpId().trim());
             Long qwExternalContactId = qwExternalContactMapper.getQwExternalContactId(contactHParam);
             Long qwExternalContactId = qwExternalContactMapper.getQwExternalContactId(contactHParam);
 
 
-            for (QwSopLogsDoSendListTVO log : logsByJsApi) {
+            for (QwSopLogsDoSendListTVO log : result) {
 
 
                 try {
                 try {
-                    switch (log.getSendType()){
-                        case 3:
-                            try {
-                                // 使用现代的日期时间 API
-                                LocalDateTime sendTime = log.getSendTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
-                                LocalDateTime expiryDateTime = sendTime.plusHours(12);
-                                LocalDateTime now = LocalDateTime.now();
+                    if (log.getSendType()==8){
+                        //(AI消息不做任何判断 直接入)切 只取了一条
+                        sendJsApiList.add(log);
 
 
-                                // 判断是否过期
-                                if (now.isAfter(expiryDateTime)) {
-                                    // 作废消息
-                                    qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "已过期,不发送");
-                                } else {
-                                    sendJsApiList.add(log);
+                    }else {
+                        switch (log.getSendType()) {
+                            case 3:
+                            case 7:
+                                try {
+                                    // 使用现代的日期时间 API
+                                    LocalDateTime sendTime = log.getSendTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+                                    LocalDateTime expiryDateTime = sendTime.plusHours(12);
+                                    LocalDateTime now = LocalDateTime.now();
+
+                                    // 判断是否过期
+                                    if (now.isAfter(expiryDateTime)) {
+                                        // 作废消息
+                                        qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "已过期,不发送");
+                                    } else {
+                                        sendJsApiList.add(log);
+                                    }
+                                } catch (Exception e) {
+                                    // 记录错误日志
+                                    logger.error("Error processing log in logsByJsApiOver: {}", log.getId() + ":" + e);
                                 }
                                 }
-                            } catch (Exception e) {
-                                // 记录错误日志
-                                logger.error("Error processing log in logsByJsApiOver: {}", log.getId()+":"+e);
-                            }
-                            break;
-                        case 6:
-                            sendJsApiList.add(log);
-                            break;
-                        default:
-                            QwSopTempSetting.Content content = JSON.parseObject(log.getContentJson(), QwSopTempSetting.Content.class);
-                            List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
-
-                            //有app的异步推送消息
-                            if (!setting.isEmpty()) {
-                                asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
-                            }
+                                break;
+                            case 6:
+                            case 9:
+                            case 10:
+                                sendJsApiList.add(log);
+                                break;
+                            default:
+                                QwSopTempSetting.Content content = JSON.parseObject(log.getContentJson(), QwSopTempSetting.Content.class);
+                                List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
 
 
-                            if (log.getExpiryTime() == null) {
-                                // 作废消息
-                                qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "SOP任务被删除");
-                            } else {
-                                Integer expiryTime = log.getExpiryTime();
-                                LocalDateTime sendTime = log.getSendTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
-                                LocalDateTime expiryDateTime = sendTime.plusHours(expiryTime);
-                                LocalDateTime now = LocalDateTime.now();
+                                //有app的异步推送消息
+                                if (!setting.isEmpty()) {
+                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
+                                }
 
 
-                                // 判断是否过期
-                                if (now.isAfter(expiryDateTime)) {
+                                if (log.getExpiryTime() == null) {
                                     // 作废消息
                                     // 作废消息
-                                    qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "已过期,不发送");
-                                }
-                                else {
-                                    switch (content.getType()) {
-                                        case 1:
-                                            // 普通消息,不做判断,加入发送列表
-                                            sendJsApiList.add(log);
-                                            break;
-                                        case 2:
-                                            // 课程消息,进行复杂的条件判断
-                                            FsCourseWatchLog watchLog = watchLogService.getWatchCourseLogVideoBySop(
-                                                    Long.valueOf(content.getVideoId()),
-                                                    String.valueOf(qwId),
-                                                    qwExternalContactId
-                                            );
-
-                                            Integer courseType = content.getCourseType();
-                                            String logId = log.getId();
-
-                                            if (content.getCourseType()==null){
-                                                qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "模板未选消息类型,不发送");
-                                            }
-                                            if (watchLog != null) {
+                                    qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "SOP任务被删除");
+                                } else {
+                                    Integer expiryTime = log.getExpiryTime();
+                                    LocalDateTime sendTime = log.getSendTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+                                    LocalDateTime expiryDateTime = sendTime.plusHours(expiryTime);
+                                    LocalDateTime now = LocalDateTime.now();
 
 
-                                                //新逻辑
-                                                if (isCourseTypeValid(courseType, watchLog.getLogType())) {
-                                                    // 加入到发送列表中
-                                                    sendJsApiList.add(log);
-                                                } else {
-                                                    // 作废消息
-                                                    qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
+                                    // 判断是否过期
+                                    if (now.isAfter(expiryDateTime)) {
+                                        // 作废消息
+                                        qwSopLogsService.updateQwSopLogsByWatchLogType(log.getId(), "已过期,不发送");
+                                    } else {
+                                        switch (content.getType()) {
+                                            case 1:
+                                                // 普通消息,不做判断,加入发送列表
+                                                sendJsApiList.add(log);
+                                                break;
+                                            case 2:
+                                                // 课程消息,进行复杂的条件判断
+                                                FsCourseWatchLog watchLog = watchLogService.getWatchCourseLogVideoBySop(
+                                                        Long.valueOf(content.getVideoId()),
+                                                        String.valueOf(qwId),
+                                                        qwExternalContactId
+                                                );
+
+                                                Integer courseType = content.getCourseType();
+                                                String logId = log.getId();
+
+                                                if (content.getCourseType() == null) {
+                                                    qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "模板未选消息类型,不发送");
                                                 }
                                                 }
+                                                if (watchLog != null) {
+
+                                                    //新逻辑
+                                                    if (isCourseTypeValid(courseType, watchLog.getLogType())) {
+                                                        // 加入到发送列表中
+                                                        sendJsApiList.add(log);
+                                                    } else {
+                                                        // 作废消息
+                                                        qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
+                                                    }
 
 
 
 
-                                            } else {
-                                                // 没有观看记录,只发普通消息
-                                                if (courseType == 0) {
-                                                    sendJsApiList.add(log);
                                                 } else {
                                                 } else {
-                                                    qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "无观看记录,不发送");
+                                                    // 没有观看记录,只发普通消息
+                                                    if (courseType == 0) {
+                                                        sendJsApiList.add(log);
+                                                    } else {
+                                                        qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "无观看记录,不发送");
+                                                    }
                                                 }
                                                 }
-                                            }
-                                            break;
-                                        case 3:
-                                            // 订单消息,不做判断,加入发送列表
-                                            sendJsApiList.add(log);
-                                            break;
-                                        case 4:
-                                            // Ai消息,不加入序列单独处理
-                                            try {
-                                                logger.info("Ai触达消息 : {}", log.getId());
-                                                //  fastGptChatSessionService.sendAiTouch(log,param.getCorpId().trim());
-                                            }catch (Exception e){
-                                                logger.error("Ai消息 : {}", content.getType());
-                                            }
-                                            break;
-                                        default:
-                                            // 未知类型,记录警告
-                                            logger.error("Unknown content type logsByJsApi: {}", content.getType());
-                                            break;
+                                                break;
+                                            case 3:
+                                                // 订单消息,不做判断,加入发送列表
+                                                sendJsApiList.add(log);
+                                                break;
+                                            case 4:
+                                                // Ai消息,不加入序列单独处理
+                                                try {
+                                                    logger.info("Ai触达消息 : {}", log.getId());
+                                                    //  fastGptChatSessionService.sendAiTouch(log,param.getCorpId().trim());
+                                                } catch (Exception e) {
+                                                    logger.error("Ai消息 : {}", content.getType());
+                                                }
+                                                break;
+                                            default:
+                                                // 未知类型,记录警告
+                                                logger.error("Unknown content type logsByJsApi: {}", content.getType());
+                                                break;
+                                        }
                                     }
                                     }
                                 }
                                 }
-                            }
-                            break;
+                                break;
+                        }
                     }
                     }
-
                 } catch (Exception e) {
                 } catch (Exception e) {
                     // 记录错误日志
                     // 记录错误日志
                     logger.error("Error processing log in logsByJsApi: {}", log.getId()+" : " + e);
                     logger.error("Error processing log in logsByJsApi: {}", log.getId()+" : " + e);
@@ -1241,7 +1379,7 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                                             if (content == null || content.getSetting() == null) continue;
                                             if (content == null || content.getSetting() == null) continue;
                                             Long courseId = content.getCourseId();
                                             Long courseId = content.getCourseId();
                                             for (QwSopTempSetting.Content.Setting set : content.getSetting()) {
                                             for (QwSopTempSetting.Content.Setting set : content.getSetting()) {
-                                                processContent(set, corpId, templateSop, attachments, courseId,config);
+                                                processContent(set, corpId, templateSop, attachments, courseId);
                                             }
                                             }
                                         } catch (Exception e) {
                                         } catch (Exception e) {
                                             logger.error("消息内容解析失败,logId:{}", log.getId(), e);
                                             logger.error("消息内容解析失败,logId:{}", log.getId(), e);
@@ -1323,10 +1461,267 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
         logger.info("企业微信群发消息创建任务执行完成,总耗时: {} 毫秒", (endTime - startTime));
         logger.info("企业微信群发消息创建任务执行完成,总耗时: {} 毫秒", (endTime - startTime));
     }
     }
 
 
+    // 处理不同类型的内容
+//    private void processContent(QwSopTempSetting.Content.Setting set, String corpId,
+//                                QwMsgTemplateSop templateSop, List<QwMsgTemplateSop.Attachment> attachments
+//                                ,Long courseId,CourseConfig config) {
+//        switch (set.getContentType()) {
+//            case "1":
+//                templateSop.setTextContent(set.getValue());
+//                break;
+//            case "2":
+//                handleImageAttachment(set, corpId, attachments);
+//                break;
+//            case "3":
+//                handleLinkAttachment(set, corpId, attachments);
+//                break;
+//            case "4":
+//                handleMiniProgramAttachment(set, corpId, attachments,courseId,config);
+//                break;
+//        }
+//    }
+//
+//    // 处理图片附件
+//    private void handleImageAttachment(QwSopTempSetting.Content.Setting set, String corpId,
+//                                       List<QwMsgTemplateSop.Attachment> attachments) {
+//        if (StringUtils.isNotEmpty(set.getImgUrl())) {
+//            try {
+//                QwUploadImgResult result = qwApiService.uploadimgs(set.getImgUrl(), corpId);
+//                if (result.getErrcode() == 0) {
+//                    QwMsgTemplateSop.Attachment attachment = new QwMsgTemplateSop.Attachment();
+//                    attachment.setType(2);
+//                    attachment.setImagePicUrl(result.getUrl());
+//                    attachments.add(attachment);
+//                }
+//            } catch (Exception e) {
+//                logger.error("图片上传失败", e);
+//            }
+//        }
+//    }
+//
+//    // 处理链接附件
+//    private void handleLinkAttachment(QwSopTempSetting.Content.Setting set, String corpId,
+//                                      List<QwMsgTemplateSop.Attachment> attachments) {
+//        if (StringUtils.isNotEmpty(set.getLinkImageUrl())) {
+//            try {
+////                QwUploadImgResult result = qwApiService.uploadimgs(set.getLinkImageUrl(), corpId);
+////                if (result.getErrcode() == 0) {
+//                    QwMsgTemplateSop.Attachment attachment = new QwMsgTemplateSop.Attachment();
+//                    attachment.setType(3);
+//                    attachment.setLinkTitle(set.getLinkTitle());
+//                    attachment.setLinkPicurl(set.getLinkImageUrl());
+//                    attachment.setLinkDesc(set.getLinkDescribe());
+//                    attachment.setLinkUrl(set.getLinkUrl());
+//                    attachments.add(attachment);
+////                }
+//            } catch (Exception e) {
+//                logger.error("链接图片上传失败", e);
+//            }
+//        }
+//    }
+//
+//    private void handleMiniProgramAttachment(QwSopTempSetting.Content.Setting set, String corpId,
+//                                             List<QwMsgTemplateSop.Attachment> attachments,
+//                                             Long courseId,CourseConfig config) {
+//        if (StringUtils.isNotEmpty(set.getMiniprogramPage())) {
+//            try {
+//                String key = String.format("miniprogram:%s:%s", corpId, courseId);
+//                String mediaId = redisCache.getCacheObject(key);
+//
+//                if (StringUtils.isNotEmpty(mediaId)) {
+//
+//                    QwMsgTemplateSop.Attachment attachment = new QwMsgTemplateSop.Attachment();
+//                    attachment.setType(4);
+////                    attachment.setMiniProgramTitle(set.getMiniprogramTitle());
+//                    //强制限制
+//                    String title = set.getMiniprogramTitle() != null ? set.getMiniprogramTitle() : "";
+//                    int maxLength = 20;
+//                    attachment.setMiniProgramTitle(title.length() > maxLength ? title.substring(0, maxLength) : title);
+//
+//                    attachment.setMiniProgramPicMediaId(mediaId);
+//
+//                    if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())){
+//                        logger.error("小程序配置为空,设置成固定的默认值。");
+//                        attachment.setMiniProgramAppId("wxc84c6f789ba7f176");
+//                    }else {
+//                        attachment.setMiniProgramAppId(config.getMiniprogramAppid());
+//                    }
+//
+//                    attachment.setMiniProgramPage(set.getMiniprogramPage());
+//                    attachments.add(attachment);
+//                } else {
+//                    logger.error("未找到小程序图片mediaId, corpId:{}, courseId:{}", corpId, courseId);
+//                }
+//            } catch (Exception e) {
+//                logger.error("获取小程序图片mediaId失败", e);
+//            }
+//        }
+//    }
+
+    @Override
+    public void createCorpMassSendingByUserLogs(String date) {
+
+        long startTime = System.currentTimeMillis();
+        logger.info("开始执行企业微信群发消息创建任务");
+
+        List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectSopLogsByCreateCorpMassSending(date);
+        if (qwSopLogsList.isEmpty()) {
+            logger.error("zyp \n【企微官方群发记录为空】");
+            return;
+        }
+
+        Map<String, List<QwSopLogs>> grouped = qwSopLogsList.stream().collect(
+                Collectors.groupingBy(log -> log.getQwUserid() + "|" + log.getCorpId() + "|" + log.getSopId() + "|" + log.getUserLogsId())
+        );
+
+        int threadCount = Math.min(10, Runtime.getRuntime().availableProcessors() + 1);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+        Queue<QwSopLogs> updateQueue = new ConcurrentLinkedQueue<>();
+
+        try {
+            // 并行提交所有分组
+            List<CompletableFuture<Void>> futures = grouped.values().stream()
+                    .map(group -> CompletableFuture.runAsync(() -> handleGroup(group, updateQueue), executor))
+                    .collect(Collectors.toList());
+
+            // 等待所有任务完成
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+        } catch (Exception e) {
+            logger.error("批量并发执行异常", e);
+        } finally {
+            executor.shutdown();
+            try {
+                if (!executor.awaitTermination(300, TimeUnit.SECONDS)) {
+                    logger.error("Executor 未完全关闭");
+                }
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        // 批量更新发送状态
+        batchUpdate(updateQueue);
+
+        long endTime = System.currentTimeMillis();
+        logger.info("企业微信群发任务完成,总耗时:{} 毫秒,更新记录数:{}",
+                endTime - startTime, updateQueue.size());
+
+    }
+
+    private void handleGroup(List<QwSopLogs> logsGroup, Queue<QwSopLogs> updateQueue) {
+
+        try {
+            String firstKey = logsGroup.get(0).getCorpId() + "|" + logsGroup.get(0).getQwUserid();
+            String[] keyParts = firstKey.split("\\|");
+            String corpId = keyParts[0].trim();
+            String qwUserid = keyParts[1].trim();
+
+            QwUser qwUser = qwExternalContactService.getQwUserByRedis(corpId, qwUserid);
+            if (qwUser == null || qwUser.getIsDel() != 0) {
+                logger.error("员工信息无效-不存在或被删除,corpId:{},userId:{}", corpId, qwUserid);
+                logsGroup.forEach(log -> {
+                    log.setSendStatus(3L);
+                    log.setRemark("员工信息无效");
+                    updateQueue.add(log);
+                });
+                return;
+            }
+
+            // 提取内容与目标客户列表
+            String contentJson = logsGroup.get(0).getContentJson();
+            QwSopTempSetting.Content content = JSON.parseObject(contentJson, QwSopTempSetting.Content.class);
+            if (content == null || content.getSetting() == null || content.getSetting().isEmpty()) {
+                logger.warn("消息内容为空或格式异常,key={},json={} ", firstKey, contentJson);
+                logsGroup.forEach(log -> {
+                    log.setSendStatus(3L);
+                    log.setRemark("消息内容为空或格式异常");
+                    updateQueue.add(log);
+                });
+                return;
+            }
+
+            QwMsgTemplateSop template = new QwMsgTemplateSop();
+            template.setChatType("single");
+            template.setAllowSelect(false);
+            template.setSender(qwUserid);
+
+            List<String> externalUserIds = logsGroup.stream()
+                    .map(QwSopLogs::getExternalUserId)
+                    .filter(Objects::nonNull)
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .collect(Collectors.toList());
+            template.setExternalUseridList(externalUserIds);
+
+            List<QwMsgTemplateSop.Attachment> attachments = new ArrayList<>();
+            // 解析并填充消息体
+            for (QwSopTempSetting.Content.Setting set : content.getSetting()) {
+                processContent(set, corpId, template, attachments, content.getCourseId());
+            }
+            template.setAttachments(attachments);
+
+            // 调用企业微信接口
+            QwAddMsgTemplateResult result = qwApiService.addMsgTemplateBySop(template, corpId);
+
+            int errCode = result.getErrCode();
+            String errMsg = result.getErrMsg();
+            Integer nowSort = (errCode == 0 || errCode == 41063) ? 1 : 3;
+            String remark;
+            if (errCode == 0 || errCode == 41063) {
+                remark = null;
+            } else if (errCode == 45033) {
+                remark = "官方接口达到上限补发";
+            } else {
+                remark = "官方有误,sop补发";
+            }
+
+            LocalDateTime now = LocalDateTime.now();
+            String sendTime = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+
+            // 收集更新项
+            for (QwSopLogs log : logsGroup) {
+                if (errCode == 0 || errCode == 41063) {
+                    log.setSendStatus(1L);
+                    log.setMsgId(result.getMsgId());
+                } else {
+                    log.setSendType(2);
+                    log.setSendStatus(3L);
+                    log.setRemark(remark);
+                    log.setReceivingStatus(0L);
+                    log.setSendTime(sendTime);
+                    log.setSort(nowSort);
+                }
+                updateQueue.add(log);
+            }
+
+            if (errCode != 0 && errCode != 41063) {
+                logger.error("企业微信接口-消息发送失败-进入sop补偿,corpId:{},errCode:{},errMsg:{}",
+                        corpId, errCode, errMsg);
+            }
+
+        } catch (Exception e) {
+            logger.error("处理分组异常", e);
+        }
+    }
+
+    /**
+     * 批量更新发送状态
+     */
+    private void batchUpdate(Collection<QwSopLogs> toUpdate) {
+        if (toUpdate.isEmpty()) return;
+        logger.info("开始批量更新,记录数量:{}", toUpdate.size());
+        List<QwSopLogs> list = new ArrayList<>(toUpdate);
+        int batchSize = 500;
+        for (int i = 0; i < list.size(); i += batchSize) {
+            int end = Math.min(i + batchSize, list.size());
+            List<QwSopLogs> subList = list.subList(i, end);
+            qwSopLogsMapper.batchUpdateStatus(subList);
+        }
+    }
+
     // 处理不同类型的内容
     // 处理不同类型的内容
     private void processContent(QwSopTempSetting.Content.Setting set, String corpId,
     private void processContent(QwSopTempSetting.Content.Setting set, String corpId,
-                                QwMsgTemplateSop templateSop, List<QwMsgTemplateSop.Attachment> attachments
-                                ,Long courseId,CourseConfig config) {
+                                QwMsgTemplateSop templateSop, List<QwMsgTemplateSop.Attachment> attachments,Long courseId) {
         switch (set.getContentType()) {
         switch (set.getContentType()) {
             case "1":
             case "1":
                 templateSop.setTextContent(set.getValue());
                 templateSop.setTextContent(set.getValue());
@@ -1338,7 +1733,7 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                 handleLinkAttachment(set, corpId, attachments);
                 handleLinkAttachment(set, corpId, attachments);
                 break;
                 break;
             case "4":
             case "4":
-                handleMiniProgramAttachment(set, corpId, attachments,courseId,config);
+                handleMiniProgramAttachment(set, corpId, attachments,courseId);
                 break;
                 break;
         }
         }
     }
     }
@@ -1368,13 +1763,13 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             try {
             try {
 //                QwUploadImgResult result = qwApiService.uploadimgs(set.getLinkImageUrl(), corpId);
 //                QwUploadImgResult result = qwApiService.uploadimgs(set.getLinkImageUrl(), corpId);
 //                if (result.getErrcode() == 0) {
 //                if (result.getErrcode() == 0) {
-                    QwMsgTemplateSop.Attachment attachment = new QwMsgTemplateSop.Attachment();
-                    attachment.setType(3);
-                    attachment.setLinkTitle(set.getLinkTitle());
-                    attachment.setLinkPicurl(set.getLinkImageUrl());
-                    attachment.setLinkDesc(set.getLinkDescribe());
-                    attachment.setLinkUrl(set.getLinkUrl());
-                    attachments.add(attachment);
+                QwMsgTemplateSop.Attachment attachment = new QwMsgTemplateSop.Attachment();
+                attachment.setType(3);
+                attachment.setLinkTitle(set.getLinkTitle());
+                attachment.setLinkPicurl(set.getLinkImageUrl());
+                attachment.setLinkDesc(set.getLinkDescribe());
+                attachment.setLinkUrl(set.getLinkUrl());
+                attachments.add(attachment);
 //                }
 //                }
             } catch (Exception e) {
             } catch (Exception e) {
                 logger.error("链接图片上传失败", e);
                 logger.error("链接图片上传失败", e);
@@ -1382,9 +1777,7 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
         }
         }
     }
     }
 
 
-    private void handleMiniProgramAttachment(QwSopTempSetting.Content.Setting set, String corpId,
-                                             List<QwMsgTemplateSop.Attachment> attachments,
-                                             Long courseId,CourseConfig config) {
+    private void handleMiniProgramAttachment(QwSopTempSetting.Content.Setting set, String corpId, List<QwMsgTemplateSop.Attachment> attachments,Long courseId) {
         if (StringUtils.isNotEmpty(set.getMiniprogramPage())) {
         if (StringUtils.isNotEmpty(set.getMiniprogramPage())) {
             try {
             try {
                 String key = String.format("miniprogram:%s:%s", corpId, courseId);
                 String key = String.format("miniprogram:%s:%s", corpId, courseId);
@@ -1399,16 +1792,8 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                     String title = set.getMiniprogramTitle() != null ? set.getMiniprogramTitle() : "";
                     String title = set.getMiniprogramTitle() != null ? set.getMiniprogramTitle() : "";
                     int maxLength = 20;
                     int maxLength = 20;
                     attachment.setMiniProgramTitle(title.length() > maxLength ? title.substring(0, maxLength) : title);
                     attachment.setMiniProgramTitle(title.length() > maxLength ? title.substring(0, maxLength) : title);
-
                     attachment.setMiniProgramPicMediaId(mediaId);
                     attachment.setMiniProgramPicMediaId(mediaId);
-
-                    if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())){
-                        logger.error("小程序配置为空,设置成固定的默认值。");
-                        attachment.setMiniProgramAppId("wxc84c6f789ba7f176");
-                    }else {
-                        attachment.setMiniProgramAppId(config.getMiniprogramAppid());
-                    }
-
+                    attachment.setMiniProgramAppId(set.getMiniprogramAppid());
                     attachment.setMiniProgramPage(set.getMiniprogramPage());
                     attachment.setMiniProgramPage(set.getMiniprogramPage());
                     attachments.add(attachment);
                     attachments.add(attachment);
                 } else {
                 } else {
@@ -1421,5 +1806,4 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
     }
     }
 
 
 
 
-
 }
 }

+ 53 - 16
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -22,15 +22,16 @@ import com.fs.course.service.IFsCourseLinkService;
 import com.fs.fastGpt.domain.FastGptChatReplaceWords;
 import com.fs.fastGpt.domain.FastGptChatReplaceWords;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
 import com.fs.qw.domain.*;
 import com.fs.qw.domain.*;
-import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qw.mapper.QwGroupChatMapper;
-import com.fs.qw.mapper.QwGroupChatUserMapper;
-import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.*;
+import com.fs.qw.param.QwExternalContactVOTime;
+import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.service.impl.AsyncSopTestService;
+import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import com.fs.qw.vo.GroupUserExternalVo;
 import com.fs.qw.vo.GroupUserExternalVo;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.qw.vo.QwTagGroupListVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
@@ -128,10 +129,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
 
     @Autowired
     @Autowired
     private IFsCourseLinkService fsCourseLinkService;
     private IFsCourseLinkService fsCourseLinkService;
+    @Autowired
+    private QwTagMapper qwTagMapper;
 
 
     @Autowired
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     private QwExternalContactMapper qwExternalContactMapper;
 
 
+    @Autowired
+    private QwExternalContactServiceImpl qwExternalContactService;
+
     @Autowired
     @Autowired
     private IQwCompanyService iQwCompanyService;
     private IQwCompanyService iQwCompanyService;
 
 
@@ -151,6 +157,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.updateById(info);
         sopUserLogsInfoMapper.updateById(info);
     }
     }
 
 
+    @Override
+    public void updateSopUserInfoByExternalId(Long qwExternalId, Long userId) {
+        sopUserLogsInfoMapper.updateQwExternalContactChangeUserId(qwExternalId,userId);
+    }
+
     @Override
     @Override
     public SopUserLogsInfo getById(String id) {
     public SopUserLogsInfo getById(String id) {
         return sopUserLogsInfoMapper.selectById(id);
         return sopUserLogsInfoMapper.selectById(id);
@@ -173,7 +184,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
 
     @Override
     @Override
     public List<SopUserLogsInfo> selectSopUserLogsInfoList(SopUserLogsInfo info) {
     public List<SopUserLogsInfo> selectSopUserLogsInfoList(SopUserLogsInfo info) {
-     return  sopUserLogsInfoMapper.selectSopUserLogsInfoList(info);
+        List<SopUserLogsInfo> list = sopUserLogsInfoMapper.selectSopUserLogsInfoList(info);
+        return list;
     }
     }
     @Override
     @Override
     public List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info) {
     public List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info) {
@@ -413,6 +425,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         if (config == null) {
         if (config == null) {
             return R.error().put("msg","课程默认配置为空,请联系管理员");
             return R.error().put("msg","课程默认配置为空,请联系管理员");
         }
         }
+
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error().put("msg","企业编号为空,不能创建一键群发");
+        }
+
         List<QwSopLogs> sopLogsList;
         List<QwSopLogs> sopLogsList;
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
@@ -429,10 +446,16 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }
                 }
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                     QwGroupChat qwGroupChat = groupMap.get(groupUser.getChatId());
                     QwGroupChat qwGroupChat = groupMap.get(groupUser.getChatId());
-                    QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(qwGroupChat.getOwner(), qwGroupChat.getCorpId());
-                    if (qwUser == null) {
+
+                    QwUser qwUser = qwExternalContactService.getQwUserByRedis(qwGroupChat.getCorpId(),qwGroupChat.getOwner());
+                    if (qwUser==null){
                         throw new BaseException("企业微信用户不存在:" + qwGroupChat.getOwner());
                         throw new BaseException("企业微信用户不存在:" + qwGroupChat.getOwner());
                     }
                     }
+
+//                    QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(qwGroupChat.getOwner(), qwGroupChat.getCorpId());
+//                    if (qwUser == null) {
+//                        throw new BaseException("企业微信用户不存在:" + qwGroupChat.getOwner());
+//                    }
                     Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(groupUser.getUserList(), GroupUserExternalVo::getUserId);
                     Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(groupUser.getUserList(), GroupUserExternalVo::getUserId);
                     GroupUserExternalVo vo = userMap.get(qwGroupChat.getOwner());
                     GroupUserExternalVo vo = userMap.get(qwGroupChat.getOwner());
                     QwSopLogs sopLogs = new QwSopLogs();
                     QwSopLogs sopLogs = new QwSopLogs();
@@ -447,7 +470,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setReceivingStatus(0L);
                     sopLogs.setReceivingStatus(0L);
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setCorpId(qwGroupChat.getCorpId());
                     sopLogs.setCorpId(qwGroupChat.getCorpId());
-                    sopLogs.setSort(2);
+                    sopLogs.setSort(30000001);
                     sopLogs.setSendType(6);
                     sopLogs.setSendType(6);
                     sopLogs.setExternalUserName(groupUser.getName());
                     sopLogs.setExternalUserName(groupUser.getName());
                     //域名
                     //域名
@@ -627,19 +650,33 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             sopLogsList = new ArrayList<>();
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
+
             String[] userKey = param.getUserIdParam().split("\\|");
             String[] userKey = param.getUserIdParam().split("\\|");
+
             String qwUserId = userKey[0].trim();
             String qwUserId = userKey[0].trim();
-            String companyUserId = userKey[1].trim();
-            String companyId = userKey[2].trim();
+
             QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText(Long.valueOf(qwUserId));
             QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText(Long.valueOf(qwUserId));
+
             if (qwUser == null) {
             if (qwUser == null) {
                 return R.error().put("msg","企业微信用户不存在:"+qwUserId);
                 return R.error().put("msg","企业微信用户不存在:"+qwUserId);
             }
             }
+
+            String companyUserId = String.valueOf(qwUser.getCompanyUserId()).trim();
+            String companyId = String.valueOf(qwUser.getCompanyId()).trim();
+
             //域名
             //域名
             String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
             String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
             if (StringUtils.isEmpty(domainName)){
             if (StringUtils.isEmpty(domainName)){
                 domainName = config.getRealLinkDomainName();
                 domainName = config.getRealLinkDomainName();
             }
             }
+
+            QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+            if (qwCompany == null ) {
+                return  R.error().put("msg","企业不存在,请联系管理员");
+            }
+
+
             String finalDomainName = domainName;
             String finalDomainName = domainName;
             sopUserLogsInfos.forEach(item->{
             sopUserLogsInfos.forEach(item->{
 
 
@@ -657,7 +694,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 sopLogs.setSopId(param.getSopId());
                 sopLogs.setSopId(param.getSopId());
                 sopLogs.setCorpId(item.getCorpId());
                 sopLogs.setCorpId(item.getCorpId());
                 sopLogs.setFsUserId(item.getFsUserId());
                 sopLogs.setFsUserId(item.getFsUserId());
-                sopLogs.setSort(2);
+                sopLogs.setSort(30000000);
                 sopLogs.setSendType(5);
                 sopLogs.setSendType(5);
                 sopLogs.setExternalUserName(item.getExternalUserName());
                 sopLogs.setExternalUserName(item.getExternalUserName());
 
 
@@ -722,14 +759,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
 
 
-                            if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())){
-                                log.error("配置中无小程序id,采用默认的");
-                                st.setMiniprogramAppid("wxc84c6f789ba7f176");
+                            if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
+                                log.error("企业未配置小程序-"+param.getCorpId());
                             }else {
                             }else {
-                                st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                //置换各自的小程序
+                                st.setMiniprogramAppid(qwCompany.getMiniAppId());
                             }
                             }
-
                             st.setMiniprogramPage(linkByMiniApp);
                             st.setMiniprogramPage(linkByMiniApp);
+
                             break;
                             break;
                         //app
                         //app
                         case "9":
                         case "9":

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