Bläddra i källkod

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

15376779826 1 vecka sedan
förälder
incheckning
383e1403cc
48 ändrade filer med 585 tillägg och 79 borttagningar
  1. 31 0
      fs-admin/src/main/java/com/fs/course/task/WatchCourseStatistics.java
  2. 27 27
      fs-admin/src/main/java/com/fs/task/SgTestController.java
  3. 1 1
      fs-admin/src/main/resources/logback.xml
  4. 2 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  5. 59 0
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  6. 26 0
      fs-common/src/main/java/com/fs/common/utils/model/DateTimeEntity.java
  7. 13 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java
  8. 20 2
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  9. 15 14
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  10. 16 1
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java
  11. 3 1
      fs-company/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java
  12. 4 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  13. 3 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  14. 9 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  15. 12 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java
  16. 4 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  17. 2 1
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  18. 7 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseFinishTempService.java
  19. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  20. 9 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java
  21. 2 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  22. 9 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  23. 7 0
      fs-service/src/main/java/com/fs/crm/param/CrmCustomerListQueryParam.java
  24. 7 2
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatUserMapper.java
  25. 39 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  26. 3 0
      fs-service/src/main/java/com/fs/qw/param/SopUserLogsVO.java
  27. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java
  28. 24 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  29. 8 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogVo.java
  30. 17 1
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogStatisticsListVO.java
  31. 14 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java
  32. 3 0
      fs-service/src/main/java/com/fs/sop/domain/SopUserLogs.java
  33. 27 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  34. 2 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java
  35. 18 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  36. 5 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  37. 14 0
      fs-service/src/main/java/com/fs/sop/vo/ReplaceUserDto.java
  38. 2 0
      fs-service/src/main/java/com/fs/sop/vo/SopUserLogsVo.java
  39. 29 0
      fs-service/src/main/java/com/fs/statis/dto/WatchCourseStatisticsResultDTO.java
  40. 5 0
      fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java
  41. 32 0
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  42. 22 22
      fs-service/src/main/java/com/fs/tulin/service/impl/TulinInfoSyncLogServiceImpl.java
  43. 3 3
      fs-service/src/main/resources/application-dev.yml
  44. 21 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  45. 1 0
      fs-service/src/main/resources/mapper/his/FsExportTaskMapper.xml
  46. 2 2
      fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml
  47. 1 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  48. 1 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

+ 31 - 0
fs-admin/src/main/java/com/fs/course/task/WatchCourseStatistics.java

@@ -0,0 +1,31 @@
+package com.fs.course.task;
+
+import com.fs.statis.service.IStatisticsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @description: 看客相关统计定时任务
+ * @author: Xgb
+ * @createDate: 2025/10/27
+ * @version: 1.0
+ */
+@Component("watchCourseStatistics")
+public class WatchCourseStatistics {
+
+    @Autowired
+    private IStatisticsService statisticsService;
+
+    /**
+     * @Description: 统计统计按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;各公司的观看人数和完播人数,
+     * 各公司的观看人数和完播人数, 存到redis中,定时任务每15分钟执行一次
+     *
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/27 11:47
+     */
+    public void watchCourseStatisticsGroupByCompany() {
+        statisticsService.watchCourseStatisticsGroupByCompany();
+    }
+}

+ 27 - 27
fs-admin/src/main/java/com/fs/task/SgTestController.java

@@ -1,27 +1,27 @@
-//package com.fs.task;
-//
-//import org.springframework.web.bind.annotation.RequestMapping;
-//import org.springframework.web.bind.annotation.RestController;
-//
-//import javax.annotation.Resource;
-//
-///**
-// * @description:
-// * @author: Guos
-// * @time: 2025/10/23 下午2:18
-// */
-//@RestController
-//@RequestMapping("/sg/test")
-//public class SgTestController {
-//
-//    @Resource
-//    private SyncTuLinStudentInfoTask syncTuLinStudentInfoTask;
-//
-//
-//    @RequestMapping("/execute")
-//    public void execute(){
-//        syncTuLinStudentInfoTask.execute();
-//    }
-//
-//
-//}
+package com.fs.task;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/23 下午2:18
+ */
+@RestController
+@RequestMapping("/sg/test")
+public class SgTestController {
+
+    @Resource
+    private SyncTuLinStudentInfoTask syncTuLinStudentInfoTask;
+
+
+    @RequestMapping("/execute")
+    public void execute(){
+        syncTuLinStudentInfoTask.execute();
+    }
+
+
+}

+ 1 - 1
fs-admin/src/main/resources/logback.xml

@@ -72,7 +72,7 @@
     </appender>
 
 	<!-- 系统模块日志级别控制  -->
-	<logger name="com.fs" level="info" />
+	<logger name="com.fs" level="debug" />
 	<!-- Spring日志级别控制  -->
 	<logger name="org.springframework" level="warn" />
 

+ 2 - 0
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -16,4 +16,6 @@ public interface FsConstants {
     String COMPANY_MONEY_KEY = "company:money:";
     // 公司余额redis 锁
     String COMPANY_MONEY_LOCK = "company_money_lock:";
+    // 看客统计  按公司分组 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;
+    String WATCH_COURSE_STATISTICS_GROUP_COMPANY = "watch_course_statistics:group_company:";
 }

+ 59 - 0
fs-common/src/main/java/com/fs/common/utils/DateUtils.java

