Bladeren bron

代码提交

xgb 3 weken geleden
bovenliggende
commit
e2fe082ad7

+ 60 - 1
fs-company/src/main/java/com/fs/app/controller/statistic/courseStatisticController.java

@@ -1,9 +1,11 @@
 package com.fs.app.controller.statistic;
 
 import com.fs.common.annotation.Excel;
+import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -12,6 +14,7 @@ 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.AppSalesWatchLogReportVO;
 import com.fs.his.vo.AppWatchLogReportVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -45,7 +48,7 @@ public class courseStatisticController extends BaseController {
      * @return
      */
     @GetMapping("/appWatchLogReport")
-    public TableDataInfo aPPWatchLogReport(FsCourseWatchLogStatisticsListParam param) {
+    public TableDataInfo appWatchLogReport(FsCourseWatchLogStatisticsListParam param) {
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId(loginUser.getCompany().getCompanyId());
@@ -102,5 +105,61 @@ public class courseStatisticController extends BaseController {
                 .collect(Collectors.toList());
     }
 
+    /**
+     * APP端销售维度看课统计报表
+     * 注意:必须放在 /{logId} 之前,避免路径冲突
+     */
+//    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:appSalesReport')")
+    @GetMapping("/appSalesWatchLogReport")
+    public TableDataInfo appSalesWatchLogReport(FsCourseWatchLogStatisticsListParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AppSalesWatchLogReportVO> list = courseWatchLogService.selectAppSalesWatchLogReportVO(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出APP端销售维度看课统计报表
+     */
+    @Log(title = "APP销售维度看课统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/appSalesWatchLogReportExport")
+    public AjaxResult appSalesWatchLogReportExport(FsCourseWatchLogStatisticsListParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AppSalesWatchLogReportVO> list = courseWatchLogService.selectAppSalesWatchLogReportVO(param);
+        // 根据维度获取需要导出的字段
+        List<String> selectedFields = getAppSalesWatchLogReportFields(param.getDimension());
+        ExcelUtil<AppSalesWatchLogReportVO> util = new ExcelUtil<AppSalesWatchLogReportVO>(AppSalesWatchLogReportVO.class);
+        return util.exportExcelSelectedColumns(list, "APP销售维度看课统计报表", selectedFields);
+    }
+
+
+    /**
+     * 获取AppSalesWatchLogReportVO需要导出的字段
+     * @param dimension 维度:sales-销售维度, dept-销售部门维度
+     */
+    private List<String> getAppSalesWatchLogReportFields(String dimension) {
+        // 销售维度字段
+        List<String> salesFields = Arrays.asList(
+                "salesName", "appUserCount", "newAppUserCount", "salesDept",
+                "salesCompany", "trainingCampName", "periodName", "videoTitle",
+                "finishedCount", "unfinishedCount", "completionRate",
+                "notWatchedCount", "notAnsweredCount", "redPacketAmount", "historyOrderCount"
+        );
+
+        // 销售部门维度字段(去掉销售列,销售数放在销售部门后面)
+        List<String> deptFields = Arrays.asList(
+                "salesDept", "salesCount", "appUserCount", "newAppUserCount",
+                "salesCompany", "trainingCampName", "periodName", "videoTitle",
+                "finishedCount", "unfinishedCount", "completionRate",
+                "notWatchedCount", "notAnsweredCount", "redPacketAmount", "historyOrderCount"
+        );
+
+        return "dept".equals(dimension) ? deptFields : salesFields;
+    }
+
 
 }

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

@@ -55,4 +55,17 @@ public class FsUserCourseTrainingCampController {
         return AjaxResult.success(new PageInfo<>(list));
     }
 
+    @GetMapping("/getCampListLikeName")
+    public R getCampListLikeName(@RequestParam(required = false) String name,
+                                 @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                 @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("name", name);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<OptionsVO> campList = fsUserCourseTrainingCampService.selectCampListByMap(params);
+        return R.ok().put("data", new PageInfo<>(campList));
+
+    }
+
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/dto/BatchSendCourseDTO.java

@@ -81,4 +81,7 @@ public class BatchSendCourseDTO implements Serializable {
 
     // 看课链接主键
     private Long linkId;
+
+    // im发送主表记录id
+    private Long logId;
 }

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

@@ -6,6 +6,7 @@ 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.AppSalesWatchLogReportVO;
 import com.fs.his.vo.AppWatchLogReportVO;
 import com.fs.his.vo.WatchLogReportVO;
 import com.fs.qw.domain.QwExternalContact;
@@ -776,4 +777,35 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      */
     List<WatchLogReportVO> selectAnswerStats(@Param("logIds") List<Long> logIds);
 
+    /**
+     * 销售维度APP会员数统计
+     */
+    List<AppSalesWatchLogReportVO> selectAppSalesUserStats(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售维度基础数据+看课统计(合并查询)
+     */
+    List<AppSalesWatchLogReportVO> selectAppSalesWatchStats(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售维度订单统计
+     */
+    List<AppSalesWatchLogReportVO> selectAppSalesOrderStats(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppSalesWatchLogReportVO> selectAppSalesCampPeriod(@Param("periodIds") List<Long> periodIds);
+
+    /**
+     * 销售部门维度APP会员数统计
+     */
+    List<AppSalesWatchLogReportVO> selectAppDeptUserStats(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售部门维度基础数据+看课统计(合并查询)
+     */
+    List<AppSalesWatchLogReportVO> selectAppDeptWatchStats(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售部门维度订单统计
+     */
+    List<AppSalesWatchLogReportVO> selectAppDeptOrderStats(FsCourseWatchLogStatisticsListParam param);
 }

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

@@ -88,4 +88,53 @@ public class FsCourseWatchLogStatisticsListParam {
      * 手机号
      */
     private  String userPhone;
+
+
+    /**
+     * 营期id
+     */
+    private  Long periodId;
+
+
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+
+    /**
+     * 销售
+     */
+    private  Long salesId;
+
+    /**
+     * 营期ids
+     */
+    private List<Long> periodIds;
+
+    /**
+     * 订单开始时间
+     */
+    private  Date orderSTime;
+
+    /**
+     * 订单结束时间
+     */
+    private  Date orderETime;
+
+    private  List<Long> deptIds;
+
+    /**
+     * 看课方式:1 app  2 小程序
+     */
+    private  Integer watchType;
+
+    private  List<Long> logIds;
+
+    /**
+     * 训练营id
+     */
+    private Long  trainingCampId;
+
 }

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

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
 import com.fs.his.vo.AppCourseReportVO;
+import com.fs.his.vo.AppSalesWatchLogReportVO;
 import com.fs.his.vo.AppWatchLogReportVO;
 import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
@@ -179,4 +180,6 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     List<AppCourseReportVO> selectAppCourseReportVO(FsCourseWatchLogStatisticsListParam param);
 
     List<AppWatchLogReportVO> selectUserAppWatchLogReportVO(FsCourseWatchLogStatisticsListParam param);
+
+    List<AppSalesWatchLogReportVO> selectAppSalesWatchLogReportVO(FsCourseWatchLogStatisticsListParam param);
 }

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

@@ -38,6 +38,7 @@ 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.AppSalesWatchLogReportVO;
 import com.fs.his.vo.AppWatchLogReportVO;
 import com.fs.his.vo.WatchLogReportVO;
 import com.fs.qw.Bean.MsgBean;
@@ -1854,6 +1855,209 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         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;
+    }
+
     /**
      * 组装APP统计数据
      */

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

@@ -209,8 +209,9 @@ public class FsUser extends BaseEntity
     {
         if(StringUtils.isNotEmpty(nickName)){
             return EmojiParser.parseToUnicode(nickName);
-        }
-        else{
+        }else if(StringUtils.isNotEmpty(nickname)){
+            return EmojiParser.parseToUnicode(nickname);
+        } else{
             return nickName;
         }
     }

+ 89 - 0
fs-service/src/main/java/com/fs/his/vo/AppSalesWatchLogReportVO.java

@@ -0,0 +1,89 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class AppSalesWatchLogReportVO {
+
+    /** 销售ID */
+    private Long salesId;
+
+    /** 销售名称 */
+    @Excel(name = "销售")
+    private String salesName;
+
+    /** APP会员数 */
+    @Excel(name = "APP会员数")
+    private Integer appUserCount;
+
+    /** 新注册APP会员数 */
+    @Excel(name = "新注册APP会员数")
+    private Integer newAppUserCount;
+
+    /** 所属销售部门 */
+    @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 Integer finishedCount;
+
+    /** 未完课数 */
+    @Excel(name = "未完课数")
+    private Integer unfinishedCount;
+
+    /** 完课率 */
+    @Excel(name = "完课率")
+    private BigDecimal completionRate;
+
+    /** 未看数 */
+    @Excel(name = "未看数")
+    private Integer notWatchedCount;
+
+    /** 未答题数 */
+    @Excel(name = "未答题数")
+    private Integer notAnsweredCount;
+
+    /** 红包金额 */
+    @Excel(name = "红包金额")
+    private BigDecimal redPacketAmount;
+
+    /** 历史疗法订单数 */
+    @Excel(name = "历史疗法订单数")
+    private Integer historyOrderCount;
+
+    /** 销售数(部门维度特有) */
+    @Excel(name = "销售数")
+    private Integer salesCount;
+
+    /** 营期ID */
+    private Long periodId;
+
+    /** 视频ID */
+    private Long videoId;
+
+    /** 部门ID */
+    private Long deptId;
+
+    /** 公司ID */
+    private Long companyId;
+}

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

@@ -1459,6 +1459,239 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
         GROUP BY l.watch_log_id
     </select>
+    <!-- 销售维度订单统计 -->
+    <select id="selectAppSalesOrderStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cu.user_id AS salesId,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        COUNT(DISTINCT CASE WHEN po.status = 3 THEN po.order_id END) AS historyOrderCount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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_package_order po ON po.user_id = log.user_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cu.user_id, log.period_id, log.video_id
+    </select>
+
+    <!-- 销售维度基础数据+看课统计(合并查询) -->
+    <select id="selectAppSalesWatchStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cu.user_id AS salesId,
+        cu.nick_name AS salesName,
+        cd.dept_name AS salesDept,
+        c.company_name AS salesCompany,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        cv.title AS videoTitle,
+        cd.dept_id AS deptId,
+        c.company_id AS companyId,
+        COUNT(DISTINCT CASE WHEN log.log_type = '2' THEN log.log_id END) AS finishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '1' THEN log.log_id END) AS unfinishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '3' THEN log.log_id END) AS notWatchedCount,
+        COUNT(DISTINCT CASE WHEN a.log_id IS NULL THEN log.log_id END) AS notAnsweredCount,
+        COALESCE(SUM(rpl.amount), 0) AS redPacketAmount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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
+        LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = log.log_id
+        LEFT JOIN fs_course_red_packet_log rpl ON rpl.watch_log_id = log.log_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cu.user_id, log.period_id, log.video_id
+        ORDER BY cu.user_id
+    </select>
+
+    <!-- 销售维度基础数据+看课统计(合并查询) -->
+    <select id="selectAppSalesWatchStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cu.user_id AS salesId,
+        cu.nick_name AS salesName,
+        cd.dept_name AS salesDept,
+        c.company_name AS salesCompany,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        cv.title AS videoTitle,
+        cd.dept_id AS deptId,
+        c.company_id AS companyId,
+        COUNT(DISTINCT CASE WHEN log.log_type = '2' THEN log.log_id END) AS finishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '1' THEN log.log_id END) AS unfinishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '3' THEN log.log_id END) AS notWatchedCount,
+        COUNT(DISTINCT CASE WHEN a.log_id IS NULL THEN log.log_id END) AS notAnsweredCount,
+        COALESCE(SUM(rpl.amount), 0) AS redPacketAmount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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
+        LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = log.log_id
+        LEFT JOIN fs_course_red_packet_log rpl ON rpl.watch_log_id = log.log_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cu.user_id, log.period_id, log.video_id
+        ORDER BY cu.user_id
+    </select>
+
+
+
+
+
+
+
+    <!-- 销售维度订单统计 -->
+    <select id="selectAppSalesOrderStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cu.user_id AS salesId,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        COUNT(DISTINCT CASE WHEN po.status = 3 THEN po.order_id END) AS historyOrderCount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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_package_order po ON po.user_id = log.user_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cu.user_id, log.period_id, log.video_id
+    </select>
+
+    <!-- 销售维度营期信息 -->
+    <select id="selectAppSalesCampPeriod" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cp.period_id periodId,
+        cp.period_name periodName,
+        camp.training_camp_name trainingCampName
+        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>
+
+    <!-- 销售部门维度APP会员数统计 -->
+    <select id="selectAppDeptUserStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cd.dept_id AS deptId,
+        COUNT(DISTINCT CASE WHEN u.source IS NOT NULL THEN u.user_id END) AS appUserCount,
+        <choose>
+            <when test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                COUNT(DISTINCT CASE WHEN u.source IS NOT NULL AND u.register_date &gt;= #{startDate} AND u.register_date &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY) THEN u.user_id END)
+            </when>
+            <otherwise>
+                COUNT(DISTINCT CASE WHEN u.source IS NOT NULL THEN u.user_id END)
+            </otherwise>
+        </choose> AS newAppUserCount,
+        COUNT(DISTINCT cu.user_id) AS salesCount
+        FROM fs_user u
+        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 cuu.company_id = c.company_id
+        LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
+        WHERE u.source IS NOT NULL
+        <if test="companyId != null and companyId != ''">
+            AND cuu.company_id = #{companyId}
+        </if>
+        <if test="deptId != null and deptId != ''">
+            AND cu.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>
+        GROUP BY cd.dept_id
+    </select>
+
+    <!-- 销售部门维度基础数据+看课统计(合并查询) -->
+    <select id="selectAppDeptWatchStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cd.dept_id AS deptId,
+        cd.dept_name AS salesDept,
+        c.company_name AS salesCompany,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        cv.title AS videoTitle,
+        c.company_id AS companyId,
+        COUNT(DISTINCT CASE WHEN log.log_type = '2' THEN log.log_id END) AS finishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '1' THEN log.log_id END) AS unfinishedCount,
+        COUNT(DISTINCT CASE WHEN log.log_type = '3' THEN log.log_id END) AS notWatchedCount,
+        COUNT(DISTINCT CASE WHEN a.log_id IS NULL THEN log.log_id END) AS notAnsweredCount,
+        COALESCE(SUM(rpl.amount), 0) AS redPacketAmount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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
+        LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = log.log_id
+        LEFT JOIN fs_course_red_packet_log rpl ON rpl.watch_log_id = log.log_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cd.dept_id, log.period_id, log.video_id
+        ORDER BY cd.dept_id
+    </select>
+
+    <!-- 销售部门维度订单统计 -->
+    <select id="selectAppDeptOrderStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cd.dept_id AS deptId,
+        log.period_id AS periodId,
+        log.video_id AS videoId,
+        COUNT(DISTINCT CASE WHEN po.status = 3 THEN po.order_id END) AS historyOrderCount
+        FROM fs_course_watch_log log
+        LEFT JOIN company_user cu ON log.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_package_order po ON po.user_id = log.user_id
+        WHERE log.send_type = 1
+        AND log.watch_type = 1
+        <include refid="commonConditions"/>
+        GROUP BY cd.dept_id, log.period_id, log.video_id
+    </select>
+    <!-- 销售维度APP会员数统计(直接查fs_user表) -->
+    <select id="selectAppSalesUserStats" resultType="com.fs.his.vo.AppSalesWatchLogReportVO">
+        SELECT
+        cu.user_id AS salesId,
+        COUNT(DISTINCT CASE WHEN u.source IS NOT NULL THEN u.user_id END) AS appUserCount,
+        <choose>
+            <when test="startDate != null and startDate != '' and endDate != null and endDate != ''">
+                COUNT(DISTINCT CASE WHEN u.source IS NOT NULL AND u.register_date &gt;= #{startDate} AND u.register_date &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY) THEN u.user_id END)
+            </when>
+            <otherwise>
+                COUNT(DISTINCT CASE WHEN u.source IS NOT NULL THEN u.user_id END)
+            </otherwise>
+        </choose> AS newAppUserCount
+        FROM fs_user u
+        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 cuu.company_id = c.company_id
+        LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
+        WHERE u.source IS NOT NULL
+        <if test="companyId != null and companyId != ''">
+            AND cuu.company_id = #{companyId}
+        </if>
+        <if test="deptId != null and deptId != ''">
+            AND cu.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>
+        GROUP BY cu.user_id
+    </select>
 
     <sql id="commonConditions">
         <!-- 销售公司 -->
@@ -1478,7 +1711,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         <!-- 项目 -->
         <if test="project != null and project != ''">
-            AND cuu.project_id = #{project}
+            AND log.project = #{project}
         </if>
         <!-- 时间范围 -->
         <if test="startDate != null and startDate != '' and endDate != null and endDate != ''">

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

@@ -2467,7 +2467,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         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
+        WHERE u.status = 1 and is_del = 0
         <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>

+ 11 - 1
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -397,6 +397,12 @@ public class AppLoginController extends AppBaseController{
                     return R.ok(map);
                 }
             }
+            if(user.getAppCreateTime()== null){
+                FsUser update = new FsUser();
+                update.setAppCreateTime(new Date());
+                update.setUserId(user.getUserId());
+                userService.updateFsUser(update);
+            }
             /*if (user.getStatus()==0){
                 return R.error("登录失败,账户被禁用");
             }*/
@@ -422,6 +428,7 @@ public class AppLoginController extends AppBaseController{
         newUser.setPassword(Md5Utils.hash(param.getPassword()));
         newUser.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
         newUser.setCreateTime(new Date());
+        newUser.setAppCreateTime(new Date());
         newUser.setStatus(1);
         newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
         if (StringUtils.isNotEmpty(param.getJpushId())) {
@@ -449,9 +456,12 @@ public class AppLoginController extends AppBaseController{
 
         userMap.setLoginDevice(param.getLoginDevice());
         userMap.setSource(param.getSource());
-        if (userMap.getNickName().equals("匿名用户**")) {
+        if ("匿名用户**".equals(userMap.getNickName())) {
             userMap.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
         }
+        if (userMap.getAppCreateTime()== null){
+            userMap.setAppCreateTime(new Date());
+        }
         userMap.setAppleKey(param.getAppleKey());
         if (userService.updateFsUser(userMap)>0){
             return generateTokenAndReturn(userMap);