|
|
@@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.fs.common.core.domain.R;
|
|
|
import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.exception.base.BusinessException;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.fs.common.utils.DictUtils;
|
|
|
import com.fs.common.utils.date.DateUtil;
|
|
|
@@ -25,6 +26,11 @@ import com.fs.course.config.RedisKeyScanner;
|
|
|
import com.fs.course.domain.*;
|
|
|
import com.fs.course.mapper.*;
|
|
|
import com.fs.course.param.*;
|
|
|
+import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.vo.AppSalesCourseStatisticsVO;
|
|
|
+import com.fs.his.vo.AppSalesWatchLogReportVO;
|
|
|
+import com.fs.his.vo.AppWatchLogReportVO;
|
|
|
+import com.fs.his.vo.WatchLogReportVO;
|
|
|
import com.fs.hisStore.domain.FsStoreOrderScrm;
|
|
|
import com.fs.hisStore.dto.FsStoreCartDTO;
|
|
|
import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
|
|
|
@@ -187,6 +193,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
@Autowired
|
|
|
private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper userMapper;
|
|
|
+
|
|
|
/**
|
|
|
* 查询短链课程看课记录
|
|
|
*
|
|
|
@@ -1767,7 +1776,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
Long companyUserId = param.getCompanyUserId() != null ? param.getCompanyUserId() : null;
|
|
|
|
|
|
// 总体数据
|
|
|
-
|
|
|
+
|
|
|
// 1. 查询视频时长(只返回duration字段)
|
|
|
FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
|
|
|
vo.setVideoDuration(fsUserCourseVideo != null ? fsUserCourseVideo.getDuration() : 0L);
|
|
|
@@ -1778,11 +1787,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
// 2. 统计累计观看人数(对userId去重)
|
|
|
Long totalWatchCount = fsCourseWatchLogMapper.countDistinctWatchUsers(videoId, periodId,companyId,companyUserId);
|
|
|
vo.setTotalWatchCount(totalWatchCount != null ? totalWatchCount : 0L);
|
|
|
-
|
|
|
+
|
|
|
// 3. 统计累计完课人数(duration >= 1200秒,即20分钟,对userId去重)
|
|
|
Long totalCompleteCount = fsCourseWatchLogMapper.countDistinctCompleteUsers(videoId, periodId,companyId,companyUserId);
|
|
|
vo.setTotalCompleteCount(totalCompleteCount != null ? totalCompleteCount : 0L);
|
|
|
-
|
|
|
+
|
|
|
// 4. 计算到课完课率 = 累计完课人数 / 累计观看人数
|
|
|
BigDecimal completeRate = BigDecimal.ZERO;
|
|
|
if (vo.getTotalWatchCount() != null && vo.getTotalWatchCount() > 0) {
|
|
|
@@ -1908,7 +1917,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
vo.setProductList(productList);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
@@ -1928,6 +1937,413 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
return fsCourseWatchLogMapper.selectCourseStatisticsUserDetailExportList(param);
|
|
|
}
|
|
|
|
|
|
+ @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);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<AppSalesWatchLogReportVO> selectAppSalesWatchLogReportVO(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()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据维度选择查询方式
|
|
|
+ String dimension = param.getDimension();
|
|
|
+ if ("dept".equals(dimension)) {
|
|
|
+ return selectAppDeptWatchLogReportVO(param);
|
|
|
+ } else {
|
|
|
+ return selectAppSalesWatchLogReportVOBySales(param);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 销售维度APP看课统计报表
|
|
|
+ */
|
|
|
+ private List<AppSalesWatchLogReportVO> selectAppSalesWatchLogReportVOBySales(FsCourseWatchLogStatisticsListParam param) {
|
|
|
+ // 1. 批量查询统计数据
|
|
|
+ // APP会员数统计(直接查fs_user表,按销售ID分组)
|
|
|
+
|
|
|
+ // 基础数据+看课统计+答题统计+红包统计(合并查询,按销售ID+营期ID+视频ID分组)
|
|
|
+ List<AppSalesWatchLogReportVO> watchStatsList = fsCourseWatchLogMapper.selectAppSalesWatchStats(param);
|
|
|
+ if (CollectionUtils.isEmpty(watchStatsList)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<AppSalesWatchLogReportVO> userStatsList = fsCourseWatchLogMapper.selectAppSalesUserStats(param);
|
|
|
+ // 订单统计
|
|
|
+// List<AppSalesWatchLogReportVO> orderStatsList = fsCourseWatchLogMapper.selectAppSalesOrderStats(param);
|
|
|
+
|
|
|
+ // 2. 查询营期信息
|
|
|
+ List<Long> periodIds = watchStatsList.stream()
|
|
|
+ .map(AppSalesWatchLogReportVO::getPeriodId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<AppSalesWatchLogReportVO> campPeriodList = fsCourseWatchLogMapper.selectAppSalesCampPeriod(periodIds);
|
|
|
+
|
|
|
+ // 3. 转换为Map便于查找
|
|
|
+ // APP会员数统计按销售ID分组
|
|
|
+ Map<Long, AppSalesWatchLogReportVO> userStatsMap = userStatsList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesWatchLogReportVO::getSalesId,
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+ // 看课统计(已包含基础数据、答题、红包)按销售ID+营期ID+视频ID分组
|
|
|
+ Map<String, AppSalesWatchLogReportVO> watchStatsMap = watchStatsList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getSalesId() + "_" + item.getPeriodId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+ // 订单统计按销售ID+营期ID+视频ID分组
|
|
|
+// Map<String, AppSalesWatchLogReportVO> orderStatsMap = orderStatsList.stream()
|
|
|
+// .collect(Collectors.toMap(
|
|
|
+// item -> item.getSalesId() + "_" + item.getPeriodId() + "_" + item.getVideoId(),
|
|
|
+// Function.identity(),
|
|
|
+// (e, r) -> e
|
|
|
+// ));
|
|
|
+ Map<Long, AppSalesWatchLogReportVO> campPeriodMap = campPeriodList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesWatchLogReportVO::getPeriodId,
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 4. 组装数据
|
|
|
+ for (AppSalesWatchLogReportVO vo : watchStatsList) {
|
|
|
+ String key = vo.getSalesId() + "_" + vo.getPeriodId() + "_" + vo.getVideoId();
|
|
|
+
|
|
|
+ // APP会员数统计(按销售ID)
|
|
|
+ AppSalesWatchLogReportVO userStats = userStatsMap.get(vo.getSalesId());
|
|
|
+ if (userStats != null) {
|
|
|
+ vo.setAppUserCount(userStats.getAppUserCount());
|
|
|
+ vo.setNewAppUserCount(userStats.getNewAppUserCount());
|
|
|
+ }else {
|
|
|
+ vo.setAppUserCount(0);
|
|
|
+ vo.setNewAppUserCount(0);
|
|
|
+ }
|
|
|
+ // 计算完课率 = 完课数 / (完课数 + 未完课数 + 未看数) * 100%
|
|
|
+ int total = vo.getFinishedCount() + vo.getUnfinishedCount() + vo.getNotWatchedCount();
|
|
|
+ if (total > 0) {
|
|
|
+ vo.setCompletionRate(BigDecimal.valueOf(vo.getFinishedCount())
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setCompletionRate(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 订单统计
|
|
|
+// AppSalesWatchLogReportVO orderStats = orderStatsMap.get(key);
|
|
|
+// if (orderStats != null) {
|
|
|
+// vo.setHistoryOrderCount(orderStats.getHistoryOrderCount());
|
|
|
+// }
|
|
|
+
|
|
|
+ // 营期信息
|
|
|
+ AppSalesWatchLogReportVO campPeriod = campPeriodMap.get(vo.getPeriodId());
|
|
|
+ if (campPeriod != null) {
|
|
|
+ vo.setPeriodName(campPeriod.getPeriodName());
|
|
|
+ vo.setTrainingCampName(campPeriod.getTrainingCampName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return watchStatsList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 销售部门维度APP看课统计报表
|
|
|
+ */
|
|
|
+ private List<AppSalesWatchLogReportVO> selectAppDeptWatchLogReportVO(FsCourseWatchLogStatisticsListParam param) {
|
|
|
+ // 1. 批量查询统计数据
|
|
|
+ // APP会员数统计(直接查fs_user表,按部门ID分组)
|
|
|
+ List<AppSalesWatchLogReportVO> userStatsList = fsCourseWatchLogMapper.selectAppDeptUserStats(param);
|
|
|
+ // 基础数据+看课统计+答题统计+红包统计(合并查询,按部门ID+营期ID+视频ID分组)
|
|
|
+ List<AppSalesWatchLogReportVO> watchStatsList = fsCourseWatchLogMapper.selectAppDeptWatchStats(param);
|
|
|
+ if (CollectionUtils.isEmpty(watchStatsList)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ // 订单统计
|
|
|
+ List<AppSalesWatchLogReportVO> orderStatsList = fsCourseWatchLogMapper.selectAppDeptOrderStats(param);
|
|
|
+
|
|
|
+ // 2. 查询营期信息
|
|
|
+ List<Long> periodIds = watchStatsList.stream()
|
|
|
+ .map(AppSalesWatchLogReportVO::getPeriodId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<AppSalesWatchLogReportVO> campPeriodList = fsCourseWatchLogMapper.selectAppSalesCampPeriod(periodIds);
|
|
|
+
|
|
|
+ // 3. 转换为Map便于查找
|
|
|
+ // APP会员数统计按部门ID分组
|
|
|
+ Map<Long, AppSalesWatchLogReportVO> userStatsMap = userStatsList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesWatchLogReportVO::getDeptId,
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+ // 看课统计(已包含基础数据、答题、红包)按部门ID+营期ID+视频ID分组
|
|
|
+ Map<String, AppSalesWatchLogReportVO> watchStatsMap = watchStatsList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getDeptId() + "_" + item.getPeriodId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+ // 订单统计按部门ID+营期ID+视频ID分组
|
|
|
+ Map<String, AppSalesWatchLogReportVO> orderStatsMap = orderStatsList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getDeptId() + "_" + item.getPeriodId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+ Map<Long, AppSalesWatchLogReportVO> campPeriodMap = campPeriodList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesWatchLogReportVO::getPeriodId,
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 4. 组装数据
|
|
|
+ for (AppSalesWatchLogReportVO vo : watchStatsList) {
|
|
|
+ String key = vo.getDeptId() + "_" + vo.getPeriodId() + "_" + vo.getVideoId();
|
|
|
+
|
|
|
+ // APP会员数统计(按部门ID)
|
|
|
+ AppSalesWatchLogReportVO userStats = userStatsMap.get(vo.getDeptId());
|
|
|
+ if (userStats != null) {
|
|
|
+ vo.setAppUserCount(userStats.getAppUserCount());
|
|
|
+ vo.setNewAppUserCount(userStats.getNewAppUserCount());
|
|
|
+ vo.setSalesCount(userStats.getSalesCount()); // 销售数(部门维度特有)
|
|
|
+ }else {
|
|
|
+ vo.setAppUserCount(0);
|
|
|
+ vo.setNewAppUserCount(0);
|
|
|
+ vo.setSalesCount(0);
|
|
|
+ }
|
|
|
+ // 计算完课率 = 完课数 / (完课数 + 未完课数 + 未看数) * 100%
|
|
|
+ int total = vo.getFinishedCount() + vo.getUnfinishedCount() + vo.getNotWatchedCount();
|
|
|
+ if (total > 0) {
|
|
|
+ vo.setCompletionRate(BigDecimal.valueOf(vo.getFinishedCount())
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setCompletionRate(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 订单统计
|
|
|
+ AppSalesWatchLogReportVO orderStats = orderStatsMap.get(key);
|
|
|
+ if (orderStats != null) {
|
|
|
+ vo.setHistoryOrderCount(orderStats.getHistoryOrderCount());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 营期信息
|
|
|
+ AppSalesWatchLogReportVO campPeriod = campPeriodMap.get(vo.getPeriodId());
|
|
|
+ if (campPeriod != null) {
|
|
|
+ vo.setPeriodName(campPeriod.getPeriodName());
|
|
|
+ vo.setTrainingCampName(campPeriod.getTrainingCampName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return watchStatsList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description: app 看课统计
|
|
|
+ * @Param:
|
|
|
+ * @Return:
|
|
|
+ * @Author xgb
|
|
|
+ * @Date 2026/3/23 16:10
|
|
|
+ */
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<AppSalesCourseStatisticsVO> selectAppSalesCourseStatisticsVO(FsCourseWatchLogStatisticsListParam param) {
|
|
|
+
|
|
|
+ // 校验时间为必输字段而且不能大于一个月
|
|
|
+ if (StringUtils.isEmpty(param.getStartDate()) || StringUtils.isEmpty(param.getEndDate())) {
|
|
|
+ throw new BusinessException("请选择时间");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 看课记录
|
|
|
+ CompletableFuture<List<AppSalesCourseStatisticsVO>> watchFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ return fsCourseWatchLogMapper.selectAppSalesCourseStatisticsVO(param);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 答题记录
|
|
|
+ CompletableFuture<List<AppSalesCourseStatisticsVO>> answerFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ return fsCourseAnswerLogsMapper.selectAppSalesAnswerStatisticsVO(param);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 红包记录
|
|
|
+ CompletableFuture<List<AppSalesCourseStatisticsVO>> redPacketFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ return fsCourseRedPacketLogMapper.selectAppSalesRedPacketStatisticsVO(param);
|
|
|
+ });
|
|
|
+
|
|
|
+ CompletableFuture.allOf(watchFuture, answerFuture, redPacketFuture).join();
|
|
|
+
|
|
|
+ List<AppSalesCourseStatisticsVO> list = watchFuture.join();
|
|
|
+ if (CollectionUtils.isEmpty(list)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 整合数据
|
|
|
+ List<AppSalesCourseStatisticsVO> answerList = answerFuture.join();
|
|
|
+ List<AppSalesCourseStatisticsVO> redPacketList = redPacketFuture.join();
|
|
|
+
|
|
|
+ Map<String, AppSalesCourseStatisticsVO> dataMap = list.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getCompanyUserId() + "_" + item.getCourseId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(answerList)) {
|
|
|
+ Map<String, AppSalesCourseStatisticsVO> answerMap = answerList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getCompanyUserId() + "_" + item.getCourseId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ for (Map.Entry<String, AppSalesCourseStatisticsVO> entry : dataMap.entrySet()) {
|
|
|
+ AppSalesCourseStatisticsVO vo = entry.getValue();
|
|
|
+ AppSalesCourseStatisticsVO answerVO = answerMap.get(entry.getKey());
|
|
|
+ if (answerVO != null) {
|
|
|
+ vo.setAnsweredCount(answerVO.getAnsweredCount());
|
|
|
+ vo.setCorrectCount(answerVO.getCorrectCount());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(redPacketList)) {
|
|
|
+ Map<String, AppSalesCourseStatisticsVO> redPacketMap = redPacketList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ item -> item.getCompanyUserId() + "_" + item.getCourseId() + "_" + item.getVideoId(),
|
|
|
+ Function.identity(),
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ for (Map.Entry<String, AppSalesCourseStatisticsVO> entry : dataMap.entrySet()) {
|
|
|
+ AppSalesCourseStatisticsVO vo = entry.getValue();
|
|
|
+ AppSalesCourseStatisticsVO redPacketVO = redPacketMap.get(entry.getKey());
|
|
|
+ if (redPacketVO != null) {
|
|
|
+ vo.setRedPacketAmount(redPacketVO.getRedPacketAmount());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取销售的 app会员数和 新注册的会员数
|
|
|
+ CompletableFuture<List<AppSalesCourseStatisticsVO>> appNewUserFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ return userMapper.selectAppSalesNewUserCountVO(param);
|
|
|
+ });
|
|
|
+
|
|
|
+ CompletableFuture<List<AppSalesCourseStatisticsVO>> appUserFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ return userMapper.selectAppSalesUserCountVO(param);
|
|
|
+ });
|
|
|
+
|
|
|
+ CompletableFuture.allOf(appNewUserFuture, appUserFuture).join();
|
|
|
+
|
|
|
+ // 整合数据
|
|
|
+ List<AppSalesCourseStatisticsVO> appNewUserCountList = appNewUserFuture.join();
|
|
|
+ List<AppSalesCourseStatisticsVO> appUserCountList = appUserFuture.join();
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(appNewUserCountList)) {
|
|
|
+ Map<Long, Long> newUserCountMap = appNewUserCountList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesCourseStatisticsVO::getCompanyUserId,
|
|
|
+ AppSalesCourseStatisticsVO::getNewAppUserCount,
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ for (AppSalesCourseStatisticsVO vo : dataMap.values()) {
|
|
|
+ Long newAppUserCount = newUserCountMap.get(vo.getCompanyUserId());
|
|
|
+ if (newAppUserCount != null) {
|
|
|
+ vo.setNewAppUserCount(newAppUserCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(appUserCountList)) {
|
|
|
+ Map<Long, Long> userCountMap = appUserCountList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ AppSalesCourseStatisticsVO::getCompanyUserId,
|
|
|
+ AppSalesCourseStatisticsVO::getAppUserCount,
|
|
|
+ (e, r) -> e
|
|
|
+ ));
|
|
|
+
|
|
|
+ for (AppSalesCourseStatisticsVO vo : dataMap.values()) {
|
|
|
+ Long appUserCount = userCountMap.get(vo.getCompanyUserId());
|
|
|
+ if (appUserCount != null) {
|
|
|
+ vo.setAppUserCount(appUserCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<AppSalesCourseStatisticsVO> resultList = new ArrayList<>(dataMap.values());
|
|
|
+ resultList.forEach(this::setDefaultValues);
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setDefaultValues(AppSalesCourseStatisticsVO vo) {
|
|
|
+ vo.setFinishedCount(vo.getFinishedCount() != null ? vo.getFinishedCount() : 0);
|
|
|
+ vo.setNotWatchedCount(vo.getNotWatchedCount() != null ? vo.getNotWatchedCount() : 0);
|
|
|
+ vo.setInterruptCount(vo.getInterruptCount() != null ? vo.getInterruptCount() : 0);
|
|
|
+ vo.setWatchingCount(vo.getWatchingCount() != null ? vo.getWatchingCount() : 0);
|
|
|
+ vo.setAnsweredCount(vo.getAnsweredCount() != null ? vo.getAnsweredCount() : 0);
|
|
|
+ vo.setCorrectCount(vo.getCorrectCount() != null ? vo.getCorrectCount() : 0);
|
|
|
+ vo.setNewAppUserCount(vo.getNewAppUserCount() != null ? vo.getNewAppUserCount() : 0L);
|
|
|
+ vo.setAppUserCount(vo.getAppUserCount() != null ? vo.getAppUserCount() : 0L);
|
|
|
+ vo.setRedPacketCount(vo.getRedPacketCount() != null ? vo.getRedPacketCount() : 0);
|
|
|
+
|
|
|
+ if (vo.getRedPacketAmount() == null) {
|
|
|
+ vo.setRedPacketAmount(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算完课率 = 完课数 / (完课数 + 未完课数 + 中断数 + 看课中数) * 100%,保留两位小数
|
|
|
+ int total = vo.getFinishedCount() + vo.getNotWatchedCount() + vo.getInterruptCount() + vo.getWatchingCount();
|
|
|
+ if (total > 0) {
|
|
|
+ BigDecimal finished = new BigDecimal(vo.getFinishedCount());
|
|
|
+ BigDecimal totalCount = new BigDecimal(total);
|
|
|
+ vo.setCompletionRate(finished.divide(totalCount, 4, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setCompletionRate(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+ // 计算一下答题正确率
|
|
|
+ if (vo.getAnsweredCount() > 0) {
|
|
|
+ BigDecimal answered = new BigDecimal(vo.getAnsweredCount());
|
|
|
+ BigDecimal correct = new BigDecimal(vo.getCorrectCount());
|
|
|
+ vo.setCorrectRate(correct.divide(answered, 4, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setCorrectRate(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (vo.getSalesName() == null) {
|
|
|
+ vo.setSalesName("");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (vo.getCourseName() == null) {
|
|
|
+ vo.setCourseName("");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (vo.getVideoTitle() == null) {
|
|
|
+ vo.setVideoTitle("");
|
|
|
+ }
|
|
|
+ }
|
|
|
/**
|
|
|
* 从 Map 中安全获取 Long 值,兼容 MyBatis 返回的驼峰/小写键名
|
|
|
*/
|
|
|
@@ -1940,4 +2356,124 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
|
|
|
try { return Long.parseLong(String.valueOf(v)); } catch (NumberFormatException e) { return null; }
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装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> 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 // 当出现重复键时,保留第一个值
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 红包数据转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 // 当出现重复键时,保留第一个值
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
}
|