@@ -6,8 +6,12 @@ import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Map;
+
+import com.fs.common.utils.model.DateTimeEntity;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
 /**
@@ -296,4 +300,59 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         return calendar.getTimeInMillis();
     }
 
+
+    /**
+     * @Description: 根据类型返回开始时间和结束时间 最大时间是今天23:59:59 如本月 是1号00:00:00到今天的23:59:59
+     * @Param:  type: 0-今天 1-昨天 2-本周 3-本月 4-上月
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/27 14:09
+     */
+    public static DateTimeEntity getBetweenTime(int type){
+        // 根据type计算出时间范围
+        String startDate = "";
+        String endDate = "";
+
+        LocalDateTime now = LocalDateTime.now();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalTime startOfDayTime = LocalTime.MIN;
+        LocalTime endOfDayTime = LocalTime.of(23, 59, 59);
+        if(0 == type){
+            LocalDateTime startOfDay = now.with(startOfDayTime);
+            LocalDateTime endOfDay = now.with(endOfDayTime);
+            startDate = startOfDay.format(formatter);
+            endDate = endOfDay.format(formatter);
+        } else if(1 == type){
+            LocalDateTime yesterday = now.minusDays(1);
+            LocalDateTime startOfYesterday = yesterday.with(startOfDayTime);
+            LocalDateTime endOfYesterday = yesterday.with(endOfDayTime);
+            startDate = startOfYesterday.format(formatter);
+            endDate = endOfYesterday.format(formatter);
+        } else if(2 == type) {
+            LocalDateTime startOfWeek = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
+            LocalDateTime startOfStartOfWeek = startOfWeek.with(startOfDayTime);
+            LocalDateTime endOfToday = now.with(endOfDayTime);
+            startDate = startOfStartOfWeek.format(formatter);
+            endDate = endOfToday.format(formatter);
+        } else if(3 == type) {
+            LocalDateTime startOfMonth = now.withDayOfMonth(1);
+            LocalDateTime startOfStartOfMonth = startOfMonth.with(startOfDayTime);
+            LocalDateTime endOfToday = now.with(endOfDayTime);
+            startDate = startOfStartOfMonth.format(formatter);
+            endDate = endOfToday.format(formatter);
+        } else if(4 == type) {
+            LocalDateTime firstDayOfPreviousMonth = now.minusMonths(1).withDayOfMonth(1);
+            LocalDateTime lastDayOfPreviousMonth = now.withDayOfMonth(1).minusDays(1);
+
+            LocalDateTime startOfPrevMonthStart = firstDayOfPreviousMonth.with(startOfDayTime);
+            LocalDateTime endOfPrevMonthEnd = lastDayOfPreviousMonth.with(endOfDayTime);
+
+            startDate = startOfPrevMonthStart.format(formatter);
+            endDate = endOfPrevMonthEnd.format(formatter);
+        }
+
+        return new DateTimeEntity(startDate, endDate);
+    }
+
 }

+ 26 - 0
fs-common/src/main/java/com/fs/common/utils/model/DateTimeEntity.java

@@ -0,0 +1,26 @@
+package com.fs.common.utils.model;
+
+import lombok.Data;
+
+/**
+ * @description: 时间对象
+ * @author: Xgb
+ * @createDate: 2025/10/27
+ * @version: 1.0
+ */
+
+@Data
+public class DateTimeEntity {
+
+    private String startTime;
+
+    private String endTime;
+
+    public DateTimeEntity(String startTime, String endTime) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+    }
+
+    public DateTimeEntity() {
+    }
+}

+ 13 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java

@@ -1,10 +1,12 @@
 package com.fs.company.controller.course;
 
+import com.alibaba.fastjson.JSON;
 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.DateUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.service.IFsCourseFinishTempService;
@@ -104,4 +106,15 @@ public class FsCourseFinishTempController extends BaseController
     {
         return toAjax(fsCourseFinishTempService.deleteFsCourseFinishTempByIds(ids));
     }
+
+
+    @Log(title = "完课模板", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateStatusBatch")
+    public AjaxResult updateStatusBatch(@RequestBody FsCourseFinishTemp fsCourseFinishTemp)
+    {
+        fsCourseFinishTemp.setUpdateTime(DateUtils.getNowDate());
+
+        return toAjax(fsCourseFinishTempService.updateFsCourseFinishTempBatch(fsCourseFinishTemp));
+    }
+
 }

+ 20 - 2
fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java

@@ -19,6 +19,7 @@ import com.fs.framework.service.TokenService;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
+import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -81,6 +82,21 @@ public class FsQwCourseWatchLogController extends BaseController
         List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
         return getDataTable(list);
     }
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @GetMapping("/statisticsExport")
+    public AjaxResult statisticsExport(FsCourseWatchLogStatisticsListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        if (param.getSTime()==null||param.getETime()==null){
+            return AjaxResult.error("请选择时间");
+        }
+        param.setSendType(2);
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+        return util.exportExcel(list, "企微看课统计");
+
+    }
 
     @GetMapping("/qwWatchLogStatisticsList")
     public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
@@ -196,8 +212,10 @@ public class FsQwCourseWatchLogController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
-        ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
+
+        List<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVOExport(param);
+
+        ExcelUtil<QwWatchLogStatisticsListVO> util = new ExcelUtil<QwWatchLogStatisticsListVO>(QwWatchLogStatisticsListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");
     }
 

+ 15 - 14
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -63,6 +63,21 @@ public class QwSopTempController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 导出sop模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:sopTemp:export') or @ss.hasPermi('qw:sopTemp:myExport') or @ss.hasPermi('qw:sopTemp:deptExport')")
+    @Log(title = "sop模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwSopTemp qwSopTemp)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwSopTemp.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwSopTemp> list = qwSopTempService.selectQwSopTempListNew(qwSopTemp);
+        ExcelUtil<QwSopTemp> util = new ExcelUtil<QwSopTemp>(QwSopTemp.class);
+        return util.exportExcel(list, "sop模板数据");
+    }
+
     /**
      * 查询我创建的sop模板列表
      */
