dongdong.xiang 1 month ago
parent
commit
e0310179a1
62 changed files with 3203 additions and 60 deletions
  1. 6 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserController.java
  2. 3 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  3. 61 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  4. 142 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  5. 97 0
      fs-admin/src/main/java/com/fs/transfer/CustomerTransferApprovalController.java
  6. 115 19
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  7. 15 0
      fs-service/pom.xml
  8. 22 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyCacheService.java
  9. 9 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyTagCacheService.java
  10. 32 0
      fs-service/src/main/java/com/fs/company/cache/ICompanyUserCacheService.java
  11. 36 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyTagCacheServiceImpl.java
  12. 59 0
      fs-service/src/main/java/com/fs/company/cache/impl/CompanyUserCacheServiceImpl.java
  13. 46 0
      fs-service/src/main/java/com/fs/company/cache/impl/ICompanyCacheServiceImpl.java
  14. 15 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  15. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  16. 20 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  17. 5 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  18. 19 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java
  19. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  20. 7 0
      fs-service/src/main/java/com/fs/course/service/cache/IFsUserCourseVideoCacheService.java
  21. 29 0
      fs-service/src/main/java/com/fs/course/service/cache/impl/FsUserCourseVideoCacheServiceImpl.java
  22. 77 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  23. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListVO.java
  24. 21 2
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  25. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  26. 6 0
      fs-service/src/main/java/com/fs/his/service/IFsUserService.java
  27. 21 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  28. 19 0
      fs-service/src/main/java/com/fs/qw/cache/IQwExternalContactCacheService.java
  29. 5 0
      fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
  30. 49 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwExternalContactCacheServiceImpl.java
  31. 34 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java
  32. 99 0
      fs-service/src/main/java/com/fs/qw/domain/CustomerTransferApproval.java
  33. 31 0
      fs-service/src/main/java/com/fs/qw/dto/FsUserTransferParamDTO.java
  34. 62 0
      fs-service/src/main/java/com/fs/qw/mapper/CustomerTransferApprovalMapper.java
  35. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  36. 36 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  37. 19 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  38. 62 0
      fs-service/src/main/java/com/fs/qw/service/ICustomerTransferApprovalService.java
  39. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  40. 6 2
      fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java
  41. 266 0
      fs-service/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java
  42. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  43. 199 12
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  44. 26 0
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogAllStatisticsListVO.java
  45. 37 22
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogStatisticsListVO.java
  46. 30 0
      fs-service/src/main/java/com/fs/qw/vo/TransferCustomDTO.java
  47. 241 0
      fs-service/src/main/java/com/fs/store/domain/FsUserCourseCount.java
  48. 77 0
      fs-service/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java
  49. 69 0
      fs-service/src/main/java/com/fs/store/service/IFsUserCourseCountService.java
  50. 14 0
      fs-service/src/main/java/com/fs/store/service/cache/IFsUserCacheService.java
  51. 21 0
      fs-service/src/main/java/com/fs/store/service/cache/IFsUserCourseCacheService.java
  52. 8 0
      fs-service/src/main/java/com/fs/store/service/cache/IFsUserCourseCountCacheService.java
  53. 45 0
      fs-service/src/main/java/com/fs/store/service/cache/impl/FsUserCourseCacheServiceImpl.java
  54. 36 0
      fs-service/src/main/java/com/fs/store/service/cache/impl/FsUserCourseCountCacheServiceImpl.java
  55. 29 0
      fs-service/src/main/java/com/fs/store/service/cache/impl/IFsUserCacheServiceImpl.java
  56. 138 0
      fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java
  57. 16 1
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  58. 80 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  59. 9 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  60. 122 0
      fs-service/src/main/resources/mapper/qw/CustomerTransferApprovalMapper.xml
  61. 194 0
      fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml
  62. 221 0
      fs-service/src/main/resources/mapper/store/FsUserCourseCountMapper.xml

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

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

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

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

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

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

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

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

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

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

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

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

+ 15 - 0
fs-service/pom.xml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -134,4 +134,12 @@ public interface ICompanyUserService {
      * @return  list
      */
     List<CompanyUser> selectCompanyUserByIds(Set<Long> ids);
+
+    CompanyUser selectCompanyUserByUserId(Long userId);
+
+    String selectCompanyUserNameUserById(Long userId);
+
+    List<Long> selectUserAllCompanyUserId(Long companyUserId);
+
+    List<CompanyUser> getAllUserListLimit(Long companyId, String keywords);
 }

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

@@ -401,4 +401,24 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<CompanyUser> selectCompanyUserByIds(Set<Long> ids) {
         return companyUserMapper.selectCompanyUserByIds(ids);
     }
+
+    @Override
+    public CompanyUser selectCompanyUserByUserId(Long userId) {
+        return companyUserMapper.selectCompanyUserByUserId(userId);
+    }
+
+    @Override
+    public String selectCompanyUserNameUserById(Long userId) {
+        return companyUserMapper.selectCompanyUserNameUserById(userId);
+    }
+
+    @Override
+    public List<Long> selectUserAllCompanyUserId(Long companyUserId) {
+        return companyUserMapper.selectUserAllCompanyUserId(companyUserId);
+    }
+
+    @Override
+    public List<CompanyUser> getAllUserListLimit(Long companyId, String keywords) {
+        return companyUserMapper.getAllUserListLimit(companyId,keywords);
+    }
 }

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

@@ -196,7 +196,7 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "LEFT JOIN qw_user qu on qu.id=o.qw_user_id\n" +
             "LEFT JOIN fs_user_course_video v on v.video_id=o.video_id \n" +
             "LEFT JOIN fs_user_course uc on uc.course_id=v.course_id\n" +
-            "where o.company_id=#{companyId}  " +
+            "where o.company_id=#{companyId} AND send_type=2 " +
             "<if test= 'sTime != null '> " +
             "       and DATE(o.create_time) &gt;= DATE(#{sTime})\n" +
             "</if>\n" +
@@ -270,4 +270,8 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     FsCourseWatchLog getWatchCourseVideoByFsUser(@Param("userId") Long userId, @Param("videoId") Long videoId, @Param("companyUserId") Long companyUserId);
 
     FsCourseWatchLog getWatchLogByFsUser(@Param("videoId") Long videoId, @Param("fsUserId") Long fsUserId, @Param("companyUserId") Long companyUserId);
+
+    List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVONew(FsCourseWatchLogStatisticsListParam param);
+
+    long selectFsCourseWatchLogStatisticsListVONewCount(FsCourseWatchLogStatisticsListParam param);
 }

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

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

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

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

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

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

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

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

+ 77 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -5,9 +5,14 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyUser;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.domain.FsCourseWatchLog;
@@ -19,11 +24,14 @@ import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.*;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
 import com.fs.his.config.FsSysConfig;
