Sfoglia il codice sorgente

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

caoliqin 1 settimana fa
parent
commit
9a32ded8ad
100 ha cambiato i file con 5609 aggiunte e 383 eliminazioni
  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);
         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")
     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;
 
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.List;
 
+import com.fs.common.exception.CustomException;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 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;
 
+import java.util.ArrayList;
 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.FsCourseWatchLogStatisticsListParam;
 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.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -36,6 +43,8 @@ public class FsCourseWatchLogController extends BaseController
     @Autowired
     private IFsCourseWatchLogService fsCourseWatchLogService;
 
+    @Autowired
+    private IQwWatchLogService qwWatchLogService;
     /**
      * 查询短链课程看课记录列表
      */
@@ -48,6 +57,58 @@ public class FsCourseWatchLogController extends BaseController
         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.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.ITencentCloudCosService;
 import com.fs.crm.param.SmsSendParam;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
@@ -210,13 +211,19 @@ public class Task {
     @Autowired
     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
     {
        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());
         qwSopTemp.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
+        if(qwSopTemp.getSendType() == 11){
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         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:
   profiles:
     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)
     {
-        return userId != null && (1L == userId || 145L == userId);
+        return userId != null && (1L == userId);
     }
 
     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.text.ParseException;
 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;
 
 /**
  * 时间工具类
- * 
+ *
 
  */
 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 YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
-    
+
     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"};
+    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     /**
      * 获取当前Date型日期
-     * 
+     *
      * @return Date() 当前日期
      */
     public static Date getNowDate()
@@ -41,7 +45,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
 
     /**
      * 获取当前日期, 默认格式为yyyy-MM-dd
-     * 
+     *
      * @return String
      */
     public static String getDate()
@@ -122,7 +126,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
             return null;
         }
     }
-    
+
     /**
      * 获取服务器启动时间
      */
@@ -153,20 +157,112 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         // long sec = diff % nd % nh % nm / ns;
         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")
     public TableDataInfo qwWatchLogStatisticsList(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<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogStatisticsListVONew(param);
     }
     @GetMapping("/myQwWatchLogStatisticsList")
     public TableDataInfo myQwWatchLogStatisticsList(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<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
-        return getDataTable(list);
+        return qwWatchLogService.selectQwWatchLogStatisticsListVONew(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);
+        return qwWatchLogService.selectQwWatchLogAllStatisticsListVONew(param);
     }
     @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);
+        return qwWatchLogService.selectQwWatchLogAllStatisticsListVONew(param);
     }
     @GetMapping("/watchLogStatistics")
     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.setCreateBy(loginUser.getUser().getUserId().toString());
         int i = qwSopTempService.addNew(qwSopTemp);
-        if(qwSopTemp.getSendType() == 5){
+        if(qwSopTemp.getSendType() == 11){
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }
         return toAjax(i);
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "sop模板update", businessType = BusinessType.UPDATE)
     @PostMapping("/update")