@@ -111,20 +126,6 @@ public class QwSopTempController extends BaseController
         return getDataTable(list);
     }
 
-
-    /**
-     * 导出sop模板列表
-     */
-    @PreAuthorize("@ss.hasPermi('qw:sopTemp:export') or @ss.hasPermi('qw:sopTemp:myExport') or @ss.hasPermi('qw:sopTemp:deptExport')")
-    @Log(title = "sop模板", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSopTemp qwSopTemp)
-    {
-        List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
-        ExcelUtil<QwSopTemp> util = new ExcelUtil<QwSopTemp>(QwSopTemp.class);
-        return util.exportExcel(list, "sop模板数据");
-    }
-
     /**
      * 获取sop模板详细信息
      */

+ 16 - 1
fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java

@@ -22,6 +22,7 @@ import com.fs.sop.dto.SopUserLogsParamDTO;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.params.SopUserLogsParam;
 import com.fs.sop.service.ISopUserLogsService;
+import com.fs.sop.vo.ReplaceUserDto;
 import com.fs.voice.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -31,6 +32,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * sopUserLogsController
@@ -101,7 +103,11 @@ public class SopUserLogsController extends BaseController
         if (!list.isEmpty()){
             // 收集所有需要查询的 qwUserId 和 corpId 组合
             List<SopUserLogsParamDTO> queryList = list.stream()
-                    .map(item -> new SopUserLogsParamDTO(item.getQwUserId(), item.getCorpId()))
+                    .flatMap(item -> Stream.of(
+                            new SopUserLogsParamDTO(item.getQwUserId(), item.getCorpId()),
+                            new SopUserLogsParamDTO(item.getActualQwUserId(), item.getCorpId())
+                    ))
+                    .filter(dto -> dto.getQwUserId() != null) // 过滤掉null的qwUserId
                     .distinct()  // 去重
                     .collect(Collectors.toList());
 
@@ -115,7 +121,9 @@ public class SopUserLogsController extends BaseController
             // 设置用户名
             list.forEach(item -> {
                 String key = item.getQwUserId() + "_" + item.getCorpId();
+                String key2 = item.getActualQwUserId() + "_" + item.getCorpId();
                 item.setQwUserName(userMap.get(key));
+                item.setActualQwUserName(userMap.get(key2));
             });
         }
         return getDataTable(list);
@@ -180,4 +188,11 @@ public class SopUserLogsController extends BaseController
         sopUserLogsService.addGroupChat(vo);
         return R.ok();
     }
+
+    @Log(title = "更换实际发送人", businessType = BusinessType.UPDATE)
+    @PostMapping("/replaceUser")
+    public R replaceUser(@RequestBody ReplaceUserDto vo){
+        sopUserLogsService.replaceUser(vo);
+        return R.ok();
+    }
 }

+ 3 - 1
fs-company/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java

@@ -9,6 +9,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.framework.security.LoginUser;
+import com.fs.framework.security.SecurityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,7 +21,7 @@ import org.springframework.stereotype.Service;
 /**
  * 用户验证处理
  *
- 
+
  */
 @Service
 public class UserDetailsServiceImpl implements UserDetailsService
@@ -45,6 +46,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
 
 
         CompanyUser user = userService.selectUserByUserName(username);
+
         if (StringUtils.isNull(user))
         {
             log.info("登录用户:{} 不存在.", username);

+ 4 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.app.taskService.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -844,6 +845,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         sopLogs.setFsUserId(fsUserId);
         sopLogs.setUserLogsId(logVo.getId());
 
+        if (ObjectUtil.isNotEmpty(logVo.getActualQwId())){
+            sopLogs.setQwUserKey(logVo.getActualQwId());
+        }
         return sopLogs;
     }
 

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

@@ -38,6 +38,7 @@ import com.fs.store.config.CompanyMenuConfig;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysConfigService;
+import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
@@ -840,7 +841,8 @@ public class CompanyServiceImpl implements ICompanyService
     @Override
     public List<DeptDataVO> getDeptData(Long companyId, Long currentCompanyUserId, Long currentDeptId) {
         List<DeptDataVO> result = new ArrayList<>();
-
+        // 线程中可能会残留的分页信息,这里清除,解决报错
+        PageHelper.clearPage();
         Long isAdmin = companyUserRoleMapper.companyUserIsAdmin(currentCompanyUserId);
         logger.info("当前用户 {} 是公司admin 返回公司所有部门树",currentDeptId);
         if(isAdmin!=null){

+ 9 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java

@@ -1,9 +1,12 @@
 package com.fs.course.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * 完课模板对象 fs_course_finish_temp
  *
@@ -57,4 +60,10 @@ public class FsCourseFinishTemp extends BaseEntity
 
     @Excel(name = "全选销售标志")
     private Integer isAllCompanyUser;
+
+    /**
+     * 用于批量更新状态
+     */
+    @TableField(exist = false)
+    private List<Long> ids;
 }

+ 12 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java

@@ -6,6 +6,7 @@ import com.fs.course.vo.FsCourseFinishTempListVO;
 import com.fs.course.vo.FsCourseFinishTempVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 /**
  * 完课模板Mapper接口
@@ -111,4 +112,15 @@ public interface FsCourseFinishTempMapper
     public FsCourseFinishTempVO selectFsCourseFinishTempByIdVO(Long id);
 
     void deleteByParentIds(@Param("ids") Long[] ids);
+
+    @Update({"<script> " +
+            " update fs_course_finish_temp " +
+            " set status = #{data.status} ," +
+            " update_time = #{data.updateTime} " +
+            " where id in " +
+            " <foreach collection='data.ids' item='id' open='(' separator=',' close=')'>" +
+              " #{id} " +
+            " </foreach> " +
+            "</script>"})
+    int updateFsCourseFinishTempBatch(@Param("data") FsCourseFinishTemp fsCourseFinishTemp);
 }

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

@@ -5,10 +5,10 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
-import com.fs.im.dto.OpenImBatchResponseDataDTO;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.vo.QwRatingVO;
+import com.fs.statis.dto.WatchCourseStatisticsResultDTO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -539,4 +539,7 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      * 看课统计
      * */
     List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
+
+    // 统计当天各公司的观看人数和完播人数, 存到redis中,定时任务每 ? 分钟执行一次
+    List<WatchCourseStatisticsResultDTO> watchCourseStatisticsGroupByCompany(@Param("params") Map<String, Object> params);
 }

