Browse Source

feat(course): 添加自由学习模式并优化题库导入功能

- 在营期相关表中增加 free_mode 字段以支持自由学习模式
- 更新营期状态、课程时间判断逻辑,适配自由模式
-优化题库导入功能,支持根据标题去重并实现批量更新- 增加题库标题查询接口及批量更新SQL逻辑- 调整导入结果统计方式,区分新增与更新数量
- 定时任务添加订单结算状态刷新方法
xw 2 days ago
parent
commit
84b542a9cd

+ 5 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -1443,5 +1443,10 @@ public class Task {
         }
     }
 
+    //定时任务刷新订单结算状态
+    public void refreshOrderSettlementStatus(){
+        fsStoreOrderScrmService.refreshOrderSettlementStatus();
+    }
+
 
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java

@@ -118,4 +118,10 @@ public class FsUserCoursePeriod
      */
     private Date periodLine;
 
+    /**
+     * 自由学习模式,0-未开启,1-开启(开启后学员不受营期时间限制)
+     */
+    @Excel(name = "自由学习模式")
+    private Integer freeMode;
+
 }

+ 16 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseQuestionBankMapper.java

@@ -74,4 +74,20 @@ public interface FsCourseQuestionBankMapper
 
 
     int insertFsCourseQuestionBankBatch(@Param("list") List<FsCourseQuestionBank> fsCourseQuestionBank);
+
+    /**
+     * 根据标题查询题库
+     *
+     * @param title 标题
+     * @return 题库
+     */
+    FsCourseQuestionBank selectFsCourseQuestionBankByTitle(@Param("title") String title);
+
+    /**
+     * 批量更新题库(根据标题匹配)
+     *
+     * @param fsCourseQuestionBankList 题库列表
+     * @return 结果
+     */
+    int updateFsCourseQuestionBankBatch(@Param("list") List<FsCourseQuestionBank> fsCourseQuestionBankList);
 }

+ 4 - 4
fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java

@@ -118,17 +118,17 @@ public interface FsUserCoursePeriodMapper
     int countPeriodByCampIds(@Param("params") Map<String, Object> params);
 
     /**
-     * 开营
+     * 开营(跳过自由模式的营期)
      * @param now   当前日期
      */
-    @Update("update fs_user_course_period set period_status = 2, update_time = now() where period_status = 1 and period_starting_time <= #{now}")
+    @Update("update fs_user_course_period set period_status = 2, update_time = now() where period_status = 1 and period_starting_time <= #{now} and (free_mode is null or free_mode != 1)")
     void startPeriod(@Param("now") LocalDate now);
 
     /**
-     * 关营
+     * 关营(跳过自由模式的营期)
      * @param now   当前日期
      */
-    @Update("update fs_user_course_period set period_status = 3, update_time = now() where period_status = 2 and period_end_time < #{now}")
+    @Update("update fs_user_course_period set period_status = 3, update_time = now() where period_status = 2 and period_end_time < #{now} and (free_mode is null or free_mode != 1)")
     void endPeriod(@Param("now") LocalDate now);
 
 

+ 48 - 13
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -465,16 +465,19 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
      *
      * @param list     数据
      * @param nickName 昵称
+     * @param userId   用户ID
      * @return String
      */
     @Override
