|
|
@@ -1,6 +1,7 @@
|
|
|
package com.etotem.cfc.service;
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.etotem.cfc.common.DataMaskUtil;
|
|
|
import com.etotem.cfc.common.Result;
|
|
|
@@ -45,6 +46,7 @@ public class ActivityRegistrationService {
|
|
|
}
|
|
|
Result<Void> gate = checkRegistrationGate(activity, userId);
|
|
|
if (gate != null) return Result.error(gate.getMessage());
|
|
|
+ boolean needReview = activity.getRequireReview() != null && activity.getRequireReview() == 1;
|
|
|
List<Long> ids = new ArrayList<>();
|
|
|
for (Map<String, Object> reg : registrants) {
|
|
|
String name = (String) reg.get("name");
|
|
|
@@ -57,11 +59,21 @@ public class ActivityRegistrationService {
|
|
|
return Result.error("已报名: " + name);
|
|
|
}
|
|
|
}
|
|
|
+ // 免审活动:报名即通过并占用名额;名额已满则中止(已写入的本次批次有效)
|
|
|
+ if (!needReview) {
|
|
|
+ if (!occupyQuota(activityId)) {
|
|
|
+ return Result.error("名额已满,报名失败(含: " + name + ")");
|
|
|
+ }
|
|
|
+ }
|
|
|
ActivityRegistration ar = new ActivityRegistration();
|
|
|
ar.setActivityId(activityId); ar.setUserId(userId); ar.setChildId(memberId);
|
|
|
ar.setRegistrantName(name.trim()); ar.setRegistrantPhone(phone != null ? phone.trim() : null);
|
|
|
- ar.setStatus("pending"); ar.setRegisteredAt(LocalDateTime.now());
|
|
|
+ ar.setStatus(needReview ? "pending" : "approved"); ar.setRegisteredAt(LocalDateTime.now());
|
|
|
ar.setCreatedAt(LocalDateTime.now()); ar.setUpdatedAt(LocalDateTime.now());
|
|
|
+ if (!needReview) {
|
|
|
+ ar.setApprovedAt(LocalDateTime.now());
|
|
|
+ ar.setApprovedBy(userId);
|
|
|
+ }
|
|
|
registrationMapper.insert(ar);
|
|
|
ids.add(ar.getId());
|
|
|
}
|
|
|
@@ -82,22 +94,43 @@ public class ActivityRegistrationService {
|
|
|
Result<Void> gate = checkRegistrationGate(activity, userId);
|
|
|
if (gate != null) return Result.error(gate.getMessage());
|
|
|
if (isRegistered(activityId, memberId)) return Result.error("您已报名此活动");
|
|
|
+ boolean needReview = activity.getRequireReview() != null && activity.getRequireReview() == 1;
|
|
|
+ // 免审活动:报名即通过并占用名额
|
|
|
+ if (!needReview) {
|
|
|
+ if (!occupyQuota(activityId)) {
|
|
|
+ return Result.error("名额已满,报名失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
ActivityRegistration reg = new ActivityRegistration();
|
|
|
reg.setActivityId(activityId); reg.setUserId(userId); reg.setChildId(memberId);
|
|
|
- reg.setStatus("pending"); reg.setRegisteredAt(LocalDateTime.now());
|
|
|
+ reg.setStatus(needReview ? "pending" : "approved"); reg.setRegisteredAt(LocalDateTime.now());
|
|
|
reg.setCreatedAt(LocalDateTime.now()); reg.setUpdatedAt(LocalDateTime.now());
|
|
|
+ if (!needReview) {
|
|
|
+ reg.setApprovedAt(LocalDateTime.now());
|
|
|
+ reg.setApprovedBy(userId);
|
|
|
+ }
|
|
|
registrationMapper.insert(reg);
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
- result.put("registrationId", reg.getId()); result.put("status", "pending");
|
|
|
+ result.put("registrationId", reg.getId()); result.put("status", reg.getStatus());
|
|
|
return Result.success(result);
|
|
|
}
|
|
|
|
|
|
@Transactional
|
|
|
public Result<Void> cancel(Long activityId, Long userId, Long memberId) {
|
|
|
- ActivityRegistration reg = findRegistration(activityId, memberId, userId, "pending");
|
|
|
+ ActivityRegistration reg = registrationMapper.selectOne(new LambdaQueryWrapper<ActivityRegistration>()
|
|
|
+ .eq(ActivityRegistration::getActivityId, activityId)
|
|
|
+ .eq(ActivityRegistration::getChildId, memberId)
|
|
|
+ .eq(ActivityRegistration::getUserId, userId)
|
|
|
+ .in(ActivityRegistration::getStatus, "pending", "approved")
|
|
|
+ .last("LIMIT 1"));
|
|
|
if (reg == null) return Result.error("报名记录不存在");
|
|
|
+ String prevStatus = reg.getStatus();
|
|
|
reg.setStatus("cancelled"); reg.setUpdatedAt(LocalDateTime.now());
|
|
|
registrationMapper.updateById(reg);
|
|
|
+ // 已通过报名取消时回退名额(退款链路见 P0-2)
|
|
|
+ if ("approved".equals(prevStatus)) {
|
|
|
+ releaseQuota(activityId);
|
|
|
+ }
|
|
|
return Result.success(null);
|
|
|
}
|
|
|
|
|
|
@@ -106,6 +139,10 @@ public class ActivityRegistrationService {
|
|
|
ActivityRegistration reg = registrationMapper.selectById(registrationId);
|
|
|
if (reg == null) return Result.error("报名记录不存在");
|
|
|
if (!"pending".equals(reg.getStatus())) return Result.error("当前状态不允许审核");
|
|
|
+ // 审核通过即占用名额(原子校验,满员则无法通过)
|
|
|
+ if (!occupyQuota(reg.getActivityId())) {
|
|
|
+ return Result.error("名额已满,无法通过审核");
|
|
|
+ }
|
|
|
reg.setStatus("approved"); reg.setApprovedAt(LocalDateTime.now());
|
|
|
reg.setApprovedBy(adminUserId); reg.setUpdatedAt(LocalDateTime.now());
|
|
|
registrationMapper.updateById(reg);
|
|
|
@@ -131,6 +168,37 @@ public class ActivityRegistrationService {
|
|
|
return Result.success(null);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 原子占用一个名额:仅当 approved_count < max_participants 时自增,返回是否成功。
|
|
|
+ * 无人数限制(maxParticipants 为空或 <=0)视为不限,直接自增计数。
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public boolean occupyQuota(Long activityId) {
|
|
|
+ Activity activity = activityMapper.selectById(activityId);
|
|
|
+ if (activity == null) return false;
|
|
|
+ Integer max = activity.getMaxParticipants();
|
|
|
+ if (max != null && max > 0) {
|
|
|
+ int rows = activityMapper.update(null, new UpdateWrapper<Activity>()
|
|
|
+ .eq("id", activityId)
|
|
|
+ .lt("approved_count", max)
|
|
|
+ .setSql("approved_count = approved_count + 1"));
|
|
|
+ return rows > 0;
|
|
|
+ }
|
|
|
+ activityMapper.update(null, new UpdateWrapper<Activity>()
|
|
|
+ .eq("id", activityId)
|
|
|
+ .setSql("approved_count = approved_count + 1"));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 原子回退一个名额(仅当 approved_count > 0 时减一)。 */
|
|
|
+ @Transactional
|
|
|
+ public void releaseQuota(Long activityId) {
|
|
|
+ activityMapper.update(null, new UpdateWrapper<Activity>()
|
|
|
+ .eq("id", activityId)
|
|
|
+ .gt("approved_count", 0)
|
|
|
+ .setSql("approved_count = approved_count - 1"));
|
|
|
+ }
|
|
|
+
|
|
|
public Result<Map<String, Object>> getMyRegistrations(Long userId, Long memberId, Integer page, Integer size) {
|
|
|
if (page == null || page < 1) page = 1;
|
|
|
if (size == null || size < 1) size = 10;
|
|
|
@@ -202,9 +270,6 @@ public class ActivityRegistrationService {
|
|
|
Activity activity = activityMapper.selectById(activityId);
|
|
|
if (activity == null) return Result.error("活动不存在");
|
|
|
|
|
|
- long approvedCount = registrationMapper.selectCount(new LambdaQueryWrapper<ActivityRegistration>()
|
|
|
- .eq(ActivityRegistration::getActivityId, activityId)
|
|
|
- .eq(ActivityRegistration::getStatus, "approved"));
|
|
|
long pendingCount = registrationMapper.selectCount(new LambdaQueryWrapper<ActivityRegistration>()
|
|
|
.eq(ActivityRegistration::getActivityId, activityId)
|
|
|
.eq(ActivityRegistration::getStatus, "pending"));
|
|
|
@@ -223,15 +288,17 @@ public class ActivityRegistrationService {
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
|
Integer max = activity.getMaxParticipants();
|
|
|
+ // 名额占用以 approved_count 列(原子维护)为准,与报名审核状态一致
|
|
|
+ Integer approved = activity.getApprovedCount() != null ? activity.getApprovedCount() : 0;
|
|
|
Integer remaining = null;
|
|
|
if (max != null && max > 0) {
|
|
|
- remaining = (int) Math.max(0, max - approvedCount);
|
|
|
+ remaining = (int) Math.max(0, max - approved);
|
|
|
}
|
|
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
data.put("activityId", activityId);
|
|
|
data.put("maxParticipants", max != null ? max : 0);
|
|
|
- data.put("approvedCount", approvedCount);
|
|
|
+ data.put("approvedCount", approved);
|
|
|
data.put("pendingCount", pendingCount);
|
|
|
data.put("remaining", remaining);
|
|
|
data.put("registrants", registrants);
|