xdd преди 1 ден
родител
ревизия
8cdd10381a
променени са 67 файла, в които са добавени 4517 реда и са изтрити 433 реда
  1. 199 0
      fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java
  2. 206 0
      fs-company/src/main/java/com/fs/company/controller/stats/SalesWatchStatisController.java
  3. 5 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyDeptCacheService.java
  4. 5 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyUserCacheService.java
  5. 27 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyDeptCacheServiceImpl.java
  6. 31 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyUserCacheServiceImpl.java
  7. 18 6
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  8. 10 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  9. 21 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  10. 4 6
      fs-service/src/main/java/com/fs/company/service/ICompanyDeptService.java
  11. 11 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  12. 13 7
      fs-service/src/main/java/com/fs/company/service/impl/CompanyDeptServiceImpl.java
  13. 433 9
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  14. 22 0
      fs-service/src/main/java/com/fs/company/vo/DeptDataVO.java
  15. 60 0
      fs-service/src/main/java/com/fs/qw/cache/QwSopCacheService.java
  16. 61 0
      fs-service/src/main/java/com/fs/qw/cache/QwUserCacheService.java
  17. 12 0
      fs-service/src/main/java/com/fs/qw/dto/QwUserDTO.java
  18. 14 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  19. 2 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopLogs.java
  20. 14 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java
  21. 57 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopMapper.java
  22. 24 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  23. 12 0
      fs-service/src/main/java/com/fs/sop/params/GetSOPTaskDataParam.java
  24. 6 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopService.java
  25. 58 4
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  26. 28 0
      fs-service/src/main/java/com/fs/sop/vo/QwSopTask.java
  27. 14 0
      fs-service/src/main/java/com/fs/statis/IFsStatisQwWatchService.java
  28. 6 3
      fs-service/src/main/java/com/fs/statis/domain/FsStatisPeriodWatch.java
  29. 47 0
      fs-service/src/main/java/com/fs/statis/domain/FsStatisQwTempParam.java
  30. 190 0
      fs-service/src/main/java/com/fs/statis/domain/FsStatisQwWatch.java
  31. 52 2
      fs-service/src/main/java/com/fs/statis/domain/FsStatisSalerWatch.java
  32. 157 0
      fs-service/src/main/java/com/fs/statis/domain/FsStatisSopWatch.java
  33. 41 0
      fs-service/src/main/java/com/fs/statis/domain/FsStatisTempFsuser.java
  34. 59 0
      fs-service/src/main/java/com/fs/statis/domain/FsStatisTempParam.java
  35. 24 0
      fs-service/src/main/java/com/fs/statis/domain/FsTempPeriodQuery.java
  36. 23 0
      fs-service/src/main/java/com/fs/statis/dto/FsStatisQwWatchWriteDataDTO.java
  37. 3 0
      fs-service/src/main/java/com/fs/statis/dto/StatsWatchLogPageListDTO.java
  38. 15 0
      fs-service/src/main/java/com/fs/statis/dto/WatchCourseStatisticsDTO.java
  39. 172 0
      fs-service/src/main/java/com/fs/statis/impl/FsStatisQwWatchServiceImpl.java
  40. 8 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisPeriodWatchMapper.java
  41. 116 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisQwTempParamMapper.java
  42. 97 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisQwWatchMapper.java
  43. 31 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java
  44. 68 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisTempFsuserMapper.java
  45. 85 0
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisTempParamMapper.java
  46. 58 0
      fs-service/src/main/java/com/fs/statis/mapper/FsTempPeriodQueryMapper.java
  47. 22 0
      fs-service/src/main/java/com/fs/statis/param/WatchCourseStatisticsParam.java
  48. 0 47
      fs-service/src/main/java/com/fs/statis/service/FsStatisEveryDayWatchService.java
  49. 0 56
      fs-service/src/main/java/com/fs/statis/service/FsStatisPeriodWatchService.java
  50. 15 1
      fs-service/src/main/java/com/fs/statis/service/FsStatisSalerWatchService.java
  51. 2 6
      fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java
  52. 0 70
      fs-service/src/main/java/com/fs/statis/service/impl/FsStatisEveryDayWatchServiceImpl.java
  53. 0 81
      fs-service/src/main/java/com/fs/statis/service/impl/FsStatisPeriodWatchServiceImpl.java
  54. 325 67
      fs-service/src/main/java/com/fs/statis/service/impl/FsStatisSalerWatchServiceImpl.java
  55. 6 2
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java
  56. 8 9
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  57. 12 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  58. 35 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  59. 23 0
      fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml
  60. 82 48
      fs-service/src/main/resources/mapper/sop/QwSopMapper.xml
  61. 39 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  62. 120 0
      fs-service/src/main/resources/mapper/statis/FsStatisQwTempParamMapper.xml
  63. 505 0
      fs-service/src/main/resources/mapper/statis/FsStatisQwWatchMapper.xml
  64. 499 9
      fs-service/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml
  65. 68 0
      fs-service/src/main/resources/mapper/statis/FsStatisTempFsuserMapper.xml
  66. 128 0
      fs-service/src/main/resources/mapper/statis/FsStatisTempParamMapper.xml
  67. 9 0
      fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java

+ 199 - 0
fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java

@@ -0,0 +1,199 @@
+package com.fs.stats;
+
+import com.fs.common.annotation.RateLimiter;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.DeptDataVO;
+import com.fs.sop.params.GetSOPTaskDataParam;
+import com.fs.sop.service.IQwSopLogsService;
+import com.fs.sop.service.IQwSopService;
+import com.fs.sop.vo.QwSopTask;
+import com.fs.statis.IFsStatisQwWatchService;
+import com.fs.statis.domain.FsStatisEveryDayWatch;
+import com.fs.statis.domain.FsStatisQwWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.domain.FsStatisSopWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 看课统计接口
+ */
+@RestController
+@RequestMapping("/stats")
+@AllArgsConstructor
+public class SalesWatchStatisController {
+
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+
+    @Autowired
+    private IQwSopService qwSopService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private IFsStatisQwWatchService fsStatisQwWatchService;
+
+    @Autowired
+    private IQwSopLogsService qwSopLogsService;
+
+    @GetMapping("/computedData")
+    public R computedData(@RequestParam("date") String date){
+        fsStatisSalerWatchService.writeData(date);
+        return R.ok();
+    }
+    /**
+     * 销售完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/seller/pageList")
+    public R sellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryList(param);
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+
+    /**
+     * 进线转化统计
+     * @param param 参数
+     * @return R
+     */
+    @PostMapping("/inline/pageList")
+    public R inlineTransferStats(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisQwWatch> list = fsStatisQwWatchService.queryList(param);
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+    @PostMapping("/inline/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportInlineQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisQwWatch> list = fsStatisQwWatchService.exportQueryList(param);
+        ExcelUtil<FsStatisQwWatch> util = new ExcelUtil<>(FsStatisQwWatch.class);
+        return util.exportExcel(list, "销售完播统计");
+    }
+
+
+    @PostMapping("/seller/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportSellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.export(param);
+        ExcelUtil<FsStatisSalerWatch> util = new ExcelUtil<>(FsStatisSalerWatch.class);
+        return util.exportExcel(list, "销售完播统计");
+    }
+
+    /**
+     * 训练营完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/period/pageList")
+    public R periodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryPeriodList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    @PostMapping("/period/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportPeriodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisSopWatch> list = fsStatisSalerWatchService.exportQueryPeriodList(param);
+        ExcelUtil<FsStatisSopWatch> util = new ExcelUtil<>(FsStatisSopWatch.class);
+        return util.exportExcel(list, "SOP任务完播统计");
+    }
+
+    /**
+     * 每日完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/everyDay/pageList")
+    public R everyDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryTodayList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+    @PostMapping("/everyDay/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportEveryDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisEveryDayWatch> list = fsStatisSalerWatchService.exportQueryEveryDayList(param);
+        ExcelUtil<FsStatisEveryDayWatch> util = new ExcelUtil<>(FsStatisEveryDayWatch.class);
+        return util.exportExcel(list, "每日完播统计");
+    }
+    /**
+     * 获取SOP任务数据
+     * @return
+     */
+    @PostMapping("/sopTaskData")
+    public R getSOPTaskData(@RequestBody GetSOPTaskDataParam param){
+        if(StringUtils.isBlank(param.getStartDate()) && StringUtils.isBlank(param.getEndDate())) {
+            param.setStartDate(LocalDate.now().minusDays(7).toString());
+            param.setEndDate(LocalDate.now().toString());
+        }
+        List<QwSopTask> qwSopTaskList = qwSopService.getQwSopTaskList(param);
+        return R.ok().put("data",qwSopTaskList);
+    }
+
+    /**
+     * 获取部门数据
+     * @return
+     */
+    @GetMapping("/getDeptData")
+    public R getDeptData(Long companyId){
+        List<DeptDataVO> data = companyUserCacheService.getDeptData(companyId);
+        return R.ok().put("data",data);
+    }
+
+}

+ 206 - 0
fs-company/src/main/java/com/fs/company/controller/stats/SalesWatchStatisController.java

@@ -0,0 +1,206 @@
+package com.fs.company.controller.stats;
+
+import com.fs.common.annotation.RateLimiter;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.DeptDataVO;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.sop.params.GetSOPTaskDataParam;
+import com.fs.sop.service.IQwSopService;
+import com.fs.sop.vo.QwSopTask;
+import com.fs.statis.IFsStatisQwWatchService;
+import com.fs.statis.domain.FsStatisEveryDayWatch;
+import com.fs.statis.domain.FsStatisQwWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.domain.FsStatisSopWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import lombok.AllArgsConstructor;
+import org.apache.http.util.Asserts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 看课统计接口
+ */
+@RestController
+@RequestMapping("/stats")
+@AllArgsConstructor
+public class SalesWatchStatisController {
+
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+
+    @Autowired
+    private IQwSopService qwSopService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private IFsStatisQwWatchService fsStatisQwWatchService;
+
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 销售完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/seller/pageList")
+    public R sellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryList(param);
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+
+    /**
+     * 进线转化统计
+     * @param param 参数
+     * @return R
+     */
+    @PostMapping("/inline/pageList")
+    public R inlineTransferStats(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisQwWatch> list = fsStatisQwWatchService.queryList(param);
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+    @PostMapping("/inline/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportInlineQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisQwWatch> list = fsStatisQwWatchService.exportQueryList(param);
+        ExcelUtil<FsStatisQwWatch> util = new ExcelUtil<>(FsStatisQwWatch.class);
+        return util.exportExcel(list, "销售完播统计");
+    }
+
+
+    @PostMapping("/seller/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportSellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.export(param);
+        ExcelUtil<FsStatisSalerWatch> util = new ExcelUtil<>(FsStatisSalerWatch.class);
+        return util.exportExcel(list, "销售完播统计");
+    }
+
+    /**
+     * 训练营完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/period/pageList")
+    public R periodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryPeriodList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+    @PostMapping("/period/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportPeriodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisSopWatch> list = fsStatisSalerWatchService.exportQueryPeriodList(param);
+        ExcelUtil<FsStatisSopWatch> util = new ExcelUtil<>(FsStatisSopWatch.class);
+        return util.exportExcel(list, "SOP任务完播统计");
+    }
+
+    /**
+     * 每日完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/everyDay/pageList")
+    public R everyDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryTodayList(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+    @PostMapping("/everyDay/export")
+    @RateLimiter(time=5,count = 1)
+    public AjaxResult exportEveryDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        param.setPageNum(null);
+        param.setPageSize(null);
+        List<FsStatisEveryDayWatch> list = fsStatisSalerWatchService.exportQueryEveryDayList(param);
+        ExcelUtil<FsStatisEveryDayWatch> util = new ExcelUtil<>(FsStatisEveryDayWatch.class);
+        return util.exportExcel(list, "每日完播统计");
+    }
+    /**
+     * 获取SOP任务数据
+     * @return
+     */
+    @PostMapping("/sopTaskData")
+    public R getSOPTaskData(@RequestBody GetSOPTaskDataParam param){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        if(StringUtils.isBlank(param.getStartDate()) && StringUtils.isBlank(param.getEndDate())) {
+            param.setStartDate(LocalDate.now().minusDays(7).toString());
+            param.setEndDate(LocalDate.now().toString());
+        }
+        param.setCompanyId(companyId);
+        List<QwSopTask> qwSopTaskList = qwSopService.getQwSopTaskList(param);
+        return R.ok().put("data",qwSopTaskList);
+    }
+    /**
+     * 获取部门数据
+     * @return
+     */
+    @GetMapping("/getDeptData")
+    public R getDeptData(Long companyId){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyId = loginUser.getCompany().getCompanyId();
+
+        Long userId = loginUser.getUser().getUserId();
+        Long deptId = loginUser.getUser().getDeptId();
+        Asserts.notNull(companyId,"公司id");
+        Asserts.notNull(userId,"销售id");
+        Asserts.notNull(deptId,"部门id");
+        List<DeptDataVO> data = companyService.getDeptData(companyId,userId,deptId);
+        return R.ok().put("data",data);
+    }
+
+}

+ 5 - 0
fs-service/src/main/java/com/fs/company/cache/ICompanyDeptCacheService.java

@@ -0,0 +1,5 @@
+package com.fs.company.cache;
+
+public interface ICompanyDeptCacheService {
+    String getDeptNameById(Long deptId);
+}

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

@@ -1,7 +1,9 @@
 package com.fs.company.cache;
 
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.vo.DeptDataVO;
 
+import java.util.List;
 import java.util.Set;
 
 ;
@@ -29,4 +31,7 @@ public interface ICompanyUserCacheService {
      */
     public Set<Long> selectUserAllCompanyUserId(Long companyUserId);
 
+    List<DeptDataVO> getDeptData(Long companyId);
+    List<DeptDataVO> getDeptData(Long companyId,Long companyUserId);
+
 }

+ 27 - 0
fs-service/src/main/java/com/fs/company/cache/impl/CompanyDeptCacheServiceImpl.java

@@ -0,0 +1,27 @@
+package com.fs.company.cache.impl;
+
+import com.fs.company.cache.ICompanyDeptCacheService;
+import com.fs.company.service.ICompanyDeptService;
+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 CompanyDeptCacheServiceImpl implements ICompanyDeptCacheService {
+    private static final Cache<Long, String> COMPANY_DEPT_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .build();
+    @Autowired
+    private ICompanyDeptService companyDeptService;
+    @Override
+    public String getDeptNameById(Long deptId) {
+        if(deptId == null) {
+            return "-";
+        }
+        return COMPANY_DEPT_CACHE.get(deptId,e-> companyDeptService.selectDeptNameById(deptId));
+    }
+}

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

@@ -2,13 +2,17 @@ package com.fs.company.cache.impl;
 
 import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.company.vo.DeptDataVO;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -21,6 +25,9 @@ public class CompanyUserCacheServiceImpl implements ICompanyUserCacheService {
     @Autowired
     private ICompanyUserService companyUserService;
 
+    @Autowired
+    private ICompanyService companyService;
+
     private static final Cache<Long, CompanyUser> USER_CACHE = Caffeine.newBuilder()
             .maximumSize(1000)
             .expireAfterWrite(3, TimeUnit.MINUTES)
@@ -36,6 +43,11 @@ public class CompanyUserCacheServiceImpl implements ICompanyUserCacheService {
             .expireAfterWrite(5, TimeUnit.MINUTES)
             .build();
 
+    private static final Cache<Long,List<DeptDataVO>> COMPANY_USER_TREE_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .build();
+
 
     @Override
     public CompanyUser selectCompanyUserById(Long userId) {
@@ -56,4 +68,23 @@ public class CompanyUserCacheServiceImpl implements ICompanyUserCacheService {
             return set;
         });
     }
+
+    @Override
+    public List<DeptDataVO> getDeptData(Long companyId) {
+        if(companyId == null) {
+            companyId = -1L;
+        }
+        Long finalCompanyId = companyId;
+        return COMPANY_USER_TREE_CACHE.get(companyId, e->{
+            if(ObjectUtils.equals(finalCompanyId,-1L)) {
+                return companyService.getDeptData(null);
+            }
+            return companyService.getDeptData(finalCompanyId);
+        });
+    }
+
+    @Override
+    public List<DeptDataVO> getDeptData(Long companyId, Long companyUserId) {
+        return Collections.emptyList();
+    }
 }

+ 18 - 6
fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java

@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 部门Mapper接口
@@ -30,6 +31,9 @@ public interface CompanyDeptMapper
      */
     public List<CompanyDept> selectCompanyDeptList(CompanyDept companyDept);
 
+    @Select("select * from company_dept where dept_id = #{parentId} or parent_id = #{parentId} and company_id=#{companyId} and status=#{status}")
+    public List<CompanyDept> selectCompanyDeptListByDeptAndParent(CompanyDept companyDept);
+
     /**
      * 新增部门
      *
@@ -83,12 +87,20 @@ public interface CompanyDeptMapper
 
     @Select("select company_id from company_dept where dept_id =#{deptId} ")
     Long selectCompanyDeptByIdCompany(Long deptId);
-
     /**
-     * 获取公司默认部门
-     * @param companyId 公司ID
-     * @return  公司部门
+     * 获取所有部门的数据
+     * @param companyId 公司id
+     * @return 部门数据
      */
-    @Select("select cd.* from company_dept cd where cd.company_id = #{companyId} and cd.dept_name = '默认' and cd.parent_id = 0 limit 1")
-    CompanyDept getTopCompanyDeptByCompanyId(@Param("companyId") Long companyId);
+    @Select("select * from company_dept where company_id = ${companyId}")
+    List<CompanyDept> selectDeptDataByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("select dept_id from company_dept where parent_id=#{parentId}")
+    List<Long> selectCompanyDeptByParentId(@Param("parentId") Long parentId);
+
+    @Select("select * from company_dept where status='0' and del_flag='0'")
+    List<CompanyDept> queryDeptDataAll();
+
+    @Select("select dept_name from company_dept where dept_id=${deptId} limit 1")
+    String selectDeptNameById(@Param("deptId") Long deptId);
 }

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

@@ -8,6 +8,7 @@ import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
 import com.fs.his.vo.OptionsVO;
+import com.fs.huifuPay.sdk.opps.core.utils.StringUtil;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -170,6 +171,15 @@ public interface CompanyMapper
     @Select("select company_id from company")
     List<Long> selectCompanyIds();
 
+    String selectCompanyNameCompanyByIds(@Param("companyIds") String companyIds);
+
+    String selectDoctorIdsByCompanyId(Long companyId);
+
+    List<Company> selectCompanyAllList();
+
+    List<OptionsVO> selectByIds(@Param("ids") List<Long> ids);
+
+
     /**
      * 通过企业id批量查询
      * **/

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

@@ -17,6 +17,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import java.time.LocalDate;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -264,4 +265,24 @@ public interface CompanyUserMapper
     List<CompanyUser> getUserInfoByUserIds(@Param("ids") List<Long> ids);
     @DataSource(DataSourceType.MASTER)
     CompanyUser selectCompanyUserByPhone(String phone);
+
+    @Select("select user_id,dept_id,user_name,company_id,nick_name from company_user where ifnull(del_flag,0)=0 and status=0")
+    List<CompanyUser> selectAllCompanyUserList();
+
+    /**
+     * 获取对应的销售观看记录数
+     *
+     * @param companyUserId
+     * @return
+     */
+    Long queryCompanyUserWatchCount(@Param("companyUserId") Long companyUserId,
+                                    @Param("previousDay") LocalDate previousDay);
+
+    Long queryCompanyUserWatchCountCompleted(@Param("companyUserId") Long companyUserId,
+                                             @Param("previousDay") LocalDate previousDay);
+
+    Long queryCompanyUserInterruptCount(@Param("companyUserId") Long companyUserId,
+                                        @Param("previousDay") LocalDate previousDay);
+
+
 }

+ 4 - 6
fs-service/src/main/java/com/fs/company/service/ICompanyDeptService.java

@@ -1,5 +1,6 @@
 package com.fs.company.service;
 
+import com.fs.common.core.domain.entity.SysDept;
 import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyDeptTreeSelect;
 
@@ -28,6 +29,7 @@ public interface ICompanyDeptService
      * @return 部门集合
      */
     public List<CompanyDept> selectCompanyDeptList(CompanyDept companyDept);
+    public List<CompanyDept> selectCompanyDeptListByDeptAndParent(CompanyDept companyDept);
 
     /**
      * 新增部门
@@ -78,11 +80,7 @@ public interface ICompanyDeptService
 
     List<String> selectCompanyDeptNamesByIds(String ids);
 
+    List<Long> selectCompanyDeptByParentId(Long parentId);
 
-    /**
-     * 获取公司默认部门
-     * @param companyId 公司ID
-     * @return 部门
-     */
-    CompanyDept getDefaultCompanyDeptByCompanyId(Long companyId);
+    String selectDeptNameById(Long deptId);
 }

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

@@ -9,6 +9,7 @@ import com.fs.company.param.CompanyParam;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
+import com.fs.company.vo.DeptDataVO;
 import com.fs.his.domain.FsInquiryOrder;
 import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.domain.FsStorePayment;
@@ -115,6 +116,16 @@ public interface ICompanyService
 
     List<Long> selectCompanyIds();
 
+    /**
+     * 查询多个公司的名字
+     * @param companyIds
+     * @return
+     */
+    String selectCompanyByIds(String companyIds);
+
+    List<DeptDataVO> getDeptData(Long companyId);
+    List<DeptDataVO> getDeptData(Long companyId,Long currentCompanyUserId,Long currentDeptId);
+
     void configUserCheck(Long companyId, Integer userIsDefaultBlack);
 
     void addRedPacketCompanyMoney(BigDecimal money, Long companyId);

+ 13 - 7
fs-service/src/main/java/com/fs/company/service/impl/CompanyDeptServiceImpl.java

@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -57,6 +58,11 @@ public class CompanyDeptServiceImpl implements ICompanyDeptService
         return companyDeptMapper.selectCompanyDeptList(companyDept);
     }
 
+    @Override
+    public List<CompanyDept> selectCompanyDeptListByDeptAndParent(CompanyDept companyDept) {
+        return companyDeptMapper.selectCompanyDeptListByDeptAndParent(companyDept);
+    }
+
     /**
      * 新增部门
      *
@@ -224,14 +230,14 @@ public class CompanyDeptServiceImpl implements ICompanyDeptService
         return companyDeptMapper.selectCompanyDeptNamesByIds(ids);
     }
 
-    /**
-     * 获取公司默认部门
-     * @param companyId 公司ID
-     * @return 部门
-     */
     @Override