-    public String importData(List<FsCourseQuestionBankImportDTO> list, String nickName,Long userId) {
+    @Transactional
+    public String importData(List<FsCourseQuestionBankImportDTO> list, String nickName, Long userId) {
         if (Objects.isNull(list) || list.isEmpty()) {
             throw new ServiceException("导入数据不能为空");
         }
 
         ImportResult result = new ImportResult();
-        List<FsCourseQuestionBank> importData = new ArrayList<>();
+        List<FsCourseQuestionBank> insertList = new ArrayList<>();
+        List<FsCourseQuestionBank> updateList = new ArrayList<>();
         Map<String, FsUserCourseCategory> categoryData = courseCategoryMapper.queryAllCategoryData();
 
         for (FsCourseQuestionBankImportDTO importDTO : list) {
@@ -489,17 +492,34 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                 // 构建题目对象
                 FsCourseQuestionBank questionBank = buildQuestionBank(importDTO, categoryData, nickName);
                 questionBank.setUserId(userId);
-                importData.add(questionBank);
-                result.addSuccess(importDTO.getTitle());
+
+                // 根据标题查询是否存在
+                FsCourseQuestionBank existingQuestion = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByTitle(importDTO.getTitle());
+                
+                if (existingQuestion != null) {
+                    // 标题存在,更新除标题外的其他信息
+                    questionBank.setId(existingQuestion.getId());
+                    updateList.add(questionBank);
+                    result.addUpdate(importDTO.getTitle());
+                } else {
+                    // 标题不存在,新增
+                    insertList.add(questionBank);
+                    result.addInsert(importDTO.getTitle());
+                }
 
             } catch (Exception e) {
                 result.addFailure(importDTO.getTitle(), "导入异常: " + e.getMessage());
             }
         }
 
-        // 批量保存
-        if (!importData.isEmpty()) {
-            fsCourseQuestionBankMapper.insertFsCourseQuestionBankBatch(importData);
+        // 批量新增
+        if (!insertList.isEmpty()) {
+            fsCourseQuestionBankMapper.insertFsCourseQuestionBankBatch(insertList);
+        }
+
+        // 批量更新
+        if (!updateList.isEmpty()) {
+            fsCourseQuestionBankMapper.updateFsCourseQuestionBankBatch(updateList);
         }
 
         return result.buildResultMessage();
@@ -739,14 +759,21 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     }
 
     private static class ImportResult {
-        private int successNum = 0;
+        private int insertNum = 0;
+        private int updateNum = 0;
         private int failureNum = 0;
-        private final StringBuilder successMsg = new StringBuilder();
+        private final StringBuilder insertMsg = new StringBuilder();
+        private final StringBuilder updateMsg = new StringBuilder();
         private final StringBuilder errorMsg = new StringBuilder();
 
-        public void addSuccess(String title) {
-            successNum++;
-            successMsg.append("<br/>").append(successNum).append("、题目 ").append(title).append(" 导入成功");
+        public void addInsert(String title) {
+            insertNum++;
+            insertMsg.append("<br/>").append(insertNum).append("、题目 ").append(title).append(" 新增成功");
+        }
+
+        public void addUpdate(String title) {
+            updateNum++;
+            updateMsg.append("<br/>").append(updateNum).append("、题目 ").append(title).append(" 更新成功");
         }
 
         public void addFailure(String title, String error) {
@@ -755,7 +782,15 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         }
 
         public String buildResultMessage() {
-            return "导入完成!成功" + successNum + " 条,失败" + failureNum + "条。" + errorMsg + successMsg;
+            int totalSuccess = insertNum + updateNum;
+            StringBuilder message = new StringBuilder();
+            message.append("导入完成!成功").append(totalSuccess).append(" 条");
+            message.append("(新增").append(insertNum).append("条,更新").append(updateNum).append("条)");
+            message.append(",失败").append(failureNum).append("条。");
+            message.append(errorMsg);
+            message.append(insertMsg);
+            message.append(updateMsg);
+            return message.toString();
         }
     }
 

+ 59 - 24
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -152,14 +152,19 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     public R addCourse(FsUserCoursePeriodDays entity) {
         FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(entity.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;
-        }else{
-            days = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
+        
+        // 如果开启了自由模式,则不需要检查营期天数限制
+        if (period.getFreeMode() == null || period.getFreeMode() != 1) {
+            long days;
+            if(period.getPeriodType() == 2){
+                days = 1;
+            }else{
+                days = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
+            }
+            days++;
+            if(dayList.size() + entity.getVideoIds().size() > days) return R.error("课程不能超过营期范围");
         }
-        days++;
-        if(dayList.size() + entity.getVideoIds().size() > days) return R.error("课程不能超过营期范围");
+        
         if(dayList.stream().anyMatch(e -> entity.getVideoIds().contains(e.getVideoId()))) return R.error("不能添加相同章节");
         AtomicInteger i = new AtomicInteger(0);
         FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
@@ -194,12 +199,19 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
             }
             day.setVideoId(e);
             day.setCreateTime(new Date());
-            // 默认开启今天及以后的两天
-            LocalDate compareDay = LocalDate.now().plusDays(1);
-            if(day.getDayDate().isBefore(compareDay)){
+            
+            // 设置课程状态:如果开启自由模式,默认为进行中(status=1)
+            if (period.getFreeMode() != null && period.getFreeMode() == 1) {
+                // 自由模式:直接设置为进行中
                 day.setStatus(1);
             } else {
-                day.setStatus(0);
+                // 固定模式:默认开启今天及以后的两天
+                LocalDate compareDay = LocalDate.now().plusDays(1);
+                if(day.getDayDate().isBefore(compareDay)){
+                    day.setStatus(1);
+                } else {
+                    day.setStatus(0);
+                }
             }
             return day;
         }).collect(Collectors.toList());
@@ -236,9 +248,12 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
                 totalDays = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
             }
             totalDays ++;
-            // 5. 验证是否超过营期范围
-            if (existingDays.size() + param.getVideoIds().size() > totalDays) {
-                return R.error("添加的课程数量超过营期范围,营期总天数:" + totalDays + ",当前已有:" + existingDays.size() + ",尝试添加:" + param.getVideoIds().size());
+            
+            // 5. 验证是否超过营期范围(开启自由模式时跳过此验证)
+            if (period.getFreeMode() == null || period.getFreeMode() != 1) {
+                if (existingDays.size() + param.getVideoIds().size() > totalDays) {
+                    return R.error("添加的课程数量超过营期范围,营期总天数:" + totalDays + ",当前已有:" + existingDays.size() + ",尝试添加:" + param.getVideoIds().size());
+                }
             }
 
             // 6. 检查重复视频
@@ -351,12 +366,18 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
                     }
                 }
                 
