xgb пре 3 недеља
родитељ
комит
19a77b7a7a

+ 2 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java

@@ -76,4 +76,6 @@ public class FsCourseRedPacketLog extends BaseEntity
     //商户号
     private String mchId;
 
+    private Integer watchType;
+
 }

+ 165 - 7
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -10,6 +10,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.base.BusinessException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
 import com.fs.common.utils.date.DateUtil;
@@ -1889,23 +1890,180 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Override
     public List<AppSalesCourseStatisticsVO> selectAppSalesCourseStatisticsVO(FsCourseWatchLogStatisticsListParam param) {
 
-        // 课程统计
-        List<AppSalesCourseStatisticsVO> list = fsCourseWatchLogMapper.selectAppSalesCourseStatisticsVO(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());
+                }
+            }
+        }
 
-        // 答题统计
-        List<AppSalesCourseStatisticsVO> answerList = fsCourseAnswerLogsMapper.selectAppSalesAnswerStatisticsVO(param);
+        // 获取销售的 app会员数和 新注册的会员数
+        CompletableFuture<List<AppSalesCourseStatisticsVO>> appNewUserFuture = CompletableFuture.supplyAsync(() -> {
+            return userMapper.selectAppSalesNewUserCountVO(param);
+        });
 
-        // 红包统计
-        List<AppSalesCourseStatisticsVO> redPacketList = fsCourseRedPacketLogMapper.selectAppSalesRedPacketStatisticsVO(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("");
+        }
 
-        return Collections.emptyList();
+        if (vo.getVideoTitle() == null) {
+            vo.setVideoTitle("");
+        }
     }
 
+
+
     /**
      * 销售维度APP看课统计报表
      */

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

@@ -5407,6 +5407,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
                     redPacketLog.setPeriodId(log.getPeriodId());
                     redPacketLog.setAppId(packetParam.getAppId());
+                    redPacketLog.setWatchType(1);
 
                     redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
 

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

@@ -14,6 +14,7 @@ import com.fs.his.dto.AppUserCompanyDTO;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
 import com.fs.his.param.FsUserParam;
+import com.fs.his.vo.AppSalesCourseStatisticsVO;
 import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.OptionsVO;
@@ -488,4 +489,8 @@ public interface FsUserMapper
     List<Map<String, Object>>  selectRegisterCount(@Param("companyId") Long companyId,@Param("startDate") String startDate,@Param("endDate") String endDate);
 
     UserDetailsVO selectCountWatchCourse(@Param("userId") Long userId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag,@Param("userCompanyId")  Long userCompanyId);