+import com.fs.his.domain.FsUser;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
+import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwUser;
@@ -39,7 +47,10 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.sop.service.IQwSopLogsService;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
+import com.hc.openapi.tool.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -87,6 +98,24 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private ConfigUtil configUtil;
 
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
+
+    @Autowired
+    private IFsUserCourseCacheService fsUserCourseCacheService;
+
+    @Autowired
+    private IFsUserCourseVideoCacheService fsUserCourseVideoCacheService;
+
+    @Autowired
+    private IFsUserCacheService fsUserCacheService;
+
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+
+    @Autowired
+    private IQwUserCacheService qwUserCacheService;
     /**
      * 查询短链课程看课记录
      *
@@ -182,6 +211,54 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId,qwUserId,externalId);
     }
 
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVONew(FsCourseWatchLogStatisticsListParam param) {
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVONew(param);
+        for (FsCourseWatchLogStatisticsListVO item : list) {
+            // 项目名
+            if(ObjectUtils.isNotNull(item.getProject())){
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    item.setProjectName(sysCourseProject);
+                }
+            }
+            // 课程名
+            if(ObjectUtils.isNotNull(item.getCourseId())) {
+                String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(item.getCourseId());
+                if(ObjectUtils.isNotNull(courseName)){
+                    item.setCourseName(courseName);
+                }
+            }
+            // 小节名
+            if(ObjectUtils.isNotNull(item.getVideoId())) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
+                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                    item.setVideoName(fsUserCourseVideo.getTitle());
+                }
+            }
+            // 用户名
+            if(ObjectUtils.isNotNull(item.getUserId())) {
+                FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
+                if(ObjectUtils.isNotNull(fsUser)){
+                    item.setUserName(String.format("%s_%d",fsUser.getNickName(),fsUser.getUserId()));
+                }
+            }
+            // 销售名
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())) {
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setCompanyUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public long selectFsCourseWatchLogStatisticsListVONewCount(FsCourseWatchLogStatisticsListParam param) {
+        return fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVONewCount(param);
+    }
+
     @Override
     public FsCourseWatchLog getWatchCourseLogVideoBySop(Long videoId,String qwUserId,Long externalId )
     {

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

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

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

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

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

@@ -8,6 +8,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserVO;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -249,4 +250,6 @@ public interface FsUserMapper
     int batchUpdateFsUserByIds(@Param("ids") String[] ids, @Param("status") Integer status);
 
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
+
+    void transferCompanyUser(FsUserTransferParamDTO param);
 }

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

@@ -12,6 +12,7 @@ import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -124,4 +125,9 @@ public interface IFsUserService
     List<FsUserPageListVO> selectFsUserPageList(FsUserPageListParam param);
 
     Boolean disabledUser(String[] ids, boolean status);
+
+    FsUser selectFsUserById(Long userId);
+
+    void transfer(FsUserTransferParamDTO transferParam);
+
 }

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -8,6 +8,7 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
@@ -29,6 +30,7 @@ import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -36,6 +38,8 @@ import com.fs.store.vo.h5.FsUserPageListVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.watch.domain.WatchUser;
 import com.fs.watch.service.WatchUserService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.http.util.Asserts;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -43,6 +47,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
@@ -503,4 +508,20 @@ public class FsUserServiceImpl implements IFsUserService
         return result;
     }
 
+    @Override
+    public FsUser selectFsUserById(Long userId) {
+        return fsUserMapper.selectFsUserById(userId);
+    }
+
+    @Override
+    @Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
+    public void transfer(FsUserTransferParamDTO param) {
+        Asserts.check(ObjectUtils.isNotNull(param.getSourceCompanyUserId()), "来源用户不能为空!");
+        Asserts.check(ObjectUtils.isNotNull(param.getTargetCompanyUserId()), "转移用户不能为空!");
+        Asserts.check(CollectionUtils.isNotEmpty(param.getUserIds()), "要转移的客户列表不能为空!");
+
+        fsUserMapper.transferCompanyUser(param);
+    }
+
+
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -195,4 +195,40 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "    DATE(qec.create_time) "+
             "</script>"})
     List<QwWatchLogAllStatisticsListVO> selectQwExtCountByDayAndLine(QwWatchLogStatisticsListParam param);
+
+    List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVONew(@Param("companyUserIds") List<Long> userIds,
+                                                                               @Param("sDate") String sDate, @Param("eDate") String eDate,
+                                                                               @Param("project") Long project,
+                                                                               @Param("courseId") Long courseId,
+                                                                               @Param("videoId") Long videoId);
+
+    Long selectQwWatchLogAllStatisticsListVONewCount(@Param("companyUserIds") List<Long> userIds,
+                                                     @Param("sDate") String sTime,
+                                                     @Param("eDate") String eTime,
+                                                     @Param("project") Long project,
+                                                     @Param("courseId") Long courseId,
+                                                     @Param("videoId") Long videoId);
+
+    List<QwWatchLogStatisticsListVO> selectQwWatchLogByCompanyUserId(
+            @Param("companyUserIds") List<Long> companyUserIds,
+            @Param("sTime") String sTime,
+            @Param("dTime") String dTime,
+            @Param("project") Long project,
+            @Param("courseId") Long courseId,
+            @Param("videoId") Long videoId,
+            @Param("pageNum") Long pageNum,
+            @Param("pageSize") Long pageSize
+    );
+
+    Long selectQwWatchLogByCompanyUserIdCount(
+            @Param("companyUserIds") List<Long> companyUserIds,
+            @Param("sTime") String sTime,
+            @Param("dTime") String dTime,
+            @Param("project") Long project,
+            @Param("courseId") Long courseId,
+            @Param("videoId") Long videoId
+    );
+
+    Long selectQwExtCountByDayAndCount(QwWatchLogStatisticsListParam param);
+
 }

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

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

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

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

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

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

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

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

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

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

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

@@ -308,6 +308,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return list;
     }
 
+    @Override
+    public Integer selectQwIsRepeat(Long id) {
+        return qwExternalContactMapper.selectQwIsRepeat(id);
+    }
+
     /**
      * 处理一个分组(组内串行)
      */

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

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

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

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

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

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

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

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

+ 241 - 0
fs-service/src/main/java/com/fs/store/domain/FsUserCourseCount.java