-                // 设置状态 - 默认开启今天及以后的两天
-                LocalDate compareDay = LocalDate.now().plusDays(1);
-                if (courseDate.isBefore(compareDay)) {
-                    newDay.setStatus(1); // 已开始
+                // 设置状态:如果开启自由模式,默认为进行中(status=1)
+                if (period.getFreeMode() != null && period.getFreeMode() == 1) {
+                    // 自由模式:直接设置为进行中
+                    newDay.setStatus(1);
                 } else {
-                    newDay.setStatus(0); // 未开始
+                    // 固定模式:默认开启今天及以后的两天
+                    LocalDate compareDay = LocalDate.now().plusDays(1);
+                    if (courseDate.isBefore(compareDay)) {
+                        newDay.setStatus(1); // 已开始
+                    } else {
+                        newDay.setStatus(0); // 未开始
+                    }
                 }
                 
                 newDay.setCreateTime(new Date());
@@ -568,17 +589,31 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     public R updateCourseDate(UpdateCourseTimeVo vo) {
         FsUserCoursePeriodDays day = getById(vo.getId());
         FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(day.getPeriodId());
-        if(!DateUtil.isWithinRangeSafe(vo.getDayDate(), period.getPeriodStartingTime(), period.getPeriodEndTime())) return R.error("时间不在营期范围内");
+        
+        // 如果未开启自由模式,则需要检查日期是否在营期范围内
+        if (period.getFreeMode() == null || period.getFreeMode() != 1) {
+            if(!DateUtil.isWithinRangeSafe(vo.getDayDate(), period.getPeriodStartingTime(), period.getPeriodEndTime())) {
+                return R.error("时间不在营期范围内");
+            }
+        }
+        
         day.setDayDate(vo.getDayDate());
         day.setStartDateTime(LocalDateTime.of(day.getDayDate(), day.getStartDateTime().toLocalTime()));
         day.setEndDateTime(LocalDateTime.of(day.getDayDate(), day.getEndDateTime().toLocalTime()));
         day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), day.getLastJoinTime().toLocalTime()));
