Browse Source

Merge branch 'master' into 会员关联项目

# Conflicts:
#	fs-service-system/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java
#	fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java
#	fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml
Long 2 tuần trước cách đây
mục cha
commit
411f6a25d7
54 tập tin đã thay đổi với 1553 bổ sung139 xóa
  1. 25 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyMenuController.java
  2. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  3. 2 12
      fs-admin/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java
  4. 2 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserWatchStatisticsController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/store/controller/FsUserController.java
  6. 104 0
      fs-admin/src/main/java/com/fs/store/controller/FsUserOnlineStateController.java
  7. 10 0
      fs-admin/src/main/java/com/fs/task/StoreTask.java
  8. 2 12
      fs-company/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java
  9. 125 0
      fs-company/src/main/java/com/fs/store/controller/FsUserOnlineStateController.java
  10. 1 1
      fs-company/src/main/java/com/fs/users/controller/FsUserController.java
  11. 46 12
      fs-service-system/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  12. 14 2
      fs-service-system/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java
  13. 5 0
      fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseTrainingCamp.java
  14. 14 1
      fs-service-system/src/main/java/com/fs/course/domain/FsUserWatchCourseStatistics.java
  15. 10 3
      fs-service-system/src/main/java/com/fs/course/domain/FsUserWatchStatistics.java
  16. 13 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  17. 8 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java
  18. 12 1
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  19. 1 0
      fs-service-system/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java
  20. 64 26
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  21. 82 6
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  22. 4 3
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  23. 37 3
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java
  24. 6 2
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  25. 22 1
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserWatchCourseStatisticsServiceImpl.java
  26. 15 1
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserWatchStatisticsServiceImpl.java
  27. 4 0
      fs-service-system/src/main/java/com/fs/course/vo/FsUserCoursePeriodVO.java
  28. 6 2
      fs-service-system/src/main/java/com/fs/course/vo/FsUserWatchCourseStatisticsExportVO.java
  29. 6 0
      fs-service-system/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java
  30. 13 0
      fs-service-system/src/main/java/com/fs/store/config/CompanyMenuConfig.java
  31. 95 0
      fs-service-system/src/main/java/com/fs/store/domain/FsUserOnlineState.java
  32. 3 3
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreOrderMapper.java
  33. 4 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserCompanyUserMapper.java
  34. 9 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java
  35. 75 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserOnlineStateMapper.java
  36. 1 1
      fs-service-system/src/main/java/com/fs/store/param/FsStoreOrderParam.java
  37. 4 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java
  38. 67 0
      fs-service-system/src/main/java/com/fs/store/service/IFsUserOnlineStateService.java
  39. 68 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java
  40. 136 0
      fs-service-system/src/main/java/com/fs/store/service/impl/FsUserOnlineStateServiceImpl.java
  41. 23 4
      fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java
  42. 36 0
      fs-service-system/src/main/java/com/fs/store/vo/FsUserLastCount.java
  43. 3 0
      fs-service-system/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml
  44. 34 9
      fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodDaysMapper.xml
  45. 15 8
      fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml
  46. 3 1
      fs-service-system/src/main/resources/mapper/course/FsUserCourseTrainingCampMapper.xml
  47. 13 3
      fs-service-system/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml
  48. 25 1
      fs-service-system/src/main/resources/mapper/course/FsUserWatchCourseStatisticsMapper.xml
  49. 11 1
      fs-service-system/src/main/resources/mapper/course/FsUserWatchStatisticsMapper.xml
  50. 11 0
      fs-service-system/src/main/resources/mapper/store/FsUserCompanyUserMapper.xml
  51. 27 1
      fs-service-system/src/main/resources/mapper/store/FsUserCourseCountMapper.xml
  52. 19 17
      fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml
  53. 202 0
      fs-service-system/src/main/resources/mapper/store/FsUserOnlineStateMapper.xml
  54. 7 0
      fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

+ 25 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyMenuController.java

@@ -1,6 +1,11 @@
 package com.fs.company.controller;
 
 import java.util.List;
+
+import com.fs.common.core.domain.entity.SysMenu;
+import com.fs.common.utils.ServletUtils;
+import com.fs.core.security.LoginUser;
+import com.fs.core.web.service.TokenService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -22,7 +27,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 菜单权限Controller
- * 
+ *
  * @author fs
  * @date 2021-10-04
  */
@@ -33,6 +38,9 @@ public class CompanyMenuController extends BaseController
     @Autowired
     private ICompanyMenuService companyMenuService;
 
+    @Autowired
+    private TokenService tokenService;
+
     /**
      * 查询菜单权限列表
      */
@@ -100,4 +108,20 @@ public class CompanyMenuController extends BaseController
     {
         return toAjax(companyMenuService.deleteCompanyMenuByIds(menuIds));
     }
+
+    /**
+     * 获取菜单下拉树列表
+     */
+    /**
+     * 获取菜单下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(CompanyMenu menu)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        List<CompanyMenu> menus = companyMenuService.selectMenuList(menu, userId,"00");
+        return AjaxResult.success(companyMenuService.buildMenuTreeSelect(menus));
+    }
+
 }

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -132,6 +132,14 @@ public class FsUserCoursePeriodController extends BaseController {
         return toAjax(fsUserCoursePeriodService.deleteFsUserCoursePeriodByIds(periodIds));
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:dayRemove')")
+    @Log(title = "会员营期课程删除", businessType = BusinessType.DELETE)
+    @DeleteMapping("/day/{ids}")
+    public AjaxResult dayRemove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserCoursePeriodDaysService.deleteFsUserCoursePeriodDaysByIds(ids));
+    }
+
     @GetMapping("/getDays")
     public R getDays(FsUserCoursePeriodDays fsUserCoursePeriodDays){
 //        startPage();

+ 2 - 12
fs-admin/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java

@@ -46,12 +46,6 @@ public class FsUserWatchCourseStatisticsController extends BaseController
     {
         startPage();
         List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
-        if(!list.isEmpty()){
-            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
-                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
-                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
-            }
-        }
         return getDataTable(list);
     }
 
@@ -64,12 +58,6 @@ public class FsUserWatchCourseStatisticsController extends BaseController
     public AjaxResult export(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
     {
         List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
-        if(!list.isEmpty()){
-            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
-                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
-                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
-            }
-        }
         ExcelUtil<FsUserWatchCourseStatistics> util = new ExcelUtil<FsUserWatchCourseStatistics>(FsUserWatchCourseStatistics.class);
         return util.exportExcel(list, "会员观看数据明细");
     }
@@ -129,6 +117,7 @@ public class FsUserWatchCourseStatisticsController extends BaseController
         if(!list.isEmpty()){
             for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
                 userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
+                userWatchCourseStatistics.setOnlineRatePercent(userWatchCourseStatistics.getOnlineRate() + "%");
             }
         }
         return getDataTable(list);
@@ -146,6 +135,7 @@ public class FsUserWatchCourseStatisticsController extends BaseController
             FsUserWatchCourseStatisticsExportVO vo = new FsUserWatchCourseStatisticsExportVO();
             BeanUtils.copyProperties(v, vo);
             vo.setCompleteWatchRatePercent(v.getCompleteWatchRate() + "%");
+            vo.setOnlineRatePercent(v.getOnlineRate() + "%");
             return vo;
         }).collect(Collectors.toList());
         ExcelUtil<FsUserWatchCourseStatisticsExportVO> util = new ExcelUtil<FsUserWatchCourseStatisticsExportVO>(FsUserWatchCourseStatisticsExportVO.class);

+ 2 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserWatchStatisticsController.java

@@ -47,6 +47,7 @@ public class FsUserWatchStatisticsController extends BaseController
         if(!list.isEmpty()){
             for (FsUserWatchStatistics userWatchStatistics : list) {
                 userWatchStatistics.setCompleteWatchRatePercent(userWatchStatistics.getCompleteWatchRate() + "%");
+                userWatchStatistics.setOnlineRatePercent(userWatchStatistics.getOnlineRate() + "%");
             }
         }
         return getDataTable(list);
@@ -64,6 +65,7 @@ public class FsUserWatchStatisticsController extends BaseController
         if(!list.isEmpty()){
             for (FsUserWatchStatistics userWatchStatistics : list) {
                 userWatchStatistics.setCompleteWatchRatePercent(userWatchStatistics.getCompleteWatchRate() + "%");
+                userWatchStatistics.setOnlineRatePercent(userWatchStatistics.getOnlineRate() + "%");
             }
         }
         ExcelUtil<FsUserWatchStatistics> util = new ExcelUtil<FsUserWatchStatistics>(FsUserWatchStatistics.class);

+ 1 - 1
fs-admin/src/main/java/com/fs/store/controller/FsUserController.java

@@ -167,7 +167,7 @@ public class FsUserController extends BaseController
         }
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("rows", fsUserPageListVOPageInfo.getList());
-        map.put("total", fsUserPageListVOPageInfo.getList().size());
+        map.put("total", fsUserPageListVOPageInfo.getTotal());
         return R.ok(map);
     }
 

+ 104 - 0
fs-admin/src/main/java/com/fs/store/controller/FsUserOnlineStateController.java

@@ -0,0 +1,104 @@
+package com.fs.store.controller;
+
+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.poi.ExcelUtil;
+import com.fs.store.domain.FsUserOnlineState;
+import com.fs.store.service.IFsUserOnlineStateService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户上线情况Controller
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@RestController
+@RequestMapping("/store/userOnlineState")
+public class FsUserOnlineStateController extends BaseController
+{
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
+    /**
+     * 查询用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserOnlineState fsUserOnlineState)
+    {
+        startPage();
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:export')")
+    @Log(title = "用户上线情况", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserOnlineState fsUserOnlineState)
+    {
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        ExcelUtil<FsUserOnlineState> util = new ExcelUtil<FsUserOnlineState>(FsUserOnlineState.class);
+        return util.exportExcel(list, "未上线会员");
+    }
+
+    /**
+     * 获取用户上线情况详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(fsUserOnlineStateService.selectFsUserOnlineStateById(userId));
+    }
+
+    /**
+     * 新增用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:add')")
+    @Log(title = "用户上线情况", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.insertFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 修改用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:edit')")
+    @Log(title = "用户上线情况", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.updateFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 删除用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:remove')")
+    @Log(title = "用户上线情况", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(fsUserOnlineStateService.deleteFsUserOnlineStateByIds(userIds));
+    }
+
+    @ApiOperation("新增/更新未上线人员-定时任务")
+    @PostMapping("/testTask")
+    public void test(){
+        fsUserOnlineStateService.insertUserNotOnline();
+    }
+}

+ 10 - 0
fs-admin/src/main/java/com/fs/task/StoreTask.java

@@ -136,6 +136,9 @@ public class StoreTask
     @Autowired
     private IFsUserWatchStatisticsService fsUserWatchStatisticsService;
 
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
     public void PushErp() throws ParseException {
         List<Long> ids;
         // 开启审核
@@ -480,4 +483,11 @@ public class StoreTask
         /***************************************进入营期会员看课明细统计定时任务结束**********************************************/
     }
 
+    /**
+     * 定时查询未上线的用户
+     */
+    public void insertUserNotOnline(){
+        fsUserOnlineStateService.insertUserNotOnline();
+    }
+
 }

+ 2 - 12
fs-company/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java

@@ -46,12 +46,6 @@ public class FsUserWatchCourseStatisticsController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         fsUserWatchCourseStatistics.setCompanyId( loginUser.getCompany().getCompanyId());
         List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
-        if(!list.isEmpty()){
-            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
-                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
-                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
-            }
-        }
         return getDataTable(list);
     }
 
@@ -63,12 +57,6 @@ public class FsUserWatchCourseStatisticsController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         fsUserWatchCourseStatistics.setCompanyId( loginUser.getCompany().getCompanyId());
         List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
-        if(!list.isEmpty()){
-            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
-                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
-                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
-            }
-        }
         ExcelUtil<FsUserWatchCourseStatistics> util = new ExcelUtil<FsUserWatchCourseStatistics>(FsUserWatchCourseStatistics.class);
         return util.exportExcel(list, "会员观看数据明细");
     }
@@ -87,6 +75,7 @@ public class FsUserWatchCourseStatisticsController extends BaseController
         if(!list.isEmpty()){
             for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
                 userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
+                userWatchCourseStatistics.setOnlineRatePercent(userWatchCourseStatistics.getOnlineRate() + "%");
             }
         }
         return getDataTable(list);