@@ -147,12 +148,14 @@ public class QwSopTempController extends BaseController
         int update = qwSopTempService.update(qwSopTemp);
         return toAjax(update);
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "addOrUpdateSop模板规则", businessType = BusinessType.UPDATE)
     @PostMapping("/addOrUpdateSetting")
     public AjaxResult addOrUpdateSetting(@RequestBody QwSopTempDay day){
         return AjaxResult.success(qwSopTempService.addOrUpdateSetting(day));
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @Log(title = "sop模板规则delRules", businessType = BusinessType.DELETE)
     @GetMapping("/delRules")
@@ -165,18 +168,21 @@ public class QwSopTempController extends BaseController
     public AjaxResult selectRulesInfo(Long id){
         return AjaxResult.success(qwSopTempService.selectRulesInfo(id));
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PostMapping("/copyTemplate")
     public AjaxResult copyTemplate(@RequestBody QwSopTemp qwSopTemp){
         qwSopTempService.copyTemplate(qwSopTemp);
         return toAjax(1);
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @PostMapping("/sortDay")
     public AjaxResult sortDay(@RequestBody List<SortDayVo> list){
         qwSopTempService.sortDay(list);
         return toAjax(1);
     }
+
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit')")
     @GetMapping("/dayList")
     public AjaxResult dayList(String id){
@@ -187,6 +193,7 @@ public class QwSopTempController extends BaseController
     public AjaxResult redList(String id){
         return AjaxResult.success(qwSopTempService.redList(id));
     }
+
     @PostMapping("/updateRedPackage")
     public AjaxResult updateRedPackage(@RequestBody UpdateRedVo data){
         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;
 
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 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.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 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.QwTagSearchParam;
 import com.fs.qw.param.SopExternalContactInfo;
@@ -54,6 +58,8 @@ public class SopUserLogsInfoController extends BaseController
     @Autowired
     private IQwTagService iQwTagService;
     @Autowired
+    private QwTagMapper qwTagMapper;
+    @Autowired
     private IQwGroupChatUserService qwGroupChatUserService;
 
     private static final Gson GSON = new Gson();
@@ -66,45 +72,72 @@ public class SopUserLogsInfoController extends BaseController
     public TableDataInfo list(SopUserLogsInfo sopUserLogsInfo) {
         startPage();
         if (sopUserLogsInfo.getFilterMode() == 1) {
+            startPage();
             List<SopUserLogsInfo> list = sopUserLogsInfoService.selectSopUserLogsInfoList(sopUserLogsInfo);
 
             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()
                         .collect(Collectors.toMap(
                                 QwExternalContactVOTime::getId,
                                 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 -> {
-                    SopExternalContactInfo info = externalContactInfoMap.getOrDefault(item.getExternalId(), new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
+                    SopExternalContactInfo info = externalContactInfoMap.getOrDefault(
+                            item.getExternalId(),
+                            new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
                     item.setInComTime(info.getCreateTime());
                     item.setTagIds(info.getTagIds());
                     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()
-                        .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());
             }
 
+            // 处理标签名称
+            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);
         } else {
             List<QwGroupChatUser> list = qwGroupChatUserService.selectByChatId(sopUserLogsInfo);
@@ -125,7 +158,7 @@ public class SopUserLogsInfoController extends BaseController
                 Map<String, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                         .collect(Collectors.toMap(
                                 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
@@ -133,6 +166,7 @@ public class SopUserLogsInfoController extends BaseController
                     SopExternalContactInfo info = externalContactInfoMap.getOrDefault(item.getUserId(), new SopExternalContactInfo("无进线时间", "无标签", "无备注"));
                     item.setInComTime(info.getCreateTime());
                     item.setTagIds(info.getTagIds());
+                    item.setTagNames(info.getTagNames());
                     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")
     public R testSop3(String date) throws Exception {
-        qwSopLogsService.createCorpMassSending(date);
+//        qwSopLogsService.createCorpMassSending(date);
 //        QwGetGroupmsgSendParam qwGetGroupmsgSendParam = new QwGetGroupmsgSendParam();
 //        qwGetGroupmsgSendParam.setMsgid("msg7tWFCgAAjJC-HqurNKsOJif5oUHQiA");
 //        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);
     }
 
+//    /**
+//    * 定时 发送 通过调用 企业微信接口 发送的 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【企微官方接口群发开始】");
 //        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);
+        qwSopLogsService.createCorpMassSendingByUserLogs(date);
     }
 
+
     /**
-    * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果
-    */
+     * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果(新版-安装营期发)
+     */
     @Scheduled(cron = "0 0 8 * * ?")
-    public void GetQwApiSopLogResultTimer(){
-        qwSopLogsService.qwSopLogsResult();
+    public void GetQwApiSopLogResultTimerNew(){
+        qwSopLogsService.qwSopLogsResultNew();
     }
 
     /**
@@ -206,6 +229,7 @@ public class qwTask {
         }
     }
 
+
     // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
     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.param.FsCourseLinkCreateParam;
 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.QwUserMapper;
+import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwGroupChatService;
 import com.fs.qw.service.IQwGroupChatUserService;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
@@ -163,6 +161,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     private ICompanyUserService companyUserService;
 
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
 
     @PostConstruct
     public void init() {
@@ -301,45 +302,48 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             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()
-                .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));
 
+
+        // 查询销售二级域名
+//        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());
 
         CountDownLatch sopGroupLatch = new CountDownLatch(sopLogsGroupedById.size());
@@ -347,7 +351,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config);
         }
 
         // 等待所有 SOP 分组处理完成
@@ -367,9 +371,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             maxAttempts = 3,
             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 {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap);
+            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config);
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } 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);
 
         if (ruleTimeVO == null) {
@@ -392,6 +398,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             log.error("SOP ID {} 模板不存在,相关日志已清除。", sopId);
             return;
         }
+
         ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
         ruleTimeVO.setTempGap(qwSopTemp.getGap());
 
@@ -410,9 +417,16 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             return;
         }
 
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(ruleTimeVO.getCorpId());
+
+        if (qwCompany == null ) {
+            log.error("SOP ID {} 的 公司信息为空 为空,跳过处理。", sopId);
+            return ;
+        }
+
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         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,
             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 {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap);
+            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config);
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } 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 {
+
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
             LocalDate currentDate = currentTime.toLocalDate();
 
@@ -472,14 +490,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 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里或者从库里取
@@ -489,6 +508,26 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 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);
 
@@ -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) {
@@ -637,7 +678,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     //消息处理
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                    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()
                 .atZone(ZoneId.systemDefault())
                 .format(DATE_TIME_FORMATTER);
@@ -674,7 +716,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     log.error("群聊创建看课记录失败!", e);
                 }
                 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 {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
@@ -682,7 +724,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         ruleTimeVO.setRemark("客户群催课");
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         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();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     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) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
@@ -745,7 +787,23 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         sopLogs.setCorpId(logVo.getCorpId());
         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);
@@ -754,11 +812,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         String[] userKey = logVo.getUserId().split("\\|");
         sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
         sopLogs.setSopId(logVo.getSopId());
-
+        sopLogs.setSort(Integer.valueOf(logVo.getStartTime().replaceAll("-","")));
         sopLogs.setExternalUserId(externalContactId);
         sopLogs.setExternalId(externalId);
         sopLogs.setExternalUserName(externalUserName);
         sopLogs.setFsUserId(fsUserId);
+        sopLogs.setUserLogsId(logVo.getId());
 
         return sopLogs;
     }
@@ -766,14 +825,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       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) {
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
                 break;
             case 2:
                 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;
             case 3:
                 handleOrderMessage(sopLogs, content);
@@ -805,7 +865,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      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
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -816,6 +877,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //
 //        Integer courseType = clonedContent.getCourseType();
 
+        String isOfficial = clonedContent.getIsOfficial();
+
         List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
         if (settings == null || settings.isEmpty()) {
             log.error("Cloned content settings are empty, skipping.");
@@ -848,7 +911,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         } else {
                             addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
                             link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
-                                    qwUserId, companyUserId, companyId, externalId, fsUserId);
+                                    qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
                         }
 
                         if (StringUtils.isNotEmpty(link)) {
@@ -859,7 +922,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 if (currentValue == null) {
                                     setting.setValue(link);
                                 } else {
-//                                    setting.setValue(currentValue + "\n" + sortLink);
                                     setting.setValue(currentValue
                                             .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                             + "\n" + link);
@@ -882,12 +944,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
 
                     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]+", ""));
 
                     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) {
                         log.error("赋值-小程序封面地址失败-" + e);
                     }
@@ -933,7 +1001,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private String generateShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                      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;
         synchronized(configLock) {
@@ -954,7 +1022,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCorpId(logVo.getCorpId());
         link.setCourseId(courseId.longValue());
         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();
         courseMap.setCompanyId(link.getCompanyId());
@@ -964,9 +1047,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         courseMap.setCorpId(link.getCorpId());
         courseMap.setCourseId(link.getCourseId());
         courseMap.setQwExternalId(link.getQwExternalId());
-        courseMap.setLinkType(0);
         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 realLinkFull = REAL_LINK_PREFIX + courseJson;
         link.setRealLink(realLinkFull);
@@ -1112,7 +1211,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                      Long courseId, Long videoId, String qwUserId,
-                                     String companyUserId, String companyId, String externalId) {
+                                     String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId) {
         // 获取缓存的配置
         CourseConfig config;
         synchronized(configLock) {
@@ -1137,7 +1236,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCorpId(logVo.getCorpId());
         link.setCourseId(courseId.longValue());
         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();
         if (StringUtil.strIsNullOrEmpty(randomString)){
@@ -1153,7 +1267,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         String courseJson = JSON.toJSONString(courseMap);
         String realLinkFull = miniappRealLink + courseJson;
-//        String realLinkFull = config.getMiniprogramPage() + courseJson;
         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
     private ConfigUtil configUtil;
 
-    @Value("${hook.path}")
-    private String hookPath;
-
     @Autowired
     private StringRedisTemplate redisTemplate;  // 使用 RedisTemplate 进行分布式锁
 

+ 15 - 0
fs-service/pom.xml

@@ -253,6 +253,21 @@
             <version>0.2.16</version>
         </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>
             <groupId>org.mapstruct</groupId>
             <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);
 
     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 selectCompanyUserByIdForRedis(Long userId);
+
     /**
      * 查询物业公司管理员信息列表
      *
@@ -134,4 +136,12 @@ public interface ICompanyUserService {
      * @return  list
      */
     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;
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.DataScope;
 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.StringUtils;
 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.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,6 +29,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -57,6 +61,10 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
     @Autowired
     public IFsCityService iFsCityService;
+
+    @Autowired
+    private RedisCache redisCache;
+
     /**
      * 查询物业公司管理员信息
      *
@@ -69,6 +77,20 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         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) {
         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 = "域名")
     private String domainName;
 
+    private Long companyId;
+
+    private Long companyUserId;
+
     /** 状态 0 停用  1正常 */
     @Excel(name = "状态 0 停用  1正常")
     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);
 
+
+    @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);
 
     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 java.util.ArrayList;
+import java.util.Date;
 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 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" +
-            "where o.company_id=#{companyId}  " +
+            "where o.company_id=#{companyId} AND send_type=2 " +
             "<if test= 'sTime != null '> " +
             "       and DATE(o.create_time) &gt;= DATE(#{sTime})\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 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;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.utils.DateUtils;
 import lombok.Data;
 
 import java.util.Date;
@@ -10,6 +11,8 @@ import java.util.List;
 public class FsCourseWatchLogStatisticsListParam {
 
     private Long companyId;
+    private Long companyUserId;
+    private Long userId;
 
     private String nickName;
 
@@ -21,4 +24,20 @@ public class FsCourseWatchLogStatisticsListParam {
     private Date eTime;
     @JsonFormat(pattern = "yyyy-MM-dd")
     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();
 
     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.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
 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.domain.FsCourseFinishTemp;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsUserCourse;
 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.service.IFsCourseWatchLogService;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
 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.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
+import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactInfo;
 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.SopUserLogsMapper;
 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.hc.openapi.tool.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.Duration;
@@ -87,6 +97,31 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     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);
     }
 
+    @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
     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.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.system.service.ISysConfigService;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
@@ -86,6 +87,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private FsCourseQuestionBankMapper courseQuestionBankMapper;
     @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+    @Autowired
+    private FsCourseLinkMapper fsCourseLinkMapper;
+
 
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
@@ -161,7 +167,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         FsUserCourseVideo courseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
 
         BeanCopyUtils.copy(courseVideo,fsUserCourseVideoQVO);
-
+        if (courseVideo.getRedPacketMoney()!=null){
+            fsUserCourseVideoQVO.setRedPacketMoney(courseVideo.getRedPacketMoney().toString());
+        }
         if (StringUtils.isNotEmpty(courseVideo.getQuestionBankId())){
             List<FsCourseQuestionBank> fsCourseQuestionBanks = courseQuestionBankMapper.selectFsCourseQuestionBankByIdVO(courseVideo.getQuestionBankId().split(","));
             fsUserCourseVideoQVO.setQuestionBankList(fsCourseQuestionBanks);
@@ -427,13 +435,31 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             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());
 
@@ -463,6 +489,16 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             }
             log.setUpdateTime(new Date());
             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();
 
         }else {
@@ -471,6 +507,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             contact.setId(param.getQwExternalId());
             contact.setFsUserId(param.getUserId());
             qwExternalContactMapper.updateQwExternalContact(contact);
+
+            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
+
             FsUser user = new FsUser();
             user.setUserId(param.getUserId());
             user.setIsAddQw(1);
@@ -490,6 +529,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
             log.setUpdateTime(new Date());
             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();
         }
     }
@@ -656,14 +701,17 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         if (param.getLinkType() != null && param.getLinkType() == 1) {
             FsCourseRedPacketLog packetLog = redPacketLogMapper.selectFsCourseRedPacketLogByTemporary(param.getVideoId(), param.getUserId());
             if (packetLog != null) {
+                System.out.println("奖励已发放1");
                 return R.error("奖励已发放");
             }
         } else {
             log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
             if (log == null) {
+                System.out.println("无记录");
                 return R.error("无记录");
             }
             if (log.getRewardType() != null) {
+                System.out.println("奖励已发放2");
                 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 {
 
 
+    private Integer project;
+    @Excel(name = "项目名称")
+    private String projectName;
+
+    private Long courseId;
     @Excel(name = "课程名称")
     private String courseName;
 
+    private Long videoId;
     @Excel(name = "小节名称")
     private String videoName;
 
@@ -27,7 +33,15 @@ public class FsCourseWatchLogStatisticsListVO {
     @Excel(name = "企业微信员工名称")
     private String qwUserName;
 
+    private Long userId;
+    @Excel(name = "员工名称")
+    private String userName;
+
     @Excel(name = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd")
     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 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 */
     private Long userId;
+    private String username;
+    private String password;
+    private String realName;
+    private Long birthday;
+    private String idCard;
 
     /** 用户昵称 */
     @Excel(name = "用户昵称")
@@ -37,10 +42,13 @@ public class FsUser extends BaseEntity
     /** 用户头像 */
     @Excel(name = "用户头像")
     private String avatar;
+    private String remark;
 
     /** 手机号码 */
     @Excel(name = "手机号码")
     private String phone;
+    private BigDecimal nowMoney;
+    private BigDecimal brokeragePrice;
 
     /** 用户积分 */
     @Excel(name = "用户积分")
@@ -53,11 +61,17 @@ public class FsUser extends BaseEntity
     /** 推广上级用户ID */
     @Excel(name = "推广上级用户ID")
     private String tuiUserId;
+    private Date spreadTime;
 
     /** 推广员关联时间 */
     @JsonFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "推广员关联时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date tuiTime;
+    private String userType;
+    private Integer isPromoter;
+    private Long payCount;
+    private Long spreadCount;
+    private String addres;
 
     /** 下级人数 */
     @Excel(name = "下级人数")
@@ -85,7 +99,12 @@ public class FsUser extends BaseEntity
 
     private Long companyId;
     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 */
     @Excel(name = "最后一次登录ip")
     private String lastIp;
@@ -100,7 +119,6 @@ public class FsUser extends BaseEntity
 
     private Integer integralStatus;
 
-    private String password;
     private Integer isBuy;
 
     private String jpushId;
@@ -126,6 +144,7 @@ public class FsUser extends BaseEntity
     private String source;//app来源
 
     private Integer isAddQw;//是否添加企微客服
+    private Integer isShow;//是否展示购买以及订单状态
 
     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);
 
     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.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserVO;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -249,4 +250,23 @@ public interface FsUserMapper
     int batchUpdateFsUserByIds(@Param("ids") String[] ids, @Param("status") Integer status);
 
     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);
 
     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 com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.course.param.newfs.FsUserCourseBeMemberParam;
 import com.fs.his.domain.FsUser;
 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.FsUserVO;
 import com.fs.his.vo.UserVo;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -124,4 +126,14 @@ public interface IFsUserService
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
 
     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);
     }
 
+    @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 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.page.TableDataInfo;
 import com.fs.common.utils.DateUtils;
 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.CompanyTagUser;
 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.FsUserIntegralLogs;
 import com.fs.his.mapper.*;
-import com.fs.his.param.FsStoreOrderParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserVO;
 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.vo.QwFsUserVO;
+import com.fs.store.domain.FsUserCourseCount;
 import com.fs.store.param.h5.FsUserPageListParam;
+import com.fs.store.service.cache.IFsUserCourseCountCacheService;
 import com.fs.store.vo.h5.FsUserPageListVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.watch.domain.WatchUser;
 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.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -43,6 +53,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
@@ -79,6 +90,18 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     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;
     }
 
+    @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)
     private String tagIds;
     @TableField(exist = false)
+    private List<String> tagNames;
+    @TableField(exist = false)
     private String fsUserId;
     @TableField(exist = false)
     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<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}" +
             "</foreach>" +
             "</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);
 
-    @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);
 
     @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")
     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);
 
     @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) "+
             "</script>"})
     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.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -98,4 +99,22 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
             "</script>"
     })
     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 lombok.Data;
 
