|
|
@@ -0,0 +1,211 @@
|
|
|
+package com.train.controller;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.train.common.Result;
|
|
|
+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.TrainEnrollment;
|
|
|
+import com.train.entity.TrainInvite;
|
|
|
+import com.train.entity.TrainUser;
|
|
|
+import com.train.mapper.TrainClassMapper;
|
|
|
+import com.train.mapper.TrainEnrollmentMapper;
|
|
|
+import com.train.mapper.TrainInviteMapper;
|
|
|
+import com.train.mapper.TrainUserMapper;
|
|
|
+import io.swagger.v3.oas.annotations.Operation;
|
|
|
+import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestAttribute;
|
|
|
+import org.springframework.web.bind.annotation.RequestBody;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Tag(name = "报名", description = "课程报名、我的报名")
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/enroll")
|
|
|
+public class EnrollmentController {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TrainClassMapper trainClassMapper;
|
|
|
+ @Resource
|
|
|
+ private TrainEnrollmentMapper trainEnrollmentMapper;
|
|
|
+ @Resource
|
|
|
+ private TrainInviteMapper trainInviteMapper;
|
|
|
+ @Resource
|
|
|
+ private TrainUserMapper trainUserMapper;
|
|
|
+ @Resource
|
|
|
+ private CfcActivityMapper cfcActivityMapper;
|
|
|
+
|
|
|
+ /** 占用名额的报名状态 */
|
|
|
+ private static final List<String> OCCUPY_STATUS = Arrays.asList("pending", "paid", "confirmed");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 可报名班次列表(含剩余名额/价格)
|
|
|
+ */
|
|
|
+ @Operation(summary = "可报名班次列表")
|
|
|
+ @PostMapping("/classes")
|
|
|
+ public Result<List<Map<String, Object>>> listClasses(@RequestBody(required = false) Map<String, Object> body) {
|
|
|
+ List<TrainClass> classes = trainClassMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<TrainClass>()
|
|
|
+ .eq(TrainClass::getStatus, "active")
|
|
|
+ .orderByAsc(TrainClass::getId));
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ for (TrainClass c : classes) {
|
|
|
+ Long occupied = trainEnrollmentMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
+ .eq(TrainEnrollment::getClassId, c.getId())
|
|
|
+ .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
+ row.put("id", c.getId());
|
|
|
+ row.put("name", c.getName());
|
|
|
+ row.put("time", c.getTime());
|
|
|
+ row.put("place", c.getPlace());
|
|
|
+ row.put("capacity", c.getCapacity());
|
|
|
+ row.put("num", occupied == null ? 0L : occupied);
|
|
|
+ row.put("status", c.getStatus());
|
|
|
+ // 价格来自 cfc activities(分为单位,cfc 同库)
|
|
|
+ int price = 0;
|
|
|
+ int memberPrice = 0;
|
|
|
+ if (c.getActivityId() != null) {
|
|
|
+ CfcActivity activity = cfcActivityMapper.selectById(c.getActivityId());
|
|
|
+ if (activity != null) {
|
|
|
+ price = activity.getPrice() == null ? 0 : activity.getPrice();
|
|
|
+ memberPrice = activity.getMemberPrice() == null ? price : activity.getMemberPrice();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ row.put("price", price);
|
|
|
+ row.put("memberPrice", memberPrice);
|
|
|
+ result.add(row);
|
|
|
+ }
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建报名单(带邀请码则记录转介绍关系)
|
|
|
+ */
|
|
|
+ @Operation(summary = "创建报名单")
|
|
|
+ @PostMapping("/create")
|
|
|
+ public Result<EnrollmentVO> create(@RequestBody Map<String, Object> body,
|
|
|
+ @RequestAttribute("userId") Long userId) {
|
|
|
+ Object classIdObj = body.get("classId");
|
|
|
+ if (classIdObj == null) {
|
|
|
+ return Result.error("请选择班次");
|
|
|
+ }
|
|
|
+ Long classId = Long.valueOf(classIdObj.toString());
|
|
|
+ String name = body.get("name") == null ? "" : body.get("name").toString().trim();
|
|
|
+ String phone = body.get("phone") == null ? "" : body.get("phone").toString().trim();
|
|
|
+ if (!StringUtils.hasText(name) || !StringUtils.hasText(phone)) {
|
|
|
+ return Result.error("请填写姓名和手机号");
|
|
|
+ }
|
|
|
+ TrainClass trainClass = trainClassMapper.selectById(classId);
|
|
|
+ if (trainClass == null || !"active".equals(trainClass.getStatus())) {
|
|
|
+ return Result.error("班次不存在或不可报名");
|
|
|
+ }
|
|
|
+ // 容量校验(并发下允许少量超卖,最终以支付为准)
|
|
|
+ Long occupied = trainEnrollmentMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
+ .eq(TrainEnrollment::getClassId, classId)
|
|
|
+ .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
|
|
|
+ if (trainClass.getCapacity() != null && occupied != null && occupied >= trainClass.getCapacity()) {
|
|
|
+ return Result.error("该班次名额已满");
|
|
|
+ }
|
|
|
+ // 防重复报名(同班次已有 pending 报名单)
|
|
|
+ Long exists = trainEnrollmentMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
+ .eq(TrainEnrollment::getUid, userId)
|
|
|
+ .eq(TrainEnrollment::getClassId, classId)
|
|
|
+ .eq(TrainEnrollment::getStatus, "pending"));
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
+ return Result.error("您已报名该班次,请勿重复提交");
|
|
|
+ }
|
|
|
+
|
|
|
+ String inviteCode = body.get("inviteCode") == null ? "" : body.get("inviteCode").toString().trim();
|
|
|
+ TrainEnrollment e = new TrainEnrollment();
|
|
|
+ e.setUid(userId);
|
|
|
+ e.setClassId(classId);
|
|
|
+ e.setName(name);
|
|
|
+ e.setPhone(phone);
|
|
|
+ e.setBringLaptop(body.get("bringLaptop") != null ? Integer.valueOf(body.get("bringLaptop").toString()) : 0);
|
|
|
+ e.setDataReady(body.get("dataReady") == null ? null : body.get("dataReady").toString());
|
|
|
+ e.setTopProblems(body.get("topProblems") == null ? null : body.get("topProblems").toString());
|
|
|
+ e.setSource(StringUtils.hasText(inviteCode) ? "invite" : "scene");
|
|
|
+ e.setInviteCode(StringUtils.hasText(inviteCode) ? inviteCode : null);
|
|
|
+ e.setStatus("pending");
|
|
|
+ trainEnrollmentMapper.insert(e);
|
|
|
+
|
|
|
+ // 转介绍关系(幂等:uk_code_invitee 唯一键兜底)
|
|
|
+ if (StringUtils.hasText(inviteCode)) {
|
|
|
+ TrainUser inviter = trainUserMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<TrainUser>()
|
|
|
+ .eq(TrainUser::getInviteCode, inviteCode)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (inviter != null && !inviter.getId().equals(userId)) {
|
|
|
+ Long invCount = trainInviteMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<TrainInvite>()
|
|
|
+ .eq(TrainInvite::getInviteCode, inviteCode)
|
|
|
+ .eq(TrainInvite::getInviteeId, userId));
|
|
|
+ if (invCount == null || invCount == 0) {
|
|
|
+ TrainInvite inv = new TrainInvite();
|
|
|
+ inv.setInviterId(inviter.getId());
|
|
|
+ inv.setInviteeId(userId);
|
|
|
+ inv.setInviteCode(inviteCode);
|
|
|
+ inv.setSuccessful(0);
|
|
|
+ inv.setRewardStatus("pending");
|
|
|
+ try {
|
|
|
+ trainInviteMapper.insert(inv);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ // 唯一键冲突(并发重复注册)可忽略
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ EnrollmentVO vo = new EnrollmentVO();
|
|
|
+ vo.setId(e.getId());
|
|
|
+ vo.setClassId(classId);
|
|
|
+ vo.setClassName(trainClass.getName());
|
|
|
+ vo.setName(name);
|
|
|
+ vo.setPhone(phone);
|
|
|
+ vo.setSource(e.getSource());
|
|
|
+ vo.setInviteCode(e.getInviteCode());
|
|
|
+ vo.setStatus(e.getStatus());
|
|
|
+ return Result.success(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 我的报名单列表
|
|
|
+ */
|
|
|
+ @Operation(summary = "我的报名单列表")
|
|
|
+ @PostMapping("/mine")
|
|
|
+ public Result<List<Map<String, Object>>> mine(@RequestAttribute("userId") Long userId) {
|
|
|
+ List<TrainEnrollment> list = trainEnrollmentMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
+ .eq(TrainEnrollment::getUid, userId)
|
|
|
+ .orderByDesc(TrainEnrollment::getId));
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ for (TrainEnrollment e : list) {
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
+ row.put("id", e.getId());
|
|
|
+ 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("name", e.getName());
|
|
|
+ row.put("phone", e.getPhone());
|
|
|
+ row.put("source", e.getSource());
|
|
|
+ row.put("inviteCode", e.getInviteCode());
|
|
|
+ row.put("status", e.getStatus());
|
|
|
+ row.put("createdAt", e.getCreatedAt());
|
|
|
+ result.add(row);
|
|
|
+ }
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+}
|