+ 2 - 1
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -102,5 +102,6 @@ public class FsCourseWatchLogListParam implements Serializable {
      * 企微名称
      */
     private String qwUserName;
-
+    private Long deptId;
+    private String ids;
 }

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

@@ -77,4 +77,11 @@ public interface IFsCourseFinishTempService
      * 完课用户打备注
      */
     public void finishCourseExtContactIdByRemark(FsCourseWatchLog watchLog);
+
+    /**
+     * 批量更新完课模板状态
+     * @param fsCourseFinishTemp
+     * @return
+     */
+    int updateFsCourseFinishTempBatch(FsCourseFinishTemp fsCourseFinishTemp);
 }

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

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
 import com.fs.qw.param.QwSidebarStatsParam;
+import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
 import java.time.LocalDateTime;
 import java.util.List;

+ 9 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java

@@ -497,4 +497,13 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
             log.error("保存重试记录失败", e);
         }
     }
+
+    /**
+     * 批量更新完课模板状态
+     * @param fsCourseFinishTemp
+     * @return
+     */
+    public int updateFsCourseFinishTempBatch(FsCourseFinishTemp fsCourseFinishTemp){
+        return fsCourseFinishTempMapper.updateFsCourseFinishTempBatch(fsCourseFinishTemp);
+    }
 }

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

@@ -42,6 +42,7 @@ import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.param.SendSopParamDetails;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopTempSetting;
+import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.store.service.cache.IFsUserCacheService;
@@ -1241,4 +1242,5 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectQwFsCourseWatchLogStatisticsListVO(param);
     }
 
+
 }

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

@@ -462,6 +462,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             return R.error(504,"未授权");
         }
 
+        if (StringUtil.strIsNullOrEmpty(fsUser.getMpOpenId())){
+            return R.error(401,"授权后可继续!");
+        }
+
         if (fsUser.getStatus()==0){
             return R.error("会员被停用,无权限,请联系客服!");
         }
@@ -1110,6 +1114,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             if (user.getStatus()==0){
                 return R.error("会员被停用,无权限,请联系客服!");
             }
+
+            if (StringUtil.strIsNullOrEmpty(user.getMpOpenId())){
+                return R.error(401,"授权后可继续!");
+            }
+
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
 
             // 根据链接类型判断是否已发放奖励

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

@@ -1,6 +1,7 @@
 package com.fs.crm.param;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
@@ -96,5 +97,11 @@ public class CrmCustomerListQueryParam extends BaseQueryParam
 
     private String[] receiveTimeList;
 
+    /** 开始时间 */
+    private String beginTime;
+
+    /** 结束时间 */
+    private String endTime;
+
 
 }

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

@@ -97,15 +97,20 @@ public interface QwGroupChatUserMapper
             "</script> ")
     public List<HashMap<String,String>> getDataWeekMonthCount(@Param("map") QwGroupChatUserDataType qwGroupChatUserDataType);
 
-    @Select("select " +
+    @Select(" <script> " +
+            " select " +
             "DATE_FORMAT(join_time,'%Y-%m') as toDay, " +
             "COUNT(join_time) AS dailyJoinCount , " +
             "COUNT(out_time) AS dailyOutCount " +
             "from qw_group_chat_user " +
             "where DATE_FORMAT(join_time,'%Y-%m')> DATE_FORMAT(date_sub(curdate(), interval 12 month),'%Y-%m') " +
+            " <if test='map.chatId != null'>" +
+            " and chat_id = #{map.chatId}" +
+            " </if>" +
             "group  by  DATE_FORMAT(join_time,'%Y-%m') " +
             "ORDER BY  " +
-            "  DATE_FORMAT(join_time,'%Y-%m') asc ")
+            "  DATE_FORMAT(join_time,'%Y-%m') asc " +
+            " </script> ")
     public List<HashMap<String,String>> getDataWeekMonthCountMonth(@Param("map") QwGroupChatUserDataType qwGroupChatUserDataType);
 
 

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

@@ -1,6 +1,7 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
@@ -103,6 +104,44 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "    DATE(qec.create_time) "+
             "</script>"})
     List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAnd(QwWatchLogStatisticsListParam param);
