Просмотр исходного кода

Merge remote-tracking branch 'origin/Payment-Configuration' into Payment-Configuration

# Conflicts:
#	fs-service/src/main/java/com/fs/his/domain/FsUser.java
#	fs-service/src/main/resources/mapper/his/FsUserMapper.xml
yys 4 недель назад
Родитель
Сommit
e4f6eb8beb
24 измененных файлов с 1261 добавлено и 16 удалено
  1. 56 0
      fs-admin/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java
  2. 106 0
      fs-company/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java
  3. 10 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java
  4. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  5. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseAnswerLogs.java
  6. 35 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  7. 7 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogStatisticsListParam.java
  8. 12 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  9. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  10. 321 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  11. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java
  12. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  13. 23 0
      fs-service/src/main/java/com/fs/his/dto/AppUserCompanyDTO.java
  14. 10 3
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  15. 17 3
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  16. 87 0
      fs-service/src/main/java/com/fs/his/vo/AppCourseReportVO.java
  17. 146 0
      fs-service/src/main/java/com/fs/his/vo/AppWatchLogReportVO.java
  18. 177 0
      fs-service/src/main/java/com/fs/his/vo/WatchLogReportVO.java
  19. 9 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  20. 2 0
      fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml
  21. 194 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  22. 19 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  23. 17 7
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java
  24. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

+ 56 - 0
fs-admin/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java

@@ -0,0 +1,56 @@
+package com.fs.app.controller.statistic;
+
+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.utils.poi.ExcelUtil;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.his.vo.AppCourseReportVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @description: app 看课统计
+ * @author: Xgb
+ * @createDate: 2026/3/16
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/app/statistics")
+public class courseStatisticController extends BaseController {
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+
+    /**
+     * app看课统计报表
+     * @param param
+     * @return
+     */
+    @GetMapping("/appCourseReport")
+    public TableDataInfo selectFsAppCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        startPage();
+        List<AppCourseReportVO> list = fsCourseWatchLogService.selectAppCourseReportVO(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出app看课统计报表
+     * @param param
+     * @return
+     */
+    @GetMapping("/exportAppCourseReport")
+    public AjaxResult exportAppCourseReport(FsCourseWatchLogStatisticsListParam param) {
+        List<AppCourseReportVO> list = fsCourseWatchLogService.selectAppCourseReportVO(param);
+        ExcelUtil<AppCourseReportVO> util = new ExcelUtil<AppCourseReportVO>(AppCourseReportVO.class);
+        return util.exportExcel(list, "APP看课统计报表");
+    }
+
+
+}

+ 106 - 0
fs-company/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java

@@ -0,0 +1,106 @@
+package com.fs.app.controller.statistic;
+
+import com.fs.common.annotation.Excel;
+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.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.vo.AppCourseReportVO;
+import com.fs.his.vo.AppWatchLogReportVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @description: app 看课统计
+ * @author: Xgb
+ * @createDate: 2026/3/16
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/app/statistics")
+public class courseStatisticController extends BaseController {
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private IFsCourseWatchLogService courseWatchLogService;
+
+    /**
+     * 销售后台app看课统计 会员维度
+     * @param param
+     * @return
+     */
+    @GetMapping("/appWatchLogReport")
+    public TableDataInfo aPPWatchLogReport(FsCourseWatchLogStatisticsListParam param) {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        return getDataTable(courseWatchLogService.selectUserAppWatchLogReportVO(param));
+    }
+
+    /**
+     * 销售后台app看课统计 会员维度导出
+     * @param param
+     * @return
+     */
+    @GetMapping("/appWatchLogReportExport")
+    public AjaxResult appWatchLogReportExport(FsCourseWatchLogStatisticsListParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AppWatchLogReportVO> list = courseWatchLogService.selectUserAppWatchLogReportVO(param);
+        // 转换登录渠道和答题状态
+        list.forEach(this::convertAppWatchLogReportVO);
+        // 获取所有字段,排除会员维度的三个字段
+        List<String> selectedFields = getAppWatchLogReportFields();
+        ExcelUtil<AppWatchLogReportVO> util = new ExcelUtil<AppWatchLogReportVO>(AppWatchLogReportVO.class);
+        return util.exportExcelSelectedColumns(list, "APP看课统计报表", selectedFields);
+    }
+
+    /**
+     * 转换AppWatchLogReportVO字段
+     * - 登录渠道:有值显示"app",无值显示"小程序"
+     * - 答题状态:无值显示"未答题"
+     */
+    private void convertAppWatchLogReportVO(AppWatchLogReportVO vo) {
+        // 登录渠道转换
+        if (StringUtils.isNotEmpty(vo.getLoginChannel())) {
+            vo.setLoginChannel("app");
+        } else {
+            vo.setLoginChannel("小程序");
+        }
+        // 答题状态转换
+        if (StringUtils.isEmpty(vo.getAnswerStatus())) {
+            vo.setAnswerStatus("未答题");
+        }
+    }
+
+    /**
+     * 获取AppWatchLogReportVO需要导出的字段(排除特定字段)
+     */
+    private List<String> getAppWatchLogReportFields() {
+        // 需要排除的字段:app会员数、销售数、新注册app会员数
+        List<String> excludeFields = Arrays.asList("AppUserCount", "salesCount", "AppNewUser");
+
+        return Arrays.stream(AppWatchLogReportVO.class.getDeclaredFields())
+                .filter(field -> field.isAnnotationPresent(Excel.class))
+                .map(Field::getName)
+                .filter(fieldName -> !excludeFields.contains(fieldName))
+                .collect(Collectors.toList());
+    }
+
+
+}

+ 10 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java

@@ -184,7 +184,16 @@ public class CompanyDeptController extends BaseController
         return toAjax(deptService.deleteCompanyDeptById(deptId));
     }
 
-
+    /**
+     * 获取部门列表树
+     */
+    @GetMapping("/selectDeptTree")
+    public AjaxResult selectDeptTree(CompanyDept dept)
+    {
+        dept.setStatus("0");
+        List<CompanyDept> depts = deptService.selectCompanyDeptList(dept);
+        return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
+    }
 
 
 

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

