Browse Source

feat: 报名挂课程,班次只做进班上课实体 (enrollment冗余courseId/后端接口回填课程信息)

liaoxg 1 tuần trước cách đây
mục cha
commit
4605b2c

+ 11 - 1
train-backend/src/main/java/com/train/controller/CourseController.java

@@ -242,8 +242,18 @@ public class CourseController {
         return c.getStatus() == null ? "draft" : c.getStatus();
     }
 
-    /** 学员在指定课程下的最新一条报名单(按该课程全部班次查找) */
+    /** 学员在指定课程下的最新一条报名单(报名挂在课程上,优先按 course_id 查询) */
     private TrainEnrollment latestEnrollmentOf(Long uid, Long courseId) {
+        TrainEnrollment byCourse = trainEnrollmentMapper.selectOne(
+                new LambdaQueryWrapper<TrainEnrollment>()
+                        .eq(TrainEnrollment::getUid, uid)
+                        .eq(TrainEnrollment::getCourseId, courseId)
+                        .orderByDesc(TrainEnrollment::getId)
+                        .last("LIMIT 1"));
+        if (byCourse != null) {
+            return byCourse;
+        }
+        // 兜底:存量报名单未冗余 course_id 时,按课程全部班次反查
         List<Long> classIds = trainClassMapper.selectList(
                         new LambdaQueryWrapper<TrainClass>().eq(TrainClass::getCourseId, courseId))
                 .stream().map(TrainClass::getId).collect(java.util.stream.Collectors.toList());

+ 27 - 0
train-backend/src/main/java/com/train/controller/EnrollmentController.java

@@ -6,11 +6,13 @@ import com.train.cfc.entity.CfcActivity;
 import com.train.cfc.mapper.CfcActivityMapper;
 import com.train.dto.EnrollmentVO;
 import com.train.entity.TrainClass;
+import com.train.entity.TrainCourse;
 import com.train.entity.TrainEnrollment;
 import com.train.entity.TrainInvite;
 import com.train.entity.TrainOrder;
 import com.train.entity.TrainUser;
 import com.train.mapper.TrainClassMapper;
+import com.train.mapper.TrainCourseMapper;
 import com.train.mapper.TrainEnrollmentMapper;
 import com.train.mapper.TrainInviteMapper;
 import com.train.mapper.TrainOrderMapper;
@@ -39,6 +41,8 @@ public class EnrollmentController {
     @Resource
     private TrainClassMapper trainClassMapper;
     @Resource
+    private TrainCourseMapper trainCourseMapper;
+    @Resource
     private TrainEnrollmentMapper trainEnrollmentMapper;
     @Resource
     private TrainInviteMapper trainInviteMapper;
@@ -70,6 +74,10 @@ public class EnrollmentController {
                             .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
             Map<String, Object> row = new HashMap<>();
             row.put("id", c.getId());
+            row.put("courseId", c.getCourseId());
+            // 报名=报课程:带课程名便于前端按课程分组展示该课程下的可报班次
+            TrainCourse course = c.getCourseId() == null ? null : trainCourseMapper.selectById(c.getCourseId());
+            row.put("courseName", course == null ? c.getName() : (course.getLevel() + ' ' + course.getName()));
             row.put("name", c.getName());
             row.put("time", c.getTime());
             row.put("place", c.getPlace());
@@ -114,6 +122,14 @@ public class EnrollmentController {
         if (trainClass == null || !"active".equals(trainClass.getStatus())) {
             return Result.error("班次不存在或不可报名");
         }
+        // 报名=报课程:显式传入 courseId 优先,否则由班次所属课程推导(两者不一致时以班次为准)
+        Long courseId = null;
+        if (body.get("courseId") != null && !body.get("courseId").toString().trim().isEmpty()) {
+            courseId = Long.valueOf(body.get("courseId").toString());
+        }
+        if (courseId == null) {
+            courseId = trainClass.getCourseId();
+        }
         // 容量校验(并发下允许少量超卖,最终以支付为准)
         Long occupied = trainEnrollmentMapper.selectCount(
                 new LambdaQueryWrapper<TrainEnrollment>()
@@ -135,6 +151,7 @@ public class EnrollmentController {
         String inviteCode = body.get("inviteCode") == null ? "" : body.get("inviteCode").toString().trim();
         TrainEnrollment e = new TrainEnrollment();
         e.setUid(userId);
+        e.setCourseId(courseId);
         e.setClassId(classId);
         e.setName(name);
         e.setPhone(phone);
@@ -202,6 +219,16 @@ public class EnrollmentController {
             row.put("classId", e.getClassId());
             TrainClass c = e.getClassId() == null ? null : trainClassMapper.selectById(e.getClassId());
             row.put("className", c != null ? c.getName() : null);
+            // 报名=报课程:回填课程信息,前端「我的报名」按课程展示
+            row.put("courseId", e.getCourseId());
+            String courseName = null;
+            if (e.getCourseId() != null) {
+                TrainCourse course = trainCourseMapper.selectById(e.getCourseId());
+                if (course != null) {
+                    courseName = course.getLevel() + ' ' + course.getName();
+                }
+            }
+            row.put("courseName", courseName);
             row.put("name", e.getName());
             row.put("phone", e.getPhone());
             row.put("source", e.getSource());

+ 18 - 0
train-backend/src/main/java/com/train/controller/admin/AdminBizController.java

@@ -3,6 +3,7 @@ package com.train.controller.admin;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.train.common.Result;
 import com.train.entity.TrainClass;
+import com.train.entity.TrainCourse;
 import com.train.entity.TrainEnrollment;
 import com.train.entity.TrainGroup;
 import com.train.entity.TrainGroupMember;
@@ -10,6 +11,7 @@ import com.train.entity.TrainInvite;
 import com.train.entity.TrainOrder;
 import com.train.entity.TrainUser;
 import com.train.mapper.TrainClassMapper;
+import com.train.mapper.TrainCourseMapper;
 import com.train.mapper.TrainEnrollmentMapper;
 import com.train.mapper.TrainGroupMapper;
 import com.train.mapper.TrainGroupMemberMapper;
@@ -53,6 +55,8 @@ public class AdminBizController {
     @Resource
     private TrainGroupMemberMapper trainGroupMemberMapper;
     @Resource
+    private TrainCourseMapper trainCourseMapper;
+    @Resource
     private PayService payService;
 
     /**
@@ -78,6 +82,20 @@ public class AdminBizController {
             row.put("classId", e.getClassId());
             TrainClass c = e.getClassId() == null ? null : trainClassMapper.selectById(e.getClassId());
             row.put("className", c != null ? c.getName() : null);
+            // 报名=报课程:优先读报名单冗余的 course_id,存量数据回退到班次所属课程
+            Long courseId = e.getCourseId();
+            if (courseId == null && c != null) {
+                courseId = c.getCourseId();
+            }
+            row.put("courseId", courseId);
+            String courseName = null;
+            if (courseId != null) {
+                TrainCourse course = trainCourseMapper.selectById(courseId);
+                if (course != null) {
+                    courseName = course.getLevel() + ' ' + course.getName();
+                }
+            }
+            row.put("courseName", courseName);
             row.put("uid", e.getUid());
             TrainUser u = e.getUid() == null ? null : trainUserMapper.selectById(e.getUid());
             row.put("userName", u != null ? u.getName() : null);

+ 5 - 0
train-backend/src/main/java/com/train/entity/TrainEnrollment.java

@@ -16,6 +16,11 @@ public class TrainEnrollment implements Serializable {
     private Long id;
 
     private Long uid;
+
+    /** 报名所属课程 ID(train_course.id);报名=报课程,班级/班次仅决定上课期次 */
+    private Long courseId;
+
+    /** 所选班次 ID(train_class.id,该课程下的某一期) */
     private Long classId;
     private String name;
     private String phone;

+ 5 - 2
train-backend/src/main/resources/schema.sql

@@ -69,6 +69,7 @@ ALTER TABLE train_user ADD COLUMN cfc_user_id BIGINT COMMENT 'cfc 统一会员 I
 ALTER TABLE train_class ADD COLUMN start_at DATETIME COMMENT '课程开始时间(投屏计时基准,NULL=未开始)';
 ALTER TABLE train_course ADD COLUMN start_time DATETIME COMMENT '课程开始时间(状态自动切换依据)';
 ALTER TABLE train_course ADD COLUMN end_time DATETIME COMMENT '课程结束时间(状态自动切换依据)';
+ALTER TABLE train_enrollment ADD COLUMN course_id BIGINT COMMENT '报名所属课程 ID(train_course.id,报名=报课程;班次仅确定上课期次)';
 
 -- 小组表
 CREATE TABLE IF NOT EXISTS train_group (
@@ -271,11 +272,12 @@ CREATE TABLE IF NOT EXISTS train_audit_log (
     INDEX idx_target (target_type, target_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志';
 
--- 报名单表(报名/付费/裂变一期
+-- 报名单表(报名挂课程;班次确定上课期次
 CREATE TABLE IF NOT EXISTS train_enrollment (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
     uid BIGINT COMMENT '学员ID(登录后)',
-    class_id BIGINT COMMENT '所选班次ID',
+    course_id BIGINT COMMENT '报名所属课程ID(train_course.id,报名=报课程)',
+    class_id BIGINT COMMENT '所选班次ID(该课程的某一期)',
     name VARCHAR(50) COMMENT '姓名',
     phone VARCHAR(11) COMMENT '手机号',
     bring_laptop TINYINT DEFAULT 0 COMMENT '1=带电脑',
@@ -287,6 +289,7 @@ CREATE TABLE IF NOT EXISTS train_enrollment (
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_uid (uid),
+    INDEX idx_course_id (course_id),
     INDEX idx_class_id (class_id),
     INDEX idx_status (status)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报名单';