-        // 默认开启今天及以后的两天,为进行中
-        LocalDate compareDay = LocalDate.now().plusDays(1);
-        if(day.getDayDate().isBefore(compareDay)){
+        
+        // 设置状态:如果开启自由模式,默认为进行中(status=1)
+        if (period.getFreeMode() != null && period.getFreeMode() == 1) {
+            // 自由模式:直接设置为进行中
             day.setStatus(1);
         } else {
-            day.setStatus(0);
+            // 固定模式:默认开启今天及以后的两天,为进行中
+            LocalDate compareDay = LocalDate.now().plusDays(1);
+            if(day.getDayDate().isBefore(compareDay)){
+                day.setStatus(1);
+            } else {
+                day.setStatus(0);
+            }
         }
         updateById(day);
         return R.ok();

+ 25 - 12
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -86,13 +86,20 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     public int insertFsUserCoursePeriod(FsUserCoursePeriod fsUserCoursePeriod)
     {
         fsUserCoursePeriod.setCreateTime(LocalDateTime.now());
-        // 设置营期状态
-        if(LocalDate.now().isBefore(fsUserCoursePeriod.getPeriodStartingTime())){
-            fsUserCoursePeriod.setPeriodStatus(1L);
-        } else if(LocalDate.now().isAfter(fsUserCoursePeriod.getPeriodEndTime())){
-            fsUserCoursePeriod.setPeriodStatus(3L);
-        } else{
+        
+        // 设置营期状态:如果开启自由模式,设置为进行中(status=2)
+        if (fsUserCoursePeriod.getFreeMode() != null && fsUserCoursePeriod.getFreeMode() == 1) {
+            // 自由模式:营期状态为进行中
             fsUserCoursePeriod.setPeriodStatus(2L);
+        } else {
+            // 固定模式:按照时间设置状态
+            if(LocalDate.now().isBefore(fsUserCoursePeriod.getPeriodStartingTime())){
+                fsUserCoursePeriod.setPeriodStatus(1L);
+            } else if(LocalDate.now().isAfter(fsUserCoursePeriod.getPeriodEndTime())){
+                fsUserCoursePeriod.setPeriodStatus(3L);
+            } else{
+                fsUserCoursePeriod.setPeriodStatus(2L);
+            }
         }
         return fsUserCoursePeriodMapper.insertFsUserCoursePeriod(fsUserCoursePeriod);
     }
@@ -108,13 +115,19 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
         // 1. 查询原始数据
         fsUserCoursePeriod.setUpdateTime(LocalDateTime.now());
 
-        // 设置营期状态
-        if(LocalDate.now().isBefore(fsUserCoursePeriod.getPeriodStartingTime())){
-            fsUserCoursePeriod.setPeriodStatus(1L);
-        } else if(LocalDate.now().isAfter(fsUserCoursePeriod.getPeriodEndTime())){
-            fsUserCoursePeriod.setPeriodStatus(3L);
-        } else{
+        // 设置营期状态:如果开启自由模式,设置为进行中(status=2)
+        if (fsUserCoursePeriod.getFreeMode() != null && fsUserCoursePeriod.getFreeMode() == 1) {
+            // 自由模式:营期状态为进行中
             fsUserCoursePeriod.setPeriodStatus(2L);
+        } else {
+            // 固定模式:按照时间设置状态
+            if(LocalDate.now().isBefore(fsUserCoursePeriod.getPeriodStartingTime())){
+                fsUserCoursePeriod.setPeriodStatus(1L);
+            } else if(LocalDate.now().isAfter(fsUserCoursePeriod.getPeriodEndTime())){
+                fsUserCoursePeriod.setPeriodStatus(3L);
+            } else{
+                fsUserCoursePeriod.setPeriodStatus(2L);
+            }
         }
 
         FsUserCoursePeriod fsUserCoursePeriod1 = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(fsUserCoursePeriod.getPeriodId());

+ 26 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1945,6 +1945,15 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         // 查询课程周期信息
         FsUserCoursePeriodDays periodDays = getPeriodDaysInfo(param);
 
+        // 检查是否开启自由模式
+        FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
+        if (period != null && period.getFreeMode() != null && period.getFreeMode() == 1) {
+            // 自由模式:不检查时间和状态,直接返回有效
+            log.info("自由模式营期,跳过时间和状态检查,periodId={}", param.getPeriodId());
+            return true;
+        }
+
+        // 固定模式:检查时间范围和状态
         // 获取公司用户时间段信息
         LocalDateTime[] companyUserTimeRange = getCompanyUserTimeRange(param);
         LocalDateTime companyUserStartDateTime = companyUserTimeRange[0];
@@ -2148,10 +2157,23 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             }
             vo.setStartDateTime(companyUserStartDateTime != null ? companyUserStartDateTime : days.getStartDateTime());
             vo.setEndDateTime(companyUserEndDateTime != null ? companyUserEndDateTime : days.getEndDateTime());
-            vo.setRang(DateUtil.isWithinRangeSafe(LocalDateTime.now(),
-                    companyUserStartDateTime != null ? companyUserStartDateTime : days.getStartDateTime(),
-                    companyUserEndDateTime != null ? companyUserEndDateTime : days.getEndDateTime())
-                    && days.getStatus() == 1);
+            
+            // 检查是否开启自由模式
+            FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
+            boolean canWatch;
+            
+            if (period != null && period.getFreeMode() != null && period.getFreeMode() == 1) {
+                // 自由模式:不检查时间和状态,直接允许观看
+                canWatch = true;
+            } else {
+                // 固定模式:检查时间范围和状态
+                canWatch = DateUtil.isWithinRangeSafe(LocalDateTime.now(),
+                        companyUserStartDateTime != null ? companyUserStartDateTime : days.getStartDateTime(),
+                        companyUserEndDateTime != null ? companyUserEndDateTime : days.getEndDateTime())
+                        && days.getStatus() == 1;
+            }
+            
+            vo.setRang(canWatch);
         }
         return ResponseResult.ok(vo);
     }

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