@@ -0,0 +1,241 @@
+package com.fs.store.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.Date;
+
+/**
+ * 用户看课统计对象 fs_user_course_count
+ *
+ * @author fs
+ * @date 2025-04-02
+ */
+public class FsUserCourseCount extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 看课数量 */
+    @Excel(name = "看课数量")
+    private Long watchCourseCount;
+
+    /** 缺课数量 */
+    @Excel(name = "缺课数量")
+    private Long missCourseCount;
+
+    /** 缺课状态,1-已缺课;2-未缺课 */
+    @Excel(name = "缺课状态,1-已缺课;2-未缺课")
+    private Long missCourseStatus;
+
+    /** 缺课天数 */
+    @Excel(name = "缺课天数")
+    private Long missCourseDays;
+
+    /** 关联课程(营期)id */
+    @Excel(name = "关联课程(营期)ids")
+    private String courseIds;
+
+    /** 参与营期数量 */
+    @Excel(name = "参与营期数量")
+    private String partCourseCount;
+
+    /** 最后一次看课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最后一次看课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastWatchDate;
+
+    /** 用户状态,1-正常;2-停止;3-未看 */
+    @Excel(name = "用户状态,1-正常;2-停止;3-未看")
+    private Long status;
+
+    /** 停课天数(如果状态是停止) */
+    @Excel(name = "停课天数", readConverterExp = "如=果状态是停止")
+    private Long stopWatchDays;
+
+    /** 完播时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "完播时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date completeWatchDate;
+
+    /** 完播次数 */
+    @Excel(name = "完播次数")
+    private Long completeWatchCount;
+
+    /** 观看次数 */
+    @Excel(name = "观看次数")
+    private Long watchTimes;
+
+    /** 创建日期,为了创建唯一索引 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建日期,为了创建唯一索引", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createDate;
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+    public void setWatchCourseCount(Long watchCourseCount)
+    {
+        this.watchCourseCount = watchCourseCount;
+    }
+
+    public Long getWatchCourseCount()
+    {
+        return watchCourseCount;
+    }
+    public void setMissCourseCount(Long missCourseCount)
+    {
+        this.missCourseCount = missCourseCount;
+    }
+
+    public Long getMissCourseCount()
+    {
+        return missCourseCount;
+    }
+    public void setMissCourseStatus(Long missCourseStatus)
+    {
+        this.missCourseStatus = missCourseStatus;
+    }
+
+    public Long getMissCourseStatus()
+    {
+        return missCourseStatus;
+    }
+    public void setMissCourseDays(Long missCourseDays)
+    {
+        this.missCourseDays = missCourseDays;
+    }
+
+    public Long getMissCourseDays()
+    {
+        return missCourseDays;
+    }
+    public void setCourseId(String courseId)
+    {
+        this.courseIds = courseId;
+    }
+
+    public String getCourseId()
+    {
+        return courseIds;
+    }
+    public void setPartCourseCount(String partCourseCount)
+    {
+        this.partCourseCount = partCourseCount;
+    }
+
+    public String getPartCourseCount()
+    {
+        return partCourseCount;
+    }
+    public void setLastWatchDate(Date lastWatchDate)
+    {
+        this.lastWatchDate = lastWatchDate;
+    }
+
+    public Date getLastWatchDate()
+    {
+        return lastWatchDate;
+    }
+    public void setStatus(Long status)
+    {
+        this.status = status;
+    }
+
+    public Long getStatus()
+    {
+        return status;
+    }
+    public void setStopWatchDays(Long stopWatchDays)
+    {
+        this.stopWatchDays = stopWatchDays;
+    }
+
+    public Long getStopWatchDays()
+    {
+        return stopWatchDays;
+    }
+    public void setCompleteWatchDate(Date completeWatchDate)
+    {
+        this.completeWatchDate = completeWatchDate;
+    }
+
+    public Date getCompleteWatchDate()
+    {
+        return completeWatchDate;
+    }
+    public void setCompleteWatchCount(Long completeWatchCount)
+    {
+        this.completeWatchCount = completeWatchCount;
+    }
+
+    public Long getCompleteWatchCount()
+    {
+        return completeWatchCount;
+    }
+    public void setWatchTimes(Long watchTimes)
+    {
+        this.watchTimes = watchTimes;
+    }
+
+    public Long getWatchTimes()
+    {
+        return watchTimes;
+    }
+
+    public Date getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Date createDate) {
+        this.createDate = createDate;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("userId", getUserId())
+            .append("watchCourseCount", getWatchCourseCount())
+            .append("missCourseCount", getMissCourseCount())
+            .append("missCourseStatus", getMissCourseStatus())
+            .append("missCourseDays", getMissCourseDays())
+            .append("courseId", getCourseId())
+            .append("partCourseCount", getPartCourseCount())
+            .append("lastWatchDate", getLastWatchDate())
+            .append("status", getStatus())
+            .append("stopWatchDays", getStopWatchDays())
+            .append("createTime", getCreateTime())
+            .append("updateTime", getUpdateTime())
+            .append("createBy", getCreateBy())
+            .append("updateBy", getUpdateBy())
+            .append("completeWatchDate", getCompleteWatchDate())
+            .append("completeWatchCount", getCompleteWatchCount())
+            .append("watchTimes", getWatchTimes())
+            .toString();
+    }
+}

+ 77 - 0
fs-service/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java

@@ -0,0 +1,77 @@
+package com.fs.store.mapper;
+
+import com.fs.store.domain.FsUserCourseCount;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 用户看课统计Mapper接口
+ *
+ * @author fs
+ * @date 2025-04-02
+ */
+public interface FsUserCourseCountMapper
+{
+    /**
+     * 查询用户看课统计
+     *
+     * @param id 用户看课统计ID
+     * @return 用户看课统计
+     */
+    public FsUserCourseCount selectFsUserCourseCountById(Long id);
+
+    /**
+     * 查询用户看课统计列表
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 用户看课统计集合
+     */
+    public List<FsUserCourseCount> selectFsUserCourseCountList(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 新增用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    public int insertFsUserCourseCount(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 修改用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    public int updateFsUserCourseCount(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 删除用户看课统计
+     *
+     * @param id 用户看课统计ID
+     * @return 结果
+     */
+    public int deleteFsUserCourseCountById(Long id);
+
+    /**
+     * 批量删除用户看课统计
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteFsUserCourseCountByIds(Long[] ids);
+
+    FsUserCourseCount findByUserId(@Param("userId") Long userId);
+
+    /**
+     * 获取看课统计表结果
+     * @return list
+     */
+    List<FsUserCourseCount> getCountResult();
+
+    /**
+     * 往看课统计表中插入数据
+     */
+    void insertFsUserCourseCountTask(FsUserCourseCount fsUserCourseCount);
+
+}

+ 69 - 0
fs-service/src/main/java/com/fs/store/service/IFsUserCourseCountService.java

@@ -0,0 +1,69 @@
+package com.fs.store.service;
+
+import com.fs.store.domain.FsUserCourseCount;
+
+import java.util.List;
+
+/**
+ * 用户看课统计Service接口
+ *
+ * @author fs
+ * @date 2025-04-02
+ */
+public interface IFsUserCourseCountService
+{
+    /**
+     * 查询用户看课统计
+     *
+     * @param id 用户看课统计ID
+     * @return 用户看课统计
+     */
+    public FsUserCourseCount selectFsUserCourseCountById(Long id);
+
+    /**
+     * 查询用户看课统计列表
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 用户看课统计集合
+     */
+    public List<FsUserCourseCount> selectFsUserCourseCountList(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 新增用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    public int insertFsUserCourseCount(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 修改用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    public int updateFsUserCourseCount(FsUserCourseCount fsUserCourseCount);
+
+    /**
+     * 批量删除用户看课统计
+     *
+     * @param ids 需要删除的用户看课统计ID
+     * @return 结果
+     */
+    public int deleteFsUserCourseCountByIds(Long[] ids);
+
+    /**
+     * 删除用户看课统计信息
+     *
+     * @param id 用户看课统计ID
+     * @return 结果
+     */
+    public int deleteFsUserCourseCountById(Long id);
+
+    FsUserCourseCount findByUserId(Long userId);
+
+    /**
+     * 往看课统计表中插入数据
+     */
+    void insertFsUserCourseCountTask();
+}

+ 14 - 0
fs-service/src/main/java/com/fs/store/service/cache/IFsUserCacheService.java

@@ -0,0 +1,14 @@
+package com.fs.store.service.cache;
+
+
+import com.fs.his.domain.FsUser;
+
+public interface IFsUserCacheService {
+    /**
+     * 查询用户
+     *
+     * @param userId 用户ID
+     * @return 用户
+     */
+    public FsUser selectFsUserById(Long userId);
+}

+ 21 - 0
fs-service/src/main/java/com/fs/store/service/cache/IFsUserCourseCacheService.java

@@ -0,0 +1,21 @@
+package com.fs.store.service.cache;
+
+import com.fs.course.domain.FsUserCourse;
+
+public interface IFsUserCourseCacheService {
+    /**
+     * 查询课程
+     *
+     * @param courseId 课程主键
+     * @return 课程
+     */
+    public FsUserCourse selectFsUserCourseByCourseId(Long courseId);
+
+    /**
+     * 查询课程名称
+     *
+     * @param courseId 课程主键
+     * @return 课程名称
+     */
+    public String selectCourseNameByCourseId(Long courseId);
+}

+ 8 - 0
fs-service/src/main/java/com/fs/store/service/cache/IFsUserCourseCountCacheService.java

@@ -0,0 +1,8 @@
+package com.fs.store.service.cache;
+
+import com.fs.store.domain.FsUserCourseCount;
+
+public interface IFsUserCourseCountCacheService {
+
+    public FsUserCourseCount findByUserId(Long userId);
+}

+ 45 - 0
fs-service/src/main/java/com/fs/store/service/cache/impl/FsUserCourseCacheServiceImpl.java

@@ -0,0 +1,45 @@
+package com.fs.store.service.cache.impl;
+
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.store.service.cache.IFsUserCourseCacheService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class FsUserCourseCacheServiceImpl implements IFsUserCourseCacheService {
+    private final IFsUserCourseService fsUserCourseService;
+    private static final Cache<Long, FsUserCourse> FS_USER_COURSE_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+    private static final Cache<Long, String> COURSE_ID_AND_NAME = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+    @Override
+    public FsUserCourse selectFsUserCourseByCourseId(Long courseId) {
+        return FS_USER_COURSE_CACHE.get(courseId,e-> fsUserCourseService.selectFsUserCourseByCourseId(courseId));
+    }
+
+    @Override
+    public String selectCourseNameByCourseId(Long courseId) {
+        if(courseId == null) {
+            return "-";
+        }
+        return COURSE_ID_AND_NAME.get(courseId, e-> {
+            FsUserCourse course = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
+            if(course == null) {
+                return "-";
+            }
+            return course.getCourseName();
+        });
+    }
+}

+ 36 - 0
fs-service/src/main/java/com/fs/store/service/cache/impl/FsUserCourseCountCacheServiceImpl.java

@@ -0,0 +1,36 @@
+package com.fs.store.service.cache.impl;
+
+import com.fs.store.domain.FsUserCourseCount;
+import com.fs.store.service.IFsUserCourseCountService;
+import com.fs.store.service.cache.IFsUserCourseCountCacheService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class FsUserCourseCountCacheServiceImpl implements IFsUserCourseCountCacheService {
+    private static final Cache<Long, FsUserCourseCount> USER_COURSE_COUNT_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+
+
+    @Autowired
+    private IFsUserCourseCountService fsUserCourseCountService;
+
+
+    @Override
+    public FsUserCourseCount findByUserId(Long userId) {
+        return USER_COURSE_COUNT_CACHE.get(userId,e->{
+            FsUserCourseCount fsUserCourseCount = fsUserCourseCountService.findByUserId(userId);
+            return fsUserCourseCount;
+        });
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/store/service/cache/impl/IFsUserCacheServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.store.service.cache.impl;
+
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class IFsUserCacheServiceImpl implements IFsUserCacheService {
+    private final IFsUserService fsUserService;
+
+    private static final Cache<Long, FsUser> FS_USER_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.MINUTES)
+            .build();
+
+    @Override
+    public FsUser selectFsUserById(Long userId) {
+        return FS_USER_CACHE.get(userId,e-> fsUserService.selectFsUserById(userId));
+    }
+}

+ 138 - 0
fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java

@@ -0,0 +1,138 @@
+package com.fs.store.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.store.domain.FsUserCourseCount;
+import com.fs.store.mapper.FsUserCourseCountMapper;
+import com.fs.store.service.IFsUserCourseCountService;
+import com.google.common.collect.Lists;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 用户看课统计Service业务层处理
+ *
+ * @author fs
+ * @date 2025-04-02
+ */
+@Service
+public class FsUserCourseCountServiceImpl implements IFsUserCourseCountService
+{
+    @Autowired
+    private FsUserCourseCountMapper fsUserCourseCountMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private SqlSessionFactory sqlSessionFactory;
+
+    /**
+     * 查询用户看课统计
+     *
+     * @param id 用户看课统计ID
+     * @return 用户看课统计
+     */
+    @Override
+    public FsUserCourseCount selectFsUserCourseCountById(Long id)
+    {
+        return fsUserCourseCountMapper.selectFsUserCourseCountById(id);
+    }
+
+    /**
+     * 查询用户看课统计列表
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 用户看课统计
+     */
+    @Override
+    public List<FsUserCourseCount> selectFsUserCourseCountList(FsUserCourseCount fsUserCourseCount)
+    {
+        return fsUserCourseCountMapper.selectFsUserCourseCountList(fsUserCourseCount);
+    }
+
+    /**
+     * 新增用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserCourseCount(FsUserCourseCount fsUserCourseCount)
+    {
+        fsUserCourseCount.setCreateTime(DateUtils.getNowDate());
+        return fsUserCourseCountMapper.insertFsUserCourseCount(fsUserCourseCount);
+    }
+
+    /**
+     * 修改用户看课统计
+     *
+     * @param fsUserCourseCount 用户看课统计
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserCourseCount(FsUserCourseCount fsUserCourseCount)
+    {
+        fsUserCourseCount.setUpdateTime(DateUtils.getNowDate());
+        return fsUserCourseCountMapper.updateFsUserCourseCount(fsUserCourseCount);
+    }
+
+    /**
+     * 批量删除用户看课统计
+     *
+     * @param ids 需要删除的用户看课统计ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseCountByIds(Long[] ids)
+    {
+        return fsUserCourseCountMapper.deleteFsUserCourseCountByIds(ids);
+    }
+
+    /**
+     * 删除用户看课统计信息
+     *
+     * @param id 用户看课统计ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseCountById(Long id)
+    {
+        return fsUserCourseCountMapper.deleteFsUserCourseCountById(id);
+    }
+
+    @Override
+    public FsUserCourseCount findByUserId(Long userId) {
+        return fsUserCourseCountMapper.findByUserId(userId);
+    }
+
+    @Override
+    public void insertFsUserCourseCountTask() {
+        // 1、获取统计结果
+        List<FsUserCourseCount> countResult = fsUserCourseCountMapper.getCountResult();
+
+        // 2、分批插入数据
+        this.batchInsert(countResult);
+
+    }
+
+    private void batchInsert(List<FsUserCourseCount> list) {
+        // 分批次处理,一次提交500条
+        List<List<FsUserCourseCount>> batches = Lists.partition(list, 500);
+        batches.forEach(batch -> {
+            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+            try {
+                FsUserCourseCountMapper mapper = sqlSession.getMapper(FsUserCourseCountMapper.class);
+                batch.forEach(mapper::insertFsUserCourseCountTask);
+                sqlSession.commit();
+            } finally {
+                sqlSession.close();
+            }
+        });
+    }
+}

+ 16 - 1
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -259,7 +259,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             del_flag = 0
           AND (company_user.user_id = #{userId} or company_user.parent_id = #{userId})
     </select>
-
+    <select id="getAllUserListLimit" resultType="com.fs.company.domain.CompanyUser">
+        SELECT * FROM company_user
+        <where>
+            <if test="companyId != null">
+                AND company_id != #{companyId}
+            </if>
+            <if test="keywords != null and keywords != ''">
+                AND (
+                phonenumber LIKE CONCAT(#{keywords}, '%')
+                OR user_name LIKE CONCAT(#{keywords}, '%')
+                OR nick_name LIKE CONCAT(#{keywords}, '%')
+                )
+            </if>
+        </where>
+        limit 10
+    </select>
 
 
     <sql id="selectUserVo">

+ 80 - 0
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -408,6 +408,86 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           AND user_id = #{fsUserId}
           AND company_user_id = #{companyUserId}
     </select>
+    <select id="selectFsCourseWatchLogStatisticsListVONew"
+            resultType="com.fs.course.vo.FsCourseWatchLogStatisticsListVO">
+        SELECT
+        o.company_user_id,o.user_id,DATE(o.create_time) create_time,
+        SUM(CASE WHEN o.log_type = '1' THEN 1 ELSE 0 END) AS type1,
+        SUM(CASE WHEN o.log_type = '2' THEN 1 ELSE 0 END) AS type2,
+        SUM(CASE WHEN o.log_type = '3' THEN 1 ELSE 0 END) AS type3,
+        SUM(CASE WHEN o.log_type = '4' THEN 1 ELSE 0 END) AS type4,
+        o.project as project,
+        o.course_id as course_id,
+        o.video_id as video_id
+        FROM fs_course_watch_log o
+        <where>
+            send_type=1
+            <if test="companyId != null">
+                and o.company_id=#{companyId}
+            </if>
+            <if test= 'sTime != null '>
+                and o.create_time &gt;= #{startDate}
+            </if>
+            <if test='eTime != null '>
+                and o.create_time &lt;= #{endDate}
+            </if>
+            <if test ='courseId !=null'>
+                and o.course_id = #{courseId}
+            </if>
+            <if test ='videoId !=null'>
+                and o.video_id = #{videoId}
+            </if>
+            <if test="companyUserId != null">
+                and o.company_user_id = #{companyUserId}
+            </if>
+            <if test="project != null">
+                and o.project = #{project}
+            </if>
+            <if test="userId != null">
+                and o.user_id = #{userId}
+            </if>
+        </where>
+
+        GROUP BY o.video_id,o.user_id,DATE(o.create_time),o.project,o.course_id
+        ORDER BY o.video_id ,DATE(o.create_time)
+
+        limit ${(pageNum-1)*pageSize},${pageSize}
+    </select>
+    <select id="selectFsCourseWatchLogStatisticsListVONewCount" resultType="java.lang.Long">
+        SELECT COUNT(*)
+        FROM (
+        SELECT 1
+        FROM fs_course_watch_log o
+        <where>
+            send_type=2
+            <if test="companyId != null">
+                and o.company_id=#{companyId}
+            </if>
+            <if test= 'sTime != null '>
+                and o.create_time &gt;= #{startDate}
+            </if>
+            <if test='eTime != null '>
+                and o.create_time &lt;= #{endDate}
+            </if>
+            <if test ='courseId !=null'>
+                and o.course_id = #{courseId}
+            </if>
+            <if test ='videoId !=null'>
+                and o.video_id = #{videoId}
+            </if>
+            <if test="companyUserId != null">
+                and o.company_user_id = #{companyUserId}
+            </if>
+            <if test="project != null">
+                and o.project = #{project}
+            </if>
+            <if test="userId != null">
+                and o.user_id = #{userId}
+            </if>
+        </where>
+        GROUP BY o.video_id, o.user_id, DATE(o.create_time), o.project, o.course_id
+        ) AS grouped_results_count
+    </select>
 
 
     <update id="batchUpdateWatchLog" parameterType="java.util.List">

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

@@ -292,6 +292,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </choose>
         </if>
     </select>
+    <update id="transferCompanyUser">
+        update fs_user
+        set company_user_id=#{targetCompanyUserId}
+        where
+        user_id in
+        <foreach collection="userIds" open="(" close=")" separator="," item="item">
+            ${item}
+        </foreach>
+    </update>
 
     <update id="batchUpdateFsUserByIds" parameterType="Long">
         update fs_user

+ 122 - 0
fs-service/src/main/resources/mapper/qw/CustomerTransferApprovalMapper.xml

@@ -0,0 +1,122 @@
+<?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.qw.mapper.CustomerTransferApprovalMapper">
+
+    <resultMap type="CustomerTransferApproval" id="CustomerTransferApprovalResult">
+        <result property="id"    column="id"    />
+        <result property="corpId"    column="corp_id"    />
+        <result property="transferType"    column="transfer_type"    />
+        <result property="customerIds"    column="customer_ids"    />
+        <result property="originalUserId"    column="original_user_id"    />
+        <result property="targetUserId"    column="target_user_id"    />
+        <result property="initiatorUserId"    column="initiator_user_id"    />
+        <result property="content"    column="content"    />
+        <result property="approvalStatus"    column="approval_status"    />
+        <result property="approverUserId"    column="approver_user_id"    />
+        <result property="approvalRemark"    column="approval_remark"    />
+        <result property="processedAt"    column="processed_at"    />
+        <result property="createdAt"    column="created_at"    />
+        <result property="updatedAt"    column="updated_at"    />
+        <result property="transferBefore"    column="transfer_before"    />
+    </resultMap>
+
+    <sql id="selectCustomerTransferApprovalVo">
+        select id, corp_id, transfer_type, customer_ids, original_user_id, target_user_id, initiator_user_id, content, approval_status, approver_user_id, approval_remark, processed_at, created_at, updated_at,transfer_before from customer_transfer_approval
+    </sql>
+
+    <select id="selectCustomerTransferApprovalList" parameterType="CustomerTransferApproval" resultMap="CustomerTransferApprovalResult">
+        <include refid="selectCustomerTransferApprovalVo"/>
+        <where>
+            <if test="corpId != null  and corpId != ''"> and corp_id = #{corpId}</if>
+            <if test="transferType != null "> and transfer_type = #{transferType}</if>
+            <if test="customerIds != null  and customerIds != ''"> and customer_ids = #{customerIds}</if>
+            <if test="originalUserId != null "> and original_user_id = #{originalUserId}</if>
+            <if test="targetUserId != null "> and target_user_id = #{targetUserId}</if>
+            <if test="initiatorUserId != null "> and initiator_user_id = #{initiatorUserId}</if>
+            <if test="content != null  and content != ''"> and content = #{content}</if>
+            <if test="approvalStatus != null "> and approval_status = #{approvalStatus}</if>
+            <if test="approverUserId != null "> and approver_user_id = #{approverUserId}</if>
+            <if test="approvalRemark != null  and approvalRemark != ''"> and approval_remark = #{approvalRemark}</if>
+            <if test="processedAt != null "> and processed_at = #{processedAt}</if>
+            <if test="createdAt != null "> and created_at = #{createdAt}</if>
+            <if test="updatedAt != null "> and updated_at = #{updatedAt}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectCustomerTransferApprovalById" parameterType="Long" resultMap="CustomerTransferApprovalResult">
+        <include refid="selectCustomerTransferApprovalVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertCustomerTransferApproval" parameterType="CustomerTransferApproval" useGeneratedKeys="true" keyProperty="id">
+        insert into customer_transfer_approval
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="corpId != null and corpId != ''">corp_id,</if>
+            <if test="transferType != null">transfer_type,</if>
+            <if test="customerIds != null and customerIds != ''">customer_ids,</if>
+            <if test="originalUserId != null">original_user_id,</if>
+            <if test="targetUserId != null">target_user_id,</if>
+            <if test="initiatorUserId != null">initiator_user_id,</if>
+            <if test="content != null">content,</if>
+            <if test="approvalStatus != null">approval_status,</if>
+            <if test="approverUserId != null">approver_user_id,</if>
+            <if test="approvalRemark != null">approval_remark,</if>
+            <if test="processedAt != null">processed_at,</if>
+            <if test="createdAt != null">created_at,</if>
+            <if test="updatedAt != null">updated_at,</if>
+            <if test="transferBefore != null">transfer_before,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="corpId != null and corpId != ''">#{corpId},</if>
+            <if test="transferType != null">#{transferType},</if>
+            <if test="customerIds != null and customerIds != ''">#{customerIds},</if>
+            <if test="originalUserId != null">#{originalUserId},</if>
+            <if test="targetUserId != null">#{targetUserId},</if>
+            <if test="initiatorUserId != null">#{initiatorUserId},</if>
+            <if test="content != null">#{content},</if>
+            <if test="approvalStatus != null">#{approvalStatus},</if>
+            <if test="approverUserId != null">#{approverUserId},</if>
+            <if test="approvalRemark != null">#{approvalRemark},</if>
+            <if test="processedAt != null">#{processedAt},</if>
+            <if test="createdAt != null">#{createdAt},</if>
+            <if test="updatedAt != null">#{updatedAt},</if>
+            <if test="transferBefore != null">#{transferBefore},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCustomerTransferApproval" parameterType="CustomerTransferApproval">
+        update customer_transfer_approval
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="corpId != null and corpId != ''">corp_id = #{corpId},</if>
+            <if test="transferType != null">transfer_type = #{transferType},</if>
+            <if test="customerIds != null and customerIds != ''">customer_ids = #{customerIds},</if>
+            <if test="originalUserId != null">original_user_id = #{originalUserId},</if>
+            <if test="targetUserId != null">target_user_id = #{targetUserId},</if>
+            <if test="initiatorUserId != null">initiator_user_id = #{initiatorUserId},</if>
+            <if test="content != null">content = #{content},</if>
+            <if test="approvalStatus != null">approval_status = #{approvalStatus},</if>
+            <if test="approverUserId != null">approver_user_id = #{approverUserId},</if>
+            <if test="approvalRemark != null">approval_remark = #{approvalRemark},</if>
+            <if test="processedAt != null">processed_at = #{processedAt},</if>
+            <if test="createdAt != null">created_at = #{createdAt},</if>
+            <if test="updatedAt != null">updated_at = #{updatedAt},</if>
+            <if test="transferBefore != null">transfer_before = #{transferBefore},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCustomerTransferApprovalById" parameterType="Long">
+        delete from customer_transfer_approval where id = #{id}
+    </delete>
+
+    <delete id="deleteCustomerTransferApprovalByIds" parameterType="String">
+        delete from customer_transfer_approval where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 194 - 0
fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml

@@ -34,6 +34,200 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectQwWatchLogVo"/>
         where id = #{id}
     </select>
+    <select id="selectQwWatchLogAllStatisticsListVONew"
+            resultType="com.fs.qw.vo.QwWatchLogAllStatisticsListVO">
+        select
+        any_value(company_id) as company_id,
+        company_user_id,line_time as create_time,
+        COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,
+        COUNT(CASE WHEN day = 0 and status=2 THEN 1 END) AS firstOver,
+        COUNT(CASE WHEN day = 1 and status in (1,2) THEN 1 END) AS d1Online,
+        COUNT(CASE WHEN day = 1 and status=2 THEN 1 END) AS d1Over,
+        COUNT(CASE WHEN day = 2 and status in (1,2) THEN 1 END) AS d2Online,
+        COUNT(CASE WHEN day = 2 and status=2 THEN 1 END) AS d2Over,
+        COUNT(CASE WHEN day = 3 and status in (1,2) THEN 1 END) AS d3Online,
+        COUNT(CASE WHEN day = 3 and status=2 THEN 1 END) AS d3Over,
+        COUNT(CASE WHEN day = 4 and status in (1,2) THEN 1 END) AS d4Online,
+        COUNT(CASE WHEN day = 4 and status=2 THEN 1 END) AS d4Over,
+        COUNT(CASE WHEN day = 5 and status in (1,2) THEN 1 END) AS d5Online,
+        COUNT(CASE WHEN day = 5 and status=2 THEN 1 END) AS d5Over,
+        COUNT(CASE WHEN day = 6 and status in (1,2) THEN 1 END) AS d6Online,
+        COUNT(CASE WHEN day = 6 and status=2 THEN 1 END) AS d6Over,
+        COUNT(CASE WHEN day = 7 and status in (1,2) THEN 1 END) AS d7Online,
+        COUNT(CASE WHEN day = 7 and status=2 THEN 1 END) AS d7Over,
+        COUNT(CASE WHEN day = 8 and status in (1,2) THEN 1 END) AS d8Online,
+        COUNT(CASE WHEN day = 8 and status=2 THEN 1 END) AS d8Over,
+        COUNT(CASE WHEN day = 9 and status in (1,2) THEN 1 END) AS d9Online,
+        COUNT(CASE WHEN day = 9 and status=2 THEN 1 END) AS d9Over,
+        COUNT(CASE WHEN day = 10 and status in (1,2) THEN 1 END) AS d10Online,
+        COUNT(CASE WHEN day = 10 and status=2 THEN 1 END) AS d10Over,
+        COUNT(CASE WHEN day = 11 and status in (1,2) THEN 1 END) AS d11Online,
+        COUNT(CASE WHEN day = 11 and status=2 THEN 1 END) AS d11Over,
+        COUNT(CASE WHEN day = 12 and status in (1,2) THEN 1 END) AS d12Online,
+        COUNT(CASE WHEN day = 12 and status=2 THEN 1 END) AS d12Over,
+        COUNT(CASE WHEN day = 13 and status in (1,2) THEN 1 END) AS d13Online,
+        COUNT(CASE WHEN day = 13 and status=2 THEN 1 END) AS d13Over,
+        COUNT(CASE WHEN day = 14 and status in (1,2) THEN 1 END) AS d14Online,
+        COUNT(CASE WHEN day = 14 and status=2 THEN 1 END) AS d14Over,
+        COUNT(CASE WHEN day = 15 and status in (1,2) THEN 1 END) AS d15Online,
+        COUNT(CASE WHEN day = 15 and status=2 THEN 1 END) AS d15Over,
+        COUNT(CASE WHEN day = 16 and status in (1,2) THEN 1 END) AS d16Online,
+        COUNT(CASE WHEN day = 16 and status=2 THEN 1 END) AS d16Over,
+        COUNT(CASE WHEN day = 17 and status in (1,2) THEN 1 END) AS d17Online,
+        COUNT(CASE WHEN day = 17 and status=2 THEN 1 END) AS d17Over,
+        COUNT(CASE WHEN day = 18 and status in (1,2) THEN 1 END) AS d18Online,
+        COUNT(CASE WHEN day = 18 and status=2 THEN 1 END) AS d18Over,
+        COUNT(CASE WHEN day = 19 and status in (1,2) THEN 1 END) AS d19Online,
+        COUNT(CASE WHEN day = 19 and status=2 THEN 1 END) AS d19Over,
+        COUNT(CASE WHEN day = 20 and status in (1,2) THEN 1 END) AS d20Online,
+        COUNT(CASE WHEN day = 20 and status=2 THEN 1 END) AS d20Over,
+        COUNT(CASE WHEN day = 21 and status in (1,2) THEN 1 END) AS d21Online,
+        COUNT(CASE WHEN day = 21 and status=2 THEN 1 END) AS d21Over,
+        COUNT(CASE WHEN day = 22 and status in (1,2) THEN 1 END) AS d22Online,
+        COUNT(CASE WHEN day = 22 and status=2 THEN 1 END) AS d22Over,
+        COUNT(CASE WHEN day = 23 and status in (1,2) THEN 1 END) AS d23Online,
+        COUNT(CASE WHEN day = 23 and status=2 THEN 1 END) AS d23Over,
+        COUNT(CASE WHEN day = 24 and status in (1,2) THEN 1 END) AS d24Online,
+        COUNT(CASE WHEN day = 24 and status=2 THEN 1 END) AS d24Over,
+        COUNT(CASE WHEN day = 25 and status in (1,2) THEN 1 END) AS d25Online,
+        COUNT(CASE WHEN day = 25 and status=2 THEN 1 END) AS d25Over,
+        COUNT(CASE WHEN day = 26 and status in (1,2) THEN 1 END) AS d26Online,
+        COUNT(CASE WHEN day = 26 and status=2 THEN 1 END) AS d26Over,
+        COUNT(CASE WHEN day = 27 and status in (1,2) THEN 1 END) AS d27Online,
+        COUNT(CASE WHEN day = 27 and status=2 THEN 1 END) AS d27Over,
+        COUNT(CASE WHEN day = 28 and status in (1,2) THEN 1 END) AS d28Online,
+        COUNT(CASE WHEN day = 28 and status=2 THEN 1 END) AS d28Over,
+        COUNT(CASE WHEN day = 29 and status in (1,2) THEN 1 END) AS d29Online,
+        COUNT(CASE WHEN day = 29 and status=2 THEN 1 END) AS d29Over,
+        COUNT(CASE WHEN day = 30 and status in (1,2) THEN 1 END) AS d30Online,
+        COUNT(CASE WHEN day = 30 and status=2 THEN 1 END) AS d30Over,
+        COUNT(1) AS line,project,course_id,video_id
+        from hy_watch_log
+        <where>
+            <if test="companyUserIds != null and companyUserIds.size() != 0">
+                and company_user_id in
+                <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="project != null">
+                and project = #{project}
+            </if>
+            <if test="courseId != null">
+                and course_id = #{courseId}
+            </if>
+            <if test="videoId != null">
+                and video_id =#{videoId}
+            </if>
+            and DATE(line_time) between #{sDate} AND #{eDate} group by project,course_id,video_id,company_user_id
+        </where>
+    </select>
+    <select id="selectQwWatchLogAllStatisticsListVONewCount" resultType="java.lang.Long">
+        select count(*) from(
+        select
+        1
+        from hy_watch_log
+        <where>
+            <if test="companyUserIds != null">
+                and company_user_id in
+                <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="project != null">
+                and project = #{project}
+            </if>
+            <if test="courseId != null">
+                and course_id = #{courseId}
+            </if>
+            <if test="videoId != null">
+                and video_id =#{videoId}
+            </if>
+            and DATE(line_time) between #{sDate} AND #{eDate} group by project,course_id,video_id
+        </where>
+
+        )t
+    </select>
+    <select id="selectQwWatchLogByCompanyUserId" resultType="com.fs.qw.vo.QwWatchLogStatisticsListVO">
+        select
+        COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,
+        COUNT(CASE WHEN day = 0 and status=2 THEN 1 END) AS firstOver,
+        COUNT(CASE WHEN day = 1 and status in (1,2) THEN 1 END) AS d1Online,
+        COUNT(CASE WHEN day = 1 and status=2 THEN 1 END) AS d1Over,
+        COUNT(1) AS line,project,course_id,video_id,company_id,company_user_id
+        from hy_watch_log
+        <where>
+            <if test="companyUserIds != null and companyUserIds.size() != 0">
+                and company_user_id in
+                <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="project != null">
+                and project=#{project}
+            </if>
+            <if test="courseId != null">
+                and course_id=#{courseId}
+            </if>
+            <if test="videoId != null">
+                and video_id=#{videoId}
+            </if>
+            and DATE(line_time) between #{sTime} and #{dTime}
+        </where>
+        group by project,course_id,video_id
+        limit ${(pageNum-1)*pageSize},${pageSize}
+    </select>
+    <select id="selectQwWatchLogByCompanyUserIdCount" resultType="java.lang.Long">
+        SELECT COUNT(*)
+        FROM (
+        SELECT 1
+        FROM hy_watch_log
+        <where>
+            <if test="companyUserIds != null and companyUserIds.size() != 0">
+                and company_user_id in
+                <foreach collection="companyUserIds" item="item" open="(" close=")" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="project != null">
+                and project=#{project}
+            </if>
+            <if test="courseId != null">
+                and course_id=#{courseId}
+            </if>
+            <if test="videoId != null">
+                and video_id=#{videoId}
+            </if>
+            and DATE(line_time) between #{sTime} and #{dTime}
+        </where>
+        GROUP BY project, course_id, video_id,company_user_id
+        ) AS count_temp_table
+    </select>
+    <select id="selectQwExtCountByDayAndCount" resultType="java.lang.Long">
+        SELECT COUNT(*) AS total_count
+        FROM (
+        SELECT 1
+        FROM
+        qw_external_contact qec
+        JOIN
+        qw_user qu ON qec.qw_user_id = qu.id
+        WHERE
+        DATE(qec.create_time) &gt;= DATE(#{sTime})
+        AND DATE(qec.create_time) &lt;= DATE(#{eTime})
+        AND qec.company_id = #{companyId}
+        <if test='nickName != null and nickName != ""'>
+            AND qu.qw_user_name LIKE CONCAT(#{nickName}, '%')
+        </if>
+        <if test='ids != null and ids != ""'>
+            AND qec.qw_user_id IN
+            <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
+                #{item}
+            </foreach>
+        </if>
+        GROUP BY
+        qec.qw_user_id, DATE(qec.create_time)
+        ) AS grouped_data
+    </select>
 
     <insert id="insertQwWatchLog" parameterType="QwWatchLog" useGeneratedKeys="true" keyProperty="id">
         insert into qw_watch_log

+ 221 - 0
fs-service/src/main/resources/mapper/store/FsUserCourseCountMapper.xml

@@ -0,0 +1,221 @@
+<?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.store.mapper.FsUserCourseCountMapper">
+
+    <resultMap type="FsUserCourseCount" id="FsUserCourseCountResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="watchCourseCount"    column="watch_course_count"    />
+        <result property="missCourseCount"    column="miss_course_count"    />
+        <result property="missCourseStatus"    column="miss_course_status"    />
+        <result property="missCourseDays"    column="miss_course_days"    />
+        <result property="courseIds"    column="course_idss"    />
+        <result property="partCourseCount"    column="part_course_count"    />
+        <result property="lastWatchDate"    column="last_watch_date"    />
+        <result property="status"    column="status"    />
+        <result property="stopWatchDays"    column="stop_watch_days"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="completeWatchDate"    column="complete_watch_date"    />
+        <result property="completeWatchCount"    column="complete_watch_count"    />
+        <result property="watchTimes"    column="watch_times"    />
+        <result property="createDate"    column="create_date"    />
+    </resultMap>
+
+    <sql id="selectFsUserCourseCountVo">
+        select id, user_id, watch_course_count, miss_course_count, miss_course_status, miss_course_days, course_ids, part_course_count, last_watch_date, status, stop_watch_days, create_time, update_time, create_by, update_by, complete_watch_date, complete_watch_count, watch_times, create_date from fs_user_course_count
+    </sql>
+
+    <select id="selectFsUserCourseCountList" parameterType="FsUserCourseCount" resultMap="FsUserCourseCountResult">
+        <include refid="selectFsUserCourseCountVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="watchCourseCount != null "> and watch_course_count = #{watchCourseCount}</if>
+            <if test="missCourseCount != null "> and miss_course_count = #{missCourseCount}</if>
+            <if test="missCourseStatus != null "> and miss_course_status = #{missCourseStatus}</if>
+            <if test="missCourseDays != null "> and miss_course_days = #{missCourseDays}</if>
+            <if test="courseIds != null "> and course_ids = #{courseIds}</if>
+            <if test="partCourseCount != null  and partCourseCount != ''"> and part_course_count = #{partCourseCount}</if>
+            <if test="lastWatchDate != null "> and last_watch_date = #{lastWatchDate}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="stopWatchDays != null "> and stop_watch_days = #{stopWatchDays}</if>
+            <if test="completeWatchDate != null "> and complete_watch_date = #{completeWatchDate}</if>
+            <if test="completeWatchCount != null "> and complete_watch_count = #{completeWatchCount}</if>
+            <if test="watchTimes != null "> and watch_times = #{watchTimes}</if>
+            <if test="createDate != null "> and create_date = #{createDate}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCourseCountById" parameterType="Long" resultMap="FsUserCourseCountResult">
+        <include refid="selectFsUserCourseCountVo"/>
+        where id = #{id}
+    </select>
+    <select id="findByUserId" resultType="com.fs.store.domain.FsUserCourseCount">
+        select * from fs_user_course_count where user_id = ${userId} limit 1
+    </select>
+
+    <insert id="insertFsUserCourseCount" parameterType="FsUserCourseCount">
+        insert into fs_user_course_count
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="watchCourseCount != null">watch_course_count,</if>
+            <if test="missCourseCount != null">miss_course_count,</if>
+            <if test="missCourseStatus != null">miss_course_status,</if>
+            <if test="missCourseDays != null">miss_course_days,</if>
+            <if test="courseIds != null">course_ids,</if>
+            <if test="partCourseCount != null">part_course_count,</if>
+            <if test="lastWatchDate != null">last_watch_date,</if>
+            <if test="status != null">status,</if>
+            <if test="stopWatchDays != null">stop_watch_days,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="completeWatchDate != null">complete_watch_date,</if>
+            <if test="completeWatchCount != null">complete_watch_count,</if>
+            <if test="watchTimes != null">watch_times,</if>
+            <if test="createDate != null">create_date,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="watchCourseCount != null">#{watchCourseCount},</if>
+            <if test="missCourseCount != null">#{missCourseCount},</if>
+            <if test="missCourseStatus != null">#{missCourseStatus},</if>
+            <if test="missCourseDays != null">#{missCourseDays},</if>
+            <if test="courseIds != null">#{courseIds},</if>
+            <if test="partCourseCount != null">#{partCourseCount},</if>
+            <if test="lastWatchDate != null">#{lastWatchDate},</if>
+            <if test="status != null">#{status},</if>
+            <if test="stopWatchDays != null">#{stopWatchDays},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="completeWatchDate != null">#{completeWatchDate},</if>
+            <if test="completeWatchCount != null">#{completeWatchCount},</if>
+            <if test="watchTimes != null">#{watchTimes},</if>
+            <if test="createDate != null">#{createDate},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserCourseCount" parameterType="FsUserCourseCount">
+        update fs_user_course_count
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="watchCourseCount != null">watch_course_count = #{watchCourseCount},</if>
+            <if test="missCourseCount != null">miss_course_count = #{missCourseCount},</if>
+            <if test="missCourseStatus != null">miss_course_status = #{missCourseStatus},</if>
+            <if test="missCourseDays != null">miss_course_days = #{missCourseDays},</if>
+            <if test="courseIds != null">course_ids = #{courseIds},</if>
+            <if test="partCourseCount != null">part_course_count = #{partCourseCount},</if>
+            <if test="lastWatchDate != null">last_watch_date = #{lastWatchDate},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="stopWatchDays != null">stop_watch_days = #{stopWatchDays},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="completeWatchDate != null">complete_watch_date = #{completeWatchDate},</if>
+            <if test="completeWatchCount != null">complete_watch_count = #{completeWatchCount},</if>
+            <if test="watchTimes != null">watch_times = #{watchTimes},</if>
+            <if test="createDate != null">create_date = #{createDate},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsUserCourseCountById" parameterType="Long">
+        delete from fs_user_course_count where id = #{id}
+    </delete>
+
+    <delete id="deleteFsUserCourseCountByIds" parameterType="String">
+        delete from fs_user_course_count where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="getCountResult" resultType="FsUserCourseCount">
+        SELECT
+            fwl.user_id,
+            count( DISTINCT CASE WHEN fwl.log_type != 3 THEN fwl.video_id END ) AS watchCourseCount,
+            count( DISTINCT CASE WHEN fwl.log_type = 3 THEN fwl.video_id END ) AS missCourseCount,
+            IF
+            ( count( DISTINCT CASE WHEN fwl.log_type = 3 THEN fwl.video_id END ) > 0, 1, 2 ) AS missCourseStatus,
+            GROUP_CONCAT( DISTINCT fwl.period_id ) AS courseIds,
+            count(DISTINCT fwl.period_id ) AS partCourseCount,
+            a.last_heartbeat_time AS lastWatchDate,
+            CASE
+                WHEN a.log_type = 1
+                    OR a.log_type = 2 THEN
+                    1
+                WHEN a.log_type = 4 THEN
+                    2
+                WHEN a.log_type = 3 THEN
+                    3
+                END AS STATUS,
+            DATEDIFF(DATE_FORMAT(NOW(),'%Y-%m-%d'),DATE_FORMAT(a.last_heartbeat_time,'%Y-%m-%d')) as stop_watch_days,
+            max( CASE WHEN fwl.log_type = 2 THEN fwl.last_heartbeat_time END ) AS completeWatchDate,
+            count( CASE WHEN fwl.log_type = 2 THEN fwl.log_id END ) AS completeWatchCount,
+            count( CASE WHEN fwl.log_type != 3 THEN fwl.log_id END ) AS watch_times,
+            NOW() AS create_time,
+            NOW() AS updateTime,
+            curdate() AS create_date
+        FROM
+            ( SELECT fs_course_watch_log.user_id, Max( fs_course_watch_log.last_heartbeat_time ) AS last_heartbeat_time, log_type FROM fs_course_watch_log GROUP BY fs_course_watch_log.user_id ) a
+                INNER JOIN fs_course_watch_log fwl ON fwl.user_id = a.user_id
+        GROUP BY
+            fwl.user_id
+    </select>
+
+    <insert id="insertFsUserCourseCountTask" parameterType="FsUserCourseCount" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_user_course_count
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="watchCourseCount != null">watch_course_count,</if>
+            <if test="missCourseCount != null">miss_course_count,</if>
+            <if test="missCourseStatus != null">miss_course_status,</if>
+            <if test="courseIds != null">course_ids,</if>
+            <if test="partCourseCount != null">part_course_count,</if>
+            <if test="lastWatchDate != null">last_watch_date,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="completeWatchDate != null">complete_watch_date,</if>
+            <if test="completeWatchCount != null">complete_watch_count,</if>
+            <if test="watchTimes != null">watch_times,</if>
+            <if test="createDate != null">create_date,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="watchCourseCount != null">#{watchCourseCount},</if>
+            <if test="missCourseCount != null">#{missCourseCount},</if>
+            <if test="missCourseStatus != null">#{missCourseStatus},</if>
+            <if test="courseIds != null">#{courseIds},</if>
+            <if test="partCourseCount != null">#{partCourseCount},</if>
+            <if test="lastWatchDate != null">#{lastWatchDate},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="completeWatchDate != null">#{completeWatchDate},</if>
+            <if test="completeWatchCount != null">#{completeWatchCount},</if>
+            <if test="watchTimes != null">#{watchTimes},</if>
+            <if test="createDate != null">#{createDate},</if>
+        </trim>
+        on duplicate key update
+        <trim suffixOverrides=",">
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+    </insert>
+
+
+</mapper>