+
+    @Select({"<script> " +
+            "SELECT\n" +
+            "    qec.qw_user_id id,\n" +
+            "    qu.qw_user_name AS qw_user_name, \n" +
+            "    DATE(qec.create_time) AS create_time, \n" +
+            "    COUNT(1) AS line,\n" +
+            "    COUNT(CASE WHEN qec.is_interact = 1 THEN 1 END) AS interact,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 1 THEN 1 END) AS A,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 2 THEN 1 END) AS B,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 3 THEN 1 END) AS C,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 4 THEN 1 END) AS D,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 5 THEN 1 END) AS E,\n" +
+            "    COUNT(CASE WHEN qec.fs_user_id IS NOT NULL THEN 1 END) AS sign,\n" +
+            "    COUNT(CASE WHEN qec.`status` =3 THEN 1 END) AS los,\n" +
+            "    COUNT(CASE WHEN qec.`status` IN (4, 5,6) THEN 1 END) AS del\n" +
+            "FROM\n" +
+            "    qw_external_contact qec\n" +
+            "JOIN\n" +
+            "    qw_user qu ON qec.qw_user_id = qu.id \n" +
+            "left join company_user cu on qec.company_user_id = cu.user_id "+
+            "WHERE\n" +
+            "    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!=\"\"'>\n" +
+            "   and qu.qw_user_name like concat( #{nickName}, '%')\n" +
+            "</if>" +
+            "<if test ='deptId !=null and deptId!=\"\"'>\n" +
+            "   and cu.dept_id = #{deptId}\n" +
+            "</if>" +
+            "<if test ='ids !=null and ids!=\"\"'>\n" +
+            "   and qec.qw_user_id in (${ids})\n" +
+            "</if>" +
+            "GROUP BY\n" +
+            "    qec.qw_user_id, DATE(qec.create_time) \n" +
+            "ORDER BY\n" +
+            "    DATE(qec.create_time) "+
+            "</script>"})
+    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAnd(FsCourseWatchLogListParam param);
     @Select("select \n" +
             "COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,\n" +
             "COUNT(CASE WHEN day = 0 and status=2 THEN 1 END) AS firstOver,\n" +

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

@@ -13,6 +13,9 @@ public class SopUserLogsVO {
     private String qwUserId;
     private String qwUserName;
 
+    // 实际发送人信息
+    private String actualQwUserId;
+    private String actualQwUserName;
     /**
     * 营期时间
     */

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

@@ -2,6 +2,7 @@ package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
@@ -70,4 +71,6 @@ public interface IQwWatchLogService extends IService<QwWatchLog>{
     TableDataInfo selectQwWatchLogAllStatisticsListVONew(QwWatchLogStatisticsListParam param);
 
     TableDataInfo selectQwWatchLogStatisticsListVONew(QwWatchLogStatisticsListParam param);
+
+    List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVOExport(FsCourseWatchLogListParam param);
 }

+ 24 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java

@@ -16,6 +16,7 @@ import com.fs.company.mapper.CompanyDeptMapper;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.his.domain.FsUser;
 import com.fs.qw.domain.QwWatchLog;
@@ -144,6 +145,29 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
         return baseMapper.deleteQwWatchLogById(id);
     }
 
+
+    @Override
+    public List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVOExport(FsCourseWatchLogListParam param) {
+        CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
+        if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
+            param.setDeptId(null);
+        }
+        if (param.getCompanyUserId()!=null){
+            param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
+        }
+        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAnd(param);
+        for (QwWatchLogStatisticsListVO vo : vos) {
+            Long id = vo.getId();
+            Date createTime = vo.getCreateTime();
+            QwWatchLogStatisticsListVO stat = qwWatchLogMapper.selectQwWatchLogByQwUserId(id, createTime);
+            vo.setD1Online(stat.getD1Online());
+            vo.setD1Over(stat.getD1Over());
+            vo.setFirstOnline(stat.getFirstOnline());
+            vo.setFirstOver(stat.getFirstOver());
+        }
+        return vos;
+    }
+
     @Override
     public TableDataInfo selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param) {
         CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());

+ 8 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogVo.java

@@ -1,5 +1,7 @@
 package com.fs.qw.vo;
 
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import com.fs.company.domain.Company;
@@ -83,4 +85,10 @@ public class QwUserVoiceLogVo extends BaseEntity {
 
     private Integer pageSize;
 
+    /** 开始时间 */
+    private String beginTime;
+
+    /** 结束时间 */
+    private String endTime;
+
 }

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

@@ -1,6 +1,7 @@
 package com.fs.qw.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.util.Date;
@@ -9,10 +10,12 @@ import java.util.Date;
 public class QwWatchLogStatisticsListVO {
     private Long id;
     private String qwUserName;
+    @Excel(name = "企微员工名称")
     private String companyUserName;
     private Long companyUserId;
     private Long companyId;
     @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "进线时间",dateFormat = "yyyy-MM-dd")
     private Date createTime;
     /**
      * 项目
@@ -31,20 +34,33 @@ public class QwWatchLogStatisticsListVO {
      */
     private Long videoId;
     private String videoName;
-
+    @Excel(name = "进线数")
     private Long line;//进线数
+    @Excel(name = "先导课上线")
     private Long firstOnline;//先导课上线
+    @Excel(name = "先导课完课")
     private Long firstOver;//先导课完课
+    @Excel(name = "首日上线")
     private Long d1Online;//首日上线
+    @Excel(name = "首日完课")
     private Long d1Over;//首日完课
+    @Excel(name = "综合报名数")
     private Long sign;//综合报名数
+    @Excel(name = "互动数")
     private Long interact;//互动数
+    @Excel(name = "A级用户")
     private Long a;
+    @Excel(name = "B级用户")
     private Long b;
+    @Excel(name = "C级用户")
     private Long c;
+    @Excel(name = "D级用户")
     private Long d;
+    @Excel(name = "E级用户")
     private Long e;
+    @Excel(name = "拉黑数")
     private Long black;
     private Long los;//流失数
+    @Excel(name = "删除数")
     private Long del;//删除数
 }

+ 14 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java