-    public CompanyDept getDefaultCompanyDeptByCompanyId(Long companyId) {
-        return companyDeptMapper.getTopCompanyDeptByCompanyId(companyId);
+    public List<Long> selectCompanyDeptByParentId(Long parentId) {
+        return companyDeptMapper.selectCompanyDeptByParentId(parentId);
+    }
+
+    @Override
+    public String selectDeptNameById(Long deptId) {
+        return companyDeptMapper.selectDeptNameById(deptId);
     }
 
     /**

+ 433 - 9
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -1,10 +1,8 @@
 package com.fs.company.service.impl;
 
 import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
@@ -18,22 +16,20 @@ import com.fs.company.service.ICompanyProfitService;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
+import com.fs.company.vo.DeptDataVO;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
-import com.fs.his.domain.FsPayConfig;
 import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.domain.FsStorePayment;
 import com.fs.his.dto.InquiryConfigDTO;
 import com.fs.his.mapper.FsStoreOrderMapper;
-import com.fs.his.service.IFsInquiryOrderService;
-import com.fs.his.service.impl.FsInquiryOrderServiceImpl;
 import com.fs.his.vo.OptionsVO;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysConfigService;
 import com.google.gson.Gson;
-import io.swagger.models.auth.In;
-import lombok.Synchronized;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.ObjectUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -77,6 +73,13 @@ public class CompanyServiceImpl implements ICompanyService
     private ICompanyProfitService companyProfitService;
     @Autowired
     private SysConfigMapper sysConfigMapper;
+
+    @Autowired
+    private CompanyDeptMapper companyDeptMapper;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
     @Override
     public List<OptionsVO> selectAllCompanyList() {
         return companyMapper.selectAllCompanyList();
@@ -528,6 +531,427 @@ public class CompanyServiceImpl implements ICompanyService
         return companyMapper.selectCompanyIds() ;
     }
 
+    @Override
+    public String selectCompanyByIds(String companyIds) {
+        return companyMapper.selectCompanyNameCompanyByIds(companyIds);
+
+    }
+
+    @Override
+    public List<DeptDataVO> getDeptData(Long companyId) {
+        List<DeptDataVO> result = new ArrayList<>();
+
+        // 获取用户按部门分组的数据
+        Map<Long, List<CompanyUser>> companyUserGroupByDeptId = getCompanyUserGroupByDeptId();
+        // 获取部门按公司分组的数据
+        Map<Long, List<CompanyDept>> companyDeptGroupByCompanyId = getCompanyDeptGroupByCompanyId();
+        // 获取子部门按父部门分组的数据
+        Map<Long, List<CompanyDept>> deptGroupByParentId = getDeptGroupByParentId();
+
+        if (companyId != null) {
+            Company company = companyMapper.selectCompanyById(companyId);
+            if (company != null) {
+                DeptDataVO companyNode = buildCompanyNode(company, companyUserGroupByDeptId,
+                        companyDeptGroupByCompanyId, deptGroupByParentId);
+                result.add(companyNode);
+            }
+        } else {
+            List<Company> companies = companyMapper.selectCompanyAllList();
+            if (companies != null && !companies.isEmpty()) {
+                for (Company company : companies) {
+                    DeptDataVO companyNode = buildCompanyNode(company, companyUserGroupByDeptId,
+                            companyDeptGroupByCompanyId, deptGroupByParentId);
+                    result.add(companyNode);
+                }
+            }
+        }
+
+        return result.stream()
+                .filter(e -> CollectionUtils.isNotEmpty(e.getChildren()))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<DeptDataVO> getDeptData(Long companyId, Long currentCompanyUserId, Long currentDeptId) {
+        List<DeptDataVO> result = new ArrayList<>();
+
+        // 1. 获取所有部门数据
+        List<CompanyDept> allCompanyDepts = companyDeptMapper.queryDeptDataAll();
+
+        // 2. 按部门ID分组,方便直接获取部门信息
+        Map<Long, CompanyDept> deptMapById = allCompanyDepts.stream()
+                .filter(e -> e.getDeptId() != null)
+                .collect(Collectors.toMap(CompanyDept::getDeptId, dept -> dept, (a, b) -> a));
+
+        // 3. 按父部门ID分组,用于构建树结构
+        Map<Long, List<CompanyDept>> deptsByParentIdMap = allCompanyDepts.stream()
+                .filter(e -> e.getParentId() != null && e.getDeptId() != null)
+                .collect(Collectors.groupingBy(CompanyDept::getParentId));
+
+        // 4. 获取所有用户数据并按部门分组
+        Map<Long, List<CompanyUser>> allUsersByDeptIdMap = getCompanyUserGroupByDeptId();
+
+        // 5. 获取当前用户可见的部门ID集合(本部门及下级部门)
+        Set<Long> visibleDeptIds = new HashSet<>();
+        if (currentDeptId != null) {
+            visibleDeptIds.add(currentDeptId); // 添加当前部门
+            collectSubDepartments(currentDeptId, visibleDeptIds, deptsByParentIdMap); // 添加所有下级部门
+        }
+
+        // 6. 如果没有可见部门,直接返回空列表
+        if (visibleDeptIds.isEmpty()) {
+            return result;
+        }
+
+        // 7. 获取公司信息
+        Company company = companyMapper.selectCompanyById(companyId);
+        if (company == null) {
+            return result;
+        }
+
+        // 8. 构建公司节点
+        DeptDataVO companyNode = new DeptDataVO();
+        companyNode.setLabel(company.getCompanyName());
+        companyNode.setId(company.getCompanyId());
+
+        // 9. 构建部门树(仅包含可见部门)
+        // 先找到当前部门对象
+        CompanyDept currentDept = deptMapById.get(currentDeptId);
+        if (currentDept == null) {
+            return result;
+        }
+
+        // 获取当前部门的所有上级部门路径
+        List<Long> deptPath = new ArrayList<>();
+        Long tempDeptId = currentDeptId;
+        while (tempDeptId != null && tempDeptId > 0) {
+            CompanyDept dept = deptMapById.get(tempDeptId);
+            if (dept == null) break;
+            deptPath.add(tempDeptId);
+            tempDeptId = dept.getParentId();
+        }
+
+        // 构建以公司为根的树形结构
+        // 找出顶级部门(parentId为null或0的部门)
+        List<CompanyDept> rootDepts = allCompanyDepts.stream()
+                .filter(dept -> dept.getCompanyId() != null &&
+                        dept.getCompanyId().equals(companyId) &&
+                        (dept.getParentId() == null || dept.getParentId().equals(0L)))
+                .collect(Collectors.toList());
+
+        // 递归构建带权限的树
+        List<DeptDataVO> deptTree = buildDeptTreeWithPermission(
+                rootDepts,
+                allUsersByDeptIdMap,
+                deptsByParentIdMap,
+                visibleDeptIds,
+                deptPath,
+                currentDeptId,
+                currentCompanyUserId);
+
+        companyNode.setChildren(deptTree.isEmpty() ? null : deptTree);
+        result.add(companyNode);
+
+        // 过滤掉空公司节点
+        return result.stream()
+                .filter(node -> node.getChildren() != null && !node.getChildren().isEmpty())
+                .collect(Collectors.toList());
+    }
+    /**
+     * 递归收集所有下级部门ID
+     */
+    private void collectSubDepartments(Long parentDeptId, Set<Long> deptIds, Map<Long, List<CompanyDept>> deptsByParentIdMap) {
+        List<CompanyDept> childDepts = deptsByParentIdMap.get(parentDeptId);
+        if (childDepts != null && !childDepts.isEmpty()) {
+            for (CompanyDept childDept : childDepts) {
+                if (childDept.getDeptId() != null && deptIds.add(childDept.getDeptId())) {
+                    collectSubDepartments(childDept.getDeptId(), deptIds, deptsByParentIdMap);
+                }
+            }
+        }
+    }
+    /**
+     * 递归构建带权限控制的部门树
+     */
+    private List<DeptDataVO> buildDeptTreeWithPermission(
+            List<CompanyDept> depts,
+            Map<Long, List<CompanyUser>> allUsersByDeptIdMap,
+            Map<Long, List<CompanyDept>> deptsByParentIdMap,
+            Set<Long> visibleDeptIds,
+            List<Long> deptPath,
+            Long currentDeptId,
+            Long currentCompanyUserId) {
+
+        if (depts == null || depts.isEmpty()) {
+            return new ArrayList<>();
+        }
+        List<DeptDataVO> result = new ArrayList<>();
+        for (CompanyDept dept : depts) {
+            // 如果当前部门不在用户可见范围内,且不在部门路径中,则跳过
+            if (!visibleDeptIds.contains(dept.getDeptId()) && !deptPath.contains(dept.getDeptId())) {
+                continue;
+            }
+
+            // 先递归构建子部门
+            List<CompanyDept> childDepts = deptsByParentIdMap.get(dept.getDeptId());
+            List<DeptDataVO> children = new ArrayList<>();
+
+            if (childDepts != null && !childDepts.isEmpty()) {
+                List<DeptDataVO> childDeptNodes = buildDeptTreeWithPermission(
+                        childDepts,
+                        allUsersByDeptIdMap,
+                        deptsByParentIdMap,
+                        visibleDeptIds,
+                        deptPath,
+                        currentDeptId,
+                        currentCompanyUserId);
+                if (!childDeptNodes.isEmpty()) {
+                    children.addAll(childDeptNodes);
+                }
+            }
+            // 添加部门下的用户(需要权限控制)
+            List<DeptDataVO> userNodes = new ArrayList<>();
+            if (visibleDeptIds.contains(dept.getDeptId())) {
+                List<CompanyUser> deptUsers = allUsersByDeptIdMap.get(dept.getDeptId());
+                if (deptUsers != null && !deptUsers.isEmpty()) {
+                    for (CompanyUser user : deptUsers) {
+                        // 如果是当前部门,只显示当前用户
+                        if (dept.getDeptId().equals(currentDeptId)) {
+                            if (user.getUserId().equals(currentCompanyUserId)) {
+                                DeptDataVO userNode = new DeptDataVO();
+                                userNode.setLabel(user.getNickName() + "_" + user.getUserName());
+                                userNode.setId(user.getUserId());
+                                userNode.setChildren(null);
+                                userNodes.add(userNode);
+                            }
+                        } else {
+                            // 非当前部门,显示所有用户
+                            DeptDataVO userNode = new DeptDataVO();
+                            userNode.setLabel(user.getNickName() + "_" + user.getUserName());
+                            userNode.setId(user.getUserId());
+                            userNode.setChildren(null);
+                            userNodes.add(userNode);
+                        }
+                    }
+                }
+            }
+
+            // 将用户节点添加到子节点列表
+            if (!userNodes.isEmpty()) {
+                children.addAll(userNodes);
+            }
+            // 只有当下面有子部门或者有用户时,才添加此部门
+            if (!children.isEmpty()) {
+                DeptDataVO deptNode = new DeptDataVO();
+                deptNode.setLabel(dept.getDeptName());
+                deptNode.setId(dept.getDeptId());
+                deptNode.setChildren(children);
+                result.add(deptNode);
+            } else if (deptPath.contains(dept.getDeptId())) {
+                // 即使没有子部门和用户,如果是部门路径上的节点,仍然需要添加
+                DeptDataVO deptNode = new DeptDataVO();
+                deptNode.setLabel(dept.getDeptName());
+                deptNode.setId(dept.getDeptId());
+                deptNode.setChildren(null);
+                result.add(deptNode);
+            }
+        }
+        return result;
+    }
+    /**
+     * 获取用户按部门ID分组
+     */
+    public Map<Long, List<CompanyUser>> getCompanyUserGroupByDeptId() {
+        List<CompanyUser> userList = companyUserMapper.selectAllCompanyUserList();
+        return userList.stream()
+                .filter(user -> user.getDeptId() != null)
+                .collect(Collectors.groupingBy(CompanyUser::getDeptId));
+    }
+
+
+    /**
+     * 获取部门按公司ID分组
+     */
+    public Map<Long, List<CompanyDept>> getCompanyDeptGroupByCompanyId() {
+        List<CompanyDept> companyDepts = companyDeptMapper.queryDeptDataAll();
+        return companyDepts.stream()
+                .collect(Collectors.groupingBy(CompanyDept::getCompanyId));
+    }
+
+    /**
+     * 获取部门按父部门ID分组(新增方法)
+     */
+    public Map<Long, List<CompanyDept>> getDeptGroupByParentId() {
+        List<CompanyDept> companyDepts = companyDeptMapper.queryDeptDataAll();
+        return companyDepts.stream()
+                .filter(dept -> dept.getParentId() != null) // 过滤掉顶级部门
+                .collect(Collectors.groupingBy(CompanyDept::getParentId));
+    }
+
+    /**
+     * 构建公司节点,包含其下属多级部门和用户
+     */
+    private DeptDataVO buildCompanyNode(Company company,
+                                        Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
+                                        Map<Long, List<CompanyDept>> companyDeptGroupByCompanyId,
+                                        Map<Long, List<CompanyDept>> deptGroupByParentId) {
+        DeptDataVO companyNode = new DeptDataVO();
+        companyNode.setLabel(company.getCompanyName());
+        companyNode.setId(company.getCompanyId());
+
+        // 获取公司下的顶级部门(parentId为null或为公司ID的部门)
+        List<CompanyDept> topLevelDepts = companyDeptGroupByCompanyId.get(company.getCompanyId());
+        if (topLevelDepts != null) {
+            topLevelDepts = topLevelDepts.stream()
+                    .filter(dept -> dept.getParentId() == null || dept.getParentId().equals(0L))
+                    .collect(Collectors.toList());
+        }
+
+        List<DeptDataVO> deptDataList = buildDeptTree(topLevelDepts, companyUserGroupByDeptId, deptGroupByParentId);
+        companyNode.setChildren(deptDataList.isEmpty() ? null : deptDataList);
+
+        return companyNode;
+    }
+
+    /**
+     * 构建公司节点,包含其下属多级部门和用户
+     */
+    private DeptDataVO buildCompanyNode(Company company,
+                                        Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
+                                        Map<Long, List<CompanyDept>> companyDeptGroupByCompanyId,
+                                        Map<Long, List<CompanyDept>> deptGroupByParentId,
+                                        Long currentDeptId,
+                                        Long currentCompanyUserId
+                                        ) {
+        DeptDataVO companyNode = new DeptDataVO();
+        companyNode.setLabel(company.getCompanyName());
+        companyNode.setId(company.getCompanyId());
+
+        // 获取公司下的顶级部门(parentId为null或为公司ID的部门)
+        List<CompanyDept> topLevelDepts = companyDeptGroupByCompanyId.get(company.getCompanyId());
+        if (topLevelDepts != null) {
+            topLevelDepts = topLevelDepts.stream()
+                    .filter(dept -> dept.getParentId() == null || dept.getParentId().equals(0L))
+                    .collect(Collectors.toList());
+        }
+
+        List<DeptDataVO> deptDataList = buildDeptTree(topLevelDepts, companyUserGroupByDeptId, deptGroupByParentId,currentDeptId,currentCompanyUserId);
+        companyNode.setChildren(deptDataList.isEmpty() ? null : deptDataList);
+
+        return companyNode;
+    }
+
+    /**
+     * 递归构建部门树
+     */
+    private List<DeptDataVO> buildDeptTree(List<CompanyDept> depts,
+                                           Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
+                                           Map<Long, List<CompanyDept>> deptGroupByParentId) {
+        if (depts == null || depts.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<DeptDataVO> result = new ArrayList<>();
+
+        for (CompanyDept dept : depts) {
+            DeptDataVO deptNode = new DeptDataVO();
+            deptNode.setLabel(dept.getDeptName());
+            deptNode.setId(dept.getDeptId());
+
+            List<DeptDataVO> children = new ArrayList<>();
+
+            // 1. 添加子部门(递归)
+            List<CompanyDept> childDepts = deptGroupByParentId.get(dept.getDeptId());
+            if (childDepts != null && !childDepts.isEmpty()) {
+                List<DeptDataVO> childDeptNodes = buildDeptTree(childDepts, companyUserGroupByDeptId, deptGroupByParentId);
+                children.addAll(childDeptNodes);
+            }
+
+            // 2. 添加部门下的用户
+            List<CompanyUser> deptUsers = companyUserGroupByDeptId.get(dept.getDeptId());
+            if (deptUsers != null && !deptUsers.isEmpty()) {
+                for (CompanyUser user : deptUsers) {
+                    DeptDataVO userNode = new DeptDataVO();
+                    userNode.setLabel(user.getNickName()+"_"+user.getUserName());
+                    userNode.setId(user.getUserId());
+                    userNode.setChildren(null);
+                    children.add(userNode);
+                }
+            }
+
+            deptNode.setChildren(children.isEmpty() ? null : children);
+            result.add(deptNode);
+        }
+
+        return result;
+    }
+    /**
+     * 递归构建部门树
+     */
+    /**
+     *
+     * @param depts
+     * @param companyUserGroupByDeptId
+     * @param deptGroupByParentId
+     * @param currentDeptId 当前部门id
+     * @param currentCompanyUserId 当前销售id
+     * @return
+     */
+    private List<DeptDataVO> buildDeptTree(List<CompanyDept> depts,
+                                           Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
+                                           Map<Long, List<CompanyDept>> deptGroupByParentId,
+                                           Long currentDeptId,
+                                           Long currentCompanyUserId) {
+        if (depts == null || depts.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<DeptDataVO> result = new ArrayList<>();
+
+        for (CompanyDept dept : depts) {
+            DeptDataVO deptNode = new DeptDataVO();
+            deptNode.setLabel(dept.getDeptName());
+            deptNode.setId(dept.getDeptId());
+
+            List<DeptDataVO> children = new ArrayList<>();
+
+            // 1. 添加子部门(递归)
+            List<CompanyDept> childDepts = deptGroupByParentId.get(dept.getDeptId());
+            if (childDepts != null && !childDepts.isEmpty()) {
+                List<DeptDataVO> childDeptNodes = buildDeptTree(childDepts, companyUserGroupByDeptId, deptGroupByParentId);
+                children.addAll(childDeptNodes);
+            }
+
+            // 2. 添加部门下的用户
+            List<CompanyUser> deptUsers = companyUserGroupByDeptId.get(dept.getDeptId());
+            if (deptUsers != null && !deptUsers.isEmpty()) {
+                for (CompanyUser user : deptUsers) {
+                    // 如果是销售当前部门,不显示同级其他销售
+                    if(ObjectUtils.equals(dept.getDeptId(),currentDeptId)) {
+                        if(ObjectUtils.equals(user.getUserId(),currentCompanyUserId)) {
+                            DeptDataVO userNode = new DeptDataVO();
+                            userNode.setLabel(user.getNickName()+"_"+user.getUserName());
+                            userNode.setId(user.getUserId());
+                            userNode.setChildren(null);
+                            children.add(userNode);
+                        }
+                    } else {
+                        DeptDataVO userNode = new DeptDataVO();
+                        userNode.setLabel(user.getNickName()+"_"+user.getUserName());
+                        userNode.setId(user.getUserId());
+                        userNode.setChildren(null);
+                        children.add(userNode);
+                    }
+                }
+            }
+
+            deptNode.setChildren(children.isEmpty() ? null : children);
+            result.add(deptNode);
+        }
+
+        return result;
+    }
+
     @Override
     @Transactional
     public void refundCompanyMoney(FsStoreOrder order) {

+ 22 - 0
fs-service/src/main/java/com/fs/company/vo/DeptDataVO.java

@@ -0,0 +1,22 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class DeptDataVO implements Serializable {
+    /**
+     * 节点名称
+     */
+    private String label;
+    /**
+     * 节点id
+     */
+    private Long id;
+    /**
+     * 子节点
+     */
+    private List<DeptDataVO> children;
+}

+ 60 - 0
fs-service/src/main/java/com/fs/qw/cache/QwSopCacheService.java

@@ -0,0 +1,60 @@
+package com.fs.qw.cache;
+
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.SopUserLogsMapper;
+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.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class QwSopCacheService {
+    @Autowired
+    private QwSopMapper qwSopMapper;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+
+    private static final Cache<String, String> QW_SOP_NAME_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .build();
+
+    private static final Cache<String, String> QW_SOP_LOG_NAME_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .build();
+
+
+    public String getQwSopNameBySopId(String sopId) {
+        return QW_SOP_NAME_CACHE.get(sopId,e->{
+            String sopName = qwSopMapper.selectQwSopNameBySopId(sopId);
+            if(StringUtils.isNotBlank(sopName)){
+                return sopName;
+            }
+            return "-";
+        });
+    }
+
+    public String getQwSopLogNameBySopId(String periodId) {
+        return QW_SOP_LOG_NAME_CACHE.get(periodId,e->{
+            SopUserLogs sopUserLogs = sopUserLogsMapper.selectSopUserLogsById(periodId);
+
+            if(sopUserLogs != null) {
+                String qwUserName = qwUserMapper.selectQwUserName(sopUserLogs.getQwUserId(), sopUserLogs.getCorpId());
+                if(qwUserName != null) {
+                    return qwUserName+"_"+sopUserLogs.getStartTime();
+                }
+            }
+            return "-";
+        });
+    }
+}

+ 61 - 0
fs-service/src/main/java/com/fs/qw/cache/QwUserCacheService.java

@@ -0,0 +1,61 @@
+package com.fs.qw.cache;
+
+import com.fs.qw.dto.QwUserDTO;
+import com.fs.qw.mapper.QwUserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class QwUserCacheService {
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    private Map<String, Map<String, String>> qwUserCache = new ConcurrentHashMap<>();
+
+
+    @PostConstruct
+    public void initCache() {
+        refreshCache();
+    }
+
+    public void refreshCache() {
+        List<QwUserDTO> allUsers = qwUserMapper.selectAllQwUserMapping();
+        Map<String, Map<String, String>> newCache = new ConcurrentHashMap<>();
+
+        for (QwUserDTO user : allUsers) {
+            newCache.computeIfAbsent(user.getCorpId(), k -> new ConcurrentHashMap<>())
+                    .put(user.getQwUserId(), user.getId());
+        }
+
+        this.qwUserCache = newCache;
+    }
+
+    // 根据qwUserId和corpId获取id
+    public String getIdByQwUserIdAndCorpId(String qwUserId, String corpId) {
+        Map<String, String> corpUsers = qwUserCache.get(corpId);
+        return corpUsers != null ? corpUsers.get(qwUserId) : null;
+    }
+
+    public Map<String, String> batchGetIds(List<String> qwUserIds, String corpId) {
+        Map<String, String> result = new HashMap<>();
+        Map<String, String> corpUsers = qwUserCache.get(corpId);
+
+        if (corpUsers != null) {
+            for (String qwUserId : qwUserIds) {
+                String id = corpUsers.get(qwUserId);
+                if (id != null) {
+                    result.put(qwUserId, id);
+                }
+            }
+        }
+
+        return result;
+    }
+}

+ 12 - 0
fs-service/src/main/java/com/fs/qw/dto/QwUserDTO.java

@@ -0,0 +1,12 @@
+package com.fs.qw.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class QwUserDTO implements Serializable {
+    private String qwUserId;
+    private String corpId;
+    private String id;
+}

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

@@ -5,6 +5,7 @@ import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.dto.QwUserByToolDTO;
+import com.fs.qw.dto.QwUserDTO;
 import com.fs.qw.dto.QwUserKeyDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.vo.*;
@@ -376,4 +377,17 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     List<QwExternalListByHeavyVO> getQwExternalListByHeavy(@Param("data") FsCourseListBySidebarParam param);
 
     List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param);
+
+    @Select("select id,qw_user_id,company_user_id from qw_user where is_del=0 and company_user_id is not null")
+    List<QwUser> selectQwUserAllList();
+
+    @Select("select qw_user_id, corp_id, id from qw_user where qw_user_id is not null and corp_id is not null")
+    List<QwUserDTO> selectAllQwUserMapping();
+
+    @Select("select qw_user_name from qw_user where qw_user_id=#{qwUserId} and corp_id=#{corpId}")
+    String selectQwUserName(@Param("qwUserId") String qwUserId,@Param("corpId") String corpId);
+
+    @Select("select qw_user_id from qw_user where company_user_id = ${companyUserId}")
+    List<String> findQwUserIdListByCompanyUserId(@Param("companyUserId") Long companyUserId);
+
 }

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

@@ -88,6 +88,8 @@ public class QwSopLogs implements Serializable {
     @TableField(exist = false)
     private Integer takeRecords;
 
+    private String sopTitle;
+
     // 构造函数
 //    public QwSopLogs() {
 //        this.id = UUID.randomUUID().toString();

+ 14 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java

@@ -9,11 +9,15 @@ import com.fs.sop.params.*;
 import com.fs.sop.vo.QwSopLogsDoSendListTVO;
 import com.fs.sop.vo.QwSopLogsListCVO;
 import com.fs.sop.vo.QwSopLogsListVOByUserInfo;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 企业微信SOP  定时任务Mapper接口
@@ -293,5 +297,15 @@ public interface QwSopLogsMapper extends BaseMapper<QwSopLogs> {
     List<QwSopLogs> selectSopLogsByCreateCorpMassSendResult();
     @DataSource(DataSourceType.SOP)
     List<QwSopLogsListCVO> selectQwSopLogsListByChatSopId(@Param("map") QwSopLogsParam param);
+    @DataSource(DataSourceType.SOP)
+    String queryPeriodNameById(@Param("periodId") String periodId);
+
+    @DataSource(DataSourceType.SOP)
+    @MapKey("id")
+    Map<String,QwSopLogs> queryAllPeriod();
 
+    @DataSource(DataSourceType.SOP)
+    Long selectQwSopLogsCountByQwUserId(@Param("data") List<String> qwUserIdList,
+                                        @Param("periodId") String periodId,
+                                        @Param("previousDay") LocalDate previousDay);
 }

+ 57 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopMapper.java

@@ -12,14 +12,18 @@ import com.fs.qw.vo.ChatSopRuleTimeVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.WxSopRuleTimeVO;
 import com.fs.sop.domain.QwSop;
+import com.fs.sop.params.GetSOPTaskDataParam;
 import com.fs.sop.params.QwSopAutoByTags;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopTagsParam;
+import com.fs.qw.result.QwFilterSopCustomersResult;
+import com.fs.qw.vo.QwSopRuleTimeVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * 企微sopMapper接口
@@ -64,6 +68,35 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
     public List<QwSop> selectQwSopList(QwSop qwSop);
 
     @DataSource(DataSourceType.SOP)
+    @Select("<script>" +
+            "select * from qw_sop" +
+            " where 1=1 " +
+            "            <if test=\"name != null  and name != ''\"> and name like concat('%', #{name}, '%')</if>\n" +
+            "            <if test=\"status != null \"> and status = #{status}</if>\n" +
+            "            <if test=\"type != null \"> and type = #{type}</if>\n" +
+            "            <if test=\"id != null and id != '' \"> and id = #{id}</if>\n" +
+            "            <if test=\"companyId != null \"> and company_id = #{companyId}</if>\n" +
+            "            <if test=\"qwUserIds != null  and qwUserIds != ''\"> and FIND_IN_SET(#{qwUserIds}, qw_user_ids) > 0</if>\n" +
+            "            <if test=\"createBy != null  and createBy != ''\"> and create_by = #{createBy}</if>\n" +
+            "            <if test=\"corpId != null  and corpId != ''\"> and corp_id = #{corpId}</if>\n" +
+            "            <if test=\"createTime != null \"> and DATE(create_time) = #{createTime}</if>\n" +
+            "            <if test=\"sendType != null \"> and send_type = #{sendType}</if>\n" +
+            "            <if test=\"expiryTime != null \"> and expiry_time = #{expiryTime}</if>\n" +
+            "            <if test=\"isAutoSop != null \"> and is_auto_sop = #{isAutoSop}</if>\n" +
+            "            <if test=\"autoSopTime != null \"> and auto_sop_time = #{autoSopTime}</if>\n" +
+            "            <if test=\"minSend != null \"> and min_send = #{minSend}</if>\n" +
+            "            <if test=\"maxSend != null \"> and max_send = #{maxSend}</if>\n" +
+            "            <if test=\"stopTime != null \"> and stop_time = #{stopTime}</if>\n" +
+            "            <if test=\"qwUserIdList != null and !qwUserIdList.isEmpty() \">" +
+                            "  and ( \n" +
+                            "    <foreach collection='qwUserIdList' item='item' index='index' separator=' or '> \n" +
+                            "        find_in_set( #{item} , REGEXP_REPLACE(qw_user_ids,  '[\"\\\\[\\\\]]', '' ) ) \n" +
+                            "    </foreach> \n" +
+                            "    )" +
+            "            </if>" +
+            "            <!-- 加入固定条件 -->\n" +
+            "            and status != 6" +
+            "</script>")
     public List<QwSop> selectQwSopMyList(QwSop qwSop);
 
     /**
@@ -362,9 +395,33 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
     @Select("select * FROM qw_sop where is_rating = 1 and send_type in(2,3) and min_conversion_day is not null and max_conversion_day is not null order by create_time desc")
     List<QwSop> selectQwSopByIsRatingNotNull();
 
+    @DataSource(DataSourceType.SOP)
+    List<QwSop> selectQwSopAllList(GetSOPTaskDataParam param);
+
     @DataSource(DataSourceType.SOP)
     public List<ChatSopRuleTimeVO> executeSopChatByIds(@Param("ids") String[] ids);
 
     @DataSource(DataSourceType.SOP)
     public int updateStatusQwSopById2(@Param("ids") List<String> ids);
+
+    List<QwSop> selectQwSopByTempId(@Param("tempId") String tempId);
+
+    @DataSource(DataSourceType.SOP)
+    @Select("select name from qw_sop where id=#{sopId} limit 1")
+    String selectQwSopNameBySopId(@Param("sopId") String sopId);
+
+    /**
+     * 批量更新sop表的chId
+     * @param sopList 更新数据
+     * **/
+    @DataSource(DataSourceType.SOP)
+    void batchUpdateSopChatIdById(@Param("sopList") List<QwSop> sopList);
+
+    /**
+     * 批量查询数据
+     * @param ids id
+     * @return 结果
+     * **/
+    @DataSource(DataSourceType.SOP)
+    List<QwSop> getQwSopInfoById(@Param("ids") Set<String> ids);
 }

+ 24 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -6,6 +6,7 @@ import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.vo.UpdateSopUserLogDateVo;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.params.GetSOPTaskDataParam;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.params.SopUserLogsParam;
 import com.fs.sop.params.SopUserLogsParamByDate;
@@ -227,6 +228,29 @@ public interface SopUserLogsMapper {
     @DataSource(DataSourceType.SOP)
     void batchInsertSopUserLogs(@Param("list") List<SopUserLogs> list);
 
+    @DataSource(DataSourceType.SOP)
+    List<String> selectSopUserLogsByQwUserIds(@Param("qwUserIds") List<String> qwUserIdList);
+
+    /**
+     *  未自动创建营期 开启的 (相当于固定营期)
+     *  否则按进线时间每天创建
+     * @return
+     */
+    @DataSource(DataSourceType.SOP)
+    @Select(" SELECT log.id, log.qw_user_id,log.sop_id,log.corp_id, qw_sop.is_auto_sop,log.start_time\n" +
+            "            FROM sop_user_logs log\n" +
+            "            LEFT JOIN qw_sop ON log.sop_id = qw_sop.id\n" +
+            "            WHERE \n" +
+            "              log.status = 1\n" +
+            "             AND qw_sop.status in(2,3)")
+    List<SopUserLogs> queryAllSopUserLogs();
+
     @DataSource(DataSourceType.SOP)
     void updateSopuserLogsDateById(UpdateSopUserLogDateVo vo);
+
+    @DataSource(DataSourceType.SOP)
+    List<SopUserLogs> querySopUserLogsByParam(GetSOPTaskDataParam param);
+
+    @DataSource(DataSourceType.SOP)
+    List<SopUserLogs> getSopUserLogsInfoById(@Param("ids") String[] ids);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/sop/params/GetSOPTaskDataParam.java

@@ -0,0 +1,12 @@
+package com.fs.sop.params;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class GetSOPTaskDataParam implements Serializable {
+    private String startDate;
+    private String endDate;
+    private Long companyId;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopService.java

@@ -3,8 +3,10 @@ package com.fs.sop.service;
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.sop.domain.QwSop;
+import com.fs.sop.params.GetSOPTaskDataParam;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
+import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
 
 import java.io.IOException;
@@ -90,4 +92,8 @@ public interface IQwSopService
     List<SopVoiceListVo> getSopVoiceList(String id);
 
     List<QwSop> selectWxSop();
+
+    List<QwSopTask> getQwSopTaskList(GetSOPTaskDataParam param);
+
+    List<QwSop> selectQwSopByTempId(String tempId);
 }

+ 58 - 4
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -27,11 +27,13 @@ import com.fs.sop.domain.*;
 import com.fs.sop.mapper.*;
 import com.fs.sop.params.*;
 import com.fs.sop.service.*;
+import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
 import com.fs.sop.vo.VoiceVo;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -71,6 +73,10 @@ public class QwSopServiceImpl implements IQwSopService
 
     @Autowired
     private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+    @Autowired
+    private AsyncChatSopService asyncChatSopService;
+    @Autowired
+    private QwSopTempMapper qwSopTempMapper;
 
     @Autowired
     private IFsCourseLinkService iFsCourseLinkService;
@@ -83,8 +89,6 @@ public class QwSopServiceImpl implements IQwSopService
 
     @Autowired
     private SopUserLogsMapper sopUserLogsMapper;
-    @Autowired
-    private QwSopTempMapper qwSopTempMapper;
 
     @Autowired
     private AsyncSopService asyncSopService;
@@ -112,8 +116,6 @@ public class QwSopServiceImpl implements IQwSopService
 
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
-    @Autowired
-    private AsyncChatSopService asyncChatSopService;
 
     /**
      * 查询企微sop
@@ -1016,6 +1018,58 @@ public class QwSopServiceImpl implements IQwSopService
         return qwSopMapper.selectWxSop();
     }
 
+    @Override
+    public List<QwSopTask> getQwSopTaskList(GetSOPTaskDataParam param) {
+        List<QwSop> qwSopList = qwSopMapper.selectQwSopAllList(param);
+        Map<String, List<SopUserLogs>> sopUserLogsMap7Days = getSopUserLogsMap(param);
+
+        List<QwSopTask> qwSopTaskList = new ArrayList<>();
+
+        for (QwSop qwSop : qwSopList) {
+            QwSopTask qwSopTask = new QwSopTask();
+            qwSopTask.setId(qwSop.getId());
+            qwSopTask.setLabel(qwSop.getName());
+
+            List<QwSopTask> listData = new ArrayList<>();
+            // 获取对应SOP任务执行记录
+            List<SopUserLogs> sopUserLogs = sopUserLogsMap7Days.get(qwSop.getId());
+            if(CollectionUtils.isEmpty(sopUserLogs)){
+                log.info("当前SOP无记录!已跳过");
+                continue;
+            }
+            for (SopUserLogs sopUserLog : sopUserLogs) {
+                QwSopTask child = new QwSopTask();
+                child.setLabel(sopUserLog.getQwUserId()+"_"+sopUserLog.getStartTime());
+                child.setId(sopUserLog.getId());
+                listData.add(child);
+            }
+            qwSopTask.setChildren(listData);
+
+            qwSopTaskList.add(qwSopTask);
+        }
+        if(CollectionUtils.isEmpty(qwSopTaskList)){
+            return Collections.emptyList();
+        }
+
+        return qwSopTaskList.stream().filter(e-> CollectionUtils.isNotEmpty(e.getChildren())).collect(Collectors.toList());
+    }
+
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public List<QwSop> selectQwSopByTempId(String tempId) {
+        return qwSopMapper.selectQwSopByTempId(tempId);
+    }
+
+
+    public Map<String,List<SopUserLogs>> getSopUserLogsMap(GetSOPTaskDataParam param){
+        List<SopUserLogs> sopUserLogs = sopUserLogsMapper.querySopUserLogsByParam(param);
+        if(CollectionUtils.isEmpty(sopUserLogs)) {
+            return new HashMap<>();
+        }
+        return sopUserLogs.stream().collect(Collectors.groupingBy(SopUserLogs::getSopId));
+    }
+
     /**
      *新增员工执行SOP
      */

+ 28 - 0
fs-service/src/main/java/com/fs/sop/vo/QwSopTask.java

@@ -0,0 +1,28 @@
+package com.fs.sop.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 企微sop任务
+ */
+@Data
+public class QwSopTask implements Serializable {
+
+    /**
+     * 任务id
+     */
+    private String id;
+
+    /**
+     * 任务名称
+     */
+    private String label;
+
+    /**
+     * 任务详情
+     */
+    private List<QwSopTask> children;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/statis/IFsStatisQwWatchService.java

@@ -0,0 +1,14 @@
+package com.fs.statis;
+
+import com.fs.statis.domain.FsStatisQwWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+
+import java.util.List;
+
+public interface IFsStatisQwWatchService {
+
+    void writeData(String date);
+
+    List<FsStatisQwWatch> queryList(StatsWatchLogPageListDTO param);
+    List<FsStatisQwWatch> exportQueryList(StatsWatchLogPageListDTO param);
+}

+ 6 - 3
fs-service/src/main/java/com/fs/statis/domain/FsStatisPeriodWatch.java

@@ -1,11 +1,13 @@
 package com.fs.statis.domain;
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import java.util.Date;
+import java.time.LocalDate;
 
 /**
  * fs_statis_period_watch表 实体类
@@ -22,12 +24,13 @@ public class FsStatisPeriodWatch {
     /**
      * 主键ID (虽然注释中没有,但通常id为主键)
      */
+    @TableId(type = IdType.AUTO)
     private Integer id;
 
     /**
      * 训练营id
      */
-    private Integer periodId;
+    private String periodId;
 
     /**
      * 训练营人数
@@ -97,7 +100,7 @@ public class FsStatisPeriodWatch {
     /**
      * 数据日期
      */
-    private Date dataDate;
+    private LocalDate dataDate;
 
 
     /**

+ 47 - 0
fs-service/src/main/java/com/fs/statis/domain/FsStatisQwTempParam.java

@@ -0,0 +1,47 @@
+package com.fs.statis.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * 企微临时参数统计实体
+ * Corresponds to the fs_statis_qw_temp_param table
+ */
+@Data
+public class FsStatisQwTempParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    private Integer id;
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+    /**
+     * 当天时间
+     */
+    private LocalDate thisDate;
+
+    /**
+     * 企微id
+     */
+    private Long qwUserId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 公司
+     */
+    private Long companyId;
+
+}

+ 190 - 0
fs-service/src/main/java/com/fs/statis/domain/FsStatisQwWatch.java

@@ -0,0 +1,190 @@
+package com.fs.statis.domain;
+
+import com.fs.common.annotation.Excel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 企微观看统计
+ * fs_statis_qw_watch
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class FsStatisQwWatch {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 部门
+     */
+    private Long deptId;
+    @Excel(name = "部门")
+    private String deptName;
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+    /**
+     * 销售
+     */
+    @Excel(name = "销售")
+    private String companyUserName;
+
+    /**
+     * 企微号
+     */
+    private String qwUserId;
+
+    /**
+     * 任务数
+     */
+    @Excel(name = "任务数")
+    private Long sopTaskNum;
+
+    /**
+     * 营期数
+     */
+    @Excel(name = "营期数")
+    private Long periodNum;
+
+    /**
+     * 营期人数
+     */
+    @Excel(name = "营期人数")
+    private Long periodPersonNum;
+
+    /**
+     * 完课数
+     */
+    @Excel(name = "完课数")
+    private Long completedNum;
+
+    /**
+     * 数据日期
+     */
+    private String dataDate;
+
+    /**
+     * 发课数
+     */
+    @Excel(name = "发课数")
+    private Long sendNum;
+
+    /**
+     * 待看课数
+     */
+    @Excel(name = "待看课数")
+    private Long notRegisteredNum;
+
+    /**
+     * 中断数
+     */
+    @Excel(name = "中断数")
+    private Long interruptNum;
+
+    /**
+     * 上线数 (generated column, read-only from insert/update perspective)
+     */
+    @Excel(name = "上线数")
+    private Long registeredNum;
+
+    /**
+     * 企微重粉数
+     */
+    @Excel(name = "企微重粉数")
+    private Long qwRepeatNum;
+
+    /**
+     * 小程序重粉
+     */
+    @Excel(name = "小程序重粉")
+    private Long userRepeatNum;
+
+    /**
+     * 拉黑数
+     */
+    @Excel(name = "拉黑数")
+    private Long blackNum;
+
+    /**
+     * 删除数
+     */
+    @Excel(name = "删除数")
+    private Long deletedNum;
+
+    /**
+     * 订单总数
+     */
+    @Excel(name = "订单总数")
+    private Long orderNum;
+
+    /**
+     * 订单总金额
+     */
+    @Excel(name = "订单总金额")
+    private BigDecimal orderMoneyTotal;
+
+    /**
+     * 红包总金额
+     */
+    @Excel(name = "红包总金额")
+    private BigDecimal redPackageMoneyTotal;
+
+    /**
+     * 总拨打数
+     */
+    @Excel(name = "总拨打数")
+    private Long callNum;
+
+    /**
+     * 接通数
+     */
+    @Excel(name = "接通数")
+    private Long receivePassNum;
+
+    /**
+     * 未接通数
+     */
+    @Excel(name = "未接通数")
+    private Long receiveNotNum;
+
+    /**
+     * 通话时长(单位s)
+     */
+    @Excel(name = "通话时长(单位s)")
+    private Long callTimeTotal;
+
+    /**
+     * 催课未处理数
+     */
+    @Excel(name = "催课未处理数")
+    private Long remindPendingNum;
+
+    /**
+     * 催课已处理数
+     */
+    @Excel(name = "催课已处理数")
+    private Long remindProcessedNum;
+
+    /**
+     * 上线率 (generated column, read-only from insert/update perspective)
+     */
+    @Excel(name = "上线率")
+    private BigDecimal regRate;
+
+    /**
+     * 完课率 (generated column, read-only from insert/update perspective)
+     */
+    @Excel(name = "完课率")
+    private BigDecimal finishedRate;
+}

+ 52 - 2
fs-service/src/main/java/com/fs/statis/domain/FsStatisSalerWatch.java

@@ -1,5 +1,8 @@
 package com.fs.statis.domain;
 
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -25,39 +28,78 @@ public class FsStatisSalerWatch {
      * 部门
      */
     private Long deptId;
+    /**
+     * 部门名称
+     */
+    @Excel(name = "所属部门")
+    private String deptName;
 
     /**
      * 销售id
      */
     private Long companyUserId;
+    /**
+     * 销售名称
+     */
+    @Excel(name = "销售名称")
+    private String companyUserName;
 
     /**
      * 训练营人数
      */
+    @Excel(name = "训练营人数")
     private Long trainCampNum;
 
+    /**
+     * 发课数
+     */
+    @Excel(name = "发课数")
+    private Long sendNum;
+
     /**
      * 未报名人数
      */
+    @Excel(name = "未报名人数")
     private Long notRegisteredNum;
 
     /**
      * 已报名人数
      */
+    @Excel(name = "已报名人数")
     private Long registeredNum;
     /**
      * 完课人数
      */
+    @Excel(name = "完课人数")
     private Long completedNum;
+    /**
+     * 看课中断数
+     */
+    @Excel(name = "看课中断数")
+    private Long interruptNum;
+
+    /**
+     * 企微重粉
+     */
+    @Excel(name = "企微重粉")
+    private Long qwRepeatNum;
+
+    /**
+     * 小程序(看课)重粉
+     */
+    @Excel(name = "小程序(看课)重粉")
+    private Long userRepeatNum;
 
     /**
      * 报名率
      */
+    @Excel(name = "上线率")
     private Float regRate;
 
     /**
      * 完课率
      */
+    @Excel(name = "完课率")
     private Float finishedRate;
 
     /**
@@ -101,12 +143,20 @@ public class FsStatisSalerWatch {
     private Long onlineCompletePlayback;
 
     /**
-     * 训练营id
+     * 营id
      */
-    private Long periodId;
+    private String periodId;
+
+    /**
+     * 营期名称
+     */
+    private String periodName;
+
 
     /**
      * 数据日期
      */
     private LocalDate dataDate;
+    private String sopId;
+    private String sopTaskName;
 }

+ 157 - 0
fs-service/src/main/java/com/fs/statis/domain/FsStatisSopWatch.java

@@ -0,0 +1,157 @@
+package com.fs.statis.domain;
+
+import com.fs.common.annotation.Excel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+
+/**
+ * 销售观看统计实体类
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FsStatisSopWatch {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 部门
+     */
+    private Long deptId;
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+    /**
+     * 销售名称
+     */
+    private String companyUserName;
+
+    /**
+     * 营期名称
+     */
+    @Excel(name = "sop营期")
+    private String periodName;
+    /**
+     * 训练营人数
+     */
+    @Excel(name = "训练营人数")
+    private Long trainCampNum;
+
+    /**
+     * 发课数
+     */
+    @Excel(name = "发课数")
+    private Long sendNum;
+
+    /**
+     * 未报名人数
+     */
+    @Excel(name = "未报名人数")
+    private Long notRegisteredNum;
+
+    /**
+     * 已报名人数
+     */
+    @Excel(name = "已报名人数")
+    private Long registeredNum;
+    /**
+     * 完课人数
+     */
+    @Excel(name = "完课人数")
+    private Long completedNum;
+    /**
+     * 看课中断数
+     */
+    @Excel(name = "看课中断数")
+    private Long interruptNum;
+
+    /**
+     * 企微重粉
+     */
+    @Excel(name = "企微重粉")
+    private Long qwRepeatNum;
+
+    /**
+     * 小程序(看课)重粉
+     */
+    @Excel(name = "小程序(看课)重粉")
+    private Long userRepeatNum;
+
+    /**
+     * 报名率
+     */
+    @Excel(name = "上线率")
+    private Float regRate;
+
+    /**
+     * 完课率
+     */
+    @Excel(name = "完课率")
+    private Float finishedRate;
+
+    /**
+     * 未上线-总数
+     */
+    private Long offlineTotal;
+
+    /**
+     * 未上线-未参与
+     */
+    private Long offlineNotPart;
+
+    /**
+     * 未上线-未观看 (原SQL注释为'为观看',推测应为'未观看')
+     */
+    private Long offlineNotWatched;
+
+    /**
+     * 已上线-总数
+     */
+    private Long onlineTotal;
+
+    /**
+     * 已上线-上线率
+     */
+    private Float onlineOnlineRate;
+
+    /**
+     * 已上线-完播率
+     */
+    private Float onlinePlaybackCompleRate;
+
+    /**
+     * 已上线-未完播
+     */
+    private Long onlineIncompletePlayback;
+
+    /**
+     * 已上线-已完播
+     */
+    private Long onlineCompletePlayback;
+
+    /**
+     * 营期id
+     */
+    private String periodId;
+
+
+    /**
+     * 数据日期
+     */
+    private LocalDate dataDate;
+    private String sopId;
+}

+ 41 - 0
fs-service/src/main/java/com/fs/statis/domain/FsStatisTempFsuser.java

@@ -0,0 +1,41 @@
+package com.fs.statis.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * fs_statis_temp_fsuser 实体类
+ *
+ * @author YourName
+ * @since YYYY-MM-DD
+ */
+@Data
+public class FsStatisTempFsuser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 周期ID
+     */
+    @TableField("period_id")
+    private String periodId;
+
+    /**
+     * FS用户ID
+     */
+    @TableField("fs_user_id")
+    private Long fsUserId;
+
+    /**
+     * 企微id
+     */
+    @TableField("qw_user_id")
+    private String qwUserId;
+}

+ 59 - 0
fs-service/src/main/java/com/fs/statis/domain/FsStatisTempParam.java

@@ -0,0 +1,59 @@
+package com.fs.statis.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * DTO for fs_statis_temp_param table
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class FsStatisTempParam {
+
+    /**
+     * id
+     */
+    private Integer id;
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+    /**
+     * sop_id
+     */
+    private String sopId;
+
+    /**
+     * 当天时间
+     */
+    private LocalDate thisDate;
+
+    /**
+     * 企微id
+     */
+    private String qwUserId;
+
+    /**
+     * 营期id
+     */
+    private String periodId;
+
+    /**
+     * 营期时间
+     */
+    private String startTime;
+
+    /**
+     * create_time
+     */
+    private LocalDateTime createTime;
+
+    private Long deptId;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/statis/domain/FsTempPeriodQuery.java

@@ -0,0 +1,24 @@
+package com.fs.statis.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 临时期次查询实体类
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class FsTempPeriodQuery {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 期次ID
+     */
+    private String periodId;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/statis/dto/FsStatisQwWatchWriteDataDTO.java

@@ -0,0 +1,23 @@
+package com.fs.statis.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+@Data
+public class FsStatisQwWatchWriteDataDTO implements Serializable {
+    /**
+     * 开始时间
+     */
+    private String startTime;
+    /**
+     * 结束时间
+     */
+    private String endTime;
+    /**
+     * 当前日期
+     */
+    private LocalDate date;
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/statis/dto/StatsWatchLogPageListDTO.java

@@ -27,4 +27,7 @@ public class StatsWatchLogPageListDTO implements Serializable {
      */
     private List<String> periodList;
 
+    private Integer pageNum;
+    private Integer pageSize;
+
 }

+ 15 - 0
fs-service/src/main/java/com/fs/statis/dto/WatchCourseStatisticsDTO.java

@@ -0,0 +1,15 @@
+package com.fs.statis.dto;
+
+import com.fs.course.dto.WatchLogDTO;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 看课统计
+ */
+@Data
+public class WatchCourseStatisticsDTO implements Serializable {
+    private List<WatchLogDTO> data;
+}

+ 172 - 0
fs-service/src/main/java/com/fs/statis/impl/FsStatisQwWatchServiceImpl.java

@@ -0,0 +1,172 @@
+package com.fs.statis.impl;
+
+import com.fs.common.utils.StringUtils;
+import com.fs.company.cache.ICompanyDeptCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.statis.IFsStatisQwWatchService;
+import com.fs.statis.domain.FsStatisQwTempParam;
+import com.fs.statis.domain.FsStatisQwWatch;
+import com.fs.statis.dto.FsStatisQwWatchWriteDataDTO;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.mapper.FsStatisQwTempParamMapper;
+import com.fs.statis.mapper.FsStatisQwWatchMapper;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class FsStatisQwWatchServiceImpl implements IFsStatisQwWatchService {
+    @Autowired
+    private FsStatisQwWatchMapper fsStatisQwWatchMapper;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private ICompanyDeptCacheService companyDeptCacheService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private FsStatisQwTempParamMapper fsStatisQwTempParamMapper;
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @Override
+    public synchronized void writeData(String date) {
+
+        FsStatisQwWatchWriteDataDTO paramByDate = getParamByDate(date);
+
+
+        List<CompanyUser> companyUserList = this.companyUserMapper.selectAllCompanyUserList();
+        Map<Long, List<Long>> qwUserIdMap = queryQwListMap();
+
+
+        List<FsStatisQwTempParam> paramList = new ArrayList<>();
+        for (CompanyUser companyUser : companyUserList) {
+            if(companyUser.getCompanyId() == null) {
+                log.info("销售{} 对应公司id {} 为空!",companyUser.getUserId(),companyUser.getCompanyId());
+                continue;
+            }
+            // 找到销售关联的 企微账号
+            List<Long> qwUserIdList = qwUserIdMap.get(companyUser.getUserId());
+            if(CollectionUtils.isEmpty(qwUserIdList)){
+                log.info("当前销售 {} 没有关联企微账号!",companyUser.getUserId());
+                continue;
+            }
+
+            for (Long qwUserId : qwUserIdList) {
+                FsStatisQwTempParam param = new FsStatisQwTempParam();
+                param.setDeptId(companyUser.getDeptId());
+                param.setCompanyUserId(companyUser.getUserId());
+                param.setQwUserId(qwUserId);
+                param.setThisDate(paramByDate.getDate());
+                param.setCompanyId(companyUser.getCompanyId());
+                paramList.add(param);
+            }
+        }
+        fsStatisQwTempParamMapper.clear();
+
+        fsStatisQwTempParamMapper.insertList(paramList);
+
+        fsStatisQwWatchMapper.generateData(paramByDate);
+
+        fsStatisQwTempParamMapper.clear();
+    }
+
+    @Override
+    public List<FsStatisQwWatch> queryList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+
+        List<FsStatisQwWatch> list = fsStatisQwWatchMapper.queryList(param);
+
+        for (FsStatisQwWatch item : list) {
+            if(item.getDeptId() != null) {
+                String deptNameById = companyDeptCacheService.getDeptNameById(item.getDeptId());
+                if(StringUtils.isNotBlank(deptNameById)) {
+                    item.setDeptName(deptNameById);
+                }
+            }
+
+            if(item.getCompanyUserId() != null) {
+                String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
+                if(StringUtils.isNotBlank(companyUserName)) {
+                    item.setCompanyUserName(companyUserName);
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<FsStatisQwWatch> exportQueryList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+
+        List<FsStatisQwWatch> list = fsStatisQwWatchMapper.exportList(param);
+
+        return list;
+    }
+
+    private FsStatisQwWatchWriteDataDTO getParamByDate(String dateStr){
+        LocalDate targetDate;
+
+        if (dateStr != null && !dateStr.isEmpty()) {
+            try {
+                targetDate = LocalDate.parse(dateStr, DATE_FORMATTER);
+            } catch (DateTimeParseException e) {
+                log.info("提供的日期字符串格式无效: {} . 期望格式: yyyy-MM-dd" ,dateStr);
+                throw e;
+            }
+        } else {
+            targetDate = LocalDate.now().minusDays(1);
+        }
+
+        LocalDateTime startTime = targetDate.atStartOfDay();
+
+        LocalDateTime endTime = targetDate.plusDays(1).atStartOfDay();
+
+        FsStatisQwWatchWriteDataDTO params = new FsStatisQwWatchWriteDataDTO();
+        params.setDate(targetDate);
+        params.setStartTime(startTime.format(DATE_TIME_FORMATTER));
+        params.setEndTime(endTime.format(DATE_TIME_FORMATTER));
+        return params;
+    }
+
+
+    private Map<Long,List<Long>> queryQwListMap(){
+        List<QwUser> qwUsers = qwUserMapper.selectQwUserAllList();
+        if(CollectionUtils.isEmpty(qwUsers)){
+            return Maps.newHashMap();
+        }
+
+        return qwUsers.stream()
+                .filter(Objects::nonNull)
+                .filter(user -> user.getCompanyUserId() != null && user.getQwUserId() != null)
+                .collect(Collectors.groupingBy(
+                        QwUser::getCompanyUserId,
+                        Collectors.mapping(QwUser::getId, Collectors.toList())
+                ));
+    }
+}

+ 8 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisPeriodWatchMapper.java

@@ -1,9 +1,11 @@
 package com.fs.statis.mapper;
 
+import com.fs.sop.domain.SopUserLogs;
 import com.fs.statis.domain.FsStatisPeriodWatch;
 import com.fs.statis.dto.StatsWatchLogPageListDTO;
 import org.apache.ibatis.annotations.*;
 
+import java.time.LocalDate;
 import java.util.List;
 
 /**
@@ -91,4 +93,10 @@ public interface FsStatisPeriodWatchMapper {
     List<FsStatisPeriodWatch> selectAll();
 
     List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param);
+
+    /**
+     * 获取每个sop任务对应的营期记录
+     * @return
+     */
+    List<SopUserLogs> selectRecords(@Param("previousDay") LocalDate previousDay);
 }

+ 116 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisQwTempParamMapper.java

@@ -0,0 +1,116 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisQwTempParam;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Mapper interface for operations on the fs_statis_qw_temp_param table.
+ * The method names should correspond to the 'id' attributes in FsStatisQwTempParamMapper.xml.
+ */
+@Mapper // This annotation is commonly used with Spring Boot to mark it as a MyBatis mapper
+public interface FsStatisQwTempParamMapper {
+
+    /**
+     * Selects a record by its primary key.
+     * Corresponds to the SQL statement with id="selectByPrimaryKey" in the XML.
+     *
+     * @param id The primary key.
+     * @return The FsStatisQwTempParam object, or null if not found.
+     */
+    FsStatisQwTempParam selectByPrimaryKey(Integer id);
+
+    /**
+     * Deletes a record by its primary key.
+     * Corresponds to the SQL statement with id="deleteByPrimaryKey" in the XML.
+     *
+     * @param id The primary key.
+     * @return The number of rows affected.
+     */
+    int deleteByPrimaryKey(Integer id);
+
+    /**
+     * Inserts a new record.
+     * Corresponds to the SQL statement with id="insert" in the XML.
+     * The 'id' property of the 'record' parameter will be populated with the generated ID
+     * if 'useGeneratedKeys="true"' is set in the XML.
+     *
+     * @param record The FsStatisQwTempParam object to insert.
+     * @return The number of rows affected.
+     */
+    int insert(FsStatisQwTempParam record);
+
+    /**
+     * Inserts a new record, only including non-null fields.
+     * Corresponds to the SQL statement with id="insertSelective" in the XML.
+     * The 'id' property of the 'record' parameter will be populated with the generated ID
+     * if 'useGeneratedKeys="true"' is set in the XML.
+     *
+     * @param record The FsStatisQwTempParam object to insert.
+     * @return The number of rows affected.
+     */
+    int insertSelective(FsStatisQwTempParam record);
+
+    /**
+     * Updates an existing record by its primary key, only including non-null fields in the update.
+     * Corresponds to the SQL statement with id="updateByPrimaryKeySelective" in the XML.
+     *
+     * @param record The FsStatisQwTempParam object containing the fields to update and the primary key.
+     * @return The number of rows affected.
+     */
+    int updateByPrimaryKeySelective(FsStatisQwTempParam record);
+
+    /**
+     * Updates an existing record by its primary key, updating all fields.
+     * Corresponds to the SQL statement with id="updateByPrimaryKey" in the XML.
+     *
+     * @param record The FsStatisQwTempParam object containing all fields to update and the primary key.
+     * @return The number of rows affected.
+     */
+    int updateByPrimaryKey(FsStatisQwTempParam record);
+
+    /**
+     * Selects all records from the table.
+     * Corresponds to the SQL statement with id="selectAll" in the XML.
+     *
+     * @return A list of all FsStatisQwTempParam objects.
+     */
+    List<FsStatisQwTempParam> selectAll();
+
+    /**
+     * Selects records based on the provided criteria.
+     * Corresponds to the SQL statement with id="selectByParams" in the XML.
+     * The 'criteria' object's properties are used in the WHERE clause.
+     *
+     * @param criteria An FsStatisQwTempParam object containing the query parameters.
+     * @return A list of FsStatisQwTempParam objects matching the criteria.
+     */
+    List<FsStatisQwTempParam> selectByParams(FsStatisQwTempParam criteria);
+
+    // --- You can add more custom query methods here ---
+    // For example, if you wanted to find records by a specific company_user_id and this_date:
+    /*
+    List<FsStatisQwTempParam> findByCompanyUserAndDate(
+            @Param("companyUserId") Long companyUserId,
+            @Param("thisDate") String thisDate
+    );
+    */
+    // If you add such a method, you'd also need to add a corresponding <select>
+    // statement in your FsStatisQwTempParamMapper.xml, like:
+    /*
+    <select id="findByCompanyUserAndDate" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List" />
+        FROM fs_statis_qw_temp_param
+        WHERE company_user_id = #{companyUserId,jdbcType=BIGINT}
+          AND this_date = #{thisDate,jdbcType=VARCHAR}
+    </select>
+    */
+
+    int insertList(@Param("list") List<FsStatisQwTempParam> recordList);
+
+    @Delete("truncate table fs_statis_qw_temp_param")
+    void clear();
+}

+ 97 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisQwWatchMapper.java

@@ -0,0 +1,97 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisQwWatch;
+import com.fs.statis.dto.FsStatisQwWatchWriteDataDTO;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface FsStatisQwWatchMapper {
+
+    /**
+     * 根据主键查询
+     *
+     * @param id 主键ID
+     * @return 实体
+     */
+    FsStatisQwWatch selectByPrimaryKey(Long id);
+
+    /**
+     * 根据唯一键查询
+     *
+     * @param deptId 部门ID
+     * @param companyUserId 销售ID
+     * @param qwUserId 企微号
+     * @param dataDate 数据日期
+     * @return 实体
+     */
+    FsStatisQwWatch selectByUniqueKey(@Param("deptId") Long deptId,
+                                      @Param("companyUserId") Long companyUserId,
+                                      @Param("qwUserId") String qwUserId,
+                                      @Param("dataDate") Date dataDate);
+
+    /**
+     * 新增记录 (不包括generated columns)
+     *
+     * @param record 实体
+     * @return 影响行数
+     */
+    int insert(FsStatisQwWatch record);
+
+    /**
+     * 新增记录 - 选择性插入 (不包括generated columns)
+     *
+     * @param record 实体
+     * @return 影响行数
+     */
+    int insertSelective(FsStatisQwWatch record);
+
+    /**
+     * 根据主键更新 (不更新generated columns)
+     *
+     * @param record 实体
+     * @return 影响行数
+     */
+    int updateByPrimaryKey(FsStatisQwWatch record);
+
+    /**
+     * 根据主键选择性更新 (不更新generated columns)
+     *
+     * @param record 实体
+     * @return 影响行数
+     */
+    int updateByPrimaryKeySelective(FsStatisQwWatch record);
+
+    /**
+     * 根据主键删除
+     *
+     * @param id 主键ID
+     * @return 影响行数
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * 查询列表 (可添加查询条件参数)
+     *
+     * @param example 查询条件 (可以是一个FsStatisQwWatch对象作为查询模板)
+     * @return 列表
+     */
+    List<FsStatisQwWatch> selectList(FsStatisQwWatch example);
+
+    /**
+     * 批量插入
+     *
+     * @param list 记录列表
+     * @return 影响行数
+     */
+    int batchInsert(@Param("list") List<FsStatisQwWatch> list);
+
+    void generateData(FsStatisQwWatchWriteDataDTO param);
+
+    List<FsStatisQwWatch> queryList(StatsWatchLogPageListDTO param);
+    List<FsStatisQwWatch> exportList(StatsWatchLogPageListDTO param);
+}

+ 31 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java

@@ -1,6 +1,11 @@
 package com.fs.statis.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.statis.domain.FsStatisEveryDayWatch;
 import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.domain.FsStatisSopWatch;
+import com.fs.statis.domain.FsStatisTempFsuser;
 import com.fs.statis.dto.StatsWatchLogPageListDTO;
 import org.apache.ibatis.annotations.*;
 
@@ -74,7 +79,33 @@ public interface FsStatisSalerWatchMapper {
     @Delete("DELETE FROM fs_statis_saler_watch WHERE id = #{id}")
     int deleteById(@Param("id") Integer id);
 
+    @DataSource(DataSourceType.SLAVE)
     List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
 
     void batchSave(@Param("list") List<FsStatisSalerWatch> writeData);
+
+    @DataSource(DataSourceType.SLAVE)
+    List<FsStatisSalerWatch> queryPeriodList(StatsWatchLogPageListDTO param);
+
+    @DataSource(DataSourceType.SLAVE)
+    List<FsStatisSopWatch> queryPeriodListExport(StatsWatchLogPageListDTO param);
+
+    @DataSource(DataSourceType.SLAVE)
+    List<FsStatisSalerWatch> queryTodayList(StatsWatchLogPageListDTO param);
+
+    @DataSource(DataSourceType.SLAVE)
+    List<FsStatisSalerWatch> queryListExport(StatsWatchLogPageListDTO param);
+
+    @DataSource(DataSourceType.SLAVE)
+    List<FsStatisEveryDayWatch> queryEveryDayListExport(StatsWatchLogPageListDTO param);
+
+    void generateData(@Param("date") String date);
+
+    List<FsStatisSalerWatch> generateSopData(@Param("date") String date);
+
+    @DataSource(DataSourceType.SOP)
+    List<FsStatisTempFsuser> querySopRepeatData(@Param("date") String date);
+
+    @Delete("delete from fs_statis_saler_watch where data_date=#{date}")
+    void deleteDateData(@Param("date") String date);
 }

+ 68 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisTempFsuserMapper.java

@@ -0,0 +1,68 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisTempFsuser;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * fs_statis_temp_fsuser Mapper 接口
+ *
+ * @author YourName
+ * @since YYYY-MM-DD
+ */
+@Mapper
+public interface FsStatisTempFsuserMapper {
+
+    /**
+     * 根据ID查询
+     *
+     * @param id 主键ID
+     * @return 实体对象
+     */
+    FsStatisTempFsuser selectById(@Param("id") Long id);
+
+    /**
+     * 查询所有记录
+     *
+     * @return 实体对象列表
+     */
+    List<FsStatisTempFsuser> selectAll();
+
+    /**
+     * 新增数据
+     *
+     * @param fsStatisTempFsuser 实例对象
+     * @return 影响行数
+     */
+    int insert(FsStatisTempFsuser fsStatisTempFsuser);
+
+    /**
+     * 批量新增数据(MyBatis原生foreach方法)
+     *
+     * @param entities List<FsStatisTempFsuser> 实例对象列表
+     * @return 影响行数
+     */
+    int insertBatch(@Param("entities") List<FsStatisTempFsuser> entities);
+
+    /**
+     * 修改数据
+     *
+     * @param fsStatisTempFsuser 实例对象
+     * @return 影响行数
+     */
+    int updateById(FsStatisTempFsuser fsStatisTempFsuser);
+
+    /**
+     * 根据ID删除
+     *
+     * @param id 主键ID
+     * @return 影响行数
+     */
+    int deleteById(@Param("id") Long id);
+
+    @Delete("truncate table fs_statis_temp_fsuser")
+    void clear();
+}

+ 85 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsStatisTempParamMapper.java

@@ -0,0 +1,85 @@
+package com.fs.statis.mapper;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.statis.domain.FsStatisTempParam;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface FsStatisTempParamMapper {
+
+    /**
+     * Inserts a new record.
+     *
+     * @param fsStatisTempParam The record to insert.
+     * @return The number of rows affected.
+     */
+    int insert(FsStatisTempParam fsStatisTempParam);
+
+    void batchSave(List<FsStatisTempParam> list);
+
+    @DataSource(DataSourceType.SOP)
+    void batchSaveToSop(List<FsStatisTempParam> list);
+
+    /**
+     * Inserts a new record, only if a field is not null.
+     * (This would typically be implemented in XML with <if> tags)
+     *
+     * @param fsStatisTempParam The record to insert.
+     * @return The number of rows affected.
+     */
+    int insertSelective(FsStatisTempParam fsStatisTempParam);
+
+    /**
+     * Selects a record by its primary key.
+     *
+     * @param id The primary key.
+     * @return The found record, or null if not found.
+     */
+    FsStatisTempParam selectById(Integer id);
+
+    /**
+     * Updates an existing record by its primary key.
+     * All fields will be updated.
+     *
+     * @param fsStatisTempParam The record with updated values.
+     * @return The number of rows affected.
+     */
+    int updateById(FsStatisTempParam fsStatisTempParam);
+
+    /**
+     * Updates an existing record by its primary key.
+     * Only non-null fields in fsStatisTempParam will be updated.
+     * (This would typically be implemented in XML with <if> tags in the SET clause)
+     *
+     * @param fsStatisTempParam The record with updated values.
+     * @return The number of rows affected.
+     */
+    int updateByIdSelective(FsStatisTempParam fsStatisTempParam);
+
+    /**
+     * Deletes a record by its primary key.
+     *
+     * @param id The primary key.
+     * @return The number of rows affected.
+     */
+    int deleteById(Integer id);
+
+    /**
+     * Selects all records from the table.
+     *
+     * @return A list of all records.
+     */
+    List<FsStatisTempParam> selectAll();
+
+    @Delete("truncate table fs_statis_temp_param")
+    void clear();
+
+    @DataSource(DataSourceType.SOP)
+    @Delete("truncate table fs_statis_temp_param")
+    void clearSop();
+
+}

+ 58 - 0
fs-service/src/main/java/com/fs/statis/mapper/FsTempPeriodQueryMapper.java

@@ -0,0 +1,58 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsTempPeriodQuery;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 临时期次查询Mapper接口
+ */
+@Mapper
+public interface FsTempPeriodQueryMapper {
+
+    /**
+     * 根据ID查询临时期次信息
+     *
+     * @param id 主键ID
+     * @return 临时期次信息
+     */
+    @Select("SELECT id, period_id AS periodId FROM fs_temp_period_query WHERE id = #{id}")
+    FsTempPeriodQuery findById(Long id);
+
+    /**
+     * 新增临时期次信息
+     *
+     * @param fsTempPeriodQuery 临时期次信息
+     * @return 影响行数
+     */
+    @Insert("INSERT INTO fs_temp_period_query(id, period_id) VALUES(#{id}, #{periodId})")
+    int insert(FsTempPeriodQuery fsTempPeriodQuery);
+
+    /**
+     * 更新临时期次信息
+     *
+     * @param fsTempPeriodQuery 临时期次信息
+     * @return 影响行数
+     */
+    @Update("UPDATE fs_temp_period_query SET period_id = #{periodId} WHERE id = #{id}")
+    int update(FsTempPeriodQuery fsTempPeriodQuery);
+
+    /**
+     * 批量插入临时期次信息
+     *
+     * @param list 临时期次信息列表
+     * @return 影响行数
+     */
+    @Insert("<script>" +
+            "INSERT INTO fs_temp_period_query(period_id) VALUES " +
+            "<foreach collection='list' item='item' index='index' separator=','>" +
+            "(#{item})" +
+            "</foreach>" +
+            "</script>")
+    int insertBatch(@Param("list") List<String> list);
+
+    @Delete("truncate table fs_temp_period_query")
+    void clear();
+
+}

+ 22 - 0
fs-service/src/main/java/com/fs/statis/param/WatchCourseStatisticsParam.java

@@ -0,0 +1,22 @@
+ package com.fs.statis.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+ /**
+  * 看课统计参数
+  */
+ @Data
+ public class WatchCourseStatisticsParam implements Serializable {
+
+     /**
+      * 0 七天
+      * 1 30天
+      */
+     private Integer type;
+     /**
+      * 企微外部联系人id
+      */
+     private Long qwExternalContactId;
+ }

+ 0 - 47
fs-service/src/main/java/com/fs/statis/service/FsStatisEveryDayWatchService.java

@@ -1,47 +0,0 @@
-package com.fs.statis.service;
-
-import com.fs.statis.domain.FsStatisEveryDayWatch;
-import com.fs.statis.domain.FsStatisSalerWatch;
-import com.fs.statis.dto.StatsWatchLogPageListDTO;
-
-import java.util.List;
-
-/**
- * 每日统计数据服务接口
- */
-public interface FsStatisEveryDayWatchService {
-
-    /**
-     * 根据主键ID查询每日统计数据
-     *
-     * @param id 主键ID
-     * @return 每日统计数据对象,如果不存在则返回null
-     */
-    FsStatisEveryDayWatch findById(Integer id);
-
-    /**
-     * 新增每日统计数据
-     *
-     * @param fsStatisEveryDayWatch 待插入的每日统计数据对象
-     * @return 影响的行数,通常是1表示成功
-     */
-    int create(FsStatisEveryDayWatch fsStatisEveryDayWatch);
-
-    /**
-     * 更新每日统计数据
-     *
-     * @param fsStatisEveryDayWatch 待更新的每日统计数据对象 (必须包含ID)
-     * @return 影响的行数,通常是1表示成功,0表示未找到对应记录
-     */
-    int update(FsStatisEveryDayWatch fsStatisEveryDayWatch);
-
-    /**
-     * 查询所有每日统计数据
-     *
-     * @return 每日统计数据列表
-     */
-    List<FsStatisEveryDayWatch> findAll();
-
-    List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
-
-}

+ 0 - 56
fs-service/src/main/java/com/fs/statis/service/FsStatisPeriodWatchService.java

@@ -1,56 +0,0 @@
-package com.fs.statis.service; // 假设Service接口放在此包下
-
-import com.fs.statis.domain.FsStatisPeriodWatch;
-import com.fs.statis.dto.StatsWatchLogPageListDTO;
-
-import java.util.List;
-
-/**
- * 训练营周期统计数据服务接口
- * 对应表 fs_statis_period_watch
- */
-public interface FsStatisPeriodWatchService {
-
-    /**
-     * 根据主键ID查询训练营周期统计数据
-     *
-     * @param id 主键ID
-     * @return 训练营周期统计数据对象,如果不存在则返回null
-     */
-    FsStatisPeriodWatch findById(Integer id);
-
-    /**
-     * 新增训练营周期统计数据
-     *
-     * @param fsStatisPeriodWatch 待插入的训练营周期统计数据对象
-     * @return 影响的行数,通常是1表示成功
-     */
-    int create(FsStatisPeriodWatch fsStatisPeriodWatch);
-
-    /**
-     * 根据主键更新训练营周期统计数据
-     * (只会更新实体中非null的字段)
-     *
-     * @param fsStatisPeriodWatch 待更新的训练营周期统计数据对象 (必须包含ID)
-     * @return 影响的行数,通常是1表示成功,0表示未找到对应记录或未更新任何字段
-     */
-    int updateById(FsStatisPeriodWatch fsStatisPeriodWatch);
-
-    /**
-     * 根据主键ID删除训练营周期统计数据
-     *
-     * @param id 主键ID
-     * @return 影响的行数
-     */
-    int deleteById(Integer id);
-
-    /**
-     * 查询所有训练营周期统计数据
-     *
-     * @return 训练营周期统计数据列表
-     */
-    List<FsStatisPeriodWatch> findAll();
-
-    List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param);
-
-}

+ 15 - 1
fs-service/src/main/java/com/fs/statis/service/FsStatisSalerWatchService.java

@@ -1,6 +1,8 @@
 package com.fs.statis.service;
 
+import com.fs.statis.domain.FsStatisEveryDayWatch;
 import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.domain.FsStatisSopWatch;
 import com.fs.statis.dto.StatsWatchLogPageListDTO;
 
 import java.util.List;
@@ -50,10 +52,22 @@ public interface FsStatisSalerWatchService {
     boolean deleteById(Integer id);
 
     List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
+    List<FsStatisSalerWatch> queryPeriodList(StatsWatchLogPageListDTO param);
+    List<FsStatisSalerWatch> queryTodayList(StatsWatchLogPageListDTO param);
+    List<FsStatisSalerWatch> export(StatsWatchLogPageListDTO param);
+    List<FsStatisSopWatch> exportQueryPeriodList(StatsWatchLogPageListDTO param);
+    List<FsStatisEveryDayWatch> exportQueryEveryDayList(StatsWatchLogPageListDTO param);
+
 
 
     /**
      * 写入数据 写入前一天的数据
      */
-    void writeData();
+    void writeData(String date);
+
+    /**
+     * 计算每天的数据
+     */
+    void writeDataToday();
+
 }

+ 2 - 6
fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java

@@ -1,8 +1,7 @@
 package com.fs.statis.service;
 
 import com.fs.statis.dto.*;
-import com.fs.statistics.dto.WatchCourseStatisticsDTO;
-import com.fs.statistics.param.WatchCourseStatisticsParam;
+import com.fs.statis.param.WatchCourseStatisticsParam;
 
 import java.util.List;
 
@@ -114,9 +113,6 @@ public interface IStatisticsService {
 
     /**
      * 查询看课统计
-     *
-     * @param param
-     * @return
      */
-    public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param);
+    WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param);
 }

+ 0 - 70
fs-service/src/main/java/com/fs/statis/service/impl/FsStatisEveryDayWatchServiceImpl.java

@@ -1,70 +0,0 @@
-package com.fs.statis.service.impl;
-
-import com.fs.statis.domain.FsStatisEveryDayWatch;
-import com.fs.statis.domain.FsStatisSalerWatch;
-import com.fs.statis.dto.StatsWatchLogPageListDTO;
-import com.fs.statis.mapper.FsStatisEveryDayWatchMapper;
-import com.fs.statis.service.FsStatisEveryDayWatchService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-
-/**
- * 每日统计数据服务实现类
- */
-@Service
-public class FsStatisEveryDayWatchServiceImpl implements FsStatisEveryDayWatchService {
-
-    private final FsStatisEveryDayWatchMapper fsStatisEveryDayWatchMapper;
-
-    /**
-     * 通过构造函数注入Mapper
-     * @param fsStatisEveryDayWatchMapper 每日统计数据Mapper
-     */
-    @Autowired
-    public FsStatisEveryDayWatchServiceImpl(FsStatisEveryDayWatchMapper fsStatisEveryDayWatchMapper) {
-        this.fsStatisEveryDayWatchMapper = fsStatisEveryDayWatchMapper;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public FsStatisEveryDayWatch findById(Integer id) {
-        return fsStatisEveryDayWatchMapper.findById(id);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @Transactional
-    public int create(FsStatisEveryDayWatch fsStatisEveryDayWatch) {
-        return fsStatisEveryDayWatchMapper.insert(fsStatisEveryDayWatch);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @Transactional
-    public int update(FsStatisEveryDayWatch fsStatisEveryDayWatch) {
-        return fsStatisEveryDayWatchMapper.update(fsStatisEveryDayWatch);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<FsStatisEveryDayWatch> findAll() {
-        return fsStatisEveryDayWatchMapper.findAll();
-    }
-
-    @Override
-    public List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param) {
-        return fsStatisEveryDayWatchMapper.queryList(param);
-    }
-
-}

+ 0 - 81
fs-service/src/main/java/com/fs/statis/service/impl/FsStatisPeriodWatchServiceImpl.java

@@ -1,81 +0,0 @@
-package com.fs.statis.service.impl; // 假设Service实现类放在此包下
-
-import com.fs.statis.domain.FsStatisPeriodWatch;
-import com.fs.statis.dto.StatsWatchLogPageListDTO;
-import com.fs.statis.mapper.FsStatisPeriodWatchMapper;
-import com.fs.statis.service.FsStatisPeriodWatchService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-
-/**
- * 训练营周期统计数据服务实现类
- */
-@Service
-public class FsStatisPeriodWatchServiceImpl implements FsStatisPeriodWatchService {
-
-    private final FsStatisPeriodWatchMapper fsStatisPeriodWatchMapper;
-
-    /**
-     * 通过构造函数注入Mapper
-     * @param fsStatisPeriodWatchMapper 训练营周期统计数据Mapper
-     */
-    @Autowired
-    public FsStatisPeriodWatchServiceImpl(FsStatisPeriodWatchMapper fsStatisPeriodWatchMapper) {
-        this.fsStatisPeriodWatchMapper = fsStatisPeriodWatchMapper;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public FsStatisPeriodWatch findById(Integer id) {
-        return fsStatisPeriodWatchMapper.selectById(id);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @Transactional // 标记此方法需要事务管理
-    public int create(FsStatisPeriodWatch fsStatisPeriodWatch) {
-        // 在这里可以添加业务逻辑,例如参数校验等
-        return fsStatisPeriodWatchMapper.insert(fsStatisPeriodWatch);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @Transactional // 标记此方法需要事务管理
-    public int updateById(FsStatisPeriodWatch fsStatisPeriodWatch) {
-        // 在这里可以添加业务逻辑,例如检查记录是否存在,或在更新前进行特定校验
-        // 注意:Mapper中的updateById是动态SQL,只更新非null字段
-        return fsStatisPeriodWatchMapper.updateById(fsStatisPeriodWatch);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @Transactional // 标记此方法需要事务管理
-    public int deleteById(Integer id) {
-        // 在这里可以添加业务逻辑,例如检查关联数据等
-        return fsStatisPeriodWatchMapper.deleteById(id);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<FsStatisPeriodWatch> findAll() {
-        return fsStatisPeriodWatchMapper.selectAll();
-    }
-
-    @Override
-    public List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param) {
-        return fsStatisPeriodWatchMapper.queryList(param);
-    }
-}

+ 325 - 67
fs-service/src/main/java/com/fs/statis/service/impl/FsStatisSalerWatchServiceImpl.java

@@ -1,22 +1,43 @@
 package com.fs.statis.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.StringUtils;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyDeptMapper;
 import com.fs.company.mapper.CompanyUserMapper;
-import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.qw.cache.QwSopCacheService;
+import com.fs.qw.cache.QwUserCacheService;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
-import com.fs.sop.mapper.QwSopLogsMapper;
-import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.sop.domain.QwSop;
+import com.fs.sop.domain.QwSopLogs;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.*;
+import com.fs.statis.domain.*;
 import com.fs.statis.dto.StatsWatchLogPageListDTO;
 import com.fs.statis.mapper.FsStatisSalerWatchMapper;
+import com.fs.statis.mapper.FsStatisTempFsuserMapper;
+import com.fs.statis.mapper.FsStatisTempParamMapper;
+import com.fs.statis.mapper.FsTempPeriodQueryMapper;
 import com.fs.statis.service.FsStatisSalerWatchService;
+import com.github.pagehelper.PageHelper;
+import com.google.common.collect.Maps;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
 
 /**
  * 销售观看统计服务实现类
@@ -33,15 +54,40 @@ public class FsStatisSalerWatchServiceImpl implements FsStatisSalerWatchService
 
     private final QwSopLogsMapper qwSopLogsMapper;
 
-    private final FsUserCoursePeriodMapper fsUserCoursePeriodMapper;
+
+
+    private final SopUserLogsMapper sopUserLogsMapper;
+
+    private final CompanyDeptMapper companyDeptMapper;
+
+    private final ICompanyUserCacheService companyUserCacheService;
+
+    @Autowired
+    private QwUserCacheService qwUserCacheService;
+
+    @Autowired
+    private FsStatisTempParamMapper fsStatisTempParamMapper;
+    @Autowired
+    private FsStatisTempFsuserMapper fsStatisTempFsuserMapper;
+
+    @Autowired
+    private QwSopCacheService qwSopCacheService;
 
     @Autowired
-    public FsStatisSalerWatchServiceImpl(FsStatisSalerWatchMapper fsStatisSalerWatchMapper, CompanyUserMapper companyUserMapper, QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, FsUserCoursePeriodMapper fsUserCoursePeriodMapper) {
+    public FsStatisSalerWatchServiceImpl(FsStatisSalerWatchMapper fsStatisSalerWatchMapper,
+                                         CompanyUserMapper companyUserMapper,
+                                         QwUserMapper qwUserMapper,
+                                         QwSopLogsMapper qwSopLogsMapper,
+                                         SopUserLogsMapper sopUserLogsMapper,
+                                         CompanyDeptMapper companyDeptMapper,
+                                         ICompanyUserCacheService companyUserCacheService) {
         this.fsStatisSalerWatchMapper = fsStatisSalerWatchMapper;
         this.companyUserMapper = companyUserMapper;
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
-        this.fsUserCoursePeriodMapper = fsUserCoursePeriodMapper;
+        this.sopUserLogsMapper = sopUserLogsMapper;
+        this.companyDeptMapper = companyDeptMapper;
+        this.companyUserCacheService = companyUserCacheService;
     }
 
     /**
@@ -105,69 +151,281 @@ public class FsStatisSalerWatchServiceImpl implements FsStatisSalerWatchService
 
     @Override
     public List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param) {
-        return fsStatisSalerWatchMapper.queryList(param);
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        List<FsStatisSalerWatch> fsStatisSalerWatches = fsStatisSalerWatchMapper.queryList(param);
+        for (FsStatisSalerWatch item : fsStatisSalerWatches) {
+            if(item.getDeptId() != null) {
+                CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(item.getDeptId());
+                if(ObjectUtils.isNotNull(companyDept)) {
+                    item.setDeptName(companyDept.getDeptName());
+                }
+            }
+            if(item.getCompanyUserId() != null) {
+                String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
+                if(StringUtils.isNotEmpty(companyUserName)){
+                    item.setCompanyUserName(companyUserName);
+                }
+            }
+
+        }
+        return fsStatisSalerWatches;
+    }
+
+    @Override
+    public List<FsStatisSalerWatch> queryPeriodList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        List<FsStatisSalerWatch> fsStatisSalerWatches = fsStatisSalerWatchMapper.queryPeriodList(param);
+        for (FsStatisSalerWatch item : fsStatisSalerWatches) {
+            if(item.getDeptId() != null) {
+                CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(item.getDeptId());
+                if(ObjectUtils.isNotNull(companyDept)) {
+                    item.setDeptName(companyDept.getDeptName());
+                }
+            }
+            if(item.getCompanyUserId() != null) {
+                String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
+                if(StringUtils.isNotEmpty(companyUserName)){
+                    item.setCompanyUserName(companyUserName);
+                }
+            }
+
+            if(item.getPeriodId() != null) {
+                String qwSopLogNameBySopId = qwSopCacheService.getQwSopLogNameBySopId(item.getPeriodId());
+                if(StringUtils.isNotEmpty(qwSopLogNameBySopId)) {
+                    item.setPeriodName(qwSopLogNameBySopId);
+                }
+            }
+
+            if(item.getSopId() != null) {
+                String qwSopNameBySopId = qwSopCacheService.getQwSopNameBySopId(item.getSopId());
+                if(StringUtils.isNotEmpty(qwSopNameBySopId)) {
+                    item.setSopTaskName(qwSopNameBySopId);
+                }
+            }
+        }
+        return fsStatisSalerWatches;
+    }
+
+    @Override
+    public List<FsStatisSalerWatch> queryTodayList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        List<FsStatisSalerWatch> fsStatisSalerWatches = fsStatisSalerWatchMapper.queryTodayList(param);
+        for (FsStatisSalerWatch item : fsStatisSalerWatches) {
+            if(item.getDeptId() != null) {
+                CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(item.getDeptId());
+                if(ObjectUtils.isNotNull(companyDept)) {
+                    item.setDeptName(companyDept.getDeptName());
+                }
+            }
+            if(item.getCompanyUserId() != null) {
+                String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
+                if(StringUtils.isNotEmpty(companyUserName)){
+                    item.setCompanyUserName(companyUserName);
+                }
+            }
+
+            if(item.getPeriodId() != null) {
+                String periodName = qwSopLogsMapper.queryPeriodNameById(item.getPeriodId());
+                if(StringUtils.isNotEmpty(periodName)) {
+                    item.setPeriodName(periodName);
+                }
+            }
+
+        }
+        return fsStatisSalerWatches;
+    }
+
+    @Override
+    public List<FsStatisSalerWatch> export(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        return fsStatisSalerWatchMapper.queryListExport(param);
+    }
+
+    @Override
+    public List<FsStatisSopWatch> exportQueryPeriodList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        List<FsStatisSopWatch> fsStatisSopWatches = fsStatisSalerWatchMapper.queryPeriodListExport(param);
+        Map<String, QwSopLogs> stringQwSopLogsMap = qwSopLogsMapper.queryAllPeriod();
+
+        for (FsStatisSopWatch item : fsStatisSopWatches) {
+            if(item.getPeriodId() != null) {
+                QwSopLogs qwSopLogs = stringQwSopLogsMap.get(item.getPeriodId());
+                if(qwSopLogs != null) {
+                    item.setPeriodName(qwSopLogs.getSopTitle());
+                }
+            } else {
+                item.setPeriodName("空营期");
+            }
+        }
+        return fsStatisSopWatches;
+    }
+
+    @Override
+    public List<FsStatisEveryDayWatch> exportQueryEveryDayList(StatsWatchLogPageListDTO param) {
+        if(StringUtils.isNull(param.getStartDate()) && StringUtils.isNull(param.getEndDate())) {
+            return Collections.emptyList();
+        }
+        return fsStatisSalerWatchMapper.queryEveryDayListExport(param);
     }
 
+
     @Override
-    public void writeData() {
+    public synchronized void writeData(String date) {
+
         // 统计销售看课情况
         // 获取前一天的时间
-//        List<CompanyUser> companyUserList = this.companyUserMapper.selectAllCompanyUserList();
-//
-//
-//        List<FsStatisSalerWatch> writeData = new ArrayList<>();
-//
-//        LocalDate previousDay = LocalDate.now().minusDays(1);
-//        for (CompanyUser companyUser : companyUserList) {
-//
-//            if(companyUser.getCompanyId() == null) {
-//                log.info("销售{} 对应公司id {} 为空!",companyUser.getUserId(),companyUser.getCompanyId());
-//                continue;
-//            }
-//            // 确定当前销售对应的营期
-//            List<Long> periodList = fsUserCoursePeriodMapper.queryPeriod(companyUser.getCompanyId(),previousDay);
-//
-//            // 找到销售关联的企微账号
-//            List<String> qwUserIdList = qwUserMapper.findQwUserIdListByCompanyUserId(companyUser.getUserId());
-//
-//            for (Long periodId : periodList) {
-//                // 去sop记录表找对应的SOP发送记录,记录数作为营期人数
-//                Long periodCount = qwSopLogsMapper.selectQwSopLogsCountByQwUserId(qwUserIdList,periodId,previousDay);
-//                // 再去course_watch_log找对应的销售观看记录作为已报名数
-//                Long registerCount = companyUserMapper.queryCompanyUserWatchCount(companyUser.getUserId(),periodId);
-//                Long completedCount = companyUserMapper.queryCompanyUserWatchCountCompleted(companyUser.getUserId(),periodId);
-//
-//
-//                FsStatisSalerWatch fsStatisSalerWatch = new FsStatisSalerWatch();
-//                fsStatisSalerWatch.setDeptId(companyUser.getDeptId());
-//                fsStatisSalerWatch.setCompanyUserId(companyUser.getUserId());
-//                fsStatisSalerWatch.setTrainCampNum(periodCount);
-//                fsStatisSalerWatch.setNotRegisteredNum(periodCount - registerCount);
-//                fsStatisSalerWatch.setRegisteredNum(registerCount);
-//                fsStatisSalerWatch.setCompletedNum(completedCount);
-//
-//                float regRate = 0.0f;
-//                if(periodCount != 0) {
-//                    regRate = registerCount / periodCount;
-//                }
-//                float finishedRate = 0.0f;
-//                if(registerCount != 0) {
-//                    finishedRate = completedCount / registerCount;
-//                }
-//
-//
-//                fsStatisSalerWatch.setRegRate(regRate);
-//                fsStatisSalerWatch.setFinishedRate(finishedRate);
-//                fsStatisSalerWatch.setDataDate(previousDay);
-//
-//                fsStatisSalerWatch.setPeriodId(periodId);
-//
-//                writeData.add(fsStatisSalerWatch);
-//            }
-//        }
-//
-//        if(CollectionUtils.isNotEmpty(writeData)){
-//            fsStatisSalerWatchMapper.batchSave(writeData);
-//        }
+        List<CompanyUser> companyUserList = this.companyUserMapper.selectAllCompanyUserList();
+        // 查询所有符合条件的 SOP 用户日志
+        List<SopUserLogs> allSopUserLogs = sopUserLogsMapper.queryAllSopUserLogs();
+
+        Map<String, List<SopUserLogs>> qwUserIdToIdsMap = allSopUserLogs.stream()
+                .filter(Objects::nonNull)
+                .filter(log -> log.getQwUserId() != null && log.getId() != null)
+                .collect(Collectors.groupingBy(SopUserLogs::getQwUserId));
+
+        qwUserCacheService.initCache();
+
+        Map<Long, List<String>> qwUserIdMap = queryQwListMap();
+
+        LocalDate previousDay = LocalDate.now().minusDays(1);
+        if(date != null) {
+            previousDay = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        }
+
+        List<FsStatisTempParam> list = new ArrayList<>();
+        for (CompanyUser companyUser : companyUserList) {
+
+            if(companyUser.getCompanyId() == null) {
+                log.info("销售{} 对应公司id {} 为空!",companyUser.getUserId(),companyUser.getCompanyId());
+                continue;
+            }
+            // 找到销售关联的 企微账号
+            List<String> qwUserIdList = qwUserIdMap.get(companyUser.getUserId());
+            if(CollectionUtils.isEmpty(qwUserIdList)){
+                log.info("当前销售 {} 没有关联企微账号!",companyUser.getUserId());
+                continue;
+            }
+
+            // 确定当前销售对应的sop任务的执行记录
+            List<SopUserLogs> periodList = getPeriodListByQwUserIds(allSopUserLogs,qwUserIdToIdsMap,qwUserIdList);
+
+            if(CollectionUtils.isEmpty(periodList)){
+                log.info("当前销售 {} 没有相关的SOP营期!", companyUser.getUserId());
+                continue;
+            }
+
+
+            for (SopUserLogs sopUserLogs : periodList) {
+
+                String periodId = sopUserLogs.getId();
+                String sopId = sopUserLogs.getSopId();
+                String startTime = sopUserLogs.getStartTime();
+
+                FsStatisTempParam param = new FsStatisTempParam();
+
+                param.setCompanyUserId(companyUser.getUserId());
+                param.setSopId(sopId);
+                param.setThisDate(previousDay);
+                param.setStartTime(startTime);
+                String qwUserId = qwUserCacheService.getIdByQwUserIdAndCorpId(sopUserLogs.getQwUserId(), sopUserLogs.getCorpId());
+
+                param.setQwUserId(qwUserId);
+                param.setPeriodId(periodId);
+                param.setDeptId(companyUser.getDeptId());
+                list.add(param);
+            }
+        }
+
+        // --------清空临时表----------
+        fsStatisTempParamMapper.clearSop();
+        fsStatisTempFsuserMapper.clear();
+        fsStatisTempParamMapper.clear();
+
+        // 删除之前的数据
+        fsStatisSalerWatchMapper.deleteDateData(date);
+
+        // -------开始计算-------------
+        fsStatisTempParamMapper.batchSave(list);
+        fsStatisTempParamMapper.batchSaveToSop(list);
+
+        log.info("list 大小{}",list.size());
+
+        // 根据临时表生成数据
+        fsStatisSalerWatchMapper.generateData(date);
+
+        // 写入企微重粉数和看课重粉数临时表
+        List<FsStatisTempFsuser> tempData = fsStatisSalerWatchMapper.querySopRepeatData(date);
+        fsStatisTempFsuserMapper.insertBatch(tempData);
+        // 生成企微重粉数和看课重粉数
+        fsStatisSalerWatchMapper.generateSopData(date);
+        // 清空临时表
+        fsStatisTempFsuserMapper.clear();
+        fsStatisTempParamMapper.clear();
+        fsStatisTempParamMapper.clearSop();
+    }
+
+    // 根据企微号查询SOP营期
+    public List<SopUserLogs> getPeriodListByQwUserIds(List<SopUserLogs> allSopUserLogs,
+                                                 Map<String, List<SopUserLogs>> qwUserIdToIdsMap
+            ,List<String> qwUserIdList) {
+        if (CollectionUtils.isEmpty(allSopUserLogs) || CollectionUtils.isEmpty(qwUserIdList)) {
+            return new ArrayList<>();
+        }
+
+        return qwUserIdList.stream()
+                .filter(Objects::nonNull)
+                .map(qwUserIdToIdsMap::get)
+                .filter(Objects::nonNull)
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+    }
+
+    private Map<Long,List<String>> queryQwListMap(){
+        List<QwUser> qwUsers = qwUserMapper.selectQwUserAllList();
+        if(CollectionUtils.isEmpty(qwUsers)){
+            return Maps.newHashMap();
+        }
+
+        return qwUsers.stream()
+                .filter(Objects::nonNull)
+                .filter(user -> user.getCompanyUserId() != null && user.getQwUserId() != null)
+                .collect(Collectors.groupingBy(
+                        QwUser::getCompanyUserId,
+                        Collectors.mapping(QwUser::getQwUserId, Collectors.toList())
+                ));
+    }
+
+    private Map<String,List<Long>> queryQwIdMap(){
+        List<QwUser> qwUsers = qwUserMapper.selectQwUserAllList();
+        if(CollectionUtils.isEmpty(qwUsers)){
+            return Maps.newHashMap();
+        }
+
+        return qwUsers.stream()
+                .filter(Objects::nonNull)
+                .filter(user -> user.getId() != null && user.getQwUserId() != null)
+                .collect(Collectors.groupingBy(
+                        QwUser::getQwUserId,
+                        Collectors.mapping(QwUser::getId, Collectors.toList())
+                ));
+
+    }
+
+
+    @Override
+    public void writeDataToday() {
+        LocalDate now = LocalDate.now();
+
+        this.writeData(now.toString());
     }
 }

+ 6 - 2
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java

@@ -17,6 +17,7 @@ import com.fs.statis.dto.*;
 import com.fs.statis.mapper.ConsumptionBalanceMapper;
 import com.fs.statis.service.IStatisticsCompanyService;
 import com.fs.statis.service.utils.TrendDataFiller;
+
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
@@ -35,7 +36,6 @@ import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -949,7 +949,11 @@ public class StatisticsCompanyServiceImpl implements IStatisticsCompanyService {
 
     @Override
     public List<WatchEndPlayTrendDTO> watchEndPlayTrend(AnalysisPreviewQueryDTO param) {
-        return Collections.emptyList();
+        List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS = consumptionBalanceMapper.watchEndPlayTrend(param);
+        for (WatchEndPlayTrendDTO watchEndPlayTrendDTO : watchEndPlayTrendDTOS) {
+            watchEndPlayTrendDTO.setX(watchEndPlayTrendDTO.getStartDate());
+        }
+        return watchEndPlayTrendDTOS;
     }
 
     @Override

+ 8 - 9
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -15,10 +15,10 @@ import com.fs.his.service.IFsUserService;
 import com.fs.statis.StatisticsRedisConstant;
 import com.fs.statis.dto.*;
 import com.fs.statis.mapper.ConsumptionBalanceMapper;
+import com.fs.statis.param.WatchCourseStatisticsParam;
 import com.fs.statis.service.IStatisticsService;
 import com.fs.statis.service.utils.TrendDataFiller;
-import com.fs.statistics.dto.WatchCourseStatisticsDTO;
-import com.fs.statistics.param.WatchCourseStatisticsParam;
+
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
@@ -69,11 +69,10 @@ public class StatisticsServiceImpl implements IStatisticsService {
     @Autowired
     private IFsStoreProductService productService;
 
-    @Autowired
-    private com.fs.course.mapper.FsCourseWatchLogMapper FsCourseWatchLogMapper;
-
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
 
     @Override
     public void dataOverviewTask() {
@@ -977,10 +976,10 @@ public class StatisticsServiceImpl implements IStatisticsService {
     public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param) {
         List<WatchLogDTO> data = null;
         // 七天
-        if(org.apache.commons.lang3.ObjectUtils.equals(param.getType(),0)) {
-            data = FsCourseWatchLogMapper.selectFsCourseWatchLog7Day(param.getQwExternalContactId());
-        } else if(org.apache.commons.lang3.ObjectUtils.equals(param.getType(),1)){
-            data = FsCourseWatchLogMapper.selectFsCourseWatchLog30DayByExtId(param.getQwExternalContactId());
+        if(ObjectUtils.equals(param.getType(),0)) {
+            data = fsCourseWatchLogMapper.selectFsCourseWatchLog7Day(param.getQwExternalContactId());
+        } else if(ObjectUtils.equals(param.getType(),1)){
+            data = fsCourseWatchLogMapper.selectFsCourseWatchLog30DayByExtId(param.getQwExternalContactId());
         }
 
         WatchCourseStatisticsDTO watchCourseStatisticsDTO = new WatchCourseStatisticsDTO();

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

@@ -62,6 +62,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectCompanyVo"/>
         where company_id = #{companyId}
     </select>
+    <select id="selectCompanyNameCompanyByIds" resultType="java.lang.String">
+        select GROUP_CONCAT(DISTINCT company_name ORDER BY company_name SEPARATOR ',') AS company_name from company where company_id in
+         <foreach collection="companyIds.split(',')" item="companyId" open="(" close=")" separator=",">
+             #{companyId}
+         </foreach>
+    </select>
+    <select id="selectCompanyAllList" resultType="com.fs.company.domain.Company">
+        select company_id,company_name from company where is_del=0
+    </select>
+    <select id="selectDoctorIdsByCompanyId" resultType="java.lang.String">
+        select doctor_ids from company where company_id = #{companyId}
+    </select>
 
     <insert id="insertCompany" parameterType="Company" useGeneratedKeys="true" keyProperty="companyId">
         insert into company

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

@@ -420,4 +420,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         #{item}
     </foreach>
     </select>
+    <select id="queryCompanyUserWatchCount" resultType="java.lang.Long">
+        select count(1) from fs_course_watch_log
+        <where>
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
+            <if test="previousDay != null">
+                and camp_period_time = ${previousDay}
+            </if>
+        </where>
+    </select>
+    <select id="queryCompanyUserWatchCountCompleted" resultType="java.lang.Long">
+        select count(1) from fs_course_watch_log
+        <where>
+            AND log_type = 2
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
+            <if test="previousDay != null">
+                and camp_period_time = ${previousDay}
+            </if>
+        </where>
+    </select>
+    <select id="queryCompanyUserInterruptCount" resultType="java.lang.Long">
+        select count(1) from fs_course_watch_log
+        <where>
+            AND log_type = 4
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
+            <if test="previousDay != null">
+                and camp_period_time = ${previousDay}
+            </if>
+        </where>
+    </select>
 </mapper>

+ 23 - 0
fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml

@@ -686,5 +686,28 @@
         </where>
         ORDER BY send_time desc
     </select>
+    <select id="queryPeriodNameById" resultType="java.lang.String">
+        select concat(qw_user_key,'-',start_time) from sop_user_logs where id=#{periodId}
+    </select>
+    <select id="queryAllPeriod" resultType="com.fs.sop.domain.QwSopLogs">
+        select id,concat(qw_user_key,'-',start_time) as sop_title from sop_user_logs
+    </select>
+    <select id="selectQwSopLogsCountByQwUserId" resultType="java.lang.Long">
+        select count(1) from qw_sop_logs
+        <where>
+            AND type = '0'
+            AND send_status = '1'
+            AND DATE(real_send_time) = #{previousDay}
+            AND user_logs_id = #{periodId}
+            <if test="data != null and data.size() > 0">
+                AND qw_userid in
+                <foreach collection="data" item="item" open="(" close=")" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+
+        </where>
+
+    </select>
 
 </mapper>

+ 82 - 48
fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -31,14 +31,12 @@
         <result property="stopTime"    column="stop_time"    />
         <result property="isRating"    column="is_rating"    />
         <result property="courseDay"    column="course_day"    />
-        <result property="chatId"    column="chat_id"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
         <result property="isSampSend"    column="is_samp_send"    />
     </resultMap>
 
     <sql id="selectQwSopVo">
-        select *
-        from qw_sop
+        select *  from qw_sop
     </sql>
 
     <select id="selectQwSopAutoByTagsByForeach"  resultType="com.fs.qw.vo.QwSopRuleTimeVO">
@@ -55,13 +53,14 @@
         FROM
             qw_sop qs
                 LEFT JOIN
-            qw_sop_temp qst ON qs.temp_id = qst.id AND qst.status = '1'
+            qw_sop_temp qst ON qs.temp_id = qst.id
         WHERE
-          qs.corp_id = #{map.corpId}
+            FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+          AND qs.corp_id = #{map.corpId}
+          AND qst.status = '1'
           AND qs.status IN (2, 3, 4)
-          AND qs.send_type in (2,11)
           AND qs.is_auto_sop = 1
-          AND FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+          AND qs.send_type = #{map.sendType}
           AND (
            <foreach collection='map.tagsIdsSelectList' item='item' index='index' separator=' or '>
                      find_in_set( #{item}, TRIM(REGEXP_REPLACE(qs.tags, '[\"\\\\[\\\\]]', '')))
@@ -84,12 +83,13 @@
         FROM
         qw_sop qs
         LEFT JOIN
-        qw_sop_temp qst ON qs.temp_id = qst.id AND qst.status = '1'
+        qw_sop_temp qst ON qs.temp_id = qst.id
         WHERE
-        qs.corp_id = #{map.corpId}
+            FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+        AND qs.corp_id = #{map.corpId}
+        AND qst.status = '1'
         AND qs.status IN (2, 3, 4)
-        AND qs.send_type IN (2,11)
-        AND FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+        AND qs.send_type = #{map.sendType}
         AND (
             <foreach collection='map.tagsIdsSelectList' item='item' index='index' separator=' or '>
                 find_in_set( #{item}, TRIM(REGEXP_REPLACE(qs.tags, '[\"\\\\[\\\\]]', '')) )
@@ -111,12 +111,13 @@
         FROM
         qw_sop qs
         LEFT JOIN
-        qw_sop_temp qst ON qs.temp_id = qst.id  AND qst.status = '1'
+        qw_sop_temp qst ON qs.temp_id = qst.id
         WHERE
-         qs.corp_id = #{map.corpId}
+        FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+        AND qs.corp_id = #{map.corpId}
+        AND qst.status = '1'
         AND qs.status IN (2, 3, 4)
-        AND qs.send_type IN (2,11)
-        AND FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+        AND qs.send_type = #{map.sendType}
         AND (
         <foreach collection='map.tagsIdsSelectList' item='item' index='index' separator=' and '>
           NOT find_in_set( #{item}, TRIM(REGEXP_REPLACE(qs.tags, '[\"\\\\[\\\\]]', '')) )
@@ -138,12 +139,13 @@
         FROM
         qw_sop qs
         LEFT JOIN
-        qw_sop_temp qst ON qs.temp_id = qst.id  AND qst.status = '1'
+        qw_sop_temp qst ON qs.temp_id = qst.id
         WHERE
-         qs.corp_id = #{map.corpId}
+             FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+        AND qs.corp_id = #{map.corpId}
+        AND qst.status = '1'
         AND qs.status IN (2, 3, 4)
         AND qs.send_type = 4
-        AND FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
         AND (
         <foreach collection='map.tagsIdsSelectList' item='item' index='index' separator=' or '>
             find_in_set( #{item}, TRIM(REGEXP_REPLACE(qs.tags, '[\"\\\\[\\\\]]', '')) )
@@ -157,12 +159,13 @@
         FROM
             qw_sop qs
                 LEFT JOIN
-            qw_sop_temp qst ON qs.temp_id = qst.id  AND qst.status = '1'
+            qw_sop_temp qst ON qs.temp_id = qst.id
         WHERE
-           qs.corp_id = #{map.corpId}
+            FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+          AND qs.corp_id = #{map.corpId}
+          AND qst.status = '1'
           AND qs.status IN (2, 3, 4)
-          AND qs.send_type IN (2,11)
-          AND FIND_IN_SET(#{map.qwUserId}, COALESCE(qs.qw_user_ids, '')) > 0
+          AND qs.send_type = #{map.sendType}
     </select>
 
 
@@ -210,13 +213,13 @@
             ]]>
     </select>
 
-    <select id="selectQwSopList" parameterType="QwSop" resultMap="QwSopResult">
+    <select id="selectQwSopList" parameterType="QwSop" resultType="QwSop">
         <include refid="selectQwSopVo"/>
         <where>
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
             <if test="status != null "> and status = #{status}</if>
             <if test="type != null "> and type = #{type}</if>
-            <if test="id != null "> and id = #{id}</if>
+            <if test="id != null and id != '' "> and id = #{id}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="qwUserIds != null  and qwUserIds != ''"> and FIND_IN_SET(#{qwUserIds}, qw_user_ids) > 0</if>
             <if test="createBy != null  and createBy != ''"> and create_by = #{createBy}</if>
@@ -241,14 +244,12 @@
             and status != 6
          order by create_time desc
     </select>
-
     <select id="selectQwSopByIsRating" parameterType="QwSop" resultMap="QwSopResult">
         <include refid="selectQwSopVo"/>
         where is_rating = 1
-        and send_type in(2,3,11)
+        and send_type in(2,3)
         order by create_time desc
     </select>
-
     <select id="selectQwSopById" parameterType="String" resultType="com.fs.sop.domain.QwSop">
         <include refid="selectQwSopVo"/>
         where id = #{id}
@@ -392,16 +393,56 @@
         </where>
         order by create_time desc
     </select>
-
     <select id="selectByTemplateId" resultType="com.fs.sop.domain.QwSop">
         select * from qw_sop where temp_id = #{tempId}
     </select>
-
     <select id="selectWxSop" resultType="com.fs.sop.domain.QwSop">
         select a.* from qw_sop a
         where a.send_type != 4 and a.status in (2,3) and a.type = 1
     </select>
+    <select id="executeSopChatByIds" resultType="com.fs.qw.vo.ChatSopRuleTimeVO">
+
+        SELECT qs.*,
+        qst.name AS temp_name,
+        qst.setting AS temp_setting,
+        qst.status AS temp_status,
+        qst.gap AS temp_gap,
+        qst.sort AS temp_sort,
+        qst.create_time AS temp_create_time,
+        qst.create_by AS temp_create_by,
+        qst.corp_id AS temp_company_id
+        FROM qw_sop qs
+        LEFT JOIN qw_sop_temp qst ON qs.temp_id = qst.id
+        WHERE qs.id IN
+        <foreach item="id" collection="ids"  open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
+    <select id="selectQwSopAllList" resultType="com.fs.sop.domain.QwSop">
+
+        SELECT DISTINCT qw_sop.id,qw_sop.name
+        FROM sop_user_logs log
+        RIGHT JOIN qw_sop ON log.sop_id = qw_sop.id
+        <where>
+        <![CDATA[
+            log.start_time <= CURDATE()
+            AND log.status = 1
+            AND qw_sop.status in(2,3)
+            AND qw_sop.is_auto_sop = 1 AND log.start_time >= #{startDate}
+            AND log.start_time <= #{endDate}
+         ]]>
+            <if test="companyId != null">
+                AND qw_sop.company_id = ${companyId}
+            </if>
+        </where>
+        ORDER BY qw_sop.create_time DESC
 
+    </select>
+    <select id="selectQwSopByTempId" resultType="com.fs.sop.domain.QwSop">
+        <include refid="selectQwSopVo"/>
+        where temp_id = #{tempId}
+    </select>
     <update id="updateQwSop" parameterType="QwSop" useGeneratedKeys="false" keyProperty="id" >
         UPDATE  qw_sop
         <trim prefix="SET" suffixOverrides=",">
@@ -478,25 +519,6 @@
         SET max_send = 1
         WHERE id = #{id}
     </update>
-    <select id="executeSopChatByIds" resultType="com.fs.qw.vo.ChatSopRuleTimeVO">
-
-        SELECT qs.*,
-        qst.name AS temp_name,
-        qst.setting AS temp_setting,
-        qst.status AS temp_status,
-        qst.gap AS temp_gap,
-        qst.sort AS temp_sort,
-        qst.create_time AS temp_create_time,
-        qst.create_by AS temp_create_by,
-        qst.corp_id AS temp_company_id
-        FROM qw_sop qs
-        LEFT JOIN qw_sop_temp qst ON qs.temp_id = qst.id
-        WHERE qs.id IN
-        <foreach item="id" collection="ids"  open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </select>
-
     <update id="updateStatusQwSopById2" useGeneratedKeys="false" keyProperty="id" >
         UPDATE qw_sop
         SET status = 3
@@ -505,4 +527,16 @@
             #{id}
         </foreach>
     </update>
+
+    <update id="batchUpdateSopChatIdById">
+        <foreach collection="sopList" item="item" separator=";">
+            UPDATE qw_sop
+            SET chat_id = #{item.chatId}
+            WHERE id = #{item.id}
+        </foreach>
+    </update>
+
+    <select id="getQwSopInfoById" resultType="com.fs.sop.domain.QwSop">
+        SELECT id,filter_mode,chat_id FROM qw_sop where id IN <foreach collection="ids" item="item" index="index" open="(" separator="," close=")">#{item}</foreach>
+    </select>
 </mapper>

+ 39 - 0
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -378,5 +378,44 @@
     <select id="selectSopUserLogsByDateAndIds" resultType="com.fs.sop.domain.SopUserLogs" >
         select * from sop_user_logs where sop_id=#{sopId} and sop_temp_id=#{sopTempId}  and qw_user_id=#{qwUserId}  and corp_id=#{corpId}  and start_time=#{startTime}
     </select>
+    <select id="queryExecuteLogBySopId" resultType="com.fs.sop.domain.SopUserLogs">
+        select *
+        from
+            sop_user_logs
+
+        where sop_id=#{sopId}
+    </select>
+    <select id="selectSopUserLogsByQwUserIds" resultType="java.lang.String">
+        select id from sop_user_logs
+        <where>
+            qw_user_id in
+            <foreach collection="qwUserIds" item="item" open="(" close=")" separator=",">
+                #{item}
+            </foreach>
+        </where>
+    </select>
+    <select id="querySopUserLogsByParam" resultType="com.fs.sop.domain.SopUserLogs">
+        <![CDATA[
+        SELECT DISTINCT log.id as id,
+                        log.qw_user_id as qw_user_id,
+                        log.start_time as start_time,
+                        log.sop_id as sop_id
+        FROM sop_user_logs log
+                 LEFT JOIN qw_sop ON log.sop_id = qw_sop.id
+        WHERE log.start_time <= CURDATE()
+          AND log.status = 1
+          AND qw_sop.status in(2,3)
+          AND (
+            (qw_sop.is_auto_sop = 1 AND log.start_time >= #{startDate}
+                AND log.start_time <= #{endDate}
+                )
+            )
+        ORDER BY log.start_time DESC
+        ]]>
+    </select>
+
+    <select id="getSopUserLogsInfoById" resultType="com.fs.sop.domain.SopUserLogs">
+        select * from  sop_user_logs where id IN <foreach collection="ids" item="item" index="index" open="(" separator="," close=")">#{item}</foreach>
+    </select>
 
 </mapper>

+ 120 - 0
fs-service/src/main/resources/mapper/statis/FsStatisQwTempParamMapper.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisQwTempParamMapper"> <!-- Adjust namespace accordingly -->
+
+    <!-- Result Map for FsStatisQwTempParam -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisQwTempParam"> <!-- Adjust type path accordingly -->
+        <id column="id" jdbcType="INTEGER" property="id" />
+        <result column="company_user_id" jdbcType="BIGINT" property="companyUserId" />
+        <result column="this_date" jdbcType="VARCHAR" property="thisDate" />
+        <result column="qw_user_id" jdbcType="VARCHAR" property="qwUserId" />
+        <result column="dept_id" jdbcType="BIGINT" property="deptId" />
+    </resultMap>
+
+    <!-- Column list for reuse -->
+    <sql id="Base_Column_List">
+        id, company_user_id, this_date, qw_user_id, dept_id
+    </sql>
+
+    <!-- Select by Primary Key -->
+    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM fs_statis_qw_temp_param
+        WHERE id = #{id,jdbcType=INTEGER}
+    </select>
+
+    <!-- Delete by Primary Key -->
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
+        DELETE FROM fs_statis_qw_temp_param
+        WHERE id = #{id,jdbcType=INTEGER}
+    </delete>
+
+    <!-- Insert record -->
+    <insert id="insert" parameterType="com.fs.statis.domain.FsStatisQwTempParam" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
+        INSERT INTO fs_statis_qw_temp_param (company_user_id, this_date, qw_user_id, dept_id)
+        VALUES (#{companyUserId,jdbcType=BIGINT}, #{thisDate,jdbcType=VARCHAR}, #{qwUserId,jdbcType=VARCHAR}, #{deptId,jdbcType=BIGINT})
+    </insert>
+
+    <!-- Insert record selectively (only non-null fields) -->
+    <insert id="insertSelective" parameterType="com.fs.statis.domain.FsStatisQwTempParam" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
+        INSERT INTO fs_statis_qw_temp_param
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="thisDate != null">this_date,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="deptId != null">dept_id,</if>
+        </trim>
+        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">#{companyUserId,jdbcType=BIGINT},</if>
+            <if test="thisDate != null">#{thisDate,jdbcType=VARCHAR},</if>
+            <if test="qwUserId != null">#{qwUserId,jdbcType=VARCHAR},</if>
+            <if test="deptId != null">#{deptId,jdbcType=BIGINT},</if>
+        </trim>
+    </insert>
+    <insert id="insertList" parameterType="java.util.List">
+        REPLACE INTO fs_statis_qw_temp_param (company_user_id, this_date, qw_user_id, dept_id,company_id)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.companyUserId,jdbcType=BIGINT},
+            #{item.thisDate,jdbcType=VARCHAR},
+            #{item.qwUserId,jdbcType=BIGINT},
+            #{item.deptId,jdbcType=BIGINT},
+            #{item.companyId,jdbcType=BIGINT})
+        </foreach>
+    </insert>
+
+    <!-- Update by Primary Key selectively (only non-null fields) -->
+    <update id="updateByPrimaryKeySelective" parameterType="com.fs.statis.domain.FsStatisQwTempParam">
+        UPDATE fs_statis_qw_temp_param
+        <set>
+            <if test="companyUserId != null">company_user_id = #{companyUserId,jdbcType=BIGINT},</if>
+            <if test="thisDate != null">this_date = #{thisDate,jdbcType=VARCHAR},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId,jdbcType=VARCHAR},</if>
+            <if test="deptId != null">dept_id = #{deptId,jdbcType=BIGINT},</if>
+        </set>
+        WHERE id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- Update by Primary Key (all fields) -->
+    <update id="updateByPrimaryKey" parameterType="com.fs.statis.domain.FsStatisQwTempParam">
+        UPDATE fs_statis_qw_temp_param
+        SET company_user_id = #{companyUserId,jdbcType=BIGINT},
+            this_date = #{thisDate,jdbcType=VARCHAR},
+            qw_user_id = #{qwUserId,jdbcType=VARCHAR},
+            dept_id = #{deptId,jdbcType=BIGINT}
+        WHERE id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- Select all records -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM fs_statis_qw_temp_param
+    </select>
+
+    <!-- Example: Select by the fields in idx_param_multi -->
+    <!-- You would define a corresponding method in your Mapper interface,
+         perhaps taking a FsStatisQwTempParam object as criteria or individual params. -->
+    <select id="selectByParams" parameterType="com.fs.statis.domain.FsStatisQwTempParam" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM fs_statis_qw_temp_param
+        <where>
+            <if test="thisDate != null and thisDate != ''">
+                AND this_date = #{thisDate,jdbcType=VARCHAR}
+            </if>
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId,jdbcType=BIGINT}
+            </if>
+            <if test="qwUserId != null and qwUserId != ''">
+                AND qw_user_id = #{qwUserId,jdbcType=VARCHAR}
+            </if>
+            <if test="deptId != null">
+                AND dept_id = #{deptId,jdbcType=BIGINT}
+            </if>
+        </where>
+        <!-- You might want to add ORDER BY here -->
+    </select>
+
+</mapper>

+ 505 - 0
fs-service/src/main/resources/mapper/statis/FsStatisQwWatchMapper.xml

@@ -0,0 +1,505 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisQwWatchMapper">
+
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisQwWatch">
+        <id column="id" jdbcType="BIGINT" property="id" />
+        <result column="dept_id" jdbcType="BIGINT" property="deptId" />
+        <result column="company_user_id" jdbcType="BIGINT" property="companyUserId" />
+        <result column="qw_user_id" jdbcType="VARCHAR" property="qwUserId" />
+        <result column="sop_task_num" jdbcType="BIGINT" property="sopTaskNum" />
+        <result column="period_num" jdbcType="BIGINT" property="periodNum" />
+        <result column="period_person_num" jdbcType="BIGINT" property="periodPersonNum" />
+        <result column="completed_num" jdbcType="BIGINT" property="completedNum" />
+        <result column="data_date" jdbcType="DATE" property="dataDate" />
+        <result column="send_num" jdbcType="BIGINT" property="sendNum" />
+        <result column="not_registered_num" jdbcType="BIGINT" property="notRegisteredNum" />
+        <result column="interrupt_num" jdbcType="BIGINT" property="interruptNum" />
+        <result column="registered_num" jdbcType="BIGINT" property="registeredNum" />
+        <result column="qw_repeat_num" jdbcType="BIGINT" property="qwRepeatNum" />
+        <result column="user_repeat_num" jdbcType="BIGINT" property="userRepeatNum" />
+        <result column="black_num" jdbcType="BIGINT" property="blackNum" />
+        <result column="deleted_num" jdbcType="BIGINT" property="deletedNum" />
+        <result column="order_num" jdbcType="BIGINT" property="orderNum" />
+        <result column="order_money_total" jdbcType="DECIMAL" property="orderMoneyTotal" />
+        <result column="red_package_money_total" jdbcType="DECIMAL" property="redPackageMoneyTotal" />
+        <result column="call_num" jdbcType="BIGINT" property="callNum" />
+        <result column="receive_pass_num" jdbcType="BIGINT" property="receivePassNum" />
+        <result column="receive_not_num" jdbcType="BIGINT" property="receiveNotNum" />
+        <result column="call_time_total" jdbcType="BIGINT" property="callTimeTotal" />
+        <result column="remind_pending_num" jdbcType="BIGINT" property="remindPendingNum" />
+        <result column="remind_processed_num" jdbcType="BIGINT" property="remindProcessedNum" />
+        <result column="reg_rate" jdbcType="DECIMAL" property="regRate" />
+        <result column="finished_rate" jdbcType="DECIMAL" property="finishedRate" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id, dept_id, company_user_id, qw_user_id, sop_task_num, period_num, period_person_num,
+        completed_num, data_date, send_num, not_registered_num, interrupt_num, registered_num,
+        qw_repeat_num, user_repeat_num, black_num, deleted_num, order_num, order_money_total,
+        red_package_money_total, call_num, receive_pass_num, receive_not_num, call_time_total,
+        remind_pending_num, remind_processed_num, reg_rate, finished_rate
+    </sql>
+
+    <!-- Columns that can be inserted (excludes auto-increment and generated columns) -->
+    <sql id="Insert_Column_List">
+        dept_id, company_user_id, qw_user_id, sop_task_num, period_num, period_person_num,
+        completed_num, data_date, send_num, not_registered_num, interrupt_num, qw_repeat_num,
+        user_repeat_num, black_num, deleted_num, order_num, order_money_total,
+        red_package_money_total, call_num, receive_pass_num, receive_not_num, call_time_total,
+        remind_pending_num, remind_processed_num
+    </sql>
+
+    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_qw_watch
+        where id = #{id,jdbcType=BIGINT}
+    </select>
+
+    <select id="selectByUniqueKey" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_qw_watch
+        where dept_id = #{deptId,jdbcType=BIGINT}
+        and company_user_id = #{companyUserId,jdbcType=BIGINT}
+        and qw_user_id = #{qwUserId,jdbcType=VARCHAR}
+        and data_date = #{dataDate,jdbcType=DATE}
+    </select>
+
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+        delete from fs_statis_qw_watch
+        where id = #{id,jdbcType=BIGINT}
+    </delete>
+
+    <insert id="insert" keyProperty="id" useGeneratedKeys="true" parameterType="com.fs.statis.domain.FsStatisQwWatch">
+        insert into fs_statis_qw_watch (
+        <include refid="Insert_Column_List" />
+        )
+        values (
+        #{deptId,jdbcType=BIGINT}, #{companyUserId,jdbcType=BIGINT}, #{qwUserId,jdbcType=VARCHAR},
+        #{sopTaskNum,jdbcType=BIGINT}, #{periodNum,jdbcType=BIGINT}, #{periodPersonNum,jdbcType=BIGINT},
+        #{completedNum,jdbcType=BIGINT}, #{dataDate,jdbcType=DATE}, #{sendNum,jdbcType=BIGINT},
+        #{notRegisteredNum,jdbcType=BIGINT}, #{interruptNum,jdbcType=BIGINT},
+        #{qwRepeatNum,jdbcType=BIGINT}, #{userRepeatNum,jdbcType=BIGINT}, #{blackNum,jdbcType=BIGINT},
+        #{deletedNum,jdbcType=BIGINT}, #{orderNum,jdbcType=BIGINT}, #{orderMoneyTotal,jdbcType=DECIMAL},
+        #{redPackageMoneyTotal,jdbcType=DECIMAL}, #{callNum,jdbcType=BIGINT}, #{receivePassNum,jdbcType=BIGINT},
+        #{receiveNotNum,jdbcType=BIGINT}, #{callTimeTotal,jdbcType=BIGINT}, #{remindPendingNum,jdbcType=BIGINT},
+        #{remindProcessedNum,jdbcType=BIGINT}
+        )
+    </insert>
+
+    <insert id="insertSelective" keyProperty="id" useGeneratedKeys="true" parameterType="com.fs.statis.domain.FsStatisQwWatch">
+        insert into fs_statis_qw_watch
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="deptId != null">dept_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="sopTaskNum != null">sop_task_num,</if>
+            <if test="periodNum != null">period_num,</if>
+            <if test="periodPersonNum != null">period_person_num,</if>
+            <if test="completedNum != null">completed_num,</if>
+            <if test="dataDate != null">data_date,</if>
+            <if test="sendNum != null">send_num,</if>
+            <if test="notRegisteredNum != null">not_registered_num,</if>
+            <if test="interruptNum != null">interrupt_num,</if>
+            <if test="qwRepeatNum != null">qw_repeat_num,</if>
+            <if test="userRepeatNum != null">user_repeat_num,</if>
+            <if test="blackNum != null">black_num,</if>
+            <if test="deletedNum != null">deleted_num,</if>
+            <if test="orderNum != null">order_num,</if>
+            <if test="orderMoneyTotal != null">order_money_total,</if>
+            <if test="redPackageMoneyTotal != null">red_package_money_total,</if>
+            <if test="callNum != null">call_num,</if>
+            <if test="receivePassNum != null">receive_pass_num,</if>
+            <if test="receiveNotNum != null">receive_not_num,</if>
+            <if test="callTimeTotal != null">call_time_total,</if>
+            <if test="remindPendingNum != null">remind_pending_num,</if>
+            <if test="remindProcessedNum != null">remind_processed_num,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="deptId != null">#{deptId,jdbcType=BIGINT},</if>
+            <if test="companyUserId != null">#{companyUserId,jdbcType=BIGINT},</if>
+            <if test="qwUserId != null">#{qwUserId,jdbcType=VARCHAR},</if>
+            <if test="sopTaskNum != null">#{sopTaskNum,jdbcType=BIGINT},</if>
+            <if test="periodNum != null">#{periodNum,jdbcType=BIGINT},</if>
+            <if test="periodPersonNum != null">#{periodPersonNum,jdbcType=BIGINT},</if>
+            <if test="completedNum != null">#{completedNum,jdbcType=BIGINT},</if>
+            <if test="dataDate != null">#{dataDate,jdbcType=DATE},</if>
+            <if test="sendNum != null">#{sendNum,jdbcType=BIGINT},</if>
+            <if test="notRegisteredNum != null">#{notRegisteredNum,jdbcType=BIGINT},</if>
+            <if test="interruptNum != null">#{interruptNum,jdbcType=BIGINT},</if>
+            <if test="qwRepeatNum != null">#{qwRepeatNum,jdbcType=BIGINT},</if>
+            <if test="userRepeatNum != null">#{userRepeatNum,jdbcType=BIGINT},</if>
+            <if test="blackNum != null">#{blackNum,jdbcType=BIGINT},</if>
+            <if test="deletedNum != null">#{deletedNum,jdbcType=BIGINT},</if>
+            <if test="orderNum != null">#{orderNum,jdbcType=BIGINT},</if>
+            <if test="orderMoneyTotal != null">#{orderMoneyTotal,jdbcType=DECIMAL},</if>
+            <if test="redPackageMoneyTotal != null">#{redPackageMoneyTotal,jdbcType=DECIMAL},</if>
+            <if test="callNum != null">#{callNum,jdbcType=BIGINT},</if>
+            <if test="receivePassNum != null">#{receivePassNum,jdbcType=BIGINT},</if>
+            <if test="receiveNotNum != null">#{receiveNotNum,jdbcType=BIGINT},</if>
+            <if test="callTimeTotal != null">#{callTimeTotal,jdbcType=BIGINT},</if>
+            <if test="remindPendingNum != null">#{remindPendingNum,jdbcType=BIGINT},</if>
+            <if test="remindProcessedNum != null">#{remindProcessedNum,jdbcType=BIGINT},</if>
+        </trim>
+    </insert>
+
+    <update id="updateByPrimaryKeySelective" parameterType="com.fs.statis.domain.FsStatisQwWatch">
+        update fs_statis_qw_watch
+        <set>
+            <if test="deptId != null">dept_id = #{deptId,jdbcType=BIGINT},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId,jdbcType=BIGINT},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId,jdbcType=VARCHAR},</if>
+            <if test="sopTaskNum != null">sop_task_num = #{sopTaskNum,jdbcType=BIGINT},</if>
+            <if test="periodNum != null">period_num = #{periodNum,jdbcType=BIGINT},</if>
+            <if test="periodPersonNum != null">period_person_num = #{periodPersonNum,jdbcType=BIGINT},</if>
+            <if test="completedNum != null">completed_num = #{completedNum,jdbcType=BIGINT},</if>
+            <if test="dataDate != null">data_date = #{dataDate,jdbcType=DATE},</if>
+            <if test="sendNum != null">send_num = #{sendNum,jdbcType=BIGINT},</if>
+            <if test="notRegisteredNum != null">not_registered_num = #{notRegisteredNum,jdbcType=BIGINT},</if>
+            <if test="interruptNum != null">interrupt_num = #{interruptNum,jdbcType=BIGINT},</if>
+            <if test="qwRepeatNum != null">qw_repeat_num = #{qwRepeatNum,jdbcType=BIGINT},</if>
+            <if test="userRepeatNum != null">user_repeat_num = #{userRepeatNum,jdbcType=BIGINT},</if>
+            <if test="blackNum != null">black_num = #{blackNum,jdbcType=BIGINT},</if>
+            <if test="deletedNum != null">deleted_num = #{deletedNum,jdbcType=BIGINT},</if>
+            <if test="orderNum != null">order_num = #{orderNum,jdbcType=BIGINT},</if>
+            <if test="orderMoneyTotal != null">order_money_total = #{orderMoneyTotal,jdbcType=DECIMAL},</if>
+            <if test="redPackageMoneyTotal != null">red_package_money_total = #{redPackageMoneyTotal,jdbcType=DECIMAL},</if>
+            <if test="callNum != null">call_num = #{callNum,jdbcType=BIGINT},</if>
+            <if test="receivePassNum != null">receive_pass_num = #{receivePassNum,jdbcType=BIGINT},</if>
+            <if test="receiveNotNum != null">receive_not_num = #{receiveNotNum,jdbcType=BIGINT},</if>
+            <if test="callTimeTotal != null">call_time_total = #{callTimeTotal,jdbcType=BIGINT},</if>
+            <if test="remindPendingNum != null">remind_pending_num = #{remindPendingNum,jdbcType=BIGINT},</if>
+            <if test="remindProcessedNum != null">remind_processed_num = #{remindProcessedNum,jdbcType=BIGINT},</if>
+        </set>
+        where id = #{id,jdbcType=BIGINT}
+    </update>
+
+    <update id="updateByPrimaryKey" parameterType="com.fs.statis.domain.FsStatisQwWatch">
+        update fs_statis_qw_watch
+        set dept_id = #{deptId,jdbcType=BIGINT},
+            company_user_id = #{companyUserId,jdbcType=BIGINT},
+            qw_user_id = #{qwUserId,jdbcType=VARCHAR},
+            sop_task_num = #{sopTaskNum,jdbcType=BIGINT},
+            period_num = #{periodNum,jdbcType=BIGINT},
+            period_person_num = #{periodPersonNum,jdbcType=BIGINT},
+            completed_num = #{completedNum,jdbcType=BIGINT},
+            data_date = #{dataDate,jdbcType=DATE},
+            send_num = #{sendNum,jdbcType=BIGINT},
+            not_registered_num = #{notRegisteredNum,jdbcType=BIGINT},
+            interrupt_num = #{interruptNum,jdbcType=BIGINT},
+            qw_repeat_num = #{qwRepeatNum,jdbcType=BIGINT},
+            user_repeat_num = #{userRepeatNum,jdbcType=BIGINT},
+            black_num = #{blackNum,jdbcType=BIGINT},
+            deleted_num = #{deletedNum,jdbcType=BIGINT},
+            order_num = #{orderNum,jdbcType=BIGINT},
+            order_money_total = #{orderMoneyTotal,jdbcType=DECIMAL},
+            red_package_money_total = #{redPackageMoneyTotal,jdbcType=DECIMAL},
+            call_num = #{callNum,jdbcType=BIGINT},
+            receive_pass_num = #{receivePassNum,jdbcType=BIGINT},
+            receive_not_num = #{receiveNotNum,jdbcType=BIGINT},
+            call_time_total = #{callTimeTotal,jdbcType=BIGINT},
+            remind_pending_num = #{remindPendingNum,jdbcType=BIGINT},
+            remind_processed_num = #{remindProcessedNum,jdbcType=BIGINT}
+        where id = #{id,jdbcType=BIGINT}
+    </update>
+
+    <select id="selectList" parameterType="com.fs.statis.domain.FsStatisQwWatch" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_qw_watch
+        <where>
+            <if test="deptId != null">and dept_id = #{deptId,jdbcType=BIGINT}</if>
+            <if test="companyUserId != null">and company_user_id = #{companyUserId,jdbcType=BIGINT}</if>
+            <if test="qwUserId != null and qwUserId != ''">and qw_user_id = #{qwUserId,jdbcType=VARCHAR}</if>
+            <if test="dataDate != null">and data_date = #{dataDate,jdbcType=DATE}</if>
+            <!-- Add other conditions as needed -->
+        </where>
+        <!-- Add order by or limit clauses if necessary -->
+    </select>
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisQwWatch">
+        select
+            dept_id,
+            company_user_id,
+            qw_user_id,
+            ifnull(sum(sop_task_num),0) as sop_task_num,
+            ifnull(sum(period_num),0) as period_num,
+            ifnull(sum(period_person_num),0) as period_person_num,
+            ifnull(sum(completed_num),0) as completed_num,
+            ifnull(sum(send_num),0) as send_num,
+            ifnull(sum(not_registered_num),0) as not_registered_num,
+            ifnull(sum(interrupt_num),0) as interrupt_num,
+            ifnull(sum(registered_num),0) as registered_num,
+            ifnull(sum(qw_repeat_num),0) as qw_repeat_num,
+            ifnull(sum(user_repeat_num),0) as user_repeat_num,
+            ifnull(sum(black_num),0) as black_num,
+            ifnull(sum(deleted_num),0) as deleted_num,
+            ifnull(sum(order_num),0) as order_num,
+            ifnull(sum(order_money_total),0) as order_money_total,
+            ifnull(sum(red_package_money_total),0) as red_package_money_total,
+            ifnull(sum(call_num),0) as call_num,
+            ifnull(sum(receive_pass_num),0) as receive_pass_num,
+            ifnull(sum(receive_not_num),0) as receive_not_num,
+            ifnull(sum(call_time_total),0) as call_time_total,
+            ifnull(sum(remind_pending_num),0) as remind_pending_num,
+            ifnull(sum(remind_processed_num),0) as remind_processed_num,
+            (case when sum(send_num)>=sum(registered_num) then ROUND(SUM(registered_num) * 1.0 / SUM(send_num), 4) else 0 end) as reg_rate,
+            (case when sum(send_num)>=sum(completed_num) then ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4) else 0 end) as finished_rate
+        from fs_statis_qw_watch
+        <where>
+            <if test="startDate != null and endDate != null">
+                AND data_date between #{startDate} and #{endDate}
+            </if>
+            <if test="userIds != null and userIds.size() > 0">
+                AND company_user in
+                <foreach collection="userIds" item="item" open="(" close=")" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        group by company_user_id
+    </select>
+    <select id="exportList" resultType="com.fs.statis.domain.FsStatisQwWatch">
+        select
+        any_value(watch.dept_id),
+        concat(dept.dept_id,'_',dept.dept_name) as dept_name,
+        watch.company_user_id,
+        concat(cu.user_id,'_',cu.user_name) as company_user_name,
+        ifnull(sum(watch.sop_task_num),0) as sop_task_num,
+        ifnull(sum(watch.period_num),0) as period_num,
+        ifnull(sum(watch.period_person_num),0) as period_person_num,
+        ifnull(sum(watch.completed_num),0) as completed_num,
+        ifnull(sum(watch.send_num),0) as send_num,
+        ifnull(sum(watch.not_registered_num),0) as not_registered_num,
+        ifnull(sum(watch.interrupt_num),0) as interrupt_num,
+        ifnull(sum(watch.registered_num),0) as registered_num,
+        ifnull(sum(watch.qw_repeat_num),0) as qw_repeat_num,
+        ifnull(sum(watch.user_repeat_num),0) as user_repeat_num,
+        ifnull(sum(watch.black_num),0) as black_num,
+        ifnull(sum(watch.deleted_num),0) as deleted_num,
+        ifnull(sum(watch.order_num),0) as order_num,
+        ifnull(sum(watch.order_money_total),0) as order_money_total,
+        ifnull(sum(watch.red_package_money_total),0) as red_package_money_total,
+        ifnull(sum(watch.call_num),0) as call_num,
+        ifnull(sum(watch.receive_pass_num),0) as receive_pass_num,
+        ifnull(sum(watch.receive_not_num),0) as receive_not_num,
+        ifnull(sum(watch.call_time_total),0) as call_time_total,
+        ifnull(sum(watch.remind_pending_num),0) as remind_pending_num,
+        ifnull(sum(watch.remind_processed_num),0) as remind_processed_num,
+        (case when sum(watch.send_num)>=sum(watch.registered_num) then ROUND(SUM(watch.registered_num) * 1.0 / SUM(watch.send_num), 4) else 0 end) as reg_rate,
+        (case when sum(watch.send_num)>=sum(watch.completed_num) then ROUND(SUM(watch.completed_num) * 1.0 / SUM(watch.send_num), 4) else 0 end) as finished_rate
+        from fs_statis_qw_watch watch
+        left join company_dept dept on watch.dept_id=dept.dept_id
+        left join company_user cu on watch.company_user_id=cu.user_id
+        <where>
+            <if test="startDate != null and endDate != null">
+                AND watch.data_date between #{startDate} and #{endDate}
+            </if>
+            <if test="userIds != null and userIds.size() > 0">
+                AND watch.company_user in
+                <foreach collection="userIds" item="item" open="(" close=")" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        group by watch.company_user_id
+    </select>
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        replace into fs_statis_qw_watch (
+        <include refid="Insert_Column_List" />
+        )
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (
+            #{item.deptId,jdbcType=BIGINT}, #{item.companyUserId,jdbcType=BIGINT}, #{item.qwUserId,jdbcType=VARCHAR},
+            #{item.sopTaskNum,jdbcType=BIGINT}, #{item.periodNum,jdbcType=BIGINT}, #{item.periodPersonNum,jdbcType=BIGINT},
+            #{item.completedNum,jdbcType=BIGINT}, #{item.dataDate,jdbcType=DATE}, #{item.sendNum,jdbcType=BIGINT},
+            #{item.notRegisteredNum,jdbcType=BIGINT}, #{item.interruptNum,jdbcType=BIGINT},
+            #{item.qwRepeatNum,jdbcType=BIGINT}, #{item.userRepeatNum,jdbcType=BIGINT}, #{item.blackNum,jdbcType=BIGINT},
+            #{item.deletedNum,jdbcType=BIGINT}, #{item.orderNum,jdbcType=BIGINT}, #{item.orderMoneyTotal,jdbcType=DECIMAL},
+            #{item.redPackageMoneyTotal,jdbcType=DECIMAL}, #{item.callNum,jdbcType=BIGINT}, #{item.receivePassNum,jdbcType=BIGINT},
+            #{item.receiveNotNum,jdbcType=BIGINT}, #{item.callTimeTotal,jdbcType=BIGINT}, #{item.remindPendingNum,jdbcType=BIGINT},
+            #{item.remindProcessedNum,jdbcType=BIGINT}
+            )
+        </foreach>
+    </insert>
+    <insert id="generateData">
+        <![CDATA[
+            REPLACE INTO fs_statis_qw_watch(
+                company_user_id,
+                data_date,
+                qw_user_id,
+                send_num,
+                period_num,
+                period_person_num,
+                not_registered_num,
+                completed_num,
+                interrupt_num,
+                dept_id,
+                black_num,
+                deleted_num,
+                order_num,
+                order_money_total,
+                red_package_money_total,
+                receive_pass_num,
+                receive_not_num,
+                call_time_total,
+                remind_pending_num,
+                remind_processed_num,
+                qw_repeat_num,
+                user_repeat_num
+            )
+            WITH extended_temp AS (
+            -- 原始temp表数据
+            SELECT company_user_id, qw_user_id, this_date, dept_id
+            FROM fs_statis_qw_temp_param
+            WHERE this_date IS NOT NULL
+            UNION
+            -- 添加订单统计维度的记录
+            SELECT DISTINCT
+            o.company_user_id,
+            '-1' as qw_user_id,
+            #{date} as this_date,
+            cu.dept_id as dept_id
+            FROM fs_store_order o
+            left join company_user cu
+            on o.company_user_id=cu.user_id
+            WHERE o.status in (4,5)
+            AND o.company_user_id IS NOT NULL
+            AND o.create_time >= #{startTime}
+            AND o.create_time < #{endTime}
+            UNION
+            -- 添加红包统计维度的记录
+            SELECT DISTINCT
+            log.company_user_id,
+            '-1' as qw_user_id,
+            #{date} as this_date,
+            cu.dept_id as dept_id
+            FROM fs_course_red_packet_log log
+            left join company_user cu
+            on log.company_user_id=cu.user_id
+            WHERE log.status = 1
+            AND log.create_time >= #{startTime}
+            AND log.create_time < #{endTime}
+            )
+            SELECT
+            temp.company_user_id as company_user_id,
+            temp.this_date as data_date,
+            temp.qw_user_id as qw_user_id,
+            IFNULL(log_stats.send_count, 0) as send_num,
+            IFNULL(log_stats.period_count, 0) as period_num,
+            IFNULL(log_stats.period_person_count, 0) as period_person_num,
+            IFNULL(log_stats.not_register_count, 0) as not_registered_num,
+            IFNULL(log_stats.completed_count, 0) as completed_num,
+            IFNULL(log_stats.interrupt_count, 0) as interrupt_num,
+            temp.dept_id as dept_id,
+            IFNULL(contact.black_num, 0) as black_num,
+            IFNULL(contact.deleted_num, 0) as deleted_num,
+            IFNULL(stats_order.order_num, 0) as order_num,
+            IFNULL(stats_order.order_money_total, 0) as order_money_total,
+            IFNULL(redp.red_package_money_total, 0) as red_package_money_total,
+            IFNULL(voice.receive_pass_num, 0) as receive_pass_num,
+            IFNULL(voice.receive_not_num, 0) as receive_not_num,
+            IFNULL(voice.call_time_total, 0) as call_time_total,
+            IFNULL(qw_work.remind_pending_num, 0) as remind_pending_num,
+            IFNULL(qw_work.remind_processed_num, 0) as remind_processed_num,
+            IFNULL(contact.qw_repeat_num,0) as qw_repeat_num,
+            IFNULL(contact.user_repeat_num,0) as user_repeat_num
+            FROM extended_temp temp
+            LEFT JOIN (
+            SELECT
+            company_user_id,
+            qw_user_id,
+            COUNT(DISTINCT log_id) as send_count,
+            COUNT(DISTINCT sop_id,camp_period_time) as period_person_count,
+            COUNT(DISTINCT qw_external_contact_id) as period_count,
+            COUNT(DISTINCT CASE WHEN log_type = 3 THEN log_id END) as not_register_count,
+            COUNT(DISTINCT CASE WHEN log_type = 2 THEN log_id END) as completed_count,
+            COUNT(DISTINCT CASE WHEN (log_type = 4 OR log_type = 1) THEN log_id END) as interrupt_count
+            FROM fs_course_watch_log
+            WHERE ((update_time >= #{startTime} and update_time < #{endTime}) OR (create_time >= #{startTime} and create_time < #{endTime}))
+            GROUP BY company_user_id, qw_user_id
+            ) log_stats ON temp.company_user_id = log_stats.company_user_id
+            AND temp.qw_user_id = log_stats.qw_user_id
+
+            -- 拉黑数和删除数
+            LEFT JOIN (
+            SELECT
+                contact.company_user_id,
+                contact.qw_user_id,
+            COUNT(CASE WHEN contact.status = 4 THEN 1 END) as black_num,
+            COUNT(CASE WHEN contact.status = 5 OR contact.status = 7 THEN 1 END) as deleted_num,
+            COUNT(contact.is_repeat) as qw_repeat_num,
+            COUNT(contact.user_repeat) as user_repeat_num
+            FROM qw_external_contact contact
+            WHERE contact.qw_user_id IS NOT NULL
+            AND contact.company_user_id IS NOT NULL
+            AND contact.status IN (4, 5, 7)
+            AND contact.update_time >= #{startTime}
+            AND contact.update_time < #{endTime}
+            GROUP BY qw_user_id, company_user_id
+            ) contact ON contact.qw_user_id = temp.qw_user_id
+            AND contact.company_user_id = temp.company_user_id
+
+            -- 订单数、订单总金额
+            LEFT JOIN (
+            SELECT
+            COUNT(DISTINCT order_id) as order_num,
+            SUM(pay_money) as order_money_total,
+            company_user_id,
+            '-1' as qw_user_id
+            FROM fs_store_order
+            WHERE status = 5
+            AND company_user_id IS NOT NULL
+            AND create_time >= #{startTime}
+            AND create_time < #{endTime}
+            GROUP BY company_user_id
+            ) stats_order ON temp.company_user_id = stats_order.company_user_id
+            AND temp.qw_user_id = '-1'
+            -- 红包数
+            LEFT JOIN (
+            SELECT
+            company_user_id,
+            sum(amount) as red_package_money_total,
+            '-1' as qw_user_id
+            FROM fs_course_red_packet_log
+            WHERE status = 1
+            AND create_time >= #{startTime}
+            AND create_time < #{endTime}
+            GROUP BY company_user_id
+            ) redp ON temp.company_user_id = redp.company_user_id
+            AND temp.qw_user_id = '-1'
+            -- 通话统计(总拨打数(可以后面两项相加)、接通数、未接通数、通话时长)
+            LEFT JOIN (
+            SELECT
+            company_user_id,
+            qw_user_id,
+            COUNT(CASE WHEN status = 1 THEN 1 END) as receive_pass_num,
+            COUNT(CASE WHEN status = 2 THEN 1 END) as receive_not_num,
+            SUM(CASE WHEN status = 1 THEN duration END) as call_time_total
+            FROM qw_user_voice_log
+            WHERE create_time >= #{startTime}
+            AND create_time < #{endTime}
+            GROUP BY company_user_id, qw_user_id
+            ) voice ON temp.company_user_id = voice.company_user_id
+            AND temp.qw_user_id = voice.qw_user_id
+            -- 催课看板(催课未处理数、催课已处理数、未处理已完课数、已处理已完课数)
+            LEFT JOIN (
+            SELECT
+            company_user_id,
+            qw_user_id,
+            COUNT(CASE WHEN status = 0 THEN 1 END) as remind_pending_num,
+            COUNT(CASE WHEN status = 1 THEN 1 END) as remind_processed_num
+            FROM qw_work_task
+            WHERE create_time >= #{startTime}
+            AND create_time < #{endTime}
+            GROUP BY company_user_id, qw_user_id
+            ) qw_work ON temp.company_user_id = qw_work.company_user_id
+            AND temp.qw_user_id = qw_work.qw_user_id
+
+        ]]>
+    </insert>
+
+</mapper>

+ 499 - 9
fs-service/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml

@@ -31,34 +31,407 @@
     </sql>
 
     <select id="queryList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
-        select * from fs_statis_saler_watch
+        SELECT
+        company_user_id,
+        ANY_VALUE(dept_id) as dept_id,
+        SUM(train_camp_num) as train_camp_num,
+        SUM(not_registered_num) as not_registered_num,
+        SUM(registered_num) as registered_num,
+        SUM(completed_num) as completed_num,
+        SUM(interrupt_num) as interrupt_num,
+        SUM(offline_total) as offline_total,
+        SUM(offline_not_part) as offline_not_part,
+        SUM(offline_not_watched) as offline_not_watched,
+        SUM(online_total) as online_total,
+        SUM(online_incomplete_playback) as online_incomplete_playback,
+        SUM(online_complete_playback) as online_complete_playback,
+        sum(send_num) as send_num,
+        sum(qw_repeat_num) as qw_repeat_num,
+        sum(user_repeat_num) as user_repeat_num,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(registered_num) * 1.0 / SUM(train_camp_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as finished_rate
+        FROM fs_statis_saler_watch
         <where>
-            <if test="userIds != null and userIds.length > 0">
+            <if test="userIds != null and userIds.size() > 0">
                 AND company_user_id IN
-                 <foreach collection="userIds" open="(" close=")" separator="," item="item">
-                     ${item}
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.size() > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND send_num = 0
+                </when>
+                <otherwise>
+                    AND send_num != 0
+                </otherwise>
+            </choose>
+        </where>
+        GROUP BY company_user_id
+    </select>
+    <select id="queryPeriodList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        SELECT
+        period_id,
+        any_value(sop_id) as sop_id,
+        SUM(train_camp_num) as train_camp_num,
+        SUM(not_registered_num) as not_registered_num,
+        SUM(registered_num) as registered_num,
+        SUM(completed_num) as completed_num,
+        SUM(interrupt_num) as interrupt_num,
+        SUM(offline_total) as offline_total,
+        SUM(offline_not_part) as offline_not_part,
+        SUM(offline_not_watched) as offline_not_watched,
+        SUM(online_total) as online_total,
+        SUM(online_incomplete_playback) as online_incomplete_playback,
+        SUM(online_complete_playback) as online_complete_playback,
+        sum(send_num) as send_num,
+        sum(qw_repeat_num) as qw_repeat_num,
+        sum(user_repeat_num) as user_repeat_num,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(registered_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as finished_rate,
+        CASE WHEN SUM(online_total) > 0
+        THEN ROUND(SUM(online_complete_playback) * 1.0 / SUM(online_total), 4)
+        ELSE 0 END as online_online_rate,
+        CASE WHEN SUM(online_total) > 0
+        THEN ROUND(SUM(online_complete_playback) * 1.0 / SUM(online_total), 4)
+        ELSE 0 END as online_playback_comple_rate
+        FROM fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.size() > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.size() > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND send_num = 0
+                </when>
+                <otherwise>
+                    AND send_num != 0
+                </otherwise>
+            </choose>
+        </where>
+        GROUP BY period_id
+    </select>
+    <select id="queryTodayList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        SELECT
+        data_date,
+        SUM(train_camp_num) as train_camp_num,
+        SUM(not_registered_num) as not_registered_num,
+        SUM(registered_num) as registered_num,
+        SUM(completed_num) as completed_num,
+        SUM(interrupt_num) as interrupt_num,
+        SUM(offline_total) as offline_total,
+        SUM(offline_not_part) as offline_not_part,
+        SUM(offline_not_watched) as offline_not_watched,
+        SUM(online_total) as online_total,
+        SUM(online_incomplete_playback) as online_incomplete_playback,
+        SUM(online_complete_playback) as online_complete_playback,
+        sum(send_num) as send_num,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(registered_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as finished_rate
+        FROM fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.size() > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.size() > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND send_num = 0
+                </when>
+                <otherwise>
+                    AND send_num != 0
+                </otherwise>
+            </choose>
+        </where>
+        GROUP BY data_date
+        order by data_date desc
+    </select>
+    <select id="queryListExport" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        SELECT
+        concat(u.user_name,'_',u.nick_name) as company_user_name,
+        ANY_VALUE(concat(dept.dept_id,'_',dept.dept_name)) as dept_name,
+        SUM(watch.train_camp_num) as train_camp_num,
+        SUM(watch.not_registered_num) as not_registered_num,
+        SUM(watch.registered_num) as registered_num,
+        SUM(watch.completed_num) as completed_num,
+        SUM(watch.interrupt_num) as interrupt_num,
+        SUM(watch.offline_total) as offline_total,
+        SUM(watch.offline_not_part) as offline_not_part,
+        SUM(watch.offline_not_watched) as offline_not_watched,
+        SUM(watch.online_total) as online_total,
+        SUM(watch.online_incomplete_playback) as online_incomplete_playback,
+        SUM(watch.online_complete_playback) as online_complete_playback,
+        sum(watch.send_num) as send_num,
+        sum(watch.qw_repeat_num) as qw_repeat_num,
+        sum(watch.user_repeat_num) as user_repeat_num,
+        CASE WHEN SUM(watch.send_num) > 0
+        THEN ROUND(SUM(watch.registered_num) * 1.0 / SUM(watch.train_camp_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(watch.send_num) > 0
+        THEN ROUND(SUM(watch.completed_num) * 1.0 / SUM(watch.send_num), 4)
+        ELSE 0 END as finished_rate
+        FROM fs_statis_saler_watch watch
+        LEFT JOIN company_dept dept
+        on dept.del_flag=0 and watch.dept_id=dept.dept_id
+        LEFT JOIN company_user u
+        on u.del_flag=0 and watch.company_user_id=u.user_id
+        <where>
+            <if test="userIds != null and userIds.size() > 0">
+                AND watch.company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.size() > 0">
+                AND watch.period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND watch.data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND watch.send_num = 0
+                </when>
+                <otherwise>
+                    AND watch.send_num != 0
+                </otherwise>
+            </choose>
+        </where>
+        GROUP BY watch.company_user_id
+    </select>
+    <select id="generateSopData" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        INSERT INTO fs_statis_saler_watch (
+            company_user_id,
+            sop_id,
+            period_id,
+            data_date,
+            qw_repeat_num,
+            user_repeat_num,
+            dept_id
+        )
+        SELECT
+            p.company_user_id as company_user_id,
+            p.sop_id as sop_id,
+            p.period_id as period_id,
+            #{date} as data_date,
+            -- 企微重粉统计
+            ifnull(sum(u.qw_repeat),0) AS qw_repeat_num,
+            -- 小程序(看课)重粉统计
+            ifnull(sum(u.user_repeat),0) AS user_repeat_num,
+            p.dept_id
+
+        FROM fs_statis_temp_param p
+                 LEFT JOIN fs_statis_temp_fsuser fu ON p.period_id = fu.period_id
+            AND p.qw_user_id = fu.qw_user_id
+                 LEFT JOIN fs_user u ON fu.fs_user_id = u.user_id
+        WHERE p.company_user_id IS NOT NULL
+          AND p.sop_id IS NOT NULL
+          AND p.period_id IS NOT NULL
+        GROUP BY
+            p.sop_id,
+            p.period_id,
+            p.company_user_id
+        ON DUPLICATE key update
+             qw_repeat_num = VALUES(qw_repeat_num),
+             user_repeat_num = VALUES(user_repeat_num)
+    </select>
+    <select id="querySopRepeatData" resultType="com.fs.statis.domain.FsStatisTempFsuser">
+        SELECT DISTINCT
+            suli_filtered.user_logs_id as period_id,
+            suli_filtered.fs_user_id as fs_user_id,
+            suli_filtered.qw_user_id as qw_user_id
+        FROM (
+
+                 SELECT
+                     suli_ct.user_logs_id,
+                     suli_ct.fs_user_id,
+                     suli_ct.qw_user_id,
+                     suli_ct.sop_id
+                 FROM
+                     sop_user_logs_info suli_ct
+                 WHERE
+                     suli_ct.create_time = #{date}
+                   AND suli_ct.fs_user_id IS NOT NULL
+                   AND suli_ct.fs_user_id != 0
+                    and suli_ct.qw_user_id is not null
+                 UNION ALL
+                 SELECT
+                     suli_ut.user_logs_id,
+                     suli_ut.fs_user_id,
+                     suli_ut.qw_user_id,
+                     suli_ut.sop_id
+                 FROM
+                     sop_user_logs_info suli_ut
+                 WHERE
+                     suli_ut.update_time = #{date}
+                   AND suli_ut.fs_user_id IS NOT NULL
+                   AND suli_ut.fs_user_id != 0
+                   AND suli_ut.create_time != #{date}
+                   and suli_ut.qw_user_id is not null
+             ) suli_filtered
+                 INNER JOIN
+             fs_statis_temp_param fstp
+             ON fstp.period_id = suli_filtered.user_logs_id
+                 AND fstp.sop_id = suli_filtered.sop_id
+    </select>
+    <select id="queryPeriodListExport" resultType="com.fs.statis.domain.FsStatisSopWatch">
+        SELECT
+        period_id,
+        SUM(train_camp_num) as train_camp_num,
+        SUM(not_registered_num) as not_registered_num,
+        SUM(registered_num) as registered_num,
+        SUM(completed_num) as completed_num,
+        SUM(interrupt_num) as interrupt_num,
+        SUM(offline_total) as offline_total,
+        SUM(offline_not_part) as offline_not_part,
+        SUM(offline_not_watched) as offline_not_watched,
+        SUM(online_total) as online_total,
+        SUM(online_incomplete_playback) as online_incomplete_playback,
+        SUM(online_complete_playback) as online_complete_playback,
+        sum(send_num) as send_num,
+        sum(qw_repeat_num) as qw_repeat_num,
+        sum(user_repeat_num) as user_repeat_num,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(registered_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as finished_rate
+        FROM fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.size() > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
                 </foreach>
             </if>
-            <if test="periodList != null and periodList.length > 0">
+            <if test="periodList != null and periodList.size() > 0">
                 AND period_id IN
                 <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND send_num = 0
+                </when>
+                <otherwise>
+                    AND send_num != 0
+                </otherwise>
+            </choose>
+        </where>
+        GROUP BY period_id
+    </select>
+    <select id="queryEveryDayListExport" resultType="com.fs.statis.domain.FsStatisEveryDayWatch">
+        SELECT
+        data_date,
+        SUM(train_camp_num) as train_camp_num,
+        SUM(not_registered_num) as not_registered_num,
+        SUM(registered_num) as registered_num,
+        SUM(completed_num) as completed_num,
+        SUM(interrupt_num) as interrupt_num,
+        SUM(offline_total) as offline_total,
+        SUM(offline_not_part) as offline_not_part,
+        SUM(offline_not_watched) as offline_not_watched,
+        SUM(online_total) as online_total,
+        SUM(online_incomplete_playback) as online_incomplete_playback,
+        SUM(online_complete_playback) as online_complete_playback,
+        sum(send_num) as send_num,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(registered_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as reg_rate,
+        CASE WHEN SUM(send_num) > 0
+        THEN ROUND(SUM(completed_num) * 1.0 / SUM(send_num), 4)
+        ELSE 0 END as finished_rate
+        FROM fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.size() > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
                     ${item}
                 </foreach>
             </if>
+            <if test="periodList != null and periodList.size() > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
             <if test="startDate != null and endDate != null">
                 AND data_date BETWEEN #{startDate} AND #{endDate}
             </if>
+            <choose>
+                <when test="includeSend0 != null and includeSend0">
+                    AND send_num = 0
+                </when>
+                <otherwise>
+                    AND send_num != 0
+                </otherwise>
+            </choose>
         </where>
+        GROUP BY data_date
+        order by data_date desc
     </select>
 
+
     <insert id="batchSave">
         INSERT INTO fs_statis_saler_watch (
             dept_id, company_user_id, train_camp_num,
-            not_registered_num, registered_num, reg_rate,
+            not_registered_num, registered_num,completed_num,interrupt_num, reg_rate,
             finished_rate, offline_total, offline_not_part,
             offline_not_watched, online_total, online_online_rate,
             online_playback_comple_rate, online_incomplete_playback,
-            online_complete_playback,period_id,data_date
+            online_complete_playback,period_id,data_date,sop_id,send_num
         ) VALUES
         <foreach collection="list" item="item" separator=",">
             (
@@ -67,6 +440,8 @@
                 #{item.trainCampNum,jdbcType=INTEGER},
                 #{item.notRegisteredNum,jdbcType=INTEGER},
                 #{item.registeredNum,jdbcType=INTEGER},
+                #{item.completedNum,jdbcType=INTEGER},
+                #{item.interruptNum,jdbcType=INTEGER},
                 #{item.regRate,jdbcType=FLOAT},
                 #{item.finishedRate,jdbcType=FLOAT},
                 #{item.offlineTotal,jdbcType=INTEGER},
@@ -77,10 +452,125 @@
                 #{item.onlinePlaybackCompleRate,jdbcType=FLOAT},
                 #{item.onlineIncompletePlayback,jdbcType=INTEGER},
                 #{item.onlineCompletePlayback,jdbcType=INTEGER},
-                #{item.periodId,jdbcType=INTEGER},
-                #{item.dataDate,jdbcType=VARCHAR}
+                #{item.periodId,jdbcType=VARCHAR},
+                #{item.dataDate,jdbcType=VARCHAR},
+                #{item.sopId,jdbcType=VARCHAR},
+                #{item.sendNum,jdbcType=VARCHAR}
             )
         </foreach>
+        ON DUPLICATE KEY UPDATE
+        train_camp_num = VALUES(train_camp_num),
+        not_registered_num = VALUES(not_registered_num),
+        registered_num = VALUES(registered_num),
+        completed_num = VALUES(completed_num),
+        interrupt_num = VALUES(interrupt_num),
+        reg_rate = VALUES(reg_rate),
+        finished_rate = VALUES(finished_rate),
+        offline_total = VALUES(offline_total),
+        offline_not_part = VALUES(offline_not_part),
+        offline_not_watched = VALUES(offline_not_watched),
+        online_total = VALUES(online_total),
+        online_online_rate = VALUES(online_online_rate),
+        online_playback_comple_rate = VALUES(online_playback_comple_rate),
+        online_incomplete_playback = VALUES(online_incomplete_playback),
+        online_complete_playback = VALUES(online_complete_playback)
+    </insert>
+    <insert id="generateData">
+        REPLACE INTO fs_statis_saler_watch (
+            company_user_id,
+            sop_id,
+            period_id,
+            data_date,
+            send_num,
+            train_camp_num,
+            not_registered_num,
+            completed_num,
+            interrupt_num,
+            dept_id
+        )
+        SELECT
+            temp.company_user_id,
+            temp.sop_id,
+            temp.period_id,
+            temp.this_date as data_date,
+            COALESCE(log_stats.send_count, 0) as send_num,
+            COALESCE(log_stats.period_count, 0) as train_camp_num,
+            COALESCE(log_stats.not_register_count, 0) as not_registered_num,
+            COALESCE(log_stats.completed_count, 0) as completed_num,
+            COALESCE(log_stats.interrupt_count, 0) as interrupt_num,
+            temp.dept_id
+        FROM fs_statis_temp_param temp
+                 LEFT JOIN (
+            -- 一次性统计所有指标,避免重复扫描
+            SELECT
+                company_user_id,
+                sop_id,
+                qw_user_id,
+                camp_period_time,
+                COUNT(DISTINCT log_id) as send_count,
+                COUNT(DISTINCT qw_external_contact_id) as period_count,
+                COUNT(DISTINCT CASE WHEN log_type = 3 THEN log_id END) as not_register_count,
+                COUNT(DISTINCT CASE WHEN log_type = 2 THEN log_id END) as completed_count,
+                COUNT(DISTINCT CASE WHEN (log_type = 4 or log_type = 1) THEN log_id END) as interrupt_count
+            FROM fs_course_watch_log
+            WHERE (DATE(update_time) = #{date} OR DATE(create_time) = #{date})
+            GROUP BY company_user_id, sop_id, qw_user_id, camp_period_time
+        ) log_stats ON temp.company_user_id = log_stats.company_user_id
+            AND temp.sop_id = log_stats.sop_id
+            AND temp.start_time = log_stats.camp_period_time
+            AND temp.qw_user_id = log_stats.qw_user_id
+        WHERE temp.this_date IS NOT NULL
+
+        UNION ALL
+
+-- 孤立数据处理部分保持不变,但也进行了优化
+        SELECT
+            orphan_summary.company_user_id,
+            orphan_summary.sop_id,
+            '-1' as period_id,
+            #{date} as data_date,
+            orphan_summary.send_num,
+            orphan_summary.train_camp_num,
+            orphan_summary.not_registered_num,
+            orphan_summary.completed_num,
+            orphan_summary.interrupt_num,
+            cu.dept_id as dept_id
+        FROM (
+                 SELECT
+                     orphan_data.company_user_id,
+                     orphan_data.sop_id,
+                     SUM(orphan_data.send_count) as send_num,
+                     SUM(orphan_data.period_count) as train_camp_num,
+                     SUM(orphan_data.not_registered_num) as not_registered_num,
+                     SUM(orphan_data.completed_count) as completed_num,
+                     SUM(orphan_data.interrupt_num) as interrupt_num
+                 FROM (
+                          SELECT
+                              log_data.company_user_id,
+                              log_data.sop_id,
+                              log_data.qw_user_id,
+                              log_data.camp_period_time,
+                              COUNT(DISTINCT log_data.log_id) as send_count,
+                              COUNT(DISTINCT log_data.qw_external_contact_id) as period_count,
+                              COUNT(DISTINCT CASE WHEN log_data.log_type = 3 THEN log_data.log_id END) as not_registered_num,
+                              COUNT(DISTINCT CASE WHEN log_data.log_type = 2 THEN log_data.log_id END) as completed_count,
+                              COUNT(DISTINCT CASE WHEN (log_data.log_type = 4 or log_type = 1) THEN log_data.log_id END) as interrupt_num
+                          FROM fs_course_watch_log log_data
+                          WHERE (DATE(log_data.update_time) = #{date} OR DATE(log_data.create_time) = #{date})
+                          GROUP BY log_data.company_user_id, log_data.sop_id, log_data.qw_user_id, log_data.camp_period_time
+                      ) orphan_data
+                 WHERE NOT EXISTS (
+                     SELECT 1
+                     FROM fs_statis_temp_param temp
+                     WHERE temp.company_user_id = orphan_data.company_user_id
+                       AND ((temp.sop_id = orphan_data.sop_id) OR (temp.sop_id IS NULL AND orphan_data.sop_id IS NULL))
+                       AND temp.qw_user_id = orphan_data.qw_user_id
+                       AND ((temp.start_time = orphan_data.camp_period_time) OR (temp.start_time IS NULL AND orphan_data.camp_period_time IS NULL))
+                 )
+                 GROUP BY orphan_data.company_user_id, orphan_data.sop_id
+             ) orphan_summary
+         LEFT JOIN company_user cu ON cu.user_id = orphan_summary.company_user_id
+        WHERE cu.dept_id IS NOT NULL AND cu.dept_id != 0  -- 过滤掉dept_id为NULL或0的记录
     </insert>
 
 </mapper>

+ 68 - 0
fs-service/src/main/resources/mapper/statis/FsStatisTempFsuserMapper.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisTempFsuserMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisTempFsuser">
+        <id column="id" property="id" jdbcType="BIGINT"/>
+        <result column="period_id" property="periodId" jdbcType="VARCHAR"/>
+        <result column="fs_user_id" property="fsUserId" jdbcType="BIGINT"/>
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, period_id, fs_user_id
+    </sql>
+
+    <!-- 根据ID查询 -->
+    <select id="selectById" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM
+        fs_statis_temp_fsuser
+        WHERE id = #{id,jdbcType=BIGINT}
+    </select>
+
+    <!-- 查询所有记录 -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM
+        fs_statis_temp_fsuser
+    </select>
+
+    <!-- 新增数据 -->
+    <insert id="insert" parameterType="com.fs.statis.domain.FsStatisTempFsuser">
+        INSERT INTO fs_statis_temp_fsuser (id, period_id, fs_user_id)
+        VALUES (#{id,jdbcType=BIGINT}, #{periodId,jdbcType=VARCHAR}, #{fsUserId,jdbcType=BIGINT})
+    </insert>
+
+    <!-- 批量新增 -->
+    <insert id="insertBatch" parameterType="java.util.List">
+        REPLACE INTO fs_statis_temp_fsuser (period_id, fs_user_id,qw_user_id)
+        VALUES
+        <foreach collection="entities" item="entity" separator=",">
+            (#{entity.periodId,jdbcType=VARCHAR}, #{entity.fsUserId,jdbcType=BIGINT},#{entity.qwUserId,jdbcType=VARCHAR})
+        </foreach>
+    </insert>
+
+    <!-- 修改数据 -->
+    <update id="updateById" parameterType="com.fs.statis.domain.FsStatisTempFsuser">
+        UPDATE fs_statis_temp_fsuser
+        <set>
+            <if test="periodId != null and periodId != ''">
+                period_id = #{periodId,jdbcType=VARCHAR},
+            </if>
+            <if test="fsUserId != null">
+                fs_user_id = #{fsUserId,jdbcType=BIGINT},
+            </if>
+        </set>
+        WHERE id = #{id,jdbcType=BIGINT}
+    </update>
+
+    <!-- 根据ID删除 -->
+    <delete id="deleteById">
+        DELETE FROM fs_statis_temp_fsuser WHERE id = #{id,jdbcType=BIGINT}
+    </delete>
+
+</mapper>

+ 128 - 0
fs-service/src/main/resources/mapper/statis/FsStatisTempParamMapper.xml

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisTempParamMapper">
+
+    <!-- Enable auto-mapping from snake_case DB columns to camelCase DTO fields -->
+    <!-- This is often configured globally in mybatis-config.xml or Spring Boot properties -->
+    <!-- <settings>
+        <setting name="mapUnderscoreToCamelCase" value="true"/>
+    </settings> -->
+
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisTempParam">
+        <id column="id" property="id" jdbcType="INTEGER"/>
+        <result column="company_user_id" property="companyUserId" jdbcType="BIGINT"/>
+        <result column="sop_id" property="sopId" jdbcType="BIGINT"/>
+        <result column="this_date" property="thisDate" jdbcType="VARCHAR"/>
+        <result column="qw_user_id" property="qwUserId" jdbcType="BIGINT"/>
+        <result column="period_id" property="periodId" jdbcType="VARCHAR"/>
+        <result column="start_time" property="startTime" jdbcType="VARCHAR"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> <!-- or DATETIME -->
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id, company_user_id, sop_id, this_date, qw_user_id, period_id, start_time, create_time
+    </sql>
+
+    <select id="selectById" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List" />
+        FROM fs_statis_temp_param
+        WHERE id = #{id,jdbcType=INTEGER}
+    </select>
+
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List" />
+        FROM fs_statis_temp_param
+    </select>
+
+    <delete id="deleteById">
+        DELETE FROM fs_statis_temp_param
+        WHERE id = #{id,jdbcType=INTEGER}
+    </delete>
+
+    <insert id="insert" parameterType="com.fs.statis.domain.FsStatisTempParam" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO fs_statis_temp_param (company_user_id, sop_id, this_date,
+                                          qw_user_id, period_id, start_time, create_time)
+        VALUES (#{companyUserId,jdbcType=BIGINT}, #{sopId,jdbcType=BIGINT}, #{thisDate,jdbcType=VARCHAR},
+                #{qwUserId,jdbcType=BIGINT}, #{periodId,jdbcType=VARCHAR}, #{startTime,jdbcType=VARCHAR},
+                #{createTime,jdbcType=TIMESTAMP})
+    </insert>
+
+    <insert id="insertSelective" parameterType="com.fs.statis.domain.FsStatisTempParam" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO fs_statis_temp_param
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="sopId != null">sop_id,</if>
+            <if test="thisDate != null">this_date,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="periodId != null">period_id,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">#{companyUserId,jdbcType=BIGINT},</if>
+            <if test="sopId != null">#{sopId,jdbcType=BIGINT},</if>
+            <if test="thisDate != null">#{thisDate,jdbcType=VARCHAR},</if>
+            <if test="qwUserId != null">#{qwUserId,jdbcType=BIGINT},</if>
+            <if test="periodId != null">#{periodId,jdbcType=VARCHAR},</if>
+            <if test="startTime != null">#{startTime,jdbcType=VARCHAR},</if>
+            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
+        </trim>
+    </insert>
+    <insert id="batchSave">
+        REPLACE INTO fs_statis_temp_param (
+        company_user_id, sop_id, this_date, qw_user_id,
+        period_id, start_time, create_time,dept_id
+        )
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.companyUserId}, #{item.sopId}, #{item.thisDate}, #{item.qwUserId},
+            #{item.periodId}, #{item.startTime}, #{item.createTime},#{item.deptId}
+            )
+        </foreach>
+
+    </insert>
+    <insert id="batchSaveToSop">
+        REPLACE INTO fs_statis_temp_param (
+        company_user_id, sop_id, this_date, qw_user_id,
+        period_id, start_time, create_time,dept_id
+        )
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.companyUserId}, #{item.sopId}, #{item.thisDate}, #{item.qwUserId},
+            #{item.periodId}, #{item.startTime}, #{item.createTime},#{item.deptId}
+            )
+        </foreach>
+    </insert>
+
+    <update id="updateById" parameterType="com.fs.statis.domain.FsStatisTempParam">
+        UPDATE fs_statis_temp_param
+        SET
+            company_user_id = #{companyUserId,jdbcType=BIGINT},
+            sop_id = #{sopId,jdbcType=BIGINT},
+            this_date = #{thisDate,jdbcType=VARCHAR},
+            qw_user_id = #{qwUserId,jdbcType=BIGINT},
+            period_id = #{periodId,jdbcType=VARCHAR},
+            start_time = #{startTime,jdbcType=VARCHAR},
+            create_time = #{createTime,jdbcType=TIMESTAMP}
+        WHERE id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <update id="updateByIdSelective" parameterType="com.fs.statis.domain.FsStatisTempParam">
+        UPDATE fs_statis_temp_param
+        <set>
+            <if test="companyUserId != null">company_user_id = #{companyUserId,jdbcType=BIGINT},</if>
+            <if test="sopId != null">sop_id = #{sopId,jdbcType=BIGINT},</if>
+            <if test="thisDate != null">this_date = #{thisDate,jdbcType=VARCHAR},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId,jdbcType=BIGINT},</if>
+            <if test="periodId != null">period_id = #{periodId,jdbcType=VARCHAR},</if>
+            <if test="startTime != null">start_time = #{startTime,jdbcType=VARCHAR},</if>
+            <if test="createTime != null">create_time = #{createTime,jdbcType=TIMESTAMP},</if>
+        </set>
+        WHERE id = #{id,jdbcType=INTEGER}
+    </update>
+
+</mapper>

+ 9 - 0
fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java

@@ -0,0 +1,9 @@
+package com.fs.his.service.impl;
+
+
+class FsUserServiceImplTest {
+
+
+    void selectFsUserPageListNew() {
+    }
+}