Browse Source

feat(course): 实现课程小节批量添加功能

- 在 FsUserCoursePeriodDaysController 中新增批量添加课程小节接口 /batchAddCourseSections- 在 FsUserCoursePeriodDaysServiceImpl 中实现 batchAddCourseSections 方法,支持批量插入课程小节- 新增 BatchAddCourseSectionParam 参数类用于接收批量添加请求数据
- 优化导入包结构,移除无用导入并重新组织部分包引用顺序-修复优惠券退款逻辑中对优惠券类型的判断条件,提高代码健壮性
xw 2 weeks ago
parent
commit
141084fcf4

+ 17 - 4
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -14,7 +14,10 @@ import com.fs.course.param.*;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseVideoRedPackageService;
-import com.fs.course.vo.*;
+import com.fs.course.vo.FsPeriodCountVO;
+import com.fs.course.vo.FsUserCoursePeriodVO;
+import com.fs.course.vo.PeriodRedPacketVO;
+import com.fs.course.vo.UpdateCourseTimeVo;
 import com.fs.his.vo.OptionsVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -25,12 +28,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 
 /**
@@ -147,10 +147,23 @@ public class FsUserCoursePeriodController extends BaseController {
 
     @PreAuthorize("@ss.hasPermi('course:period:addCourse')")
     @PostMapping("/addCourse")
+    @ApiOperation("添加单个课程小节")
     public R addCourse(@RequestBody FsUserCoursePeriodDays entity){
         return fsUserCoursePeriodDaysService.addCourse(entity);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:addCourse')")
+    @PostMapping("/batchAddCourseSections")
+    @ApiOperation("一键批量添加课程小节")
+    public R batchAddCourseSections(@Validated @RequestBody BatchAddCourseSectionParam param) {
+        try {
+            return fsUserCoursePeriodDaysService.batchAddCourseSections(param);
+        } catch (Exception e) {
+            logger.error("批量添加课程小节失败,营期ID:{},错误信息:{}", param.getPeriodId(), e.getMessage(), e);
+            return R.error("批量添加课程小节失败:" + e.getMessage());
+        }
+    }
+
     @PreAuthorize("@ss.hasPermi('course:period:dayRemove')")
     @Log(title = "会员营期课程删除", businessType = BusinessType.DELETE)
     @DeleteMapping("/day/{ids}")

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

@@ -3,6 +3,7 @@ package com.fs.course.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserCoursePeriodDays;
+import com.fs.course.param.BatchAddCourseSectionParam;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.PeriodCountParam;
 import com.fs.course.vo.FsPeriodCountVO;
@@ -70,6 +71,13 @@ public interface IFsUserCoursePeriodDaysService extends IService<FsUserCoursePer
 
     R addCourse(FsUserCoursePeriodDays entity);
 
+    /**
+     * 批量添加课程小节
+     * @param param 批量添加参数
+     * @return 操作结果
+     */
+    R batchAddCourseSections(BatchAddCourseSectionParam param);
+
     R updateListCourseData(List<FsUserCoursePeriodDays> entity);
 
     /**

+ 149 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -1,6 +1,5 @@
 package com.fs.course.service.impl;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -9,13 +8,13 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.date.TimeTypeEnum;
-import com.fs.company.domain.CompanyDomain;
 import com.fs.course.domain.FsUserCoursePeriod;
 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.BatchAddCourseSectionParam;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.CourseAnalysisParam;
 import com.fs.course.param.PeriodCountParam;
@@ -27,7 +26,6 @@ import com.fs.course.vo.UpdateCourseTimeVo;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.vo.OptionsVO;
-import com.fs.sop.domain.QwSopTempRules;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import lombok.AllArgsConstructor;
@@ -209,6 +207,154 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         return R.ok();
     }
 
+    /**
+     * 批量添加课程小节
+     * @param param 批量添加参数
+     * @return 操作结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R batchAddCourseSections(BatchAddCourseSectionParam param) {
+        try {
+            // 2. 获取营期信息
+            FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
+            if (period == null) {
+                return R.error("营期不存在");
+            }
+
+            // 3. 获取已存在的课程小节
+            List<FsUserCoursePeriodDays> existingDays = list(new QueryWrapper<FsUserCoursePeriodDays>()
+                    .eq("period_id", param.getPeriodId())
+                    .eq("del_flag", "0")
+                    .orderByAsc("lesson"));
+
+            // 4. 计算营期总天数
+            long totalDays;
+            if (period.getPeriodType() == 2) {
+                totalDays = 1;
+            } else {
+                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());
+            }
+
+            // 6. 检查重复视频
+            Set<Long> existingVideoIds = existingDays.stream()
+                    .map(FsUserCoursePeriodDays::getVideoId)
+                    .collect(Collectors.toSet());
+            
+            List<Long> duplicateVideoIds = param.getVideoIds().stream()
+                    .filter(existingVideoIds::contains)
+                    .collect(Collectors.toList());
+            
+            if (!duplicateVideoIds.isEmpty()) {
+                if (param.getSkipDuplicate() != null && param.getSkipDuplicate()) {
+                    // 跳过重复的视频
+                    param.getVideoIds().removeAll(duplicateVideoIds);
+                    log.info("跳过重复的视频ID:{}", duplicateVideoIds);
+                    if (param.getVideoIds().isEmpty()) {
+                        return R.ok("所有视频都已存在,跳过添加");
+                    }
+                } else {
+                    return R.error("不能添加相同章节,重复的视频ID:" + duplicateVideoIds);
+                }
+            }
+
+            // 7. 获取视频详情
+            FsUserCourseVideo queryVideo = new FsUserCourseVideo();
+            queryVideo.setCourseId(param.getCourseId());
+            List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(queryVideo);
+            Map<Long, FsUserCourseVideo> videoMap = PubFun.listToMapByGroupObject(videoList, FsUserCourseVideo::getVideoId);
+
+            // 8. 验证视频是否都存在
+            for (Long videoId : param.getVideoIds()) {
+                if (!videoMap.containsKey(videoId)) {
+                    return R.error("视频ID:" + videoId + " 不存在于课程中");
+                }
+            }
+
+            // 9. 准备添加的小节数据
+            List<FsUserCoursePeriodDays> newDays = new ArrayList<>();
+            AtomicInteger lessonCounter = new AtomicInteger(existingDays.size());
+            
+            // 确定开始课次
+            int startLesson = param.getStartLesson() != null ? param.getStartLesson() : existingDays.size();
+            
+            for (int i = 0; i < param.getVideoIds().size(); i++) {
+                Long videoId = param.getVideoIds().get(i);
+                FsUserCourseVideo video = videoMap.get(videoId);
+                
+                FsUserCoursePeriodDays newDay = new FsUserCoursePeriodDays();
+                newDay.setPeriodId(param.getPeriodId());
+                newDay.setCourseId(param.getCourseId());
+                newDay.setVideoId(videoId);
+                
+                // 设置课次
+                if (param.getAutoSort() != null && param.getAutoSort()) {
+                    newDay.setLesson(startLesson + i);
+                } else {
+                    newDay.setLesson(lessonCounter.getAndIncrement());
+                }
+                
+                // 计算日期
+                newDay.setDayDate(period.getPeriodStartingTime().plusDays(newDay.getLesson()));
+                
+                // 设置时间 - 优先使用参数中的时间,其次使用视频默认时间
+                if (param.getStartTime() != null) {
+                    newDay.setStartDateTime(LocalDateTime.of(newDay.getDayDate(), param.getStartTime()));
+                } else if (video.getViewStartTime() != null) {
+                    newDay.setStartDateTime(LocalDateTime.of(newDay.getDayDate(), video.getViewStartTime()));
+                }
+                
+                if (param.getEndDateTime() != null) {
+                    newDay.setEndDateTime(LocalDateTime.of(newDay.getDayDate(), param.getEndDateTime()));
+                } else if (video.getViewEndTime() != null) {
+                    newDay.setEndDateTime(LocalDateTime.of(newDay.getDayDate(), video.getViewEndTime()));
+                }
+                
+                // 设置加入时间 
+                SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
+                if (ObjectUtils.isNotEmpty(config) && config.getConfigValue().equals("1")) {
+                    // 如果开启了配置,使用结束时间作为加入时间
+                    newDay.setLastJoinTime(newDay.getEndDateTime());
+                } else {
+                    if (param.getJoinTime() != null) {
+                        newDay.setLastJoinTime(LocalDateTime.of(newDay.getDayDate(), param.getJoinTime()));
+                    } else if (video.getLastJoinTime() != null) {
+                        newDay.setLastJoinTime(LocalDateTime.of(newDay.getDayDate(), video.getLastJoinTime()));
+                    }
+                }
+                
+                // 设置状态 - 默认开启今天及以后的两天
+                LocalDate compareDay = LocalDate.now().plusDays(1);
+                if (newDay.getDayDate().isBefore(compareDay)) {
+                    newDay.setStatus(1); // 已开始
+                } else {
+                    newDay.setStatus(0); // 未开始
+                }
+                
+                newDay.setCreateTime(new Date());
+                newDays.add(newDay);
+            }
+            
+            // 10. 批量保存
+            if (!newDays.isEmpty()) {
+                super.saveBatch(newDays);
+                log.info("批量添加课程小节成功,营期ID:{},添加数量:{}", param.getPeriodId(), newDays.size());
+                return R.ok("成功添加 " + newDays.size() + " 个课程小节");
+            } else {
+                return R.ok("没有需要添加的课程小节");
+            }
+            
+        } catch (Exception e) {
+            log.error("批量添加课程小节失败,营期ID:{},错误信息:{}", param.getPeriodId(), e.getMessage(), e);
+            throw new RuntimeException("批量添加课程小节失败:" + e.getMessage(), e);
+        }
+    }
+
     @Override
     public R updateListCourseData(List<FsUserCoursePeriodDays> entity) {
         List<FsUserCoursePeriodDays> collect = entity.stream().map(e -> {

+ 16 - 19
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -1,16 +1,5 @@
 package com.fs.his.service.impl;
 
-import java.lang.reflect.Field;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.math.RoundingMode;
-import java.time.LocalDateTime;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
@@ -29,7 +18,10 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.cache.ICompanyTagCacheService;
 import com.fs.company.cache.ICompanyUserCacheService;
-import com.fs.company.domain.*;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyTag;
+import com.fs.company.domain.CompanyTagUser;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.*;
 import com.fs.company.service.ICompanyTagService;
 import com.fs.course.domain.FsUserCompanyUser;
@@ -43,8 +35,6 @@ import com.fs.course.vo.newfs.FsCourseAnalysisVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.config.IntegralConfig;
 import com.fs.his.domain.*;
-import com.fs.his.domain.FsUserAddress;
-import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.*;
@@ -53,19 +43,19 @@ import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserProjectTagService;
+import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
-import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
+import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
-import com.fs.im.service.OpenIMService;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.domain.FsUserBillScrm;
-import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.hisStore.enums.BillDetailEnum;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.service.IFsUserBillScrmService;
 import com.fs.hisStore.vo.FsCompanyUserListQueryVO;
+import com.fs.im.service.OpenIMService;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
@@ -91,13 +81,20 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.fs.his.service.IFsUserService;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
 import static com.fs.his.utils.PhoneUtil.*;
 import static com.fs.hisStore.enums.BillDetailEnum.CATEGORY_1;
-import static com.fs.his.utils.PhoneUtil.*;
 import static com.fs.hisStore.enums.BillDetailEnum.CATEGORY_3;
 
 /**

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -1183,7 +1183,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     private void refundDeductionCoupon(FsStoreOrderScrm order) {
         if (order.getDeductionCouponId() != null) {
             FsStoreCouponUserScrm couponUser = couponUserService.selectFsStoreCouponUserById(order.getDeductionCouponId());
-            if (couponUser != null && couponUser.getCouponType() == 3) { // 3表示立减金
+            if (couponUser != null ) { // 3表示立减金
                 couponUser.setStatus(0);
                 couponUser.setUseTime(null);
                 couponUserService.updateFsStoreCouponUser(couponUser);