+import java.util.List;
+
 @Data
 public class QwExternalContactVOTime {
 
     private Long id;
 
     private String tagIds;
+    private List<String> tagIdsName;
 
     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;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.utils.DateUtils;
 import lombok.Data;
 
 import java.util.Date;
@@ -11,10 +12,28 @@ public class QwWatchLogStatisticsListParam {
     private Date eTime;
     @JsonFormat(pattern = "yyyy-MM-dd")
     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 corpId;
     private Long companyId;
     private Long companyUserId;
     private Long id;
     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;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.util.Date;
+
 @Data
 public class QwWorkTaskListParam {
     private Long id;
@@ -40,4 +44,23 @@ public class QwWorkTaskListParam {
     private Long companyUserId;
 
     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;
 
 
+import lombok.Data;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@Data
 public class SopExternalContactInfo {
     private String createTime;
     private String tagIds;
+    private List<String> tagNames;
     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.tagNames = (tagNames != null && !tagNames.isEmpty()) ? tagNames : Collections.singletonList("无标签");
         this.tagIds = (tagIds != null && !tagIds.equals("[]")) ? tagIds : "无标签";
         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();
 
     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;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
@@ -63,7 +64,10 @@ public interface IQwWatchLogService extends IService<QwWatchLog>{
      */
     int deleteQwWatchLogById(Long id);
 
-    List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param);
-
+    TableDataInfo selectQwWatchLogStatisticsListVO(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.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 
 import java.util.List;
@@ -75,4 +76,7 @@ public interface IQwWorkTaskService extends IService<QwWorkTask>{
 
     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.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.course.config.CourseConfig;
@@ -173,6 +174,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private QwTagMapper qwTagMapper;
+
     @Autowired
     private QwExternalContactServiceImpl qwExternalContactService;
 
@@ -290,7 +294,23 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     @Override
     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
     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;
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 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.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.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.mapper.QwExternalContactMapper;
 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.vo.QwWatchLogAllStatisticsListVO;
 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.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业务层处理
@@ -35,6 +50,22 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
     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
-    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 eTime = param.getETime();
         List<Date> datesBetween = getDatesBetween(sTime, eTime);
         if (datesBetween.size() > 7) {
-            return new ArrayList<>();
+            return rspData;
         }
         if (param.getCompanyUserId()!=null){
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
@@ -131,11 +167,10 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             vo.setFirstOver(stat.getFirstOver());
         }
 
-
-
-
-
-        return vos;
+        Long total = qwWatchLogMapper.selectQwExtCountByDayAndCount(param);
+        rspData.setRows(vos);
+        rspData.setTotal(total);
+        return rspData;
     }
 
     @Override
@@ -161,10 +196,162 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             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) {

+ 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.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.sop.domain.QwSop;
 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.stereotype.Service;
 
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 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) {
         if (day>7){
             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;
 
     /**
-     发送类型 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;
 

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

@@ -78,5 +78,31 @@ public class QwWatchLogAllStatisticsListVO {
     Long d29Over;
     Long d30Online;
     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
 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")
-    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;
 
+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 lombok.Data;
 
+import java.util.Date;
+import java.util.List;
+
 @Data
 public class QwWorkTaskListVO {
     private Long id;
@@ -21,10 +27,12 @@ public class QwWorkTaskListVO {
     /** 类别 1先导 2 课程 3 大小转 4 转人工 */
     @Excel(name = "类别 1先导 2 课程 3 大小转 4 转人工")
     private Integer type;
+    private String typeText;
 
     /** 状态 0 待处理 1 已处理 3 过期 */
     @Excel(name = "状态 0 待处理 1 已处理 3 过期")
     private Integer status;
+    private String statusText;
 
     /** 分值 */
     @Excel(name = "分值")
@@ -35,12 +43,25 @@ public class QwWorkTaskListVO {
     private String sopId;
 
     /** 公司id */
-    @Excel(name = "公司id")
     private Long companyId;
+    @Excel(name = "公司名称")
+    private String companyName;
 
     /** 用户id */
-    @Excel(name = "用户id")
     private Long companyUserId;
-
+    /**
+     * 销售名称
+     */
+    @Excel(name = "销售名称")
+    private String companyUserName;
     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 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;
     @TableField(exist = false)
     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)
     @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);
+
     @DataSource(DataSourceType.SOP)
     @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);
+
     @DataSource(DataSourceType.SOP)
     @Select("SELECT external_id,create_time  FROM sop_user_logs_info where sop_id = #{sopId} ")
     List<SopUserLogsInfo> selectSopUserLogsInfoBySopId(@Param("sopId")String sopId);
+
     @DataSource(DataSourceType.SOP)
     @Select("SELECT id  FROM sop_user_logs_info where external_id = #{extId} ")
     List<String> selectSopUserLogsInfoByExtId(@Param("extId")Long extId );
+
     @DataSource(DataSourceType.SOP)
     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 createCorpMassSendingByUserLogs(String date);
 
     /**
      *  检索执行符合条件的定时任务的结果回调(企业微信)
      */
     public void qwSopLogsResult();
-
+    public void qwSopLogsResultNew() throws InterruptedException;
 
     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 updateSopUserInfoByExternalId(Long qwExternalId,Long userId);
+
     /**
      * 根据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.format.DateTimeFormatter;
 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;
 
 /**
@@ -527,12 +525,139 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                 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 需要更新的记录列表
      */
     private void batchUpdateDatabase(List<QwSopLogs> updateList) {
-        int updateBatchSize = 1000;
+        int updateBatchSize = 500;
         for (int i = 0; i < updateList.size(); i += updateBatchSize) {
             int endIndex = Math.min(i + updateBatchSize, updateList.size());
             List<QwSopLogs> batch = updateList.subList(i, endIndex);
@@ -545,7 +670,6 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
         }
     }
 
-
     @Override
     public int updateQwSopLogsByWatchLogType(String id,String remark) {
         return qwSopLogsMapper.updateQwSopLogsByWatchLogType(id,remark);
@@ -586,6 +710,13 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             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
             QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
 
@@ -600,119 +731,126 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             contactHParam.setCorpId(param.getCorpId().trim());
             Long qwExternalContactId = qwExternalContactMapper.getQwExternalContactId(contactHParam);
 
-            for (QwSopLogsDoSendListTVO log : logsByJsApi) {
+            for (QwSopLogsDoSendListTVO log : result) {
 
                 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 {
-                                                    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) {
                     // 记录错误日志
                     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;
                                             Long courseId = content.getCourseId();
                                             for (QwSopTempSetting.Content.Setting set : content.getSetting()) {
-                                                processContent(set, corpId, templateSop, attachments, courseId,config);
+                                                processContent(set, corpId, templateSop, attachments, courseId);
                                             }
                                         } catch (Exception e) {
                                             logger.error("消息内容解析失败,logId:{}", log.getId(), e);
@@ -1323,10 +1461,267 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
         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,
-                                QwMsgTemplateSop templateSop, List<QwMsgTemplateSop.Attachment> attachments
-                                ,Long courseId,CourseConfig config) {
+                                QwMsgTemplateSop templateSop, List<QwMsgTemplateSop.Attachment> attachments,Long courseId) {
         switch (set.getContentType()) {
             case "1":
                 templateSop.setTextContent(set.getValue());
@@ -1338,7 +1733,7 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
                 handleLinkAttachment(set, corpId, attachments);
                 break;
             case "4":
-                handleMiniProgramAttachment(set, corpId, attachments,courseId,config);
+                handleMiniProgramAttachment(set, corpId, attachments,courseId);
                 break;
         }
     }
@@ -1368,13 +1763,13 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
             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);
+                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);
@@ -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())) {
             try {
                 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() : "";
                     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.setMiniProgramAppId(set.getMiniprogramAppid());
                     attachment.setMiniProgramPage(set.getMiniprogramPage());
                     attachments.add(attachment);
                 } 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.mapper.FastGptChatReplaceWordsMapper;
 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.impl.AsyncSopTestService;
+import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import com.fs.qw.vo.GroupUserExternalVo;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.qw.vo.QwTagGroupListVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.domain.SopUserLogs;
@@ -128,10 +129,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     @Autowired
     private IFsCourseLinkService fsCourseLinkService;
+    @Autowired
+    private QwTagMapper qwTagMapper;
 
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
+    @Autowired
+    private QwExternalContactServiceImpl qwExternalContactService;
+
     @Autowired
     private IQwCompanyService iQwCompanyService;
 
@@ -151,6 +157,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.updateById(info);
     }
 
+    @Override
+    public void updateSopUserInfoByExternalId(Long qwExternalId, Long userId) {
+        sopUserLogsInfoMapper.updateQwExternalContactChangeUserId(qwExternalId,userId);
+    }
+
     @Override
     public SopUserLogsInfo getById(String id) {
         return sopUserLogsInfoMapper.selectById(id);
@@ -173,7 +184,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     @Override
     public List<SopUserLogsInfo> selectSopUserLogsInfoList(SopUserLogsInfo info) {
-     return  sopUserLogsInfoMapper.selectSopUserLogsInfoList(info);
+        List<SopUserLogsInfo> list = sopUserLogsInfoMapper.selectSopUserLogsInfoList(info);
+        return list;
     }
     @Override
     public List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info) {
@@ -413,6 +425,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         if (config == null) {
             return R.error().put("msg","课程默认配置为空,请联系管理员");
         }
+
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error().put("msg","企业编号为空,不能创建一键群发");
+        }
+
         List<QwSopLogs> sopLogsList;
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
@@ -429,10 +446,16 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                     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());
                     }
+
+//                    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);
                     GroupUserExternalVo vo = userMap.get(qwGroupChat.getOwner());
                     QwSopLogs sopLogs = new QwSopLogs();
@@ -447,7 +470,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setReceivingStatus(0L);
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setCorpId(qwGroupChat.getCorpId());
-                    sopLogs.setSort(2);
+                    sopLogs.setSort(30000001);
                     sopLogs.setSendType(6);
                     sopLogs.setExternalUserName(groupUser.getName());
                     //域名
@@ -627,19 +650,33 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
+
             String[] userKey = param.getUserIdParam().split("\\|");
+
             String qwUserId = userKey[0].trim();
-            String companyUserId = userKey[1].trim();
-            String companyId = userKey[2].trim();
+
             QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText(Long.valueOf(qwUserId));
+
             if (qwUser == null) {
                 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));
             if (StringUtils.isEmpty(domainName)){
                 domainName = config.getRealLinkDomainName();
             }
+
+            QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+            if (qwCompany == null ) {
+                return  R.error().put("msg","企业不存在,请联系管理员");
+            }
+
+
             String finalDomainName = domainName;
             sopUserLogsInfos.forEach(item->{
 
@@ -657,7 +694,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 sopLogs.setSopId(param.getSopId());
                 sopLogs.setCorpId(item.getCorpId());
                 sopLogs.setFsUserId(item.getFsUserId());
-                sopLogs.setSort(2);
+                sopLogs.setSort(30000000);
                 sopLogs.setSendType(5);
                 sopLogs.setExternalUserName(item.getExternalUserName());
 
@@ -722,14 +759,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                                     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 {
-                                st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                //置换各自的小程序
+                                st.setMiniprogramAppid(qwCompany.getMiniAppId());
                             }
-
                             st.setMiniprogramPage(linkByMiniApp);
+
                             break;
                         //app
                         case "9":

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