+
+    List<AppSalesCourseStatisticsVO> selectAppSalesUserCountVO(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppSalesCourseStatisticsVO> selectAppSalesNewUserCountVO(FsCourseWatchLogStatisticsListParam param);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/vo/AppSalesCourseStatisticsVO.java

@@ -1,6 +1,7 @@
 package com.fs.his.vo;
 
 import com.fs.common.annotation.Excel;
+import lombok.Data;
 
 import java.math.BigDecimal;
 
@@ -10,6 +11,7 @@ import java.math.BigDecimal;
  * @createDate: 2026/3/23
  * @version: 1.0
  */
+@Data
 public class AppSalesCourseStatisticsVO {
         /** 销售名称 */
         @Excel(name = "销售名称")
@@ -67,9 +69,18 @@ public class AppSalesCourseStatisticsVO {
         @Excel(name = "完课率")
         private BigDecimal correctRate;
 
+        @Excel(name = "红包个数")
+        private Integer redPacketCount;
+
         /** 红包金额 */
         @Excel(name = "红包金额")
         private BigDecimal redPacketAmount;
 
+        private Long companyUserId;
+
+        private Long courseId;
+
+        private Long videoId;
+
     }
 

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

@@ -262,11 +262,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <select id="selectAppSalesAnswerStatisticsVO" resultType="com.fs.his.vo.AppSalesCourseStatisticsVO">
-        select cal.company_user_id,cal.course_id,cal.video_id
-        count(cal.log_id) AS correctCount,
+        select cal.company_user_id,cal.course_id,cal.video_id,
+        count(cal.log_id) AS answeredCount,
         SUM(CASE WHEN cal.is_right = 1 THEN 1 ELSE 0 END)  AS  correctCount
         from fs_course_answer_logs cal
-         <where>
+        where cal.watch_type = 1
             <if test="companyId != null ">
                 and cal.company_id = #{companyId}
             </if>
@@ -282,7 +282,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
                 and cal.create_time &gt;= #{startDate} and cal.create_time &lt;= #{endDate}
             </if>
-         </where>
-        group by company_user_id,course_id,vodio_id
+
+        group by cal.company_user_id,cal.course_id,cal.video_id
     </select>
 </mapper>

+ 23 - 0
fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml

@@ -116,6 +116,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="accBalanceBefore != null">acc_balance_before,</if>
             <if test="accBalanceAfter != null">acc_balance_after,</if>
             <if test="mchId != null">mch_id,</if>
+            <if test="watchType != null">watch_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="courseId != null">#{courseId},</if>
@@ -138,6 +139,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="accBalanceBefore != null">#{accBalanceBefore},</if>
             <if test="accBalanceAfter != null">#{accBalanceAfter},</if>
             <if test="mchId != null">#{mchId},</if>
+            <if test="watchType != null">#{watchType},</if>
         </trim>
     </insert>
 
@@ -287,7 +289,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         and create_time &lt;= CONCAT(CURDATE(), ' 23:59:59')
     </select>
     <select id="selectAppSalesRedPacketStatisticsVO" resultType="com.fs.his.vo.AppSalesCourseStatisticsVO">
+        select  rpl.company_user_id,rpl.course_id,rpl.video_id,
+                sum(rpl.log_id) as redPacketCount,
+                sum(rpl.amount) as redPacketAmount
+        from fs_course_red_packet_log rpl
+        where rpl.watch_type = 1
+            <if test="companyId != null ">
+                and rpl.company_id = #{companyId}
+            </if>
+            <if test="companyUserId != null ">
+                and rpl.company_user_id = #{companyUserId}
+            </if>
+            <if test="courseId != null ">
+                and rpl.course_id = #{courseId}
+            </if>
+            <if test="videoId != null ">
+                and rpl.video_id = #{videoId}
+            </if>
+            <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                and rpl.create_time &gt;= #{startDate} and rpl.create_time &lt;= #{endDate}
+            </if>
 
+        group by rpl.company_user_id,rpl.course_id,rpl.video_id
     </select>
 
 

+ 7 - 3
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -1747,12 +1747,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      <!-- 记录类型 1看课中 2完课 3待看课 4看课中断   -->
     <select id="selectAppSalesCourseStatisticsVO" resultType="com.fs.his.vo.AppSalesCourseStatisticsVO">
         SELECT l.company_user_id,l.course_id,l.video_id,
+        cu.nick_name as salesName,fuc.course_name as courseName,fuv.title as videoTitle,
         SUM(CASE WHEN l.log_type = 2 THEN 1 ELSE 0 END) AS finishedCount,
         SUM(CASE WHEN l.log_type = 3 THEN 1 ELSE 0 END) AS notWatchedCount,
         SUM(CASE WHEN l.log_type = 4 THEN 1 ELSE 0 END) AS interruptCount,
-        SUM(CASE WHEN l.log_type = 1 THEN 1 ELSE 0 END) AS watchingCount,
+        SUM(CASE WHEN l.log_type = 1 THEN 1 ELSE 0 END) AS watchingCount
         from fs_course_watch_log l
-        where send_type = 1
+        left join company_user cu on l.company_user_id=cu.user_id
+        left join fs_user_course fuc on l.course_id=fuc.course_id
+        left join fs_user_course_video fuv on l.video_id=fuv.video_id
+        where send_type = 1 and watch_type =1
             <if test="companyId != null ">
                 and l.company_id = #{companyId}
             </if>
@@ -1768,7 +1772,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
                 and l.create_time &gt;= #{startDate} and l.create_time &lt;= #{endDate}
             </if>
-        group by company_user_id,course_id,vodio_id
+        group by l.company_user_id,l.course_id,l.video_id
     </select>
 
 

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

@@ -2522,5 +2522,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY
         fs_course_watch_log.user_id
     </select>
+    <select id="selectAppSalesNewUserCountVO" resultType="com.fs.his.vo.AppSalesCourseStatisticsVO">
+        select count(distinct u.user_id) as newAppUserCount, u.company_user_id from fs_user u
+        left join fs_user_company_user ucu on u.user_id = ucu.user_id and ucu.status=1
+        where u.is_del = 0
+            <if test="companyId != null ">
+                and ucu.company_id = #{companyId}
+            </if>
+            <if test="companyUserId != null ">
+                and ucu.company_user_id = #{companyUserId}
+            </if>
+            <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>
+        group by ucu.company_user_id
+    </select>
+
+    <select id="selectAppSalesUserCountVO" resultType="com.fs.his.vo.AppSalesCourseStatisticsVO">
+        select count(distinct u.user_id) as appUserCount, u.company_user_id from fs_user u
+        left join fs_user_company_user ucu on u.user_id = ucu.user_id and ucu.status=1
+        where u.is_del = 0 and u.source is not null
+            <if test="companyId != null ">
+                and ucu.company_id = #{companyId}
+            </if>
+            <if test="companyUserId != null ">
+                and ucu.company_user_id = #{companyUserId}
+            </if>
+        group by ucu.company_user_id
+    </select>
 
 </mapper>