@@ -89,4 +89,10 @@ public class FsUserCoursePeriodVO implements Serializable {
     @Excel(name = "营期线", width = 31, dateFormat = "yyyy-MM-dd")
     private Date periodLine;
 
+    /**
+     * 自由学习模式,0-未开启,1-开启(开启后学员不受营期时间限制)
+     */
+    @Excel(name = "自由学习模式")
+    private Integer freeMode;
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoPageListVO.java

@@ -66,7 +66,11 @@ public class FsUserCourseVideoPageListVO extends BaseEntity {
 
     @ApiModelProperty(value = "项目ID")
     private Long projectId;
+    
     @ApiModelProperty(value = "项目名称")
     private String projectName;
 
+    @ApiModelProperty(value = "自由学习模式,0-未开启,1-开启")
+    private Integer freeMode;
+
 }

+ 1 - 2
fs-service/src/main/resources/mapper/company/CompanyUserChangeApplyUserMapper.xml

@@ -9,8 +9,7 @@
         select
             fu.user_id,
             fu.nick_name as userName,
-            fu.avatar,
-            cucau.project_id
+            fu.avatar
         from company_user_change_apply_user cucau
         inner join fs_user fu on fu.user_id = cucau.user_id
         where cucau.apply_id = #{applyId}

+ 4 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -64,6 +64,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="qrCodeWecom" column="qr_code_wecom"/>
         <result property="jpushId" column="jpush_id"/>
         <result property="callerNo" column="caller_no"/>
+        <result property="isNeedRegisterMember" column="is_need_register_member"/>
+        <result property="isAllowedAllRegister" column="is_allowed_all_register"/>
         <collection property="roleNames" ofType="java.lang.String" javaType="java.util.ArrayList">
             <result column="role_name"/>
         </collection>
@@ -93,6 +95,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         u.qr_code_weixin, u.user_type, u.qr_code_wecom, u.jpush_id,
         u.avatar,
         u.qw_user_id,
+        u.is_need_register_member,
+        u.is_allowed_all_register,
         d.dept_name,
         d.leader,
         cr.role_name as role_name

+ 57 - 0
fs-service/src/main/resources/mapper/course/FsCourseQuestionBankMapper.xml

@@ -15,6 +15,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="answer"    column="answer"    />
         <result property="createBy"    column="create_by"    />
         <result property="questionType"    column="question_type"    />
+        <result property="questionSubType"    column="question_sub_type"    />
     </resultMap>
 
     <sql id="selectFsCourseQuestionBankVo">
@@ -140,4 +141,60 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{item}
         </foreach>
     </select>
+
+    <select id="selectFsCourseQuestionBankByTitle" resultMap="FsCourseQuestionBankResult">
+        <include refid="selectFsCourseQuestionBankVo"/>
+        where title = #{title}
+        limit 1
+    </select>
+
+    <update id="updateFsCourseQuestionBankBatch">
+        update fs_course_question_bank
+        <trim prefix="SET" suffixOverrides=",">
+            <trim prefix="sort = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.sort}
+                </foreach>
+            </trim>
+            <trim prefix="type = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.type}
+                </foreach>
+            </trim>
+            <trim prefix="status = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.status}
+                </foreach>
+            </trim>
+            <trim prefix="question = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.question}
+                </foreach>
+            </trim>
+            <trim prefix="answer = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.answer}
+                </foreach>
+            </trim>
+            <trim prefix="create_by = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.createBy}
+                </foreach>
+            </trim>
+            <trim prefix="question_type = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.questionType}
+                </foreach>
+            </trim>
+            <trim prefix="question_sub_type = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN title = #{item.title} THEN #{item.questionSubType}
+                </foreach>
+            </trim>
+        </trim>
+        WHERE title IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.title}
+        </foreach>
+    </update>
 </mapper>