@@ -11,6 +11,7 @@ import com.fs.company.param.CompanyParam;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
+import com.fs.his.vo.AppCourseReportVO;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -253,4 +254,6 @@ public interface CompanyMapper
 
     @Select("select company_id,company_name from company where FIND_IN_SET( company_id , (select company_ids from qw_company where corp_id = #{corpId})) ")
     List<Company> getCompanyList(@Param("corpId") String corpId);
+
+    List<AppCourseReportVO> selectAllCompanies(@Param("companyId") Long companyId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseAnswerLogs.java

@@ -69,4 +69,7 @@ public class FsCourseAnswerLogs extends BaseEntity {
 
     /** 营期id */
     private Long periodId;
+
+    // 看课方式:1 app  2 小程序
+    private Integer watchType;
 }

+ 35 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -5,6 +5,9 @@ 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.his.vo.AppCourseReportVO;
+import com.fs.his.vo.AppWatchLogReportVO;
+import com.fs.his.vo.WatchLogReportVO;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.vo.QwRatingVO;
@@ -741,4 +744,36 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     List<FsUserCourseAppListVO> selectCourseByUserIdForStatusNotFinish(Long userId);
 
     FsUserCourseAppListVO getAppCourseLearningOne(long userId);
+
+    int selectActiveUserIds(ArrayList<Long> longs);
+
+    List<AppCourseReportVO> selectAppWatchStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppCourseReportVO> selectAppAnswerStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppCourseReportVO> selectAppRedPacketStatistics(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppWatchLogReportVO> selectAppUserBaseData(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售端看课报表 营期训练营明细
+     * @param
+     * @return
+     */
+    List<WatchLogReportVO>  selectCampPeriodByPeriod(@Param("periodIds") List<Long> periodIds );
+
+    /**
+     * 销售端看课报表 红包
+     * @param
+     * @return
+     */
+    List<WatchLogReportVO> selectRedPacketStats(@Param("logIds") List<Long> logIds);
+
+    /**
+     * 答题
+     * @param logIds
+     * @return
+     */
+    List<WatchLogReportVO> selectAnswerStats(@Param("logIds") List<Long> logIds);
+
 }

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

@@ -81,4 +81,11 @@ public class FsCourseWatchLogStatisticsListParam {
         });
         return longs;
     }
+
+    private String appId;
+
+    /**
+     * 手机号
+     */
+    private  String userPhone;
 }

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

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.his.vo.AppCourseReportVO;
+import com.fs.his.vo.AppWatchLogReportVO;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
@@ -167,4 +169,14 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * @return
      */
     List<FsCourseReportVO> selectFsCourseReportVO(FsCourseWatchLogStatisticsListParam param);
+
+
+    /**
+     * app端看课统计报表
+     * @param param
+     * @return
+     */
+    List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppWatchLogReportVO> selectUserAppWatchLogReportVO(FsCourseWatchLogStatisticsListParam param);
 }

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

@@ -229,6 +229,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         logs.setQuestionJson(JSONObject.toJSONString(questions));
         logs.setCreateTime(new Date());
         logs.setPeriodId(param.getPeriodId());
