|
@@ -0,0 +1,224 @@
|
|
|
|
|
+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.entity.TrainEnrollment;
|
|
|
|
|
+import com.train.entity.TrainInvite;
|
|
|
|
|
+import com.train.entity.TrainOrder;
|
|
|
|
|
+import com.train.entity.TrainSalon;
|
|
|
|
|
+import com.train.entity.TrainUser;
|
|
|
|
|
+import com.train.mapper.TrainEnrollmentMapper;
|
|
|
|
|
+import com.train.mapper.TrainInviteMapper;
|
|
|
|
|
+import com.train.mapper.TrainOrderMapper;
|
|
|
|
|
+import com.train.mapper.TrainSalonMapper;
|
|
|
|
|
+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/salon")
|
|
|
|
|
+public class SalonController {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainSalonMapper trainSalonMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainEnrollmentMapper trainEnrollmentMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainInviteMapper trainInviteMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainUserMapper trainUserMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private CfcActivityMapper cfcActivityMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainOrderMapper trainOrderMapper;
|
|
|
|
|
+
|
|
|
|
|
+ /** 占用名额的报名状态 */
|
|
|
|
|
+ private static final List<String> OCCUPY_STATUS = Arrays.asList("pending", "paid", "confirmed");
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 可报名沙龙期次列表(含剩余名额/价格)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Operation(summary = "可报名沙龙期次列表")
|
|
|
|
|
+ @PostMapping("/list")
|
|
|
|
|
+ public Result<List<Map<String, Object>>> list(@RequestBody(required = false) Map<String, Object> body) {
|
|
|
|
|
+ List<TrainSalon> salons = trainSalonMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainSalon>()
|
|
|
|
|
+ .eq(TrainSalon::getStatus, "active")
|
|
|
|
|
+ .orderByAsc(TrainSalon::getId));
|
|
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
|
|
+ for (TrainSalon salon : salons) {
|
|
|
|
|
+ int salonPrice = salon.getPrice() == null ? 0 : salon.getPrice();
|
|
|
|
|
+ int price = salonPrice;
|
|
|
|
|
+ int memberPrice = price;
|
|
|
|
|
+ // 价格来自 cfc activities(分为单位,cfc 同库),未关联活动则用沙龙自身价格
|
|
|
|
|
+ if (salon.getActivityId() != null) {
|
|
|
|
|
+ CfcActivity activity = cfcActivityMapper.selectById(salon.getActivityId());
|
|
|
|
|
+ if (activity != null) {
|
|
|
|
|
+ price = activity.getPrice() == null ? salonPrice : activity.getPrice();
|
|
|
|
|
+ memberPrice = activity.getMemberPrice() == null ? price : activity.getMemberPrice();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Long occupied = trainEnrollmentMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
|
|
+ .eq(TrainEnrollment::getSalonId, salon.getId())
|
|
|
|
|
+ .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
|
|
|
|
|
+ long num = (salon.getCapacity() == null ? 0 : salon.getCapacity()) - (occupied == null ? 0L : occupied);
|
|
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
|
|
+ row.put("id", salon.getId());
|
|
|
|
|
+ row.put("theme", salon.getTheme());
|
|
|
|
|
+ row.put("title", salon.getTitle());
|
|
|
|
|
+ row.put("time", salon.getTime());
|
|
|
|
|
+ row.put("place", salon.getPlace());
|
|
|
|
|
+ row.put("capacity", salon.getCapacity());
|
|
|
|
|
+ row.put("price", price);
|
|
|
|
|
+ row.put("memberPrice", memberPrice);
|
|
|
|
|
+ row.put("occupied", occupied == null ? 0L : occupied);
|
|
|
|
|
+ row.put("num", num);
|
|
|
|
|
+ row.put("status", salon.getStatus());
|
|
|
|
|
+ row.put("startAt", salon.getStartAt());
|
|
|
|
|
+ row.put("createdAt", salon.getCreatedAt());
|
|
|
|
|
+ result.add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建沙龙报名单(带邀请码则记录转介绍关系)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Operation(summary = "创建沙龙报名单")
|
|
|
|
|
+ @PostMapping("/create")
|
|
|
|
|
+ public Result<Map<String, Object>> create(@RequestBody Map<String, Object> body,
|
|
|
|
|
+ @RequestAttribute("userId") Long userId) {
|
|
|
|
|
+ if (body.get("salonId") == null) {
|
|
|
|
|
+ return Result.error("请选择沙龙期次");
|
|
|
|
|
+ }
|
|
|
|
|
+ Long salonId = Long.valueOf(body.get("salonId").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("请填写姓名和手机号");
|
|
|
|
|
+ }
|
|
|
|
|
+ TrainSalon salon = trainSalonMapper.selectById(salonId);
|
|
|
|
|
+ if (salon == null) {
|
|
|
|
|
+ return Result.error("沙龙不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!"active".equals(salon.getStatus())) {
|
|
|
|
|
+ return Result.error("该沙龙未开放报名");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 容量校验(并发下允许少量超卖,最终以支付为准)
|
|
|
|
|
+ Long occupied = trainEnrollmentMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
|
|
+ .eq(TrainEnrollment::getSalonId, salonId)
|
|
|
|
|
+ .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
|
|
|
|
|
+ if (salon.getCapacity() != null && occupied != null && occupied >= salon.getCapacity()) {
|
|
|
|
|
+ return Result.error("该沙龙名额已满");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 防重复报名(同期沙龙已有未支付/在途报名单)
|
|
|
|
|
+ Long exists = trainEnrollmentMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
|
|
+ .eq(TrainEnrollment::getUid, userId)
|
|
|
|
|
+ .eq(TrainEnrollment::getSalonId, salonId)
|
|
|
|
|
+ .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
|
|
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
|
|
+ return Result.error("您已报名该期沙龙");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String inviteCode = body.get("inviteCode") == null ? "" : body.get("inviteCode").toString().trim();
|
|
|
|
|
+ TrainEnrollment en = new TrainEnrollment();
|
|
|
|
|
+ en.setUid(userId);
|
|
|
|
|
+ en.setSalonId(salonId);
|
|
|
|
|
+ en.setName(name);
|
|
|
|
|
+ en.setPhone(phone);
|
|
|
|
|
+ en.setSource(StringUtils.hasText(inviteCode) ? "invite" : "scene");
|
|
|
|
|
+ en.setInviteCode(StringUtils.hasText(inviteCode) ? inviteCode : null);
|
|
|
|
|
+ en.setStatus("pending");
|
|
|
|
|
+ trainEnrollmentMapper.insert(en);
|
|
|
|
|
+
|
|
|
|
|
+ // 转介绍关系(幂等: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) {
|
|
|
|
|
+ // 唯一键冲突(并发重复注册)可忽略
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
|
|
+ row.put("id", en.getId());
|
|
|
|
|
+ row.put("salonId", salonId);
|
|
|
|
|
+ row.put("theme", salon.getTheme());
|
|
|
|
|
+ row.put("title", salon.getTitle());
|
|
|
|
|
+ row.put("status", en.getStatus());
|
|
|
|
|
+ return Result.success(row);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 我的沙龙报名单列表
|
|
|
|
|
+ */
|
|
|
|
|
+ @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)
|
|
|
|
|
+ .isNotNull(TrainEnrollment::getSalonId)
|
|
|
|
|
+ .orderByDesc(TrainEnrollment::getId));
|
|
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
|
|
+ for (TrainEnrollment e : list) {
|
|
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
|
|
+ row.put("enrollmentId", e.getId());
|
|
|
|
|
+ row.put("salonId", e.getSalonId());
|
|
|
|
|
+ TrainSalon s = e.getSalonId() == null ? null : trainSalonMapper.selectById(e.getSalonId());
|
|
|
|
|
+ row.put("theme", s == null ? null : s.getTheme());
|
|
|
|
|
+ row.put("title", s == null ? null : s.getTitle());
|
|
|
|
|
+ row.put("time", s == null ? null : s.getTime());
|
|
|
|
|
+ row.put("place", s == null ? null : s.getPlace());
|
|
|
|
|
+ row.put("status", e.getStatus());
|
|
|
|
|
+ row.put("createdAt", e.getCreatedAt());
|
|
|
|
|
+ // 补关联订单号:未关闭订单(unpaid)用于前端继续支付/取消;paid 也回填便于查询
|
|
|
|
|
+ TrainOrder order = trainOrderMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainOrder>()
|
|
|
|
|
+ .eq(TrainOrder::getEnrollmentId, e.getId())
|
|
|
|
|
+ .ne(TrainOrder::getStatus, "closed")
|
|
|
|
|
+ .orderByDesc(TrainOrder::getId)
|
|
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
+ row.put("orderNo", order == null ? null : order.getOrderNo());
|
|
|
|
|
+ result.add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success(result);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|