+ 4 - 0
fs-service/src/main/resources/mapper/course/FsUserCoursePeriodDaysMapper.xml

@@ -205,16 +205,20 @@
     <update id="startPeriodCourse">
     <![CDATA[
         update fs_user_course_period_days ucpd
+        inner join fs_user_course_period ucp on ucpd.period_id = ucp.period_id
         set ucpd.status = 1, ucpd.update_time = #{now}
         where ucpd.status = 0 and ucpd.start_date_time <= #{now}
+        and (ucp.free_mode is null or ucp.free_mode != 1)
         ]]>
     </update>
 
     <update id="endPeriodCourse">
         update fs_user_course_period_days ucpd
+        inner join fs_user_course_period ucp on ucpd.period_id = ucp.period_id
         set ucpd.status = 2, ucpd.update_time = #{now}
         <![CDATA[
         where ucpd.status = 1 and ucpd.end_date_time < #{now}
+        and (ucp.free_mode is null or ucp.free_mode != 1)
         ]]>
     </update>
 

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

@@ -21,6 +21,7 @@
         <result property="courseLogo"    column="course_logo"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
         <result property="periodLine"    column="period_line"    />
+        <result property="freeMode"    column="free_mode"    />
     </resultMap>
 
     <sql id="selectFsUserCoursePeriodVo">
@@ -54,6 +55,7 @@
         fs_user_course_period.update_time,
         fs_user_course_period.period_status,
         fs_user_course_period.max_view_num,
+        fs_user_course_period.free_mode,
         course_style,
         live_room_style,
         red_packet_grant_method,
@@ -114,6 +116,7 @@
             <if test="courseLogo != null">course_logo,</if>
             <if test="openCommentStatus != null">open_comment_status,</if>
             <if test="periodLine != null">period_line,</if>
+            <if test="freeMode != null">free_mode,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="periodId != null">#{periodId},</if>
@@ -136,6 +139,7 @@
             <if test="courseLogo != null">#{courseLogo},</if>
             <if test="openCommentStatus != null">#{openCommentStatus},</if>
             <if test="periodLine != null">#{periodLine},</if>
+            <if test="freeMode != null">#{freeMode},</if>
         </trim>
     </insert>
 
@@ -161,6 +165,7 @@
             <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>
+            <if test="freeMode != null">free_mode = #{freeMode},</if>
         </trim>
         where period_id = #{periodId}
     </update>

+ 15 - 7
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -271,7 +271,8 @@
         if(ccut.start_date_time is null, fcpd.start_date_time, ccut.start_date_time) as startDateTime,
         if(ccut.end_date_time is null, fcpd.end_date_time, ccut.end_date_time) as endDateTime,
         course.project as projectId,