@@ -24,10 +24,12 @@ public class QwSopTemp implements Serializable
 {
 
     /** id */
+    @Excel(name = "模板编号")
     @TableId(type = IdType.INPUT)
     private String id;
 
     /** 模板标题 */
+
     @Excel(name = "模板标题")
     private String name;
 
@@ -48,6 +50,7 @@ public class QwSopTemp implements Serializable
     @Excel(name = "公司id")
     private Long companyId;
 
+    @Excel(name = "间隔天数")
     private Integer gap;
 
     /**
@@ -61,27 +64,38 @@ public class QwSopTemp implements Serializable
     /**
     * 模板类型
     */
+    @Excel(name = "模板类型")
     private Integer sendType;
 
+    @Excel(name = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String createTime;
 
+    @Excel(name = "修改时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String updateTime;
 
     private String createBy;
+
     private Long project;
+
     private Long courseId;
+
     @TableField(exist = false)
     private String modeContent;
+
     @TableField(exist = false)
     private List<Map<String, Object>> rules;
+
     @TableField(exist = false)
     private List<QwSopTempDay> list = new ArrayList<>();
+
     @TableField(exist = false)
     private LocalTime time;
+
     @TableField(exist = false)
     private boolean cuoser;
+
     @TableField(exist = false)
     private List<String> timeList;
 

+ 3 - 0
fs-service/src/main/java/com/fs/sop/domain/SopUserLogs.java

@@ -13,6 +13,9 @@ public class SopUserLogs implements Serializable {
     private String sopTempId;
 
     private String qwUserId;
+    private String actualQwUserId;
+    private Long actualQwId;
+
     private String chatId;
     private String corpId;
     @JsonFormat(pattern = "yyyy-MM-dd")

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

@@ -7,6 +7,7 @@ import com.fs.qw.vo.UpdateSopUserLogDateVo;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.*;
+import com.fs.sop.vo.ReplaceUserDto;
 import com.fs.sop.vo.SopUserLogsInfoVo;
 import com.fs.sop.vo.SopUserLogsVo;
 import org.apache.ibatis.annotations.Param;
@@ -194,6 +195,7 @@ public interface SopUserLogsMapper {
             "            l.sop_id,\n" +
             "            l.sop_temp_id,\n" +
             "            l.qw_user_id,\n" +
+            "            l.actual_qw_user_id,\n" +
             "            l.chat_id,\n" +
             "            l.corp_id,\n" +
             "            l.start_time,\n" +
@@ -290,6 +292,7 @@ public interface SopUserLogsMapper {
         "            l.sop_id,\n" +
         "            l.sop_temp_id,\n" +
         "            l.qw_user_id,\n" +
+        "            l.actual_qw_user_id,\n" +
         "            l.corp_id,\n" +
         "            l.chat_id,\n" +
         "            l.start_time,\n" +
@@ -364,4 +367,28 @@ List<SopUserLogsVO> selectSopUserLogsGroupListByParam(@Param("maps") SopUserLogs
             "<foreach  item='item' index='index' collection='param.sopIds' open='(' separator=',' close=')'> #{item}    </foreach>" +
             "</script>"})
     List<String> getSopUserLogsIds(@Param("param")SendUserLogsInfoMsgParam param);
+
+    @DataSource(DataSourceType.SOP)
+    @Select("<script>" +
+            "UPDATE sop_user_logs " +
+            "SET actual_qw_user_id = " +
+            "<choose>" +
+            "  <when test='vo.actualQwUserId != null'>#{vo.actualQwUserId}</when>" +
+            "  <otherwise>null</otherwise>" +
+            "</choose>" +
+            ", actual_qw_id = " +
+            "<choose>" +
+            "  <when test='vo.actualQwId != null'>#{vo.actualQwId}</when>" +
+            "  <otherwise>null</otherwise>" +
+            "</choose>" +
+            "WHERE id IN " +
+            "<foreach collection='vo.ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    void replaceUser(@Param("vo") ReplaceUserDto vo);
+
+    @DataSource(DataSourceType.SOP)
+    @Select("SELECT * FROM sop_user_logs WHERE sop_id = #{sopId} AND qw_user_id = #{qwUserId}")
+    SopUserLogs queryUserBySopId(@Param("qwUserId")String qwUserId,@Param("sopId") String sopId);
 }

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

@@ -8,6 +8,7 @@ import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.params.SopUserLogsParam;
 import com.fs.sop.params.SopUserLogsParamByDate;
+import com.fs.sop.vo.ReplaceUserDto;
 
 import java.util.List;
 
@@ -64,4 +65,5 @@ public interface ISopUserLogsService {
 
     public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset,int pageSize,Integer notStudyDays);
 
+    void replaceUser(ReplaceUserDto vo);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.sop.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
@@ -555,6 +556,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSendType(6);
                     sopLogs.setExternalUserName(groupUser.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
+
+                    // 设置实际发送人
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
                     String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
@@ -691,6 +695,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSendType(12);
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
+                    // 设置实际发送人
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
 
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
 
@@ -995,12 +1001,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 setting.setVideoId(param.getVideoId());
                 setting.setCourseId(param.getCourseId());
                 setting.setCourseType(param.getCourseType());
+
                 try {
                     sopLogs.setQwUserKey(Long.valueOf(qwUserId));
                 }catch (Exception e){
                     log.error("设置qwUserId异常", e);
                 }
                 sopLogs.setContentJson(JSON.toJSONString(setting));
+                // 设置实际发送人
+                updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
 
                 sopLogsList.add(sopLogs);
             });
@@ -1014,6 +1023,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return R.ok();
     }
 
+    private void updateQwUserKey(QwSopLogs sopLogs, String qwUserId, String sopId) {
+        // 设置实际发送人
+        SopUserLogs qwSopLogs = sopUserLogsMapper.queryUserBySopId(qwUserId,sopId);
+        if (qwSopLogs != null && ObjectUtil.isNotEmpty(qwSopLogs.getActualQwId())){
+            // sopLogs.setQwUserid(qwUser.getQwUserId());
+            sopLogs.setQwUserKey(qwSopLogs.getActualQwId());
+        }
+    }
+
     private void createVoiceUrl(QwSopCourseFinishTempSetting.Setting st, String companyUserId, QwSop qwSop) {
         QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(Long.valueOf(companyUserId), st.getValue());
         if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {

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

@@ -39,6 +39,7 @@ import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.IQwSopTempDayService;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.sop.vo.QwRatingVO;
+import com.fs.sop.vo.ReplaceUserDto;
 import com.fs.sop.vo.SopUserLogsInfoVo;
 import com.fs.sop.vo.SopUserLogsVo;
 import com.fs.system.service.ISysConfigService;
@@ -146,6 +147,10 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
 //    public int updateSopUserLogsDistinctByList(SopUserLogsArray userLogsArray) {
 //        return sopUserLogsMapper.updateSopUserLogsDistinctByList(userLogsArray);
 //    }
+    @Override
+    public void replaceUser(ReplaceUserDto vo) {
+        sopUserLogsMapper.replaceUser(vo);
+    }
 
     @Override
     public int deleteSopUserLogsBySopId(String sopId) {

+ 14 - 0
fs-service/src/main/java/com/fs/sop/vo/ReplaceUserDto.java

@@ -0,0 +1,14 @@
+package com.fs.sop.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ReplaceUserDto {
+    // 营期id
+    private List<String> ids;
+    // 实际发送人id
+    private String actualQwUserId;
+    private Long actualQwId;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/sop/vo/SopUserLogsVo.java

@@ -12,6 +12,8 @@ public class SopUserLogsVo  {
     private String sopTempId;
 
     private String qwUserId;
+    private String actualQwUserId;
+    private Long actualQwId;
     private String userId;
     private String corpId;
     private Integer minConversionDay;

+ 29 - 0
fs-service/src/main/java/com/fs/statis/dto/WatchCourseStatisticsResultDTO.java

@@ -0,0 +1,29 @@
+package com.fs.statis.dto;
+
+import lombok.Data;
+
+/**
+ * @description: 看客统计实体类 没有统计观看完成人数
+ * @author: Xgb
+ * @createDate: 2025/10/27
+ * @version: 1.0
+ */
+@Data
+public class WatchCourseStatisticsResultDTO {
+
+    // 公司id
+    private String companyId;
+    // 公司名称
+    private String companyName;
+    // 1.个微 手动 2.企微 自动
+    private String sendType;
+    // 观看用户数
+    private Integer watchUserCount;
+    // 观看次数
+    private Integer watchCount;
+    // 观看完成数
+    private Integer finishCount;
+    // 观看完成用户数
+    private Integer finishUserCount;
+
+}

+ 5 - 0
fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java

@@ -115,4 +115,9 @@ public interface IStatisticsService {
      * 查询看课统计
      */
     WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param);
+
+    /**
+     * 统计今日看课
+     */
+    void watchCourseStatisticsGroupByCompany();
 }

+ 32 - 0
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -1,10 +1,14 @@
 package com.fs.statis.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
+import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.TimeUtils;
+import com.fs.common.utils.model.DateTimeEntity;
 import com.fs.company.cache.ICompanyCacheService;
+import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
@@ -24,6 +28,7 @@ import com.fs.statis.service.utils.TrendDataFiller;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import com.hc.openapi.tool.fastjson.JSON;
 import com.hc.openapi.tool.util.ObjectUtils;
 import com.hc.openapi.tool.util.StringUtils;
 import org.apache.http.util.Asserts;
@@ -38,7 +43,9 @@ import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import com.fs.statistics.dto.WatchCourseStatisticsDTO;
 
@@ -80,6 +87,8 @@ public class StatisticsServiceImpl implements IStatisticsService {
     @Autowired
     private IQwIpadServerService qwIpadServerService;
 
+
+
     @Override
     public void dataOverviewTask() {
         DealerAggregatedDTO dealerAggregatedDTO = this.dealerAggregated();
@@ -998,4 +1007,27 @@ public class StatisticsServiceImpl implements IStatisticsService {
 
         return watchCourseStatisticsDTO;
     }
+
+    /**
+     * @Description: 统计按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;各公司的观看人数和完播人数, 存到redis中,定时任务每 ? 分钟执行一次
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/27 13:09
+     */
+    @Override
+    public void watchCourseStatisticsGroupByCompany() {
+        for(int i = 0; i < 5; i++){
+            Map<String,Object> params=new HashMap<>();
+            DateTimeEntity timeEntity =DateUtils.getBetweenTime(i);
+            params.put("startTime",timeEntity.getStartTime());
+            params.put("endTime",timeEntity.getEndTime());
+
+            List<WatchCourseStatisticsResultDTO> watchCourseStatisticsDTOS=fsCourseWatchLogMapper.watchCourseStatisticsGroupByCompany(params);
+            // 存到redis中 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;
+            redisCache.setCacheObject(FsConstants.WATCH_COURSE_STATISTICS_GROUP_COMPANY+i, JSON.toJSONString(watchCourseStatisticsDTOS));
+        }
+    }
+
+
 }

+ 22 - 22
fs-service/src/main/java/com/fs/tulin/service/impl/TulinInfoSyncLogServiceImpl.java

@@ -74,28 +74,28 @@ public class TulinInfoSyncLogServiceImpl implements ITulinInfoSyncLogService {
                 }
             }
         }
-//        if(activeProfile.equals("dev")) {
-//            log.info("线下开始执行数据同步");
-//            for (QwExUserInfo qwExUserInfo : qwExUserInfos) {
-//                StudentInfo studentInfo = new StudentInfo();
-//                BeanUtils.copyProperties(qwExUserInfo, studentInfo);
-//                studentInfo.setAppid("wx6688e6b9b6fb8700");
-//                studentInfo.setColumn_name(ColumnNameEnum.GYBNX.getName());
-//                studentInfo.setPharmacy_id(PharmacyEnum.YJKYSY.getPharmacyId());
-//                studentInfo.setService_phone("15141712344");
-//                List<PeriodInfo> periodInfos = tulinInfoSyncLogMapper.selectPeriodInfo(qwExUserInfo.getFs_user_id());
-//                if(CollectionUtils.isNotEmpty(periodInfos)){
-//                    studentInfo.setPeriod_name(periodInfos.get(0).getCourseName());
-//                }
-//                try {
-//                    TulinInfoSyncLog tulinInfoSyncLog = SyncStudentInfoService.send(PROD_URL,PROD_CORPID, studentInfo);
-//                    tulinInfoSyncLog.setFsUserId(qwExUserInfo.getFs_user_id());
-//                    insert(tulinInfoSyncLog);
-//                } catch (JsonProcessingException e) {
-//                    throw new RuntimeException(e);
-//                }
-//            }
-//        }
+        if(activeProfile.equals("dev")) {
+            log.info("线下开始执行数据同步");
+            for (QwExUserInfo qwExUserInfo : qwExUserInfos) {
+                StudentInfo studentInfo = new StudentInfo();
+                BeanUtils.copyProperties(qwExUserInfo, studentInfo);
+                studentInfo.setAppid("wx6688e6b9b6fb8700");
+                studentInfo.setColumn_name(ColumnNameEnum.GYBNX.getName());
+                studentInfo.setPharmacy_id(PharmacyEnum.YJKYSY.getPharmacyId());
+                studentInfo.setService_phone("15141712344");
+                List<PeriodInfo> periodInfos = tulinInfoSyncLogMapper.selectPeriodInfo(qwExUserInfo.getFs_user_id());
+                if(CollectionUtils.isNotEmpty(periodInfos)){
+                    studentInfo.setPeriod_name(periodInfos.get(0).getCourseName());
+                }
+                try {
+                    TulinInfoSyncLog tulinInfoSyncLog = SyncStudentInfoService.send(PROD_URL,PROD_CORPID, studentInfo);
+                    tulinInfoSyncLog.setFsUserId(qwExUserInfo.getFs_user_id());
+                    insert(tulinInfoSyncLog);
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
     }
 
 

+ 3 - 3
fs-service/src/main/resources/application-dev.yml

@@ -43,9 +43,9 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: Rtroot
-                    password: Rtroot
+                    url: jdbc:mysql://127.0.0.1:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: 1101165230
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

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

@@ -973,4 +973,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         o.create_time DESC
     </select>
 
+    <!-- 统计当天各公司的观看人数和完播人数, 存到redis中,定时任务每 ? 分钟执行一次    -->
+    <select id="watchCourseStatisticsGroupByCompany" resultType="com.fs.statis.dto.WatchCourseStatisticsResultDTO">
+        SELECT
+            o.company_id AS companyId,
+            c.company_name AS companyName,
+            o.send_type,
+            COUNT(DISTINCT o.user_id) AS watchUserCount,
+            COUNT(o.log_id) AS watchCount,
+            sum(case when o.log_type = 2 then 1 else 0 end)  AS finishCount,
+            COUNT(DISTINCT CASE WHEN o.log_type = 2 THEN o.user_id END) AS finishUserCount
+        FROM
+            fs_course_watch_log o
+            LEFT JOIN company c ON c.company_id = o.company_id
+        WHERE
+            o.create_time &gt;= #{params.startTime}
+            AND o.create_time &lt;= #{params.endTime}
+        GROUP BY
+            o.company_id,
+            o.send_type
+    </select>
+
 </mapper>

+ 1 - 0
fs-service/src/main/resources/mapper/his/FsExportTaskMapper.xml

@@ -29,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="startTime != null "> and start_time = #{startTime}</if>
             <if test="finishTime != null "> and finish_time = #{finishTime}</if>
+            <if test="status != null "> and status = #{status}</if>
             <if test="fileUrl != null  and fileUrl != ''"> and file_url = #{fileUrl}</if>
         </where>
         order by task_id desc

+ 2 - 2
fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml

@@ -64,13 +64,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and company_id = #{companyId}
             </if>
             <if test="createTime != null ">
-                and create_time = #{createTime}
+                and DATE(create_time) = #{createTime}
             </if>
             <if test="corpId != null ">
                 and corp_id = #{corpId}
             </if>
             <if test="updateTime != null ">
-                and update_time = #{updateTime}
+                and DATE(update_time) = #{updateTime}
             </if>
             ${params.dataScope}
         </where>

+ 1 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -43,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectQwUserList" parameterType="QwUser" resultMap="QwUserResult">
         <include refid="selectQwUserVo"/>
         <where>
+            1=1 and is_del = 0 and company_user_id is not null
             <if test="qwUserId != null  and qwUserId != ''"> and qw_user_id = #{qwUserId}</if>
             <if test="qwUserName != null  and qwUserName != ''"> and qw_user_name like concat('%', #{qwUserName}, '%')</if>
             <if test="department != null  and department != ''"> and department = #{department}</if>

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

@@ -9,6 +9,7 @@
         <result property="sopId"    column="sop_id"    />
         <result property="sopTempId"    column="sop_temp_id"    />
         <result property="qwUserId"    column="qw_user_id"    />
+        <result property="actualQwUserId"    column="actual_user_id"    />
         <result property="corpId"    column="corp_id"    />
         <result property="startTime"    column="start_time"  jdbcType="TIMESTAMP"   />
         <result property="status"    column="status"    />