@@ -106,6 +95,7 @@ public class FsUserWatchCourseStatisticsController extends BaseController
             FsUserWatchCourseStatisticsExportVO vo = new FsUserWatchCourseStatisticsExportVO();
             BeanUtils.copyProperties(v, vo);
             vo.setCompleteWatchRatePercent(v.getCompleteWatchRate() + "%");
+            vo.setOnlineRatePercent(v.getOnlineRate() + "%");
             return vo;
         }).collect(Collectors.toList());
         ExcelUtil<FsUserWatchCourseStatisticsExportVO> util = new ExcelUtil<FsUserWatchCourseStatisticsExportVO>(FsUserWatchCourseStatisticsExportVO.class);

+ 125 - 0
fs-company/src/main/java/com/fs/store/controller/FsUserOnlineStateController.java

@@ -0,0 +1,125 @@
+package com.fs.store.controller;
+
+import java.util.List;
+
+import com.fs.common.utils.ServletUtils;
+import com.fs.core.security.LoginUser;
+import com.fs.core.web.service.TokenService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.store.domain.FsUserOnlineState;
+import com.fs.store.service.IFsUserOnlineStateService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 用户上线情况Controller
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@RestController
+@RequestMapping("/store/userOnlineState")
+public class FsUserOnlineStateController extends BaseController
+{
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserOnlineState fsUserOnlineState)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果是管理员,可以查询当前公司所有数据
+        if(loginUser.getUser().isAdmin()){
+            fsUserOnlineState.setCompanyId( loginUser.getCompany().getCompanyId());
+        } else{
+            fsUserOnlineState.setCompanyUserId( loginUser.getUser().getUserId());
+        }
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:export')")
+    @Log(title = "用户上线情况", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserOnlineState fsUserOnlineState)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果是管理员,可以查询当前公司所有数据
+        if(loginUser.getUser().isAdmin()){
+            fsUserOnlineState.setCompanyId( loginUser.getCompany().getCompanyId());
+        } else{
+            fsUserOnlineState.setCompanyUserId( loginUser.getUser().getUserId());
+        }
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        ExcelUtil<FsUserOnlineState> util = new ExcelUtil<FsUserOnlineState>(FsUserOnlineState.class);
+        return util.exportExcel(list, "未上线会员");
+    }
+
+    /**
+     * 获取用户上线情况详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(fsUserOnlineStateService.selectFsUserOnlineStateById(userId));
+    }
+
+    /**
+     * 新增用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:add')")
+    @Log(title = "用户上线情况", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.insertFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 修改用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:edit')")
+    @Log(title = "用户上线情况", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.updateFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 删除用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:remove')")
+    @Log(title = "用户上线情况", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(fsUserOnlineStateService.deleteFsUserOnlineStateByIds(userIds));
+    }
+
+}

+ 1 - 1
fs-company/src/main/java/com/fs/users/controller/FsUserController.java

@@ -137,7 +137,7 @@ public class FsUserController extends BaseController
         PageInfo<FsUserPageListVO> fsUserPageListVOPageInfo = fsUserService.selectFsUserPageList(param);
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("rows", fsUserPageListVOPageInfo.getList());
-        map.put("total", fsUserPageListVOPageInfo.getList().size());
+        map.put("total", fsUserPageListVOPageInfo.getTotal());
         return R.ok(map);
     }
 

+ 46 - 12
fs-service-system/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -1,12 +1,15 @@
 package com.fs.company.service.impl;
 
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyConfigParam;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.service.ICompanyRoleService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyNameVO;
@@ -14,6 +17,7 @@ import com.fs.company.vo.CompanyVO;
 import com.fs.company.vo.DeptDataVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.his.vo.OptionsVO;
+import com.fs.store.config.CompanyMenuConfig;
 import com.fs.store.config.StoreConfig;
 import com.fs.store.domain.FsStoreOrder;
 import com.fs.store.domain.FsStorePayment;
@@ -22,7 +26,6 @@ import com.fs.system.service.ISysConfigService;
 import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -56,12 +59,18 @@ public class CompanyServiceImpl implements ICompanyService
     private CompanyPostMapper postMapper;
     @Autowired
     private CompanyUserMapper userMapper;
+
+    @Autowired
+    private CompanyRoleMenuMapper roleMenuMapper;
     @Autowired
     private CompanyUserRoleMapper userRoleMapper;
     @Autowired
     private CompanyUserPostMapper userPostMapper;
     @Autowired
     private ISysConfigService configService;
+
+    @Autowired
+    private ICompanyRoleService roleService;
     @Autowired
     private CompanyMoneyLogsMapper moneyLogsMapper;
     @Autowired
@@ -149,16 +158,38 @@ public class CompanyServiceImpl implements ICompanyService
             post.setPostSort(0);
             post.setStatus("0");
             postMapper.insertCompanyPost(post);
-            //创建角色
-            CompanyRole role=new CompanyRole();
-            role.setCompanyId(company.getCompanyId());
-            role.setRoleName("管理员");
-            role.setRoleKey("admin");
-            role.setRoleSort(0);
-            role.setStatus("0");
-            roleMapper.insertCompanyRole(role);
-            //添加用户
-            CompanyUser user=new CompanyUser();
+
+            // 创建管理员角色(拥有全部权限)
+            CompanyRole adminRole = new CompanyRole();
+            adminRole.setCompanyId(company.getCompanyId());
+            adminRole.setRoleName("管理员");
+            adminRole.setRoleKey("admin");
+            adminRole.setRoleSort(0);
+            adminRole.setStatus("0");
+            roleMapper.insertCompanyRole(adminRole);
+
+            // 创建销售角色(新增部分,只有"我的"相关权限)
+            CompanyRole salesRole = new CompanyRole();
+            salesRole.setCompanyId(company.getCompanyId());
+            salesRole.setRoleName(company.getCompanyName() + "_销售");
+            salesRole.setRoleKey(company.getCompanyId()+"_sales");
+            salesRole.setRoleSort(1);
+            salesRole.setDataScope("5");
+            salesRole.setStatus("0");
+
+            //增加销售角色菜单权限
+            try {
+                String json = configService.selectConfigByKey("companymenu.config");
+                if (StringUtils.isNotEmpty(json)) {
+                    CompanyMenuConfig config = JSONUtil.toBean(json, CompanyMenuConfig.class);
+                    salesRole.setMenuIds(config.getMenuIds());
+                    roleService.insertRole(salesRole);
+                }
+            } catch (Exception e) {
+                logger.error("获取菜单配置失败", e);
+            }
+            // 6. 创建管理员用户
+            CompanyUser user = new CompanyUser();
             user.setCompanyId(company.getCompanyId());
             user.setNickName("管理员");
 
@@ -173,9 +204,12 @@ public class CompanyServiceImpl implements ICompanyService
             user.setIsDel(0);
             user.setIsAudit(1);
             userMapper.insertCompanyUser(user);
+
+
+
             //添加用户角色表
             CompanyUserRole userRole=new CompanyUserRole();
-            userRole.setRoleId(role.getRoleId());
+            userRole.setRoleId(adminRole.getRoleId());
             userRole.setUserId(user.getUserId());
             userRoleMapper.insertCompanyUserRole(userRole);
             //添加用户岗位表

+ 14 - 2
fs-service-system/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java

@@ -5,9 +5,11 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
+import java.io.Serializable;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -17,8 +19,7 @@ import java.util.List;
  * @date 2025-04-15
  */
 @Data
-public class FsUserCoursePeriod
-{
+public class FsUserCoursePeriod implements Serializable {
     private static final long serialVersionUID = 1L;
 
     @TableField(exist = false)
@@ -103,4 +104,15 @@ public class FsUserCoursePeriod
 
     @Excel(name = "开启评论或者弹幕,1-开启评论;2-开启弹幕;3-都关闭")
     private Integer openCommentStatus;
+
+    /**
+     * 删除状态0、正常,1、已删除
+     */
+    private Integer delFlag;
+
+    /**
+     * 营期线,即营期首次播放课程的日期
+     */
+    private Date periodLine;
+
 }

+ 5 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseTrainingCamp.java

@@ -38,4 +38,9 @@ public class FsUserCourseTrainingCamp
     private LocalDateTime createTime;
 
     private Long userId;
+
+    /**
+     * 删除状态0、正常,1、已删除
+     */
+    private Integer delFlag;
 }

+ 14 - 1
fs-service-system/src/main/java/com/fs/course/domain/FsUserWatchCourseStatistics.java

@@ -2,6 +2,8 @@ package com.fs.course.domain;
 
 import java.math.BigDecimal;
 import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