-        fcp.max_view_num as maxViewNum
+        fcp.max_view_num as maxViewNum,
+        fcp.free_mode as freeMode
         FROM `fs_user_course_video` video
         left join fs_user_course_period_days fcpd on fcpd.video_id = video.video_id
         left join fs_user_course_period fcp on fcp.period_id = fcpd.period_id
@@ -279,6 +280,7 @@
         LEFT JOIN fs_user_course_company_user_time ccut ON ccut.period_id = fcpd.period_id
         AND ccut.course_id = fcpd.course_id
         AND ccut.video_id = fcpd.video_id
+        AND ccut.company_id = #{companyId}
         AND ccut.company_user_id = #{companyUserId}
         where course.is_del = 0 and fcp.del_flag = 0 and fcpd.del_flag = 0
         AND FIND_IN_SET(#{companyId}, fcp.company_id)
@@ -288,8 +290,8 @@
         <if test="keyword != null and keyword !='' ">
             AND video.title LIKE concat('%',#{keyword},'%')
         </if>
-        <!-- 营销提前查看天数逻辑 -->
-        AND DATE_SUB(fcpd.day_date, INTERVAL fcp.max_view_num DAY) &lt;= now()
+        <!-- 营销提前查看天数逻辑:如果开启自由模式(freeMode=1),则跳过时间限制 -->
+        AND (fcp.free_mode = 1 OR DATE_SUB(fcpd.day_date, INTERVAL fcp.max_view_num DAY) &lt;= now())
         order by video.course_sort
     </select>
 
@@ -329,7 +331,8 @@
         fcp.period_name,
         if(ccut.start_date_time is null, fcpd.start_date_time, ccut.start_date_time) as startDateTime,
         if(ccut.end_date_time is null, fcpd.end_date_time, ccut.end_date_time) as endDateTime,
-        course.project as projectId
+        course.project as projectId,
+        fcp.free_mode as freeMode
         from `fs_user_course_video` video
         left join fs_user_course_period_days fcpd on fcpd.video_id = video.video_id
         left join fs_user_course_period fcp on fcp.period_id = fcpd.period_id
@@ -337,8 +340,11 @@
         LEFT JOIN fs_user_course_company_user_time ccut ON ccut.period_id = fcpd.period_id
         AND ccut.course_id = fcpd.course_id
         AND ccut.video_id = fcpd.video_id
+        AND ccut.company_id = #{params.companyId}
         AND ccut.company_user_id = #{params.companyUserId}
-        where course.is_del = 0 and fcp.del_flag = '0' and fcpd.del_flag = '0' AND fcpd.`status`=1
+        where course.is_del = 0 and fcp.del_flag = '0' and fcpd.del_flag = '0' 
+        <!-- 自由模式下不限制课程状态 -->
+        AND (fcp.free_mode = 1 OR fcpd.`status`=1)
         <if test="params.companyId != null">
             and FIND_IN_SET(#{params.companyId}, fcp.company_id)
         </if>
@@ -348,9 +354,11 @@
         <if test="params.keyword != null and params.keyword !='' ">
             AND video.title LIKE concat('%',#{params.keyword},'%')
         </if>
+        <!-- 时间限制逻辑:如果开启自由模式(freeMode=1),则跳过时间限制 -->
         and (
-        (fcpd.start_date_time &lt;=  CONCAT( CURDATE(), ' 23:59:59' ) and fcpd.end_date_time >= CONCAT( CURDATE(), ' 00:00:00' ))
-        or (ccut.start_date_time &lt;=  CONCAT( CURDATE(), ' 23:59:59' ) and ccut.end_date_time >= CONCAT( CURDATE(), ' 00:00:00' ))
+            fcp.free_mode = 1 OR
+            (fcpd.start_date_time &lt;=  CONCAT( CURDATE(), ' 23:59:59' ) and fcpd.end_date_time >= CONCAT( CURDATE(), ' 00:00:00' ))
+            or (ccut.start_date_time &lt;=  CONCAT( CURDATE(), ' 23:59:59' ) and ccut.end_date_time >= CONCAT( CURDATE(), ' 00:00:00' ))
         )
         order by video.course_sort
     </select>