+        logs.setWatchType(log.getWatchType());
 
         if (thisRightCount == questions.size()) {
             logs.setIsRight(1);

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

@@ -29,10 +29,17 @@ import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
+import com.fs.his.config.AppConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.AppUserCompanyDTO;
+import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.vo.AppCourseReportVO;
+import com.fs.his.vo.AppWatchLogReportVO;
+import com.fs.his.vo.WatchLogReportVO;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
@@ -51,9 +58,11 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
+import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import com.fs.tag.service.FsTagUpdateService;
 import com.github.pagehelper.PageHelper;
+import com.google.gson.Gson;
 import com.hc.openapi.tool.util.StringUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
@@ -155,6 +164,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private FinishCourseStatisticsSyncMapper finishCourseStatisticsSyncMapper;
 
+    @Autowired
+    private FsUserMapper userMapper;
+
     /**
      * 查询短链课程看课记录
      *
@@ -1692,6 +1704,315 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseReportVOS;
     }
 
+    /**
+     * @Description: app 看课统计
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/3/16 16:39
+     */
+    @Override
+    public List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param) {
+        // 时间转字符串
+        if (param.getSTime() != null && param.getETime() != null) {
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            param.setStartDate(simpleDateFormat.format(param.getSTime()));
+            param.setEndDate(simpleDateFormat.format(param.getETime()));
+        }
+
+        // 1. 查询所有公司列表
+        List<AppCourseReportVO> allCompanies = companyMapper.selectAllCompanies(param.getCompanyId());
+        if (CollectionUtils.isEmpty(allCompanies)) {
+            return Collections.emptyList();
+        }
+
+        // 2. 查询指定公司和时间范围内的 APP 会员
+//        List<AppUserCompanyDTO> appUserList = userMapper.selectAppUserListForActiveCount(param);
+
+        // 3. 如果有APP会员数据,则统计活跃用户数
+//        Map<Long, int[]> companyStatsMap = null;
+//        if (!CollectionUtils.isEmpty(appUserList)) {
+//            // 提取所有唯一的 userId 并查询活跃用户
+//            Set<Long> allUserIds = appUserList.stream()
+//                    .map(AppUserCompanyDTO::getUserId)
+//                    .collect(Collectors.toSet());
+//
+//            Set<Long> activeUserSet = new HashSet<>(
+//                    fsCourseWatchLogMapper.selectActiveUserIds(new ArrayList<>(allUserIds))
+//            );
+//
+//            // 按公司分组统计 APP 会员数据 [总数, 活跃数]
+//            companyStatsMap = new HashMap<>();
+//            for (AppUserCompanyDTO dto : appUserList) {
+//                Long cid = dto.getCompanyId();
+//                int[] stats = companyStatsMap.computeIfAbsent(cid, k -> new int[]{0, 0});
+//                stats[0]++;
+//                if (activeUserSet.contains(dto.getUserId())) {
+//                    stats[1]++;
+//                }
+//            }
+//        }
+
+        // 查询用户注册数
+        List<Map<String, Object>> registerCountMap = userMapper.selectRegisterCount(param.getCompanyId(), param.getStartDate(), param.getEndDate());
+        Map<Long, Integer> countMap = registerCountMap.stream()
+                .collect(Collectors.toMap(
+                        map ->  map.get("companyId")==null?0L:Long.parseLong(map.get("companyId").toString()),
+                        map -> Integer.parseInt(map.get("registerCount").toString())
+                ));
+
+        // 4. 批量查询看课、答题、红包统计数据
+        List<Long> companyIds = allCompanies.stream()
+                .map(AppCourseReportVO::getCompanyId)
+                .collect(Collectors.toList());
+        param.setCompanyIds(companyIds);
+
+        // 并行查询三个统计数据源
+        List<AppCourseReportVO> watchStatsList = fsCourseWatchLogMapper.selectAppWatchStatistics(param);
+        List<AppCourseReportVO> answerList = fsCourseWatchLogMapper.selectAppAnswerStatistics(param);
+        SysConfig sysConfig = configService.selectConfigByConfigKey("app.config");
+        AppConfig config = new Gson().fromJson(sysConfig.getConfigValue(), AppConfig.class);
+        param.setAppId(config.getAppId());
+        List<AppCourseReportVO> redpackList = fsCourseWatchLogMapper.selectAppRedPacketStatistics(param);
+
+        // 5. 转换为 Map 便于查找
+        Map<Long, AppCourseReportVO> watchStatsMap = watchStatsList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity()));
+        Map<Long, AppCourseReportVO> answerStatsMap = answerList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity(), (e, r) -> e));
+        Map<Long, AppCourseReportVO> redPacketStatsMap = redpackList.stream()
+                .collect(Collectors.toMap(AppCourseReportVO::getCompanyId, Function.identity(), (e, r) -> e));
+
+        // 6. 一次性组装所有数据,减少遍历次数
+        for (AppCourseReportVO vo : allCompanies) {
+            Long companyId = vo.getCompanyId();
+
+            // APP会员统计
+//            if (companyStatsMap != null) {
+//                int[] stats = companyStatsMap.getOrDefault(companyId, new int[]{0, 0});
+//                vo.setAppUserCount(stats[0]);
+//                vo.setActiveAppUserCount(stats[1]);
+//            } else {
+//                vo.setAppUserCount(0);
+//                vo.setActiveAppUserCount(0);
+//            }
+            vo.setAppUserCount(countMap.getOrDefault(companyId, 0));
+
+            // 看课统计 - 如果查不到数据就用0填充
+            AppCourseReportVO watchStats = watchStatsMap.get(companyId);
+            if (watchStats != null) {
+                vo.setPendingCount(watchStats.getPendingCount() != null ? watchStats.getPendingCount() : 0);
+                vo.setWatchingCount(watchStats.getWatchingCount() != null ? watchStats.getWatchingCount() : 0);
+                vo.setFinishedCount(watchStats.getFinishedCount() != null ? watchStats.getFinishedCount() : 0);
+                vo.setAccessCount(watchStats.getAccessCount() != null ? watchStats.getAccessCount() : 0);
+                vo.setStopCount(watchStats.getStopCount() != null ? watchStats.getStopCount() : 0);
+                vo.setWatchRate(calculateWatchingRate(
+                        vo.getWatchingCount(),
+                        vo.getFinishedCount(),
+                        vo.getAccessCount()));
+            } else {
+                // 查不到看课数据时设置默认值0
+                vo.setPendingCount(0);
+                vo.setWatchingCount(0);
+                vo.setFinishedCount(0);
+                vo.setAccessCount(0);
+                vo.setStopCount(0);
+                vo.setWatchRate(BigDecimal.ZERO);
+            }
+
+            // 答题统计 - 如果查不到数据就用0填充
+            AppCourseReportVO answerStats = answerStatsMap.getOrDefault(companyId, new AppCourseReportVO());
+            vo.setAnswerUserCount(answerStats.getAnswerUserCount() != null ? answerStats.getAnswerUserCount() : 0);
+
+            // 红包统计 - 如果查不到数据就用0填充
+            AppCourseReportVO redPacketStats = redPacketStatsMap.getOrDefault(companyId, new AppCourseReportVO());
+            vo.setPacketUserCount(redPacketStats.getPacketUserCount() != null ? redPacketStats.getPacketUserCount() : 0);
+            vo.setPacketAmount(redPacketStats.getPacketAmount() != null ? redPacketStats.getPacketAmount() : BigDecimal.ZERO);
+        }
+
+        return allCompanies;
+    }
+
+    @Override
+    public List<AppWatchLogReportVO> selectUserAppWatchLogReportVO(FsCourseWatchLogStatisticsListParam param) {
+        if (StringUtils.isNotEmpty(param.getUserPhone())) {
+            //加密手机号
+            param.setUserPhone(PhoneUtil.encryptPhone(param.getUserPhone()));
+        }
+        // 时间转字符串
+        if (param.getSTime() != null && param.getETime() != null) {
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            param.setStartDate(simpleDateFormat.format(param.getSTime()));
+            param.setEndDate(simpleDateFormat.format(param.getETime()));
+        }
+        // 获取基础数据
+        List<AppWatchLogReportVO> baseData = fsCourseWatchLogMapper.selectAppUserBaseData(param);
+        if (CollectionUtils.isEmpty(baseData)) {
+            return Collections.emptyList();
+        }
+        // 获取统计数据和组装结果
+        return assembleAppStatisticsData(baseData, param);
+    }
+
+    /**
+     * 组装APP统计数据
+     */
+    private List<AppWatchLogReportVO> assembleAppStatisticsData(List<AppWatchLogReportVO> baseData, FsCourseWatchLogStatisticsListParam param) {
+        // 准备查询条件
+        List<Long> periods = baseData.stream().map(AppWatchLogReportVO::getPeriodId).collect(Collectors.toList());
+        List<Long> logIds = baseData.stream().map(AppWatchLogReportVO::getLogId).collect(Collectors.toList());
+        List<Long> userIds = baseData.stream().map(AppWatchLogReportVO::getUserId).collect(Collectors.toList());
+
+        // 批量查询统计数据
+        // 营期数据
+        Map<Long, WatchLogReportVO> perMap = convertCampPeriodToMap(fsCourseWatchLogMapper.selectCampPeriodByPeriod(periods));
+
+        // 红包数据
+        Map<Long, WatchLogReportVO> redPacketMap = convertRedPacketToMap(
+                fsCourseWatchLogMapper.selectRedPacketStats(logIds)
+        );
+
+//        // 订单数据
+//        Map<Long, WatchLogReportVO> orderMap = convertOrderToMap(
+//                fsCourseWatchLogMapper.selectOrderStats(userIds, param)
+//        );
+
+        // 答题数据
+        Map<Long, WatchLogReportVO> answerMap = convertAnswerToMap(
+                fsCourseWatchLogMapper.selectAnswerStats(logIds)
+        );
+
+        // 学习时长数据(来自fs_user_course_study_log表)- 使用字符串时间
+//        Map<String, AppWatchLogReportVO> studyDurationMap = fsUserCourseStudyLogMapper.selectStudyDurationByUserIds(userIds, param.getStartDate(), param.getEndDate())
+//                .stream()
+//                .collect(Collectors.toMap(
+//                        item -> item.getUserId() + "_" + item.getVideoId(),
+//                        Function.identity()
+//                ));
+
+        // 组装数据
+        for (AppWatchLogReportVO item : baseData) {
+            // 营期数据
+            WatchLogReportVO watchStats = perMap.getOrDefault(item.getPeriodId(), null);
+            if (watchStats != null) {
+                item.setPeriodName(watchStats.getPeriodName());
+                item.setTrainingCampName(watchStats.getTrainingCampName());
+            }
+
+            // 红包数据
+            WatchLogReportVO redPacketStats = redPacketMap.getOrDefault(item.getLogId(), null);
+            if (redPacketStats != null) {
+                item.setRedPacketAmount(redPacketStats.getRedPacketAmount());
+            }
+
+//            // 订单数据
+//            WatchLogReportVO order = orderMap.getOrDefault(item.getUserId(), null);
+//            if (order != null) {
+//                item.setHistoryOrderCount(order.getHistoryOrderCount());
+//            }
+
+            // 答题数据
+            WatchLogReportVO answer = answerMap.getOrDefault(item.getLogId(), null);
+            if (answer != null) {
+                item.setAnswerStatus(answer.getAnswerStatus());
+            }
+
+            // 学习时长数据
+//            AppWatchLogReportVO studyDuration = studyDurationMap.get(item.getUserId() + "_" + item.getVideoId());
+//            if (studyDuration != null && studyDuration.getPublicCourseDuration() != null) {
+//                // 将秒转换为时分秒格式
+//                item.setPublicCourseDuration(formatDuration(Long.valueOf(studyDuration.getPublicCourseDuration())));
+//            }
+        }
+        return baseData;
+    }
+
+    /**
+     * 红包数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertRedPacketToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getLogId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    /**
+     * 营期数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertCampPeriodToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getPeriodId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+
+    /**
+     * 订单数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertOrderToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getUserId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    /**
+     * 答题数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertAnswerToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getLogId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    /**
+     * app 看课率((看课中人次+完课人次)/ 私域课总人次)
+     * @param watchingCount 私域看课中人次
+     * @param finishedCount 私域完课人次
+     * @param totalCount 私域课总人次
+     * @return 看课率(百分比,保留 2 位小数)
+     */
+    private BigDecimal calculateWatchingRate(Integer watchingCount, Integer finishedCount, Integer totalCount) {
+        // 防止除以 0
+        if (totalCount == null || totalCount == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        // 防止空指针
+        int watching = watchingCount != null ? watchingCount : 0;
+        int finished = finishedCount != null ? finishedCount : 0;
+
+        // 看课率 = (看课中人次 + 完课人次) / 总人次 * 100%
+        return BigDecimal.valueOf(watching + finished)
+                .divide(BigDecimal.valueOf(totalCount), 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+
     /**
      * 批量设置课程和视频名称
      *

+ 1 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java

@@ -168,7 +168,7 @@ public class FsUserTalentServiceImpl implements IFsUserTalentService
         fsUserTalent.setIsAudit(1l);
         fsUserTalent.setAuditTime(new Date());
         fsUserTalent.setStatus(1l);
-        fsUserTalent.setIsDel(1l);
+        fsUserTalent.setIsDel(0l);
         fsUserTalentMapper.insertFsUserTalent(fsUserTalent);
         return 1;
     }

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

@@ -246,6 +246,10 @@ public class FsUser extends BaseEntity
     @TableField(exist = false)
     private String nicknameExact;
 
+    // app注册时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date appCreateTime;
+
     /**
      * 储值金额
      */

+ 23 - 0
fs-service/src/main/java/com/fs/his/dto/AppUserCompanyDTO.java

@@ -0,0 +1,23 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class AppUserCompanyDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 公司 ID */
+    private Long companyId;
+
+    /** 公司名称 */
+    private String companyName;
+
+    /** 用户 ID */
+    private Long userId;
+
+    /** 销售 ID */
+    private Long companyUserId;
+}

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

@@ -7,8 +7,10 @@ import java.util.Map;
 import com.fs.course.domain.FsUserWatchCourseStatistics;
 import com.fs.course.domain.FsUserWatchStatistics;
 import com.fs.course.param.CourseAnalysisParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.AppUserCompanyDTO;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
 import com.fs.his.param.FsUserParam;
@@ -33,7 +35,7 @@ import org.apache.ibatis.annotations.*;
  */
 public interface FsUserMapper
 {
-    @Select("select * from fs_user where phone=#{phone}")
+    @Select("select * from fs_user where phone=#{phone} and is_del=0")
     List<FsUser> selectFsUsersByPhoneLimitOne(String phone);
     /**
      * 查询用户
@@ -137,6 +139,9 @@ public interface FsUserMapper
     @Update("update fs_user set is_del=1 where user_id=#{userId}")
     int updateFsUserByUserId(Long userId);
 
+    @Update("update fs_user set is_del=1,remark=#{username} where user_id=#{userId}")
+    int updateFsUserByUserId(@Param("userId") Long userId,@Param("username") String username);
+
     @Select("select f1.*,f2.nick_name tui_name,f2.phone tui_phone FROM fs_user f1 LEFT JOIN fs_user f2 ON f1.tui_user_id =f2.user_id where f1.user_id=#{userId} ")
     FsUserVO selectFsUserVoByUserId(Long userId);
 
@@ -150,7 +155,7 @@ public interface FsUserMapper
     @Select("select * from fs_user where is_del = 0 and phone=#{phone}")
     FsUser selectFsUserByPhone(String phone);
 
-    @Select("select * from fs_user where phone=#{phone} limit 1")
+    @Select("select * from fs_user where is_del=0 and  phone=#{phone} limit 1")
     FsUser selectFsUserByPhoneLimitOne(String phone);
     @Select({"<script> " +
             "select distinct f1.*,f2.nick_name tui_name,f2.phone tui_phone FROM company_user_user cuu LEFT JOIN fs_user f1 ON cuu.user_id =f1.user_id left JOIN fs_user f2 ON f1.tui_user_id =f2.user_id"+
@@ -201,7 +206,7 @@ public interface FsUserMapper
     List<FsUserVO> selectFsUserListVOByComponent(FsUserParam fsUser);
 
 
-    @Select("select * from fs_user where union_id=#{unionid} limit 1")
+    @Select("select * from fs_user where is_del=0  and union_id=#{unionid} limit 1")
     FsUser selectFsUserByUnionid(String unionid);
 
     @Select("<script>" +
@@ -479,4 +484,6 @@ public interface FsUserMapper
 
     @Select("select * from fs_user where apple_key = #{appleKey}")
     FsUser findUserByAppleKey(String appleKey);
+
+    List<Map<String, Object>>  selectRegisterCount(@Param("companyId") Long companyId,@Param("startDate") String startDate,@Param("endDate") String endDate);
 }

+ 17 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -260,16 +260,29 @@ public class FsUserServiceImpl implements IFsUserService {
     @Override
     public int updateFsUser(FsUser fsUser) {
         fsUser.setUpdateTime(DateUtils.getNowDate());
-        if (fsUser.getPhone() != null && fsUser.getPhone().length() == 11 && fsUser.getPhone().matches("\\d+")) {
-            fsUser.setPhone(encryptPhone(fsUser.getPhone()));
+        if (fsUser.getPhone() != null) {
+            //明文手机号
+            if (fsUser.getPhone().length() == 11 && fsUser.getPhone().matches("\\d+")){
+                fsUser.setPhone(encryptPhone(fsUser.getPhone()));
+                //加密手机号
+            }else if (fsUser.getPhone().length() > 11&&fsUser.getPhone().endsWith("==")){
+                fsUser.setPhone(fsUser.getPhone());
+            }else {
+                fsUser.setPhone(null);
+            }
         } else {
             fsUser.setPhone(null);
         }
+
+
+
+
         if (ObjectUtils.isNotEmpty(fsUser.getLevel())&&fsUser.getLevel().equals(1)){
             fsUser.setIsShow(1);
         }else {
             fsUser.setIsShow(0);
         }
+
         return fsUserMapper.updateFsUser(fsUser);
     }
 
@@ -295,7 +308,8 @@ public class FsUserServiceImpl implements IFsUserService {
      */
     @Override
     public int deleteFsUserByUserId(Long userId) {
-        return fsUserMapper.updateFsUserByUserId(userId);
+        String username = "绑定手机号同步了用户";
+        return fsUserMapper.updateFsUserByUserId(userId,username);
     }
 
     @Override

+ 87 - 0
fs-service/src/main/java/com/fs/his/vo/AppCourseReportVO.java

@@ -0,0 +1,87 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class AppCourseReportVO {
+    /** 公司id */
+    private  Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /**
+     * app新增注册人数
+     */
+    @Excel(name = "新增注册人数")
+    private  Integer appUserCount;
+
+    /** 活跃 APP 会员数 */
+    @Excel(name = "APP活跃人数")
+    private Integer activeAppUserCount;
+
+
+    /**
+     * 待看课人数
+     */
+    @Excel(name = "私域课待看课人次")
+    private  Integer pendingCount;
+
+    /**
+     * 看课中人数
+     */
+    @Excel(name = "私域课看课中人次")
+    private  Integer watchingCount;
+
+    /**
+     * 看课中人数
+     */
+    @Excel(name = "私域课看课中断人次")
+    private  Integer stopCount;
+
+
+    /**
+     * 完课人数
+     */
+    @Excel(name = "私域课完课人次")
+    private  Integer finishedCount;
+
+
+    /**
+     * 看课率
+     */
+    @Excel(name = "看课率")
+    private BigDecimal watchRate;
+
+    /**
+     * 答题人数
+     */
+    @Excel(name = "答题人次")
+    private  Integer answerUserCount;
+
+    /**
+     * 红包领取数
+     */
+    @Excel(name = "红包领取人次")
+    private  Integer packetUserCount;
+
+    /**
+     * 红包金额
+     */
+    @Excel(name = "红包金额")
+    private  BigDecimal packetAmount;
+
+    /**
+     * 总人数
+     */
+    private  Integer  accessCount;
+
+    /**
+     * 日志id
+     */
+    private  Long logId;
+}

+ 146 - 0
fs-service/src/main/java/com/fs/his/vo/AppWatchLogReportVO.java

@@ -0,0 +1,146 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class AppWatchLogReportVO {
+
+    @Excel(name = "会员id")
+    private Long userId;
+    /**
+     * 昵称
+     */
+    @Excel(name = "会员昵称")
+    private String nickName;
+
+
+    /**
+     * app会员数
+     */
+    @Excel(name = "app会员数")
+    private  Integer AppUserCount;
+
+    /**
+     * 新注册app会员数
+     */
+    @Excel(name = "新注册app会员数")
+    private  Integer AppNewUser;
+
+    /**
+     * 登录渠道
+     */
+    @Excel(name = "登录渠道")
+    private  String loginChannel;
+
+    /**
+     * 销售数
+     */
+    @Excel(name = "销售数")
+    private  Integer salesCount;
+
+
+    /**
+     * 所属销售数
+     */
+    @Excel(name = "所属销售")
+    private  String salesName;
+
+    /**
+     * 所属销售部门
+     */
+    @Excel(name = "销售部门")
+    private  String salesDept;
+
+    /**
+     * 所属销售公司
+     */
+    @Excel(name = "所属销售公司")
+    private  String salesCompany;
+
+    /**
+     * 培训营名称
+     */
+    @Excel(name = "训练营")
+    private String trainingCampName;
+
+    /**
+     * 营期
+     */
+    @Excel(name = "营期")
+    private  String periodName;
+
+    /**
+     * 视频名称
+     */
+    @Excel(name = "小节名称")
+    private  String videoTitle;
+
+
+    /**
+     * 公开课播放时长
+     */
+    @Excel(name = "公开课播放时长")
+    private  String publicCourseDuration;
+
+    /**
+     * 私欲看课状态
+     */
+    @Excel(name = "私域课看课状态")
+    private  String privateWatchStatus;
+
+    /**
+     * 私欲课播放时长
+      */
+    @Excel(name = "私域课播放时长")
+    private  String privateWatchDuration;
+
+    /**
+     * 观看完成时间
+     */
+    @Excel(name = "完课时间",dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date finishTime;
+
+    /**
+     * 回答状态
+     */
+    @Excel(name = "答题状态")
+    private  String answerStatus;
+
+    /**
+     * 领取红包金额
+     */
+    @Excel(name = "红包金额")
+    private BigDecimal redPacketAmount;
+
+    /**
+     * 历史订单数
+     */
+    @Excel(name = "历史疗法订单数")
+    private  Integer historyOrderCount;
+
+
+    /**
+     * 营期id
+     */
+    private  Long periodId;
+
+    /**
+     * 视频id
+     */
+    private  Long videoId;
+
+    /**
+     * 观看记录id
+     */
+    private  Long logId;
+
+    private  Long deptId;
+
+
+}

+ 177 - 0
fs-service/src/main/java/com/fs/his/vo/WatchLogReportVO.java

@@ -0,0 +1,177 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class WatchLogReportVO {
+
+    @Excel(name = "会员id")
+    private Long userId;
+    /**
+     * 昵称
+     */
+    @Excel(name = "会员昵称")
+    private String nickName;
+
+    /**
+     * 会员数
+     */
+    @Excel(name = "会员数")
+    private  Integer userCount;
+
+
+    /**
+     * 销售数
+     */
+    @Excel(name = "销售数")
+    private  Integer salesCount;
+
+    /**
+     * 所属销售数
+     */
+    @Excel(name = "所属销售")
+    private  String salesName;
+
+    /**
+     * 所属销售部门
+     */
+    @Excel(name = "销售部门")
+    private  String salesDept;
+
+    /**
+     * 所属销售公司
+     */
+    @Excel(name = "所属销售公司")
+    private  String salesCompany;
+
+    /**
+     * 在线会员数
+     */
+    @Excel(name = "当前会员线上数")
+    private  Integer onlineUserCount;
+
+    /**
+     * 培训营名称
+     */
+    @Excel(name = "训练营")
+    private String trainingCampName;
+
+    /**
+     * 营期
+     */
+    @Excel(name = "营期")
+    private  String periodName;
+
+    /**
+     * 视频名称
+     */
+    @Excel(name = "小节名称")
+    private  String videoTitle;
+
+    /**
+     * 观看状态
+     */
+    @Excel(name = "看课状态")
+    private  String watchStatus;
+
+    /**
+     * 观看时长
+     */
+    @Excel(name = "观看时长")
+    private  String duration;
+
+    /**
+     * 观看完成数
+     */
+    @Excel(name = "完课数")
+    private  Integer finishedCount;
+
+    /**
+     * 观看未完成数
+     */
+    @Excel(name = "未完课")
+    private  Integer unfinishedCount;
+
+    /**
+     * 观看完成率
+     */
+    @Excel(name = "完课率")
+    private BigDecimal completionRate;
+
+    /**
+     * 看课时间
+     */
+    @Excel(name = "看课时间",dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date courseTime;
+
+    /**
+     * 观看完成时间
+     */
+    @Excel(name = "完课时间",dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private  Date  finishTime;
+
+    /**
+     * 未看课数
+     */
+    @Excel(name = "未看数")
+    private  Integer notWatchedCount;
+
+    /**
+     * 未回答数
+     */
+    @Excel(name = "未答题人数")
+    private  Integer notAnsweredCount;
+
+    /**
+     * 回答状态
+     */
+    @Excel(name = "答题状态")
+    private  String answerStatus;
+
+    /**
+     * 领取红包金额
+     */
+    @Excel(name = "红包金额")
+    private  BigDecimal redPacketAmount;
+
+    /**
+     * 历史订单数
+     */
+    @Excel(name = "历史疗法订单数")
+    private  Integer historyOrderCount;
+
+    /**
+     * 销售id
+     */
+    private  Long companyUserId;
+
+    /**
+     * 总观看人数
+     */
+    private  Long totalLogCount;
+
+    /**
+     * 营期id
+     */
+    private  Long periodId;
+
+    /**
+     * 视频id
+     */
+    private  Long videoId;
+
+    /**
+     * 观看记录id
+     */
+    private  Long logId;
+
+    private  Long deptId;
+
+}

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

@@ -276,6 +276,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select company_id,company_name,money,red_package_money from company where is_del= 0
     </select>
 
+    <select id="selectAllCompanies" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT company_id as companyId, company_name as companyName FROM company WHERE status = 1
+        <if test="companyId != null">
+            and company_id = #{companyId}
+        </if>
+        GROUP BY company_id
+        ORDER BY company_id DESC
+    </select>
+
     <update id="batchUpdateCompany" parameterType="java.util.List">
         update company set money =
         <foreach collection="list" item="company"  index="index" separator=" " open="case company_id" close="end">

+ 2 - 0
fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml

@@ -57,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="questionJson != null">question_json,</if>
             <if test="watchLogId != null">watch_log_id,</if>
             <if test="periodId != null">period_id,</if>
+            <if test="watchType != null">watch_type,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -70,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="questionJson != null">#{questionJson},</if>
             <if test="watchLogId != null">#{watchLogId},</if>
             <if test="periodId != null">#{periodId},</if>
+            <if test="watchType != null">#{watchType},</if>
         </trim>
     </insert>
 

+ 194 - 1
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -495,7 +495,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     finish_time,
                     send_finish_msg,
                     camp_period_time,
-                    period_id
+                    period_id,
+                    watch_type
                 FROM
                     fs_course_watch_log
                 WHERE
@@ -1315,4 +1316,196 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </choose>
         ORDER BY accessCount desc
     </select>
+
+<!--    <select id="selectActiveUserIds" resultType="java.lang.Long">-->
+<!--        SELECT DISTINCT user_id-->
+<!--        FROM fs_course_watch_log-->
+<!--        WHERE user_id IN-->
+<!--        <foreach item="userId" collection="userIds" open="(" separator="," close=")">-->
+<!--            #{userId}-->
+<!--        </foreach>-->
+<!--        and send_type = 1-->
+<!--        and watch_type = 1-->
+<!--    </select>-->
+
+    <select id="selectAppWatchStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        company_id AS companyId,
+        COUNT(CASE WHEN log_type = 4 THEN log_id END) AS stopCount,
+        COUNT(CASE WHEN log_type = 3 THEN log_id END) AS pendingCount,
+        COUNT(CASE WHEN log_type = 1 THEN log_id END) AS watchingCount,
+        COUNT(CASE WHEN log_type = 2 THEN log_id END) AS finishedCount,
+        count(log_id) as accessCount
+        FROM fs_course_watch_log
+        <where>
+            watch_type =1 and send_type = 1
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                AND create_time &gt;= #{startDate} AND create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+            </if>
+            <if test="companyIds != null and companyIds.size() > 0">
+                AND company_id IN
+                <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+                    #{companyId}
+                </foreach>
+            </if>
+        </where>
+        GROUP BY company_id
+    </select>
+    <select id="selectAppAnswerStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        l.company_id AS companyId,
+        COUNT( l.log_id) AS answerUserCount
+        FROM fs_course_answer_logs l
+        WHERE l.company_id IN
+        <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+            #{companyId}
+        </foreach>
+        and l.watch_type=1
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND l.create_time &gt;= #{startDate} AND l.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        </if>
+        GROUP BY l.company_id
+    </select>
+    <select id="selectAppRedPacketStatistics" resultType="com.fs.his.vo.AppCourseReportVO">
+        SELECT
+        rpl.company_id AS companyId,
+        COUNT( rpl.log_id) AS packetUserCount,
+        COALESCE(SUM(rpl.amount), 0) AS packetAmount
+        FROM fs_course_red_packet_log rpl
+        WHERE rpl.company_id IN
+        <foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
+            #{companyId}
+        </foreach>
+        and rpl.app_id=#{appId}
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND rpl.create_time &gt;= #{startDate} AND rpl.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        </if>
+        GROUP BY rpl.company_id
+    </select>
+    <select id="selectAppUserBaseData" resultType="com.fs.his.vo.AppWatchLogReportVO">
+        SELECT
+        log.user_id userId,
+        u.nick_name AS nickName,
+        u.source loginChannel,
+        cu.nick_name AS salesName,
+        c.company_name AS salesCompany,
+        cd.dept_name AS salesDept,
+        log.period_id periodId,
+        log.video_id videoId,
+        log.log_id logId,
+        log.create_time courseTime,
+        log.finish_time finishTime,
+        log.duration privateWatchDuration,
+        log.log_type privateWatchStatus,
+        cv.title AS videoTitle
+        FROM
+        fs_course_watch_log log
+        LEFT JOIN fs_user u ON u.user_id = log.user_id
+        LEFT JOIN fs_user_company_user cuu ON cuu.user_id = u.user_id
+        LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id
+        LEFT JOIN company c ON log.company_id = c.company_id
+        LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
+        LEFT JOIN fs_user_course_video cv ON log.video_id = cv.video_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        group by log.user_id
+        ORDER BY u.register_date DESC
+    </select>
+    <select id="selectCampPeriodByPeriod" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        cp.period_id periodId,
+        cp.period_name periodName,
+        camp.training_camp_name
+        FROM
+        fs_user_course_period cp
+        LEFT JOIN fs_user_course_training_camp camp ON camp.training_camp_id = cp.training_camp_id
+        WHERE cp.period_id in
+        <foreach collection="periodIds" item="periodId" open="(" separator="," close=")">
+            #{periodId}
+        </foreach>
+    </select>
+    <select id="selectRedPacketStats" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        rp.watch_log_id  as  logId,
+        SUM(rp.amount) AS redPacketAmount
+        FROM fs_course_red_packet_log rp
+        WHERE  rp.watch_log_id IN
+        <foreach collection="logIds" item="logId" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+        GROUP BY rp.watch_log_id
+    </select>
+    <select id="selectAnswerStats" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        l.watch_log_id AS logId,
+        CASE WHEN l.log_id IS NOT NULL THEN '已答题' ELSE '未答题' END AS answerStatus,
+        (
+        SELECT COUNT(1)
+        FROM fs_course_watch_log wl
+        WHERE wl.log_id IN
+        <foreach collection="logIds" item="logId" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+        AND wl.log_id NOT IN (
+        SELECT watch_log_id
+        FROM fs_course_answer_logs
+        WHERE watch_log_id IS NOT NULL
+        )) AS notAnsweredCount
+        FROM fs_course_answer_logs l
+        WHERE l.watch_log_id IN
+        <foreach collection="logIds" item="logId" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+        GROUP BY l.watch_log_id
+    </select>
+
+    <sql id="commonConditions">
+        <!-- 销售公司 -->
+        <if test="companyId != null and companyId != ''">
+            AND c.company_id = #{companyId}
+        </if>
+
+        <!-- 销售部门 -->
+        <if test="deptId != null and deptId != ''">
+            AND cd.dept_id = #{deptId}
+        </if>
+
+        <!-- 所属销售 -->
+        <if test="salesId != null and salesId != ''">
+            AND cu.user_id = #{salesId}
+        </if>
+
+        <!-- 项目 -->
+        <if test="project != null and project != ''">
+            AND cuu.project_id = #{project}
+        </if>
+        <!-- 时间范围 -->
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND log.create_time &gt;= #{startDate} AND log.create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        </if>
+        <!-- 训练营 -->
+        <if test="trainingCampId != null and trainingCampId != ''">
+            AND log.period_id IN (SELECT period_id FROM fs_user_course_period WHERE training_camp_id = #{trainingCampId})
+        </if>
+        <!-- 营期 -->
+        <if test="periodId != null and periodId != ''">
+            AND log.period_id = #{periodId}
+        </if>
+
+        <!-- 会员ID -->
+        <if test="userId != null and userId != ''">
+            AND u.user_id = #{userId}
+        </if>
+
+        <!-- 会员手机号 -->
+        <if test="userPhone != null and userPhone != ''">
+            AND u.phone LIKE CONCAT('%', #{userPhone}, '%')
+        </if>
+
+        <!-- 会员昵称 -->
+        <if test="nickName != null and nickName != ''">
+            AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+        </if>
+    </sql>
 </mapper>

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

@@ -608,6 +608,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">app_id,</if>
             <if test="appOpenId != null">app_open_id,</if>
             <if test="appleKey != null">apple_key,</if>
+            <if test="appCreateTime != null">app_create_time,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="nickName != null">#{nickName},</if>
@@ -660,6 +661,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">#{appId},</if>
             <if test="appOpenId != null">#{appOpenId},</if>
             <if test="appleKey != null">#{appleKey},</if>
+            <if test="appCreateTime != null">#{appCreateTime},</if>
          </trim>
     </insert>
 
@@ -716,6 +718,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appOpenId != null">app_open_id = #{appOpenId},</if>
             <if test="appleKey != null">apple_key = #{appleKey},</if>
             <if test="rechargeBalance != null">recharge_balance = #{rechargeBalance},</if>
+            <if test="appCreateTime != null">app_create_time = #{appCreateTime},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -2461,5 +2464,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by user_id desc
     </select>
 
+    <select id="selectRegisterCount" resultType="java.util.Map">
+        SELECT
+        ucu.company_id AS companyId,
+        count(distinct ucu.user_id) AS registerCount
+        FROM fs_user u
+        left join fs_user_company_user ucu on u.user_id = ucu.user_id
+        WHERE u.status = 1
+        <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+            AND u.app_create_time &gt;=  #{startDate} AND  u.app_create_time &lt;= #{endDate}
+        </if>
+        <if test="companyId != null">
+            AND ucu.company_id = #{companyId}
+        </if>
+        group by ucu.company_id
+    </select>
+
 
 </mapper>

+ 17 - 7
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -310,6 +310,7 @@ public class AppLoginController extends AppBaseController{
                 // 新用户 - 添加 appId
                 user.setAppId(appId);
                 user.setCreateTime(new Date());
+                user.setAppCreateTime(new Date());
                 user.setStatus(1);
                 if (StringUtils.isNotEmpty(param.getJpushId())) {
                     user.setJpushId(param.getJpushId());
@@ -325,17 +326,23 @@ public class AppLoginController extends AppBaseController{
             } else {
                 // 老用户 - 检查并添加appId(不重复添加)
                 String updatedAppId = addAppIdIfNotExists(user.getAppId(), appId);
+
+                FsUser userMap = new FsUser();
+                userMap.setUserId(user.getUserId());
                 if (!updatedAppId.equals(user.getAppId())) {
-                    FsUser userMap = new FsUser();
-                    userMap.setUserId(user.getUserId());
                     userMap.setAppId(updatedAppId);
-                    userService.updateFsUser(userMap);
                 }
-
                 if (StringUtils.isNotEmpty(param.getJpushId())) {
-                    user.setAppOpenId(openid);
-                    updateExistingUserJpushId(user, param.getJpushId());
+                    userMap.setJpushId(param.getJpushId());
                 }
+                if (StringUtils.isNotEmpty(openid)) {
+                    userMap.setAppOpenId(openid);
+                }
+                if(user.getAppCreateTime()== null){
+                    userMap.setAppCreateTime(new Date());
+                }
+                userService.updateFsUser(userMap);
+
                 if (StringUtils.isEmpty(user.getPhone())) {
                     String token = jwtUtils.generateToken(user.getUserId());
                     redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);
@@ -621,10 +628,12 @@ public class AppLoginController extends AppBaseController{
                     keepUser.setSource(param.getSource() != null ? param.getSource() : null );
                     keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
                     keepUser.setNickName(nickname);
+                    keepUser.setIntegral(keepUser.getIntegral()+deleteUser.getIntegral());
                     keepUser.setAvatar(avatar);
                     keepUser.setSex(sex);
                     if (userService.updateFsUser(keepUser)>0){
-                        userService.realDeleteFsUserByUserId(deleteUser.getUserId());
+//                        userService.realDeleteFsUserByUserId(deleteUser.getUserId());
+                        userService.deleteFsUserByUserId(deleteUser.getUserId());
                         return generateTokenAndReturn(keepUser);
                     }
                     else {
@@ -900,6 +909,7 @@ public class AppLoginController extends AppBaseController{
         newUser.setPhone(param.getPhone());
         newUser.setCreateTime(new Date());
         newUser.setStatus(1);
+        newUser.setAppCreateTime(new Date());
         newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
         if (StringUtils.isNotEmpty(param.getJpushId())) {
             newUser.setJpushId(param.getJpushId());

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

@@ -219,6 +219,7 @@ public class IntegralController extends  AppBaseController {
         return R.ok("签到获得" + integral + "积分");
     }
 
+    // app 短视频获取积分
     @Login
     @ApiOperation("获取积分")
     @PostMapping("/addIntegral")