@@ -32,7 +34,7 @@ public class FsUserWatchCourseStatistics extends BaseEntity{
 
     /** 营期开始日期 */
     @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "营期日期", width = 30, dateFormat = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
     private Date periodStartingTime;
 
     /** 课程开始日期 */
@@ -84,10 +86,17 @@ public class FsUserWatchCourseStatistics extends BaseEntity{
     @Excel(name = "观看人数")
     private Integer watchNum;
 
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
     /** 完播人数 */
     @Excel(name = "完播人数")
     private Integer completeWatchNum;
 
+    /** 上线率 */
+    private BigDecimal onlineRate;
+
     /** 完播率 */
 //    @Excel(name = "完播率")
     private BigDecimal completeWatchRate;
@@ -123,5 +132,9 @@ public class FsUserWatchCourseStatistics extends BaseEntity{
     /** 用户的创建日期 */
     private Date userCreateDate;
 
+    /** 营期日期 */
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date periodLine;
 
 }

+ 10 - 3
fs-service-system/src/main/java/com/fs/course/domain/FsUserWatchStatistics.java

@@ -33,11 +33,11 @@ public class FsUserWatchStatistics extends BaseEntity{
 
     /** 营期开始日期 */
     @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "营期开始日期", width = 30, dateFormat = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
     private Date periodStartingTime;
 
-    /** 销售公司ids */
-//    @Excel(name = "销售公司ids")
+    /** 销售公司id */
+//    @Excel(name = "销售公司id")
     private String companyId;
 
     /** 销售公司名称 */
@@ -56,10 +56,17 @@ public class FsUserWatchStatistics extends BaseEntity{
     @Excel(name = "观看人数")
     private Integer watchNum;
 
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
     /** 完播人数 */
     @Excel(name = "完播人数")
     private Integer completeWatchNum;
 
+    /** 上线率 */
+    private BigDecimal onlineRate;
+
     /** 完播率 */
 //    @Excel(name = "完播率")
     private BigDecimal completeWatchRate;

+ 13 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.domain.FsUserWatchCourseStatistics;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
 import java.time.LocalDateTime;
 import java.util.List;
@@ -103,4 +104,16 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
      */
     List<FsUserWatchCourseStatistics> selectDaysCountList();
 
+    /**
+     * 批量删除
+     * @param ids
+     * @param delFlag
+     */
+    @Update("<script>" +
+            "UPDATE fs_user_course_period_days SET del_flag = #{delFlag} WHERE id IN " +
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 }

+ 8 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java

@@ -136,4 +136,12 @@ public interface FsUserCoursePeriodMapper
 
     List<FsUserCoursePeriod> selectPeriodListByTrainingCampIds(@Param("trainingCampIds") Long[] trainingCampIds);
 
+    @Update("<script>" +
+            "UPDATE fs_user_course_period SET del_flag = #{delFlag} WHERE period_id IN " +
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
+
 }

+ 12 - 1
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java

@@ -68,7 +68,6 @@ public interface FsUserCourseVideoRedPackageMapper
             "ON DUPLICATE KEY UPDATE red_packet_money = VALUES(red_packet_money);")
     void insertOrUpdateFsUserCourseVideoRedPackage(FsUserCourseVideoParam fsUserCourseVideo);
 
-    @Select("select * from fs_user_course_video_red_package where video_id =#{videoId} and company_id = #{companyId} and period_id = #{periodId}")
     FsUserCourseVideoRedPackage selectRedPacketByCompanyId(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);
 
     int batchSaveFsUserCourseVideoRedPackage(@Param("list") List<FsUserCourseVideoRedPackage> redPackageList);
@@ -97,4 +96,16 @@ public interface FsUserCourseVideoRedPackageMapper
     int updateRedPackageByParams(FsUserCourseVideoRedPackage videoRedPackage);
 
     List<FsUserCourseVideoRedPackage> selectByRuleIds(@Param("ruleIds") List<Long> ruleIds);
+    /**
+     * 批量删除
+     * @param ids
+     * @param delFlag
+     */
+    @Update("<script>" +
+            "UPDATE fs_user_course_video_red_package SET del_flag = #{delFlag} WHERE video_id IN " +
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 }

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

@@ -117,4 +117,5 @@ public interface IFsUserCoursePeriodDaysService extends IService<FsUserCoursePer
      * 更新营期课程状态
      */
     void changePeriodCourseStatus();
+
 }

+ 64 - 26
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -12,6 +12,7 @@ import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.PeriodCountParam;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
@@ -26,6 +27,7 @@ import com.fs.store.param.h5.CourseAnalysisParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -33,10 +35,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -59,6 +58,9 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     private final FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
     private final IFsUserCourseVideoService fsUserCourseVideoService;
 
+    @Autowired
+    private FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;
+
     /**
      * 查询营期课程
      *
@@ -118,7 +120,16 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     @Override
     public int deleteFsUserCoursePeriodDaysByIds(Long[] ids)
     {
-        return baseMapper.deleteFsUserCoursePeriodDaysByIds(ids);
+        int flag = 0;
+        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectBatchIds(Arrays.asList(ids));
+        List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
+        if(!periodDayIds.isEmpty()){
+            flag = fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
+            //删除红包记录
+            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+        }
+        return flag;
     }
 
     /**
@@ -136,7 +147,7 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     @Override
     public R addCourse(FsUserCoursePeriodDays entity) {
         FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(entity.getPeriodId());
-        List<FsUserCoursePeriodDays> dayList = list(new QueryWrapper<FsUserCoursePeriodDays>().eq("period_id", period.getPeriodId()));
+        List<FsUserCoursePeriodDays> dayList = list(new QueryWrapper<FsUserCoursePeriodDays>().eq("period_id", period.getPeriodId()).eq("del_flag","0"));
         long days;
         if(period.getPeriodType() == 2){
             days = 1;
@@ -262,34 +273,61 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
 
     @Override
     public R periodCourseMove(Long id, Long targetId) {
-        List<Long> idList = new ArrayList<>();
-        idList.add(id);
-        idList.add(targetId);
+        // 参数校验
+        if (id == null || targetId == null || id.equals(targetId)) {
+            return R.error(400, "参数不合法");
+        }
+
+        // 批量查询数据
+        List<Long> idList = Arrays.asList(id, targetId);
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = baseMapper.selectBatchIds(idList);
-        if(fsUserCoursePeriodDays.isEmpty()){
-            return R.error(404, "当前数据或者移动的目标数据不存在");
+
+        // 数据校验
+        if (fsUserCoursePeriodDays.size() != 2) {
+            return R.error(404, "当前数据或移动的目标数据不存在");
         }
 
-        List<FsUserCoursePeriodDays> list = new ArrayList<>();
-        FsUserCoursePeriodDays periodDays = fsUserCoursePeriodDays.get(0);
-        LocalDate currentDayDate = periodDays.getDayDate();
-        Integer lesson = periodDays.getLesson();
+        // 获取源数据和目标数据
+        FsUserCoursePeriodDays source = fsUserCoursePeriodDays.get(0);
+        FsUserCoursePeriodDays target = fsUserCoursePeriodDays.get(1);
 
-        FsUserCoursePeriodDays periodDaysTarget = fsUserCoursePeriodDays.get(1);
-        periodDays.setDayDate(periodDaysTarget.getDayDate());
-        periodDays.setLesson(periodDaysTarget.getLesson());
-        list.add(periodDays);
+        // 交换数据
+        swapPeriodDaysData(source, target);
 
-        periodDaysTarget.setDayDate(currentDayDate);
-        periodDaysTarget.setLesson(lesson);
-        list.add(periodDaysTarget);
+        // 批量更新
         try {
-            fsUserCoursePeriodDaysMapper.batchUpdateCoursePeriodDays(list);
+            fsUserCoursePeriodDaysMapper.batchUpdateCoursePeriodDays(Arrays.asList(source, target));
+            return R.ok();
         } catch (RuntimeException e) {
-            log.error("上移/下移异常,当前id:{}, 目标id:{}", id, targetId);
-            return R.error();
+            log.error("上移/下移异常,当前id:{},目标id:{}", id, targetId, e);
+            return R.error(500, "操作失败,请稍后重试");
         }
-        return R.ok();
+    }
+
+    /**
+     * 交换两个课程时间段的数据
+     */
+    private void swapPeriodDaysData(FsUserCoursePeriodDays source, FsUserCoursePeriodDays target) {
+        // 保存源数据
+        LocalDate tempDayDate = source.getDayDate();
+        Integer tempLesson = source.getLesson();
+        LocalDateTime tempStartDateTime = source.getStartDateTime();
+        LocalDateTime tempEndDateTime = source.getEndDateTime();
+        LocalDateTime tempLastJoinTime = source.getLastJoinTime();
+
+        // 将目标数据赋给源
+        source.setDayDate(target.getDayDate());
+        source.setLesson(target.getLesson());
+        source.setStartDateTime(target.getStartDateTime());
+        source.setEndDateTime(target.getEndDateTime());
+        source.setLastJoinTime(target.getLastJoinTime());
+
+        // 将保存的源数据赋给目标
+        target.setDayDate(tempDayDate);
+        target.setLesson(tempLesson);
+        target.setStartDateTime(tempStartDateTime);
+        target.setEndDateTime(tempEndDateTime);
+        target.setLastJoinTime(tempLastJoinTime);
     }
 
     @Override

+ 82 - 6
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -3,12 +3,16 @@ package com.fs.course.service.impl;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
 import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.service.IFsUserCourseVideoRedPackageService;
 import com.fs.course.vo.FsUserCoursePeriodVO;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -16,8 +20,9 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Objects;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 会员营期Service业务层处理
@@ -33,6 +38,9 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     @Resource
     private FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
 
+    @Resource
+    private FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;
+
     /**
      * 查询会员营期
      *
@@ -85,12 +93,45 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
      * @return 结果
      */
     @Override
-    public int updateFsUserCoursePeriod(FsUserCoursePeriod fsUserCoursePeriod)
-    {
+    public int updateFsUserCoursePeriod(FsUserCoursePeriod fsUserCoursePeriod) {
+        // 1. 查询原始数据
         fsUserCoursePeriod.setUpdateTime(LocalDateTime.now());
-        return fsUserCoursePeriodMapper.updateFsUserCoursePeriod(fsUserCoursePeriod);
+
+        FsUserCoursePeriod fsUserCoursePeriod1 = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(fsUserCoursePeriod.getPeriodId());
+        int flag = fsUserCoursePeriodMapper.updateFsUserCoursePeriod(fsUserCoursePeriod);
+
+        // 2. 判定是否变更过开始时间(periodStartingTime)
+        if (fsUserCoursePeriod1.getPeriodStartingTime().equals(fsUserCoursePeriod.getPeriodStartingTime())) {
+            // 如果开始时间没有变化,直接返回
+            return flag;
+        }
+
+        // 3. 计算课程时间差(以天数为单位)
+        long daysDifference = ChronoUnit.DAYS.between(fsUserCoursePeriod1.getPeriodStartingTime(), fsUserCoursePeriod.getPeriodStartingTime());
+
+        // 4. 获取课程天数并进行时间调整
+        FsUserCoursePeriodDays fsUserCoursePeriodDays = new FsUserCoursePeriodDays();
+        fsUserCoursePeriodDays.setPeriodId(fsUserCoursePeriod1.getPeriodId());
+        List<FsUserCoursePeriodDays> list = fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysList(fsUserCoursePeriodDays);
+
+        // 对每个课程日期进行时间增加
+        for (FsUserCoursePeriodDays coursePeriodDays : list) {
+            // 将课程的日期按照差值天数进行调整
+            LocalDate adjustedDate = coursePeriodDays.getDayDate().plusDays(daysDifference);
+            LocalDateTime startDateTime = coursePeriodDays.getStartDateTime().plusDays(daysDifference);
+            LocalDateTime endDateTime = coursePeriodDays.getEndDateTime().plusDays(daysDifference);
+            LocalDateTime lastJsonTime = coursePeriodDays.getLastJoinTime().plusDays(daysDifference);
+            coursePeriodDays.setDayDate(adjustedDate);
+            coursePeriodDays.setStartDateTime(startDateTime);
+            coursePeriodDays.setEndDateTime(endDateTime);
+            coursePeriodDays.setLastJoinTime(lastJsonTime);
+            fsUserCoursePeriodDaysMapper.updateFsUserCoursePeriodDays(coursePeriodDays); // 更新数据库中的课程日期
+        }
+
+        return flag;
     }
 
+
     /**
      * 批量删除会员营期
      *
@@ -100,9 +141,44 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     @Override
     public int deleteFsUserCoursePeriodByIds(Long[] periodIds)
     {
-        return fsUserCoursePeriodMapper.deleteFsUserCoursePeriodByIds(periodIds);
+        if (checkPeriodStatus(periodIds)) {
+            throw new ServiceException("存在进行中或已结束的营期,不允许删除");
+        }
+        int flag = fsUserCoursePeriodMapper.updateBatchDelFlag(periodIds,1);
+        //删除课程
+        Set<Long> set = Arrays.asList(periodIds).stream().collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(set)){
+            return flag;
+        }
+        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
+        List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
+        if(!periodDayIds.isEmpty()){
+            fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
+            //删除红包记录
+            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+        }
+        return flag;
     }
+    private boolean checkPeriodStatus(Long[] ids) {
+        LocalDate currentDate = LocalDate.now(); // 2025-06-30
+        Set<Long> idSet = Arrays.stream(ids).collect(Collectors.toSet());
+        List<FsUserCoursePeriod> list = fsUserCoursePeriodMapper.selectFsUserCoursePeriodsByIds(idSet);
 
+        List<FsUserCoursePeriod> filteredList = list.stream()
+                .filter(period -> {
+                    LocalDate startTime = period.getPeriodStartingTime();
+                    LocalDate endTime = period.getPeriodEndTime();
+
+                    boolean isActive = startTime != null && !startTime.isAfter(currentDate)
+                            && (endTime == null || !endTime.isBefore(currentDate));
+
+                    return isActive;
+                })
+                .collect(Collectors.toList());
+
+        return !filteredList.isEmpty();
+    }
     /**
      * 删除会员营期信息
      *

+ 4 - 3
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -445,20 +445,21 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     public R createCourseSortLink(FsCourseLinkCreateParam param) {
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        //短链参数
+        String random = generateRandomString();
 
         //新增链接表信息
         FsCourseLink link = new FsCourseLink();
         BeanUtils.copyProperties(param, link);
         link.setLinkType(0);
         link.setIsRoom(0);
+        link.setLink(random);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link, courseMap);
         String courseJson = JSON.toJSONString(courseMap);
-
         link.setRealLink(realLink + courseJson);
-        String random = generateRandomString();
-        link.setLink(random);
+
         link.setCreateTime(new Date());
 
         //获取过期时间

+ 37 - 3
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java

@@ -2,6 +2,7 @@ package com.fs.course.service.impl;
 
 
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.exception.ServiceException;
@@ -10,13 +11,17 @@ import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.domain.FsUserCourseTrainingCamp;
 import com.fs.course.dto.FsUserCourseTrainingCampDTO;
+import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
 import com.fs.course.mapper.FsUserCourseTrainingCampMapper;
+import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCourseTrainingCampService;
+import com.fs.course.service.IFsUserCourseVideoRedPackageService;
 import com.fs.course.vo.FsUserCourseTrainingCampVO;
 import com.fs.his.vo.OptionsVO;
 import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -37,6 +42,9 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
 {
 
     private final FsUserCoursePeriodMapper fsUserCoursePeriodMapper;
+    private final FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;
+
+    private final FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
     private final IFsUserCoursePeriodDaysService fsUserCoursePeriodDaysService;
 
     /**
@@ -74,17 +82,43 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
         if (checkPeriodStatus(ids)) {
             throw new ServiceException("存在进行中或已结束的营期,不允许删除");
         }
-
-        baseMapper.deleteBatchIds(Arrays.asList(ids));
+        batchUpdateDelFlag(Arrays.asList(ids),1);
 
         // 删除训练营的同时需要删除营期
         List<FsUserCoursePeriod> fsUserCoursePeriods = fsUserCoursePeriodMapper.selectPeriodListByTrainingCampIds(ids);
         List<Long> periodIds = fsUserCoursePeriods.stream().map(FsUserCoursePeriod::getPeriodId).collect(Collectors.toList());
         if(!periodIds.isEmpty()){
-            fsUserCoursePeriodMapper.deleteFsUserCoursePeriodByIds(periodIds.toArray(new Long[0]));
+            fsUserCoursePeriodMapper.updateBatchDelFlag(periodIds.toArray(new Long[0]),1);
+        }
+        //删除课程
+        Set<Long> set = periodIds.stream().collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(set)){
+            return;
         }
+        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
+        List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
+        if(!periodDayIds.isEmpty()){
+            fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
+            //删除红包记录
+            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+        }
+
     }
 
+    /**
+     * 批量修改训练列表数据
+     * @param ids
+     * @param delFlag
+     * @return
+     */
+    public int batchUpdateDelFlag(List<Long> ids, Integer delFlag) {
+        LambdaUpdateWrapper<FsUserCourseTrainingCamp> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.in(FsUserCourseTrainingCamp::getTrainingCampId, ids)
+                .set(FsUserCourseTrainingCamp::getDelFlag, delFlag);
+
+        return baseMapper.update(null, updateWrapper);
+    }
     /**
      * 检查是否存在进行中的营期
      * @param ids 训练营ID集合

+ 6 - 2
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1155,8 +1155,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
 //
         if (courseVideoDetails != null && courseVideoDetails.getDuration() != null){
-            tipsTime = courseVideoDetails.getDuration() / 3;
-            tipsTime2 = (courseVideoDetails.getDuration() * 2) / 3;
+            // 查询视频是否设置了红包,没有就不提示
+            FsUserCourseVideoRedPackage fsUserCourseVideoRedPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(param.getVideoId(), null, param.getPeriodId());
+            if(fsUserCourseVideoRedPackage != null){
+                tipsTime = courseVideoDetails.getDuration() / 3;
+                tipsTime2 = (courseVideoDetails.getDuration() * 2) / 3;
+            }
         }
         vo.setTipsTime(tipsTime);
         vo.setTipsTime2(tipsTime2);

+ 22 - 1
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserWatchCourseStatisticsServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.course.service.impl;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -59,7 +60,15 @@ public class FsUserWatchCourseStatisticsServiceImpl extends ServiceImpl<FsUserWa
     @Override
     public List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsList(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
     {
-        return baseMapper.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
+        List<FsUserWatchCourseStatistics> list = baseMapper.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
+        if(!list.isEmpty()){
+            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
+                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
+                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
+                userWatchCourseStatistics.setOnlineRatePercent(userWatchCourseStatistics.getOnlineRate() + "%");
+            }
+        }
+        return list;
     }
 
     @Override
@@ -145,6 +154,8 @@ public class FsUserWatchCourseStatisticsServiceImpl extends ServiceImpl<FsUserWa
             FsUserWatchCourseStatistics answerLogData = answerLogMap.get(key);
             List<FsUserWatchCourseStatistics> userTotalDataList = userTotalMap.get(data.getCompanyUserId());
             BeanUtils.copyProperties(data, vo);
+            // 改成使用营期线来表示营期开始时间
+            vo.setPeriodStartingTime(data.getPeriodLine() != null ? data.getPeriodLine() : data.getPeriodStartingTime());
 
             // 单独一个一个set,不用copy,避免copy出来的结果被前面的覆盖
             if(userTotalDataList != null && !userTotalDataList.isEmpty()){
@@ -190,6 +201,16 @@ public class FsUserWatchCourseStatisticsServiceImpl extends ServiceImpl<FsUserWa
                 vo.setAnswerRightRate(BigDecimal.ZERO);
             }
 
+            // 设置上线率
+            BigDecimal watchNum = new BigDecimal(vo.getWatchNum());
+            BigDecimal userNum = new BigDecimal(vo.getUserNum());
+            if(!userNum.equals(BigDecimal.ZERO)){
+                BigDecimal onlineRate = watchNum.divide(userNum, 2, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
+                vo.setOnlineRate(onlineRate);
+            } else{
+                vo.setOnlineRate(BigDecimal.ZERO);
+            }
+
             vo.setCreateTime(new Date());
             vo.setUpdateTime(new Date());
             list.add(vo);

+ 15 - 1
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserWatchStatisticsServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.course.service.impl;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.*;
@@ -151,7 +152,8 @@ public class FsUserWatchStatisticsServiceImpl extends ServiceImpl<FsUserWatchSta
                             FsUserWatchStatistics fsUserWatchStatistics = new FsUserWatchStatistics();
                             BeanUtils.copyProperties(item, fsUserWatchStatistics);
                             ZonedDateTime zonedDateTime = item.getPeriodStartingTime().atStartOfDay(ZoneId.systemDefault());
-                            fsUserWatchStatistics.setPeriodStartingTime(Date.from(zonedDateTime.toInstant()));
+                            // 改成使用营期线来表示营期开始时间
+                            fsUserWatchStatistics.setPeriodStartingTime(item.getPeriodLine() != null ? item.getPeriodLine() : Date.from(zonedDateTime.toInstant()));
                             fsUserWatchStatistics.setCompanyId(companyIdStr.trim());
                             fsUserWatchStatistics.setCompanyName(company != null ? company.getCompanyName() : null);
 
@@ -178,6 +180,18 @@ public class FsUserWatchStatisticsServiceImpl extends ServiceImpl<FsUserWatchSta
                                 fsUserWatchStatistics.setCompleteWatchRate(BigDecimal.ZERO);
                             }
 
+                            // 计算上线率
+                            BigDecimal watchNum = new BigDecimal(fsUserWatchStatistics.getWatchNum());
+                            BigDecimal userNum = new BigDecimal(fsUserWatchStatistics.getUserNum());
+                            if(!userNum.equals(BigDecimal.ZERO)){
+                                BigDecimal onlineRate = watchNum.divide(userNum, 2, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
+                                fsUserWatchStatistics.setOnlineRate(onlineRate);
+                            } else {
+                                fsUserWatchStatistics.setOnlineRate(BigDecimal.ZERO);
+                            }
+
+                            fsUserWatchStatistics.setCreateTime(new Date());
+                            fsUserWatchStatistics.setUpdateTime(new Date());
                             return fsUserWatchStatistics;
                         })).collect(Collectors.toList());
 

+ 4 - 0
fs-service-system/src/main/java/com/fs/course/vo/FsUserCoursePeriodVO.java

@@ -85,4 +85,8 @@ public class FsUserCoursePeriodVO implements Serializable {
     @Excel(name = "营期状态")
     private Long periodStatus;
 
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 31, dateFormat = "yyyy-MM-dd")
+    private Date periodLine;
+
 }

+ 6 - 2
fs-service-system/src/main/java/com/fs/course/vo/FsUserWatchCourseStatisticsExportVO.java

@@ -26,7 +26,7 @@ public class FsUserWatchCourseStatisticsExportVO extends BaseEntity{
 
     /** 营期开始日期 */
     @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "营期日期", width = 30, dateFormat = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
     private Date periodStartingTime;
 
     /** 课程开始日期 */
@@ -63,7 +63,7 @@ public class FsUserWatchCourseStatisticsExportVO extends BaseEntity{
     private Long companyUserId;
 
     /** 销售名称 */
-    @Excel(name = "所属销售")
+//    @Excel(name = "所属销售")
     private String companyUserName;
 
     /** 新增会员数量 */
@@ -78,6 +78,10 @@ public class FsUserWatchCourseStatisticsExportVO extends BaseEntity{
     @Excel(name = "观看人数")
     private Integer watchNum;
 
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
     /** 完播人数 */
     @Excel(name = "完播人数")
     private Integer completeWatchNum;

+ 6 - 0
fs-service-system/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java

@@ -17,10 +17,13 @@ import com.fs.store.domain.FsUser;
 import com.fs.store.domain.FsUserCompanyUser;
 import com.fs.store.dto.FsUserTransferParamDTO;
 import com.fs.store.service.IFsUserCompanyUserService;
+import com.fs.store.mapper.FsUserCompanyUserMapper;
 import com.fs.store.service.IFsUserService;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.hc.openapi.tool.util.StringUtils;
 import org.apache.hc.core5.util.Asserts;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.http.util.Asserts;
 import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -60,6 +63,8 @@ public class CustomerTransferApprovalServiceImpl implements ICustomerTransferApp
     @Autowired
     private IFsUserCompanyUserService userCompanyUserService;
 
+    @Autowired
+    private FsUserCompanyUserMapper fsUserCompanyUserMapper;
     /**
      * 查询客户转移审批
      *
@@ -267,6 +272,7 @@ public class CustomerTransferApprovalServiceImpl implements ICustomerTransferApp
             }
         }
 
+
         return customerTransferApprovalMapper.updateCustomerTransferApproval(item);
     }
 

+ 13 - 0
fs-service-system/src/main/java/com/fs/store/config/CompanyMenuConfig.java

@@ -0,0 +1,13 @@
+package com.fs.store.config;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class CompanyMenuConfig implements Serializable {
+
+    private Long [] menuIds;
+
+
+}

+ 95 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsUserOnlineState.java

@@ -0,0 +1,95 @@
+package com.fs.store.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户上线情况对象 fs_user_online_state
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserOnlineState extends BaseEntity{
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /** 用户id */
+    private Long userId;
+
+    /** 用户昵称 */
+    @Excel(name = "用户昵称")
+    private String nickname;
+
+    /** 用户头像 */
+    @Excel(name = "用户头像")
+    private String avatar;
+
+    /** 手机号码 */
+    @Excel(name = "手机号码")
+    private String phone;
+
+    /** 微信小程序OPENID */
+    @Excel(name = "微信小程序OPENID")
+    private String maOpenId;
+
+    /** 微信公众号OPENID */
+    @Excel(name = "微信公众号OPENID")
+    private String mpOpenId;
+
+    /** 关联ID */
+    @Excel(name = "关联ID")
+    private String unionId;
+
+    /** 用户状态,1-正常,0-禁止 */
+    @Excel(name = "用户状态,1-正常,0-禁止")
+    private Integer status;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 销售名称 */
+    @Excel(name = "所属销售")
+    private String companyUserName;
+
+    /** 上线状态,1-已上线;2-未上线 */
+    @Excel(name = "上线状态,1-已上线;2-未上线")
+    private Integer onlineStatus;
+
+    /** 上线时间(第一次看课的时间) */
+    @Excel(name = "上线时间(第一次看课的时间)")
+    private Date onlineTime;
+
+    /** 看课数量 */
+    @Excel(name = "看课数量")
+    private Integer watchCourseCount;
+
+    /** 参与营期数量 */
+    @Excel(name = "参与营期数量")
+    private Integer partCourseCount;
+
+    /** 最后一次看课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最后一次看课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastWatchDate;
+
+    /** 更新时间 */
+    @Excel(name = "更新时间", readConverterExp = "更新时间")
+    private Date updateTime;
+
+}

+ 3 - 3
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreOrderMapper.java

@@ -161,9 +161,9 @@ public interface FsStoreOrderMapper
             " AND date_format(o.delivery_send_time,'%y%m%d') &gt;= date_format(#{maps.deliverySendTimeList[0]},'%y%m%d') " +
             " AND date_format(o.delivery_send_time,'%y%m%d') &lt;= date_format(#{maps.deliverySendTimeList[1]},'%y%m%d') " +
             "</if>" +
-//            "<if test = 'maps.paid != null    '> " +
-//            "and o.paid =#{maps.paid} " +
-//            "</if>" +
+            "<if test = 'maps.paidStatus != null    '> " +
+            "and o.paid =#{maps.paidStatus} " +
+            "</if>" +
             "<if test = 'maps.payTimeList != null    '> " +
             " AND date_format(o.pay_time,'%y%m%d') &gt;= date_format(#{maps.payTimeList[0]},'%y%m%d') " +
             " AND date_format(o.pay_time,'%y%m%d') &lt;= date_format(#{maps.payTimeList[1]},'%y%m%d') " +

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserCompanyUserMapper.java

@@ -3,8 +3,10 @@ package com.fs.store.mapper;
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.store.domain.FsUserCompanyUser;
+import com.fs.store.dto.FsUserTransferParamDTO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 /**
  * 微信用户和销售关系Mapper接口
@@ -75,4 +77,6 @@ public interface FsUserCompanyUserMapper extends BaseMapper<FsUserCompanyUser>{
      * @return
      */
     List<FsUserCompanyUser> selectRepeatCompanyUserName(@Param("userIds") List<Long> userIds);
+
+    void transfer(@Param("param") FsUserTransferParamDTO transferParam);
 }

+ 9 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java

@@ -1,7 +1,10 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+import java.util.Set;
+
 import com.fs.store.domain.FsUserCourseCount;
+import com.fs.store.vo.FsUserLastCount;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -79,4 +82,10 @@ public interface FsUserCourseCountMapper
      */
     void insertFsUserCourseCountTask(FsUserCourseCount fsUserCourseCount);
 
+    /**
+     * 查询会员最新的看课状态和心跳时间
+     * @return
+     */
+    List<FsUserLastCount> selectUserLastCount(@Param("userIds") Set<Long> userIds);
+
 }

+ 75 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserOnlineStateMapper.java

@@ -0,0 +1,75 @@
+package com.fs.store.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.store.domain.FsUser;
+import com.fs.store.domain.FsUserOnlineState;
+
+/**
+ * 用户上线情况Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+public interface FsUserOnlineStateMapper extends BaseMapper<FsUserOnlineState>{
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    FsUserOnlineState selectFsUserOnlineStateById(Long userId);
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况集合
+     */
+    List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 删除用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateById(Long userId);
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateByIds(Long[] userIds);
+
+    /**
+     * 查询没有看课记录的用户
+     * @return
+     */
+    List<FsUserOnlineState> selectUserNotOnline();
+
+    /**
+     * 查询短时间已经有且仅有一条看课记录的用户(查询的时间需要大于等于定时任务的时间)
+     * @return
+     */
+    List<FsUser> selectExistWatchLogUser();
+
+}

+ 1 - 1
fs-service-system/src/main/java/com/fs/store/param/FsStoreOrderParam.java

@@ -85,6 +85,6 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
     /**
      * 支付状态 待支付 1已支付
      */
-    private int paid;
+    private int paidStatus;
 
 }

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java

@@ -108,4 +108,8 @@ public interface IFsStorePaymentService
     String transferNotify(String notifyData, HttpServletRequest request);
 
     String v3TransferNotify(String notifyData, HttpServletRequest request);
+
+
+    R sendRedPacketTest(WxSendRedPacketParam param);
+
 }

+ 67 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsUserOnlineStateService.java

@@ -0,0 +1,67 @@
+package com.fs.store.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.store.domain.FsUserOnlineState;
+
+/**
+ * 用户上线情况Service接口
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+public interface IFsUserOnlineStateService extends IService<FsUserOnlineState>{
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    FsUserOnlineState selectFsUserOnlineStateById(Long userId);
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况集合
+     */
+    List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的用户上线情况主键集合
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateByIds(Long[] userIds);
+
+    /**
+     * 删除用户上线情况信息
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateById(Long userId);
+
+    /**
+     * 统计未上线的用户(定时更新上线状态)
+     */
+    void insertUserNotOnline();
+
+}

+ 68 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java

@@ -478,6 +478,69 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         }
     }
 
+
+    @Override
+    public R sendRedPacketTest(WxSendRedPacketParam param) {
+
+        RedPacketConfig config = new RedPacketConfig();
+        // 根据红包模式获取配置
+
+        String  json = configService.selectConfigByKey("redPacket.config");
+        config = JSONUtil.toBean(json, RedPacketConfig.class);
+
+        param.setSource(1);
+        param.setOpenId("ooXAA1Fw5ekSqCT-WLKpoA0cDVDo");
+        param.setAmount(new BigDecimal(0.1));
+
+
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config, payConfig);
+//        payConfig.setPublicKeyId(null);
+//        payConfig.setPublicKeyPath(null);
+//        payConfig.setCertSerialNo("63AC73F33E0A21973BB1DE533421A2337FD91C20");
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService = wxPayService.getTransferService();
+
+        TransferBillsRequest request = new TransferBillsRequest();
+        request.setAppid(config.getAppId());
+        System.out.println("appid:"+config.getAppId());
+        request.setOpenid(param.getOpenId());
+
+        String code = String.valueOf(IdUtil.getSnowflake(0, 0).nextId());
+        request.setOutBillNo("fsCourse" + code);
+
+        Integer amount = WxPayUnifiedOrderRequest.yuanToFen(param.getAmount() != null ? param.getAmount().toString() : "0.1");
+        request.setTransferAmount(amount);
+        request.setTransferRemark("活动奖励");
+        request.setUserRecvPerception("活动奖励");
+        request.setNotifyUrl(config.getNotifyUrl());
+        request.setTransferSceneId("1000");
+
+        // 设置场景信息
+        List<TransferBillsRequest.TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<>();
+        TransferBillsRequest.TransferSceneReportInfo info1 = new TransferBillsRequest.TransferSceneReportInfo();
+        info1.setInfoType("活动名称");
+        info1.setInfoContent("新会员有礼");
+        transferSceneReportInfos.add(info1);
+
+        TransferBillsRequest.TransferSceneReportInfo info2 = new TransferBillsRequest.TransferSceneReportInfo();
+        info2.setInfoType("奖励说明");
+        info2.setInfoContent("注册会员抽奖一等奖");
+        transferSceneReportInfos.add(info2);
+        request.setTransferSceneReportInfos(transferSceneReportInfos);
+
+        try {
+            TransferBillsResult transferBillsResult = transferService.transferBills(request);
+            logger.info("商家转账支付完成:[msg:{}]", transferBillsResult);
+            return R.ok("发送红包成功").put("data", transferBillsResult);
+        } catch (WxPayException e) {
+            e.printStackTrace();
+            logger.info("商家转账支付失败:[msg:{}]", e.getMessage());
+            return R.error("发送失败");
+        }
+    }
+
     @Override
     public R sendRedPacketV3(WxSendRedPacketParam param) {
         return null;
@@ -529,9 +592,14 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
             //创建微信订单
             WxPayConfig payConfig = new WxPayConfig();
             BeanUtils.copyProperties(config,payConfig);
+//            payConfig.setCertSerialNo("63AC73F33E0A21973BB1DE533421A2337FD91C20");
             WxPayService wxPayService = new WxPayServiceImpl();
             wxPayService.setConfig(payConfig);
             SignatureHeader signatureHeader = new SignatureHeader();
+//            signatureHeader.setTimeStamp("1622450000");  // 时间戳(Unix时间戳)
+//            signatureHeader.setNonce("5K8264ILTKCH16CQ2502SI8ZNMTM67VS");  // 随机字符串
+//            signatureHeader.setSerial("63AC73F33E0A21973BB1DE533421A2337FD91C20");
+//            signatureHeader.setSignature("HbjssPzTBkM2iSCmxknS663zigo3gQ1jGQ4R6E6x9356bmV6Um4WfgGWZOH+fdCx5dxjHEiIci5kOYKl0ZdRfnexFFuM2riXLSqnRboOJZ+ew8FH4ZP/zCxtlDnmIYbARoIN46RegcRmGgfOznkLcD7ihr0JixgoZ0BOYk7YLhhcbLZaE2OJmwyyYIdJCH5lvg0mXyX1yfutNxPZz13i3OmZiU42xYr4bByJICWMFTwkzha9GVfOp67q/oVu0bEGIMgGdAVoEUJZXOijKdZdOrieXT07wMU31KITKcnizaUGl5peXejbJEd6CAQcX5e8KRlRSjY8DybHAm0JawVuAw==");
             signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
             signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
             signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));

+ 136 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsUserOnlineStateServiceImpl.java

@@ -0,0 +1,136 @@
+package com.fs.store.service.impl;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.store.domain.FsUser;
+import com.google.common.collect.Lists;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.store.mapper.FsUserOnlineStateMapper;
+import com.fs.store.domain.FsUserOnlineState;
+import com.fs.store.service.IFsUserOnlineStateService;
+
+/**
+ * 用户上线情况Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@Service
+public class FsUserOnlineStateServiceImpl extends ServiceImpl<FsUserOnlineStateMapper, FsUserOnlineState> implements IFsUserOnlineStateService {
+
+    @Autowired
+    private SqlSessionFactory sqlSessionFactory;
+
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    @Override
+    public FsUserOnlineState selectFsUserOnlineStateById(Long userId)
+    {
+        return baseMapper.selectFsUserOnlineStateById(userId);
+    }
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况
+     */
+    @Override
+    public List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState)
+    {
+        return baseMapper.selectFsUserOnlineStateList(fsUserOnlineState);
+    }
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState)
+    {
+        fsUserOnlineState.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserOnlineState(fsUserOnlineState);
+    }
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState)
+    {
+        return baseMapper.updateFsUserOnlineState(fsUserOnlineState);
+    }
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的用户上线情况主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOnlineStateByIds(Long[] userIds)
+    {
+        return baseMapper.deleteFsUserOnlineStateByIds(userIds);
+    }
+
+    /**
+     * 删除用户上线情况信息
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOnlineStateById(Long userId)
+    {
+        return baseMapper.deleteFsUserOnlineStateById(userId);
+    }
+
+    @Override
+    public void insertUserNotOnline() {
+        // 1、获取需要新增/更新的数据
+        // 获取当前所有没有看课记录的用户
+        List<FsUserOnlineState> fsUserOnlineStates = baseMapper.selectUserNotOnline();
+
+        // 2、分批次新增
+        this.batchInsertOnline(fsUserOnlineStates);
+
+        // 3、移除有看课记录的用户
+        List<FsUser> fsUsers = baseMapper.selectExistWatchLogUser();
+        List<Long> moveUserIds = fsUsers.stream().map(FsUser::getUserId).collect(Collectors.toList());
+        if(!moveUserIds.isEmpty()){
+            baseMapper.deleteFsUserOnlineStateByIds(moveUserIds.toArray(new Long[0]));
+        }
+
+    }
+
+    private void batchInsertOnline(List<FsUserOnlineState> list) {
+        // 分批次处理,一次提交500条
+        List<List<FsUserOnlineState>> batches = Lists.partition(list, 500);
+        batches.forEach(batch -> {
+            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+            try {
+                FsUserOnlineStateMapper mapper = sqlSession.getMapper(FsUserOnlineStateMapper.class);
+                batch.forEach(mapper::insertFsUserOnlineState);
+                sqlSession.commit();
+            } finally {
+                sqlSession.close();
+            }
+        });
+    }
+}

+ 23 - 4
fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java

@@ -43,6 +43,7 @@ import com.fs.store.enums.BillDetailEnum;
 import com.fs.store.mapper.FsStoreOrderMapper;
 import com.fs.store.mapper.FsStoreProductAttrValueMapper;
 import com.fs.store.mapper.FsUserMapper;
+import com.fs.store.mapper.*;
 import com.fs.store.param.SelectCusListPageParam;
 import com.fs.store.param.h5.CourseAnalysisParam;
 import com.fs.store.param.h5.FsUserPageListParam;
@@ -53,6 +54,7 @@ import com.fs.store.service.IFsUserService;
 import com.fs.store.service.cache.IFsUserCourseCountCacheService;
 import com.fs.store.vo.FSUserVO;
 import com.fs.store.vo.FsCompanyUserListQueryVO;
+import com.fs.store.vo.FsUserLastCount;
 import com.fs.store.vo.FsUserTuiVO;
 import com.fs.store.vo.h5.*;
 import com.fs.system.mapper.SysDictDataMapper;
@@ -136,7 +138,10 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
-    private SysDictDataMapper dictDataMapper; 
+    private SysDictDataMapper dictDataMapper;
+
+    @Autowired
+    private FsUserCourseCountMapper fsUserCourseCountMapper;
 
     /**
      * 查询用户
@@ -472,6 +477,10 @@ public class FsUserServiceImpl implements IFsUserService
             param.setUserId(0L);
             param.setCompanyId(companyUser.getCompanyId());
         }
+        //筛选问题
+        if (StringUtils.isNotBlank(param.getCompanyUserId())){
+            param.setUserId(Long.valueOf(param.getCompanyUserId()));
+        }
 
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
 
@@ -539,6 +548,11 @@ public class FsUserServiceImpl implements IFsUserService
 
         List<FsUserPageListVO> fsUserPageListVOS = fsUserMapper.selectFsUserPageListNew(param);
         Map<Long, CompanyTag> tagMap = companyTagCacheService.queryAllTagMap();
+        //获取会员的最新的看课状态和最后看课时间
+        Set<Long> userIds = fsUserPageListVOS.stream().map(FsUserPageListVO::getUserId).collect(Collectors.toSet());
+        List<FsUserLastCount> fsUserCourseCounts = fsUserCourseCountMapper.selectUserLastCount(userIds);
+        Map<Long, FsUserLastCount> countMap = fsUserCourseCounts.stream().collect(Collectors.toMap(FsUserLastCount::getUserId, Function.identity()));
+
         for (FsUserPageListVO item : fsUserPageListVOS) {
             if(item.getCompanyUserId() != null) {
                 String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
@@ -562,11 +576,16 @@ public class FsUserServiceImpl implements IFsUserService
                     item.setMissCourseCount(byUserId.getMissCourseCount());
                     item.setMissCourseStatus(byUserId.getMissCourseStatus());
                     if(StringUtils.isNotEmpty(byUserId.getPartCourseCount())){
-                        item.setPartCourseCount(Long.valueOf(byUserId.getPartCourseCount()));
+                        item.setPartCourseCount(new BigDecimal(byUserId.getPartCourseCount()).longValue());
                     }
-                    item.setCourseCountStatus(byUserId.getStatus());
+//                    item.setCourseCountStatus(byUserId.getStatus());
                     item.setStopWatchDays(byUserId.getStopWatchDays());
                     item.setCompleteWatchDate(byUserId.getCompleteWatchDate());
+                    item.setLastWatchDate(byUserId.getLastWatchDate());
+                }
+                FsUserLastCount fsUserCourseCount = countMap.get(item.getUserId());
+                if(fsUserCourseCount != null){
+                    item.setCourseCountStatus(fsUserCourseCount.getStatus());
                 }
                 String userTagByUserId = companyTagCacheService
                         .findUserTagByUserId(item.getUserId(),item.getCompanyUserId());
@@ -903,7 +922,7 @@ public class FsUserServiceImpl implements IFsUserService
     @Transactional(rollbackFor = Exception.class)
     public ResponseResult<Boolean> becomeMember(FsUserCourseBeMemberParam param) {
         //查询用户
-         FsUser fsUser = fsUserMapper.selectFsUserById(param.getUserId());
+        FsUser fsUser = fsUserMapper.selectFsUserById(param.getUserId());
         if (Objects.isNull(fsUser)){
             return ResponseResult.fail(404,"当前用户信息不存在");
         }

+ 36 - 0
fs-service-system/src/main/java/com/fs/store/vo/FsUserLastCount.java

@@ -0,0 +1,36 @@
+package com.fs.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 用户看课统计对象 fs_user_course_count
+ *
+ * @author fs
+ * @date 2025-04-02
+ */
+@Data
+public class FsUserLastCount {
+    /**
+     * 用户id
+     */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /**
+     * 最后一次看课时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最后一次看课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastWatchDate;
+
+    /**
+     * 用户状态,1-正常;2-停止;3-未看
+     */
+    @Excel(name = "用户状态,1-正常;2-停止;3-未看")
+    private Long status;
+
+}

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

@@ -79,6 +79,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 #{userId}
             </foreach>
             </if>
+            <if test="watchLogId != null">
+                AND cal_inner.watch_log_id = #{watchLogId}
+            </if>
         </where>
         ORDER BY cal_inner.log_id DESC
         LIMIT ${(pageNum-1)*pageSize}, ${pageSize}

+ 34 - 9
fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodDaysMapper.xml

@@ -30,6 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         inner join fs_user_course b on a.course_id = b.course_id
         inner join fs_user_course_video c on a.video_id = c.video_id
         <where>
+            and a.del_flag ='0'
             <if test="periodId != null "> and period_id = #{periodId}</if>
             <if test="courseId != null "> and a.course_id = #{courseId}</if>
             <if test="videoId != null "> and a.video_id = #{videoId}</if>
@@ -89,6 +90,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="periodId != null">period_id = #{periodId},</if>
             <if test="lesson != null">lesson = #{lesson},</if>
             <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="startDateTime != null">start_date_time = #{startDateTime},</if>
+            <if test="endDateTime != null">end_date_time = #{endDateTime},</if>
+            <if test="lastJoinTime != null">last_join_time = #{lastJoinTime},</if>
+            <if test="dayDate != null">day_date = #{dayDate},</if>
             <if test="endTime != null">end_time = #{endTime},</if>
             <if test="courseId != null">course_id = #{courseId},</if>
             <if test="videoId != null">video_id = #{videoId},</if>
@@ -115,7 +120,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <select id="selectCourseVideoList" resultType="FsUserCoursePeriodDays">
-       select * from fs_user_course_period_days where period_id in
+       select * from fs_user_course_period_days where del_flag ='0' and period_id in
         <foreach collection="periodIds" item="item" open="(" separator="," close=")">
            #{item}
         </foreach>
@@ -128,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ucp.period_name dictLabel
         from fs_user_course_period ucp
         <where>
+            and ucp.del_flag ='0'
             <if test="params.name != null and params.name != ''">
                 and ucp.period_name like concat('%', #{params.name}, '%')
             </if>
@@ -141,17 +147,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update fs_user_course_period_days
         <trim prefix="set" suffixOverrides=",">
             day_date =
-                <foreach collection="list" item="item" open="case" close=" end,">
-                    <if test="item.dayDate !=null ">
-                        when id=#{item.id} then #{item.dayDate}
-                    </if>
-                </foreach>
+            <foreach collection="list" item="item" open="case" close=" end,">
+                <if test="item.dayDate != null">
+                    when id=#{item.id} then #{item.dayDate}
+                </if>
+            </foreach>
+            start_date_time =
+            <foreach collection="list" item="item" open="case" close=" end,">
+                <if test="item.startDateTime != null">
+                    when id=#{item.id} then #{item.startDateTime}
+                </if>
+            </foreach>
+            end_date_time =
+            <foreach collection="list" item="item" open="case" close=" end,">
+                <if test="item.endDateTime != null">
+                    when id=#{item.id} then #{item.endDateTime}
+                </if>
+            </foreach>
+            last_join_time =
+            <foreach collection="list" item="item" open="case" close=" end,">
+                <if test="item.lastJoinTime != null">
+                    when id=#{item.id} then #{item.lastJoinTime}
+                </if>
+            </foreach>
             lesson =
             <foreach collection="list" item="item" open="case" close=" end,">
-                <if test="item.lesson !=null ">
+                <if test="item.lesson != null">
                     when id=#{item.id} then #{item.lesson}
                 </if>
             </foreach>
+
         </trim>
         where id in
         <foreach collection="list" index="index" item="item" separator="," open="(" close=")">
@@ -181,7 +206,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             a.start_date_time as courseStartDateTime
              ,b.course_name
              ,c.title as videoTitle
-             ,period.period_name,period.period_starting_time
+             ,period.period_name,period.period_starting_time,period.period_line
              ,company.company_id,company.company_name
              ,company_user.user_id as companyUserId, company_user.nick_name as companyUserName
         FROM
@@ -191,7 +216,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 left join fs_user_course_period period on period.period_id = a.period_id
                 left join company on FIND_IN_SET(company.company_id, period.company_id) > 0
                 left join company_user on company_user.company_id = company.company_id
-        where period.period_name is not null
+        where period.del_flag ='0' and period.period_name is not null
         ORDER BY
             a.day_date
     </select>

+ 15 - 8
fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml

@@ -20,6 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="maxViewNum"    column="max_view_num"    />
         <result property="courseLogo"    column="course_logo"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
+        <result property="periodLine"    column="period_line"    />
     </resultMap>
 
     <sql id="selectFsUserCoursePeriodVo">
@@ -29,6 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsUserCoursePeriodList" parameterType="FsUserCoursePeriod" resultMap="FsUserCoursePeriodResult">
         <include refid="selectFsUserCoursePeriodVo"/>
         <where>
+            del_flag ='0'
             <if test="periodName != null  and periodName != ''"> and period_name like concat('%', #{periodName}, '%')</if>
             <if test="companyId != null "> and FIND_IN_SET (#{companyId}, company_id ) > 0</if>
             <if test="trainingCampId != null "> and training_camp_id = #{trainingCampId}</if>
@@ -38,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="periodType != null "> and period_type = #{periodType}</if>
             <if test="periodStartingTime != null "> and period_starting_time = #{periodStartingTime}</if>
             <if test="periodEndTime != null "> and period_end_time = #{periodEndTime}</if>
+            <if test="periodLine != null "> and period_line = #{periodLine}</if>
         </where>
     </select>
 
@@ -57,6 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         period_type,
         period_end_time,
         period_starting_time,
+        period_line,
         fctc.training_camp_name AS trainingCampName,
         GROUP_CONCAT( company.company_name ) AS companyName
         FROM
@@ -64,6 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join fs_user_course_training_camp fctc on fctc.training_camp_id = fs_user_course_period.training_camp_id
         left join company on FIND_IN_SET(company.company_id, fs_user_course_period.company_id) > 0
         <where>
+            fs_user_course_period.del_flag ='0'
             <if test="trainingCampId != null"> and fs_user_course_period.training_camp_id = #{trainingCampId}</if>
             <if test="periodName != null  and periodName != ''"> and period_name like concat('%', #{periodName}, '%')</if>
             <if test="companyIdList != null and companyIdList.size() > 0 ">
@@ -75,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="periodStartingTime != null "> and period_starting_time &gt;= #{periodStartingTime}</if>
             <if test="periodEndTime != null "> and period_end_time &lt;= #{periodEndTime}</if>
+            <if test="periodLine != null "> and period_line = #{periodLine}</if>
         </where>
         group by fs_user_course_period.period_id
         order by create_time desc
@@ -82,7 +88,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectFsUserCoursePeriodById" parameterType="Long" resultMap="FsUserCoursePeriodResult">
         <include refid="selectFsUserCoursePeriodVo"/>
-        where period_id = #{periodId}
+        where del_flag ='0' and  period_id = #{periodId}
     </select>
 
     <insert id="insertFsUserCoursePeriod" parameterType="FsUserCoursePeriod" useGeneratedKeys="true" keyProperty="periodId">
@@ -107,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxViewNum != null">max_view_num,</if>
             <if test="courseLogo != null">course_logo,</if>
             <if test="openCommentStatus != null">open_comment_status,</if>
+            <if test="periodLine != null">period_line,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="periodId != null">#{periodId},</if>
@@ -128,6 +135,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxViewNum != null">#{maxViewNum},</if>
             <if test="courseLogo != null">#{courseLogo},</if>
             <if test="openCommentStatus != null">#{openCommentStatus},</if>
+            <if test="periodLine != null">#{periodLine},</if>
         </trim>
     </insert>
 
@@ -152,6 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxViewNum != null">max_view_num = #{maxViewNum},</if>
             <if test="courseLogo != null and courseLogo !=''">course_logo = #{courseLogo},</if>
             <if test="openCommentStatus != null">open_comment_status = #{openCommentStatus},</if>
+            <if test="periodLine != null">period_line = #{periodLine},</if>
         </trim>
         where period_id = #{periodId}
     </update>
@@ -232,7 +241,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectFsUserCoursePeriodsByIds" resultType="FsUserCoursePeriod">
         select * from fs_user_course_period
-        where period_id in
+        where del_flag ='0' and  period_id in
         <foreach collection="periodIds" item="item" open="(" separator="," close=")">
             #{item}
         </foreach>
@@ -242,23 +251,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select count(ucp.period_id)
         from fs_user_course_period ucp
         inner join fs_user_course_training_camp uctc on uctc.training_camp_id = ucp.training_camp_id
-        where uctc.training_camp_id in
+        where ucp.del_flag ='0' and uctc.training_camp_id in
         <foreach collection="params.ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>
         <![CDATA[
-        and ((ucp.period_starting_time <= #{params.date}
-        and ucp.period_end_time >= #{params.date}
-        ) or ucp.period_end_time < #{params.date})
+        and (ucp.period_starting_time <= #{params.date}
+        and ucp.period_end_time > #{params.date})
         ]]>
     </select>
 
     <select id="selectPeriodListByTrainingCampIds" resultType="FsUserCoursePeriod">
         select * from fs_user_course_period
-         where training_camp_id in
+         where del_flag ='0' and  training_camp_id in
         <foreach collection="trainingCampIds" item="item" open="(" separator="," close=")">
             #{item}
         </foreach>
     </select>
-
 </mapper>

+ 3 - 1
fs-service-system/src/main/resources/mapper/course/FsUserCourseTrainingCampMapper.xml

@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join fs_user_course_period ctp on ctc.training_camp_id = ctp.training_camp_id
         left join fs_course_watch_log cu on cu.period_id = ctp.period_id
         <where>
+            ctc.del_flag ='0'
             <if test="params.trainingCampName != null and params.trainingCampName != ''">
                 and ctc.training_camp_name like concat('%',#{params.trainingCampName},'%')
             </if>
@@ -44,8 +45,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ctc.training_camp_name dictLabel
         from fs_user_course_training_camp ctc
         <where>
+            ctc.del_flag ='0'
             <if test="params.name != null and params.name != ''">
-                ctc.training_camp_name like concat('%', #{params.name}, '%')
+               and ctc.training_camp_name like concat('%', #{params.name}, '%')
             </if>
         </where>
     </select>

+ 13 - 3
fs-service-system/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml

@@ -118,7 +118,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectByParamsList" resultMap="FsUserCourseVideoRedPackageResult">
         select id, company_id, video_id, red_packet_money, period_id, data_type
         from fs_user_course_video_red_package
-        where data_type = 2
+        where del_flag ='0' and data_type = 2
         and (
         <foreach collection="list" item="item" separator=" OR ">
             (period_id = #{item.periodId} AND video_id = #{item.videoId} AND company_id = #{item.companyId})
@@ -133,10 +133,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where period_id = #{periodId}
         and video_id = #{videoId}
         and company_id = #{companyId}
-        and data_type = 2
+        and del_flag ='0' and data_type = 2
     </update>
 
     <select id="selectByRuleIds" resultType="com.fs.course.domain.FsUserCourseVideoRedPackage">
-        select * from fs_user_course_video_red_package where data_type = 3 and rule_id in <foreach collection="ruleIds" open="(" separator="," close=")" item="item">#{item}</foreach>
+        select * from fs_user_course_video_red_package where del_flag ='0' and data_type = 3 and rule_id in <foreach collection="ruleIds" open="(" separator="," close=")" item="item">#{item}</foreach>
     </select>
+
+    <select id="selectRedPacketByCompanyId" resultType="com.fs.course.domain.FsUserCourseVideoRedPackage">
+        select * from fs_user_course_video_red_package
+        <where>
+        <if test="videoId != null "> and video_id =#{videoId}</if>
+        <if test="companyId != null "> and company_id = #{companyId}</if>
+        <if test="periodId != null "> and period_id = #{periodId}</if>
+        </where>
+    </select>
+
 </mapper>

+ 25 - 1
fs-service-system/src/main/resources/mapper/course/FsUserWatchCourseStatisticsMapper.xml

@@ -22,6 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="userNum"    column="user_num"    />
         <result property="watchNum"    column="watch_num"    />
         <result property="completeWatchNum"    column="complete_watch_num"    />
+        <result property="onlineRate"    column="online_rate"    />
         <result property="completeWatchRate"    column="complete_watch_rate"    />
         <result property="answerNum"    column="answer_num"    />
         <result property="answerRightNum"    column="answer_right_num"    />
@@ -31,7 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsUserWatchCourseStatisticsVo">
-        select id, period_id, period_name, course_id, course_name, video_id, video_title, company_id, company_name, company_user_id, course_start_date_time, company_user_name, period_starting_time, new_user_num, user_num, watch_num, complete_watch_num, complete_watch_rate, answer_num, answer_right_num, answer_right_rate, red_packet_num, red_packet_amount from fs_user_watch_course_statistics
+        select id, period_id, period_name, course_id, course_name, video_id, video_title, company_id, company_name, company_user_id, course_start_date_time, company_user_name, period_starting_time, new_user_num, user_num, watch_num, complete_watch_num, online_rate, complete_watch_rate, answer_num, answer_right_num, answer_right_rate, red_packet_num, red_packet_amount from fs_user_watch_course_statistics
     </sql>
 
     <select id="selectFsUserWatchCourseStatisticsList" parameterType="FsUserWatchCourseStatistics" resultMap="FsUserWatchCourseStatisticsResult">
@@ -79,6 +80,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         sum(new_user_num) as new_user_num,
         sum(user_num) as user_num,
         sum(watch_num) as watch_num,
+        ifnull(
+        ROUND(
+        ( sum(watch_num) / sum(user_num) ) * 100, 2
+        ), 0
+        ) as online_rate,
         sum(complete_watch_num) as complete_watch_num ,
         complete_watch_rate as complete_watch_rate1,
         ifnull(
@@ -139,6 +145,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">user_num,</if>
             <if test="watchNum != null">watch_num,</if>
             <if test="completeWatchNum != null">complete_watch_num,</if>
+            <if test="onlineRate != null">online_rate,</if>
             <if test="completeWatchRate != null">complete_watch_rate,</if>
             <if test="answerNum != null">answer_num,</if>
             <if test="answerRightNum != null">answer_right_num,</if>
@@ -163,6 +170,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">#{userNum},</if>
             <if test="watchNum != null">#{watchNum},</if>
             <if test="completeWatchNum != null">#{completeWatchNum},</if>
+            <if test="onlineRate != null">#{onlineRate},</if>
             <if test="completeWatchRate != null">#{completeWatchRate},</if>
             <if test="answerNum != null">#{answerNum},</if>
             <if test="answerRightNum != null">#{answerRightNum},</if>
@@ -191,6 +199,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">user_num = #{userNum},</if>
             <if test="watchNum != null">watch_num = #{watchNum},</if>
             <if test="completeWatchNum != null">complete_watch_num = #{completeWatchNum},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate},</if>
             <if test="completeWatchRate != null">complete_watch_rate = #{completeWatchRate},</if>
             <if test="answerNum != null">answer_num = #{answerNum},</if>
             <if test="answerRightNum != null">answer_right_num = #{answerRightNum},</if>
@@ -232,6 +241,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">user_num,</if>
             <if test="watchNum != null">watch_num,</if>
             <if test="completeWatchNum != null">complete_watch_num,</if>
+            <if test="onlineRate != null">online_rate,</if>
             <if test="completeWatchRate != null">complete_watch_rate,</if>
             <if test="answerNum != null">answer_num,</if>
             <if test="answerRightNum != null">answer_right_num,</if>
@@ -257,6 +267,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">#{userNum},</if>
             <if test="watchNum != null">#{watchNum},</if>
             <if test="completeWatchNum != null">#{completeWatchNum},</if>
+            <if test="onlineRate != null">#{onlineRate},</if>
             <if test="completeWatchRate != null">#{completeWatchRate},</if>
             <if test="answerNum != null">#{answerNum},</if>
             <if test="answerRightNum != null">#{answerRightNum},</if>
@@ -267,10 +278,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </trim>
         on duplicate key update
         <trim suffixOverrides=",">
+            <if test="periodId != null">period_id = #{periodId},</if>
+            <if test="periodName != null">period_name = #{periodName},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="courseName != null">course_name = #{courseName},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="videoTitle != null">video_title = #{videoTitle},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyName != null">company_name = #{companyName},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="courseStartDateTime != null">course_start_date_time = #{courseStartDateTime},</if>
+            <if test="companyUserName != null">company_user_name = #{companyUserName},</if>
+            <if test="periodStartingTime != null">period_starting_time = #{periodStartingTime},</if>
             <if test="newUserNum != null">new_user_num = #{newUserNum},</if>
             <if test="userNum != null">user_num = #{userNum},</if>
             <if test="watchNum != null">watch_num = #{watchNum},</if>
             <if test="completeWatchNum != null">complete_watch_num = #{completeWatchNum},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate},</if>
             <if test="completeWatchRate != null">complete_watch_rate = #{completeWatchRate},</if>
             <if test="answerNum != null">answer_num = #{answerNum},</if>
             <if test="answerRightNum != null">answer_right_num = #{answerRightNum},</if>

+ 11 - 1
fs-service-system/src/main/resources/mapper/course/FsUserWatchStatisticsMapper.xml

@@ -15,11 +15,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="userNum"    column="user_num"    />
         <result property="watchNum"    column="watch_num"    />
         <result property="completeWatchNum"    column="complete_watch_num"    />
+        <result property="onlineRate"    column="online_rate"    />
         <result property="completeWatchRate"    column="complete_watch_rate"    />
     </resultMap>
 
     <sql id="selectFsUserWatchStatisticsVo">
-        select id, period_id, period_name, period_starting_time, new_user_num, user_num, watch_num, complete_watch_num, complete_watch_rate, company_id, company_name from fs_user_watch_statistics
+        select id, period_id, period_name, period_starting_time, new_user_num, user_num, watch_num, complete_watch_num, online_rate, complete_watch_rate, company_id, company_name from fs_user_watch_statistics
     </sql>
 
     <select id="selectFsUserWatchStatisticsList" parameterType="FsUserWatchStatistics" resultMap="FsUserWatchStatisticsResult">
@@ -55,6 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">user_num,</if>
             <if test="watchNum != null">watch_num,</if>
             <if test="completeWatchNum != null">complete_watch_num,</if>
+            <if test="onlineRate != null">online_rate,</if>
             <if test="completeWatchRate != null">complete_watch_rate,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -67,6 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">#{userNum},</if>
             <if test="watchNum != null">#{watchNum},</if>
             <if test="completeWatchNum != null">#{completeWatchNum},</if>
+            <if test="onlineRate != null">#{onlineRate},</if>
             <if test="completeWatchRate != null">#{completeWatchRate},</if>
          </trim>
     </insert>
@@ -83,6 +86,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">user_num = #{userNum},</if>
             <if test="watchNum != null">watch_num = #{watchNum},</if>
             <if test="completeWatchNum != null">complete_watch_num = #{completeWatchNum},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate},</if>
             <if test="completeWatchRate != null">complete_watch_rate = #{completeWatchRate},</if>
         </trim>
         where id = #{id}
@@ -134,6 +138,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="watchNum != null">watch_num,</if>
             <if test="completeWatchNum != null">complete_watch_num,</if>
             <if test="completeWatchRate != null">complete_watch_rate,</if>
+            <if test="onlineRate != null">online_rate,</if>
             <if test="createTime != null">create_time,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -146,17 +151,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userNum != null">#{userNum},</if>
             <if test="watchNum != null">#{watchNum},</if>
             <if test="completeWatchNum != null">#{completeWatchNum},</if>
+            <if test="onlineRate != null">#{onlineRate},</if>
             <if test="completeWatchRate != null">#{completeWatchRate},</if>
             <if test="createTime != null">#{createTime},</if>
         </trim>
         on duplicate key update
         <trim suffixOverrides=",">
+            <if test="periodId != null">period_id = #{periodId},</if>
+            <if test="periodName != null">period_name = #{periodName},</if>
+            <if test="periodStartingTime != null">period_starting_time = #{periodStartingTime},</if>
             <if test="companyId != null and companyId !='' ">company_id = #{companyId},</if>
             <if test="companyName != null">company_name = #{companyName},</if>
             <if test="newUserNum != null">new_user_num = #{newUserNum},</if>
             <if test="userNum != null">user_num = #{userNum},</if>
             <if test="watchNum != null">watch_num = #{watchNum},</if>
             <if test="completeWatchNum != null">complete_watch_num = #{completeWatchNum},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate},</if>
             <if test="completeWatchRate != null">complete_watch_rate = #{completeWatchRate},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
         </trim>

+ 11 - 0
fs-service-system/src/main/resources/mapper/store/FsUserCompanyUserMapper.xml

@@ -79,6 +79,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </trim>
         where id = #{id}
     </update>
+    <update id="transfer">
+        update fs_user_company_user set company_user_id=#{targetCompanyUserId}
+        <where>
+            <if test="param.userIds != null and param.userIds.size() > 0">
+                user_id in
+                <foreach collection="param.userIds" item="item" separator="," open="(" close=")">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+    </update>
 
     <delete id="deleteFsUserCompanyUserById" parameterType="Long">
         delete from fs_user_company_user where id = #{id}

+ 27 - 1
fs-service-system/src/main/resources/mapper/store/FsUserCourseCountMapper.xml

@@ -55,7 +55,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
     <select id="findByUserId" resultType="com.fs.store.domain.FsUserCourseCount">
-        select * from fs_user_course_count where user_id = ${userId} limit 1
+        select
+            fs_user_course_count.user_id,
+            ifnull ( sum( fs_user_course_count.watch_course_count ), 0 ) AS watch_course_count,
+            ifnull ( sum( fs_user_course_count.miss_course_count ), 0 ) AS miss_course_count,
+            ifnull ( sum( fs_user_course_count.part_course_count ), 0 ) AS part_course_count,
+            Max( fs_user_course_count.last_watch_date ) AS last_watch_date,
+            ifnull ( sum( fs_user_course_count.stop_watch_days ), 0 ) AS stop_watch_days,
+            Max( fs_user_course_count.complete_watch_date) AS complete_watch_date
+            from fs_user_course_count where user_id = ${userId}
     </select>
 
     <insert id="insertFsUserCourseCount" parameterType="FsUserCourseCount">
@@ -245,5 +253,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </trim>
     </insert>
 
+    <select id="selectUserLastCount" resultType="FsUserLastCount">
+        SELECT
+            fs_user_course_count.user_id,
+            fs_user_course_count.`status`,
+            fs_user_course_count.last_watch_date
+        FROM
+            fs_user_course_count
+                INNER JOIN ( SELECT MAX( id ) AS id FROM fs_user_course_count
+                where fs_user_course_count.user_id in
+                <foreach item="userId" collection="userIds" open="(" separator="," close=")">
+                    #{userId}
+                </foreach>
+                GROUP BY user_id ) t2 ON fs_user_course_count.id = t2.id
+                where fs_user_course_count.user_id in
+                <foreach item="userId" collection="userIds" open="(" separator="," close=")">
+                    #{userId}
+                </foreach>
+    </select>
 
 </mapper>

+ 19 - 17
fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml

@@ -640,7 +640,7 @@
             fs_user
                 LEFT JOIN fs_user_company_user ON fs_user_company_user.user_id = fs_user.user_id
                 LEFT JOIN company_user ON company_user.user_id = fs_user_company_user.company_user_id
-        WHERE fs_user.is_del = 0
+        WHERE fs_user.is_del = 0 and fs_user_company_user.is_repeat_fans is not null
         <if test="userId != null and userId != 0 ">
             and (fs_user_company_user.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
         </if>
@@ -1246,42 +1246,44 @@
     <select id="countCourseRankingByComplete" resultType="FsCourseRankingVO">
         SELECT
         fcv.title AS videoName,
+        fcv.video_id,
         ifnull(
-        ROUND((
-        COUNT( DISTINCT CASE WHEN fcc.complete_watch_count > 0 THEN 1 END ) / count(1))* 100,
+        ROUND(
+        (
+        COUNT( DISTINCT CASE WHEN fwl.log_type = 2 THEN fwl.log_id END ) / count( DISTINCT CASE WHEN fwl.log_type != 3 THEN fwl.log_id END )) * 100,
         2
         ),
         0
         ) AS completeRate
         FROM
-        fs_user_course_count fcc
-        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id
-        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
-        LEFT JOIN company_user ON ucu.company_user_id = company_user.user_id
-        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0
-        LEFT JOIN fs_user_course_video fcv ON fcv.video_id = fcpd.video_id
+        fs_course_watch_log fwl
+        LEFT JOIN fs_user ON fs_user.user_id = fwl.user_id
+        LEFT JOIN fs_user_company_user ON fs_user_company_user.user_id = fs_user.user_id
+        LEFT JOIN company_user ON company_user.user_id = fs_user_company_user.company_user_id
+--         LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0
+        LEFT JOIN fs_user_course_video fcv ON fcv.video_id = fwl.video_id
         <where>
         <if test="userId != null and userId != 0 ">
-            AND ( ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
+            AND ( fs_user_company_user.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
         </if>
         <if test="userId != null and userId == 0 ">
-            and ucu.company_id = #{companyId}
+            and fs_user_company_user.company_id = #{companyId}
         </if>
         <if test="startTime != null and startTime !='' ">
-            AND fcc.create_time &gt;= #{startTime}
+            AND fwl.create_time &gt;= #{startTime}
         </if>
         <if test="endTime != null and endTime != ''">
-            AND fcc.create_time &lt;= #{endTime}
+            AND fwl.create_time &lt;= #{endTime}
         </if>
         <if test="periodId != null and periodId != ''">
-            AND fcpd.period_id =  #{periodId}
+            AND fwl.period_id =  #{periodId}
         </if>
         <if test="videoId != null and videoId != ''">
-            AND fcpd.video_id = #{videoId}
+            AND fwl.video_id = #{videoId}
         </if>
         </where>
         GROUP BY
-        fcpd.video_id
+        fwl.video_id
         <choose>
             <when test="order != null and order == 'asc'">
                 ORDER BY completeRate asc
@@ -1302,7 +1304,7 @@
         ifnull(
         ROUND(
         (
-        COUNT( DISTINCT CASE WHEN fs_course_answer_logs.is_right = 1 THEN 1 END ) / count(1)) * 100,
+        COUNT( DISTINCT CASE WHEN fs_course_answer_logs.is_right = 1 THEN fs_course_answer_logs.log_id END ) / count( DISTINCT fs_course_answer_logs.log_id)) * 100,
         2
         ),
         0

+ 202 - 0
fs-service-system/src/main/resources/mapper/store/FsUserOnlineStateMapper.xml

@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.store.mapper.FsUserOnlineStateMapper">
+
+    <resultMap type="FsUserOnlineState" id="FsUserOnlineStateResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="nickname"    column="nickname"    />
+        <result property="avatar"    column="avatar"    />
+        <result property="phone"    column="phone"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="maOpenId"    column="ma_open_id"    />
+        <result property="mpOpenId"    column="mp_open_id"    />
+        <result property="unionId"    column="union_id"    />
+        <result property="status"    column="status"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyName"    column="company_name"    />
+        <result property="companyUserName"    column="company_user_name"    />
+        <result property="onlineStatus"    column="online_status"    />
+        <result property="onlineTime"    column="online_time"    />
+        <result property="watchCourseCount"    column="watch_course_count"    />
+        <result property="partCourseCount"    column="part_course_count"    />
+        <result property="lastWatchDate"    column="last_watch_date"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsUserOnlineStateVo">
+        select id, user_id, nickname, avatar, phone, create_time, ma_open_id, mp_open_id, union_id, status, company_id, company_user_id, company_name, company_user_name, online_status, online_time, watch_course_count, part_course_count, last_watch_date, update_time from fs_user_online_state
+    </sql>
+
+    <select id="selectFsUserOnlineStateList" parameterType="FsUserOnlineState" resultMap="FsUserOnlineStateResult">
+        <include refid="selectFsUserOnlineStateVo"/>
+        <where>
+            <if test="nickname != null  and nickname != ''"> and nickname like concat('%', #{nickname}, '%')</if>
+            <if test="avatar != null  and avatar != ''"> and avatar = #{avatar}</if>
+            <if test="phone != null  and phone != ''"> and phone like concat ('%', #{phone}, '%')</if>
+            <if test="maOpenId != null  and maOpenId != ''"> and ma_open_id = #{maOpenId}</if>
+            <if test="mpOpenId != null  and mpOpenId != ''"> and mp_open_id = #{mpOpenId}</if>
+            <if test="unionId != null  and unionId != ''"> and union_id = #{unionId}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="companyName != null and companyName !='' "> and company_name like concat('%', #{companyName}, '%')</if>
+            <if test="companyUserName != null and companyUserName !='' "> and company_user_name like concat('%', #{companyUserName}, '%')</if>
+            <if test="onlineStatus != null "> and online_status = #{onlineStatus}</if>
+            <if test="onlineTime != null "> and online_time = #{onlineTime}</if>
+            <if test="watchCourseCount != null "> and watch_course_count = #{watchCourseCount}</if>
+            <if test="partCourseCount != null "> and part_course_count = #{partCourseCount}</if>
+            <if test="lastWatchDate != null "> and last_watch_date = #{lastWatchDate}</if>
+            <if test="updateTime != null "> and update_time = #{updateTime}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserOnlineStateById" parameterType="Long" resultMap="FsUserOnlineStateResult">
+        <include refid="selectFsUserOnlineStateVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsUserOnlineState" parameterType="FsUserOnlineState" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_user_online_state
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="nickname != null">nickname,</if>
+            <if test="avatar != null">avatar,</if>
+            <if test="phone != null">phone,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="maOpenId != null">ma_open_id,</if>
+            <if test="mpOpenId != null">mp_open_id,</if>
+            <if test="unionId != null">union_id,</if>
+            <if test="status != null">status,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyName != null">company_name,</if>
+            <if test="companyUserName != null">company_user_name,</if>
+            <if test="onlineStatus != null">online_status,</if>
+            <if test="onlineTime != null">online_time,</if>
+            <if test="watchCourseCount != null">watch_course_count,</if>
+            <if test="partCourseCount != null">part_course_count,</if>
+            <if test="lastWatchDate != null">last_watch_date,</if>
+            <if test="updateTime != null ">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="nickname != null">#{nickname},</if>
+            <if test="avatar != null">#{avatar},</if>
+            <if test="phone != null">#{phone},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="maOpenId != null">#{maOpenId},</if>
+            <if test="mpOpenId != null">#{mpOpenId},</if>
+            <if test="unionId != null">#{unionId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyName != null">#{companyName},</if>
+            <if test="companyUserName != null">#{companyUserName},</if>
+            <if test="onlineStatus != null">#{onlineStatus},</if>
+            <if test="onlineTime != null">#{onlineTime},</if>
+            <if test="watchCourseCount != null">#{watchCourseCount},</if>
+            <if test="partCourseCount != null">#{partCourseCount},</if>
+            <if test="lastWatchDate != null">#{lastWatchDate},</if>
+            <if test="updateTime != null ">#{updateTime},</if>
+         </trim>
+        on duplicate key update
+        <trim suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyName != null">company_Name = #{companyName},</if>
+            <if test="companyUserName != null">company_user_name = #{companyUserName},</if>
+            <if test="updateTime != null ">update_time = #{updateTime}</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsUserOnlineState" parameterType="FsUserOnlineState">
+        update fs_user_online_state
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="nickname != null">nickname = #{nickname},</if>
+            <if test="avatar != null">avatar = #{avatar},</if>
+            <if test="phone != null">phone = #{phone},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="maOpenId != null">ma_open_id = #{maOpenId},</if>
+            <if test="mpOpenId != null">mp_open_id = #{mpOpenId},</if>
+            <if test="unionId != null">union_id = #{unionId},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyName != null">company_Name = #{companyName},</if>
+            <if test="companyUserName != null">company_user_name = #{companyUserName},</if>
+            <if test="onlineStatus != null">online_status = #{onlineStatus},</if>
+            <if test="onlineTime != null">online_time = #{onlineTime},</if>
+            <if test="watchCourseCount != null">watch_course_count = #{watchCourseCount},</if>
+            <if test="partCourseCount != null">part_course_count = #{partCourseCount},</if>
+            <if test="lastWatchDate != null">last_watch_date = #{lastWatchDate},</if>
+            <if test="updateTime != null ">update_time = #{updateTime}</if>
+        </trim>
+        where user_id = #{userId}
+    </update>
+
+    <delete id="deleteFsUserOnlineStateById" parameterType="Long">
+        delete from fs_user_online_state where user_id = #{userId}
+    </delete>
+
+    <delete id="deleteFsUserOnlineStateByIds" parameterType="String">
+        delete from fs_user_online_state where user_id in
+        <foreach item="userId" collection="array" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </delete>
+
+    <select id="selectUserNotOnline" resultType="FsUserOnlineState">
+        SELECT
+            a.*,
+            company_user.nick_name as companyUserName,
+            company.company_name
+        FROM
+            (
+                SELECT
+                    user_id,
+                    nickname,
+                    avatar,
+                    phone,
+                    create_time,
+                    ma_open_id,
+                    mp_open_id,
+                    union_id,
+                    STATUS,
+                    company_id,
+                    company_user_id,
+                    2 AS onlineStatus,
+                    0 AS watchCourseCount,
+                    0 AS partCourseCount,
+                    NOW() AS updateTime
+                FROM
+                    fs_user
+                WHERE
+                    is_del = 0
+                  AND user_id NOT IN ( SELECT DISTINCT user_id FROM fs_course_watch_log WHERE send_type = 1 )
+            ) a
+                LEFT JOIN company_user ON company_user.user_id = a.company_user_id
+                LEFT JOIN company ON company.company_id = a.company_id
+    </select>
+
+    <select id="selectExistWatchLogUser" resultType="FsUser">
+        SELECT
+            count( 1 ),
+            fs_course_watch_log.user_id,
+            create_time
+        FROM
+            fs_course_watch_log
+        WHERE
+            send_type = 1
+        GROUP BY
+            fs_course_watch_log.user_id
+        HAVING
+            count( 1 ) = 1
+           AND create_time >= DATE_SUB( NOW(), INTERVAL 15 MINUTE )
+    </select>
+
+</mapper>

+ 7 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

@@ -111,6 +111,13 @@ public class WxPayController {
         }
     }
 
+    @PostMapping( "/test")
+    public R test() throws Exception {
+        WxSendRedPacketParam param  = new WxSendRedPacketParam();
+        storePaymentService.sendRedPacketTest(param);
+        return R.ok();
+    }
+
     @PostMapping( "/TransferNotify")
     public String TransferNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws Exception {
         return storePaymentService.transferNotify(notifyData,request);