Files
gym-manage/src/main/java/com/gym/manage/application/service/BookingService.java
T
2026-03-05 13:48:13 +08:00

140 lines
6.4 KiB
Java

package com.gym.manage.application.service;
import com.gym.manage.api.dto.request.BookingCreateRequest;
import com.gym.manage.api.dto.response.BookingRecordResponse;
import com.gym.manage.common.constant.ErrorCode;
import com.gym.manage.common.exception.BusinessException;
import com.gym.manage.domain.entity.BookingRecord;
import com.gym.manage.domain.entity.BookingSlot;
import com.gym.manage.domain.repository.BookingRecordRepository;
import com.gym.manage.domain.repository.BookingSlotRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
public class BookingService {
private final BookingSlotRepository bookingSlotRepository;
private final BookingRecordRepository bookingRecordRepository;
@Transactional
public Mono<BookingRecordResponse> createBooking(BookingCreateRequest request) {
log.info("创建预约: memberId={}, slotId={}", request.getMemberId(), request.getSlotId());
return validateAndBook(request)
.map(this::toBookingRecordResponse)
.doOnSuccess(response -> log.info("预约创建成功: bookingId={}", response.getId()))
.doOnError(e -> log.error("预约创建失败: memberId={}, slotId={}, error={}",
request.getMemberId(), request.getSlotId(), e.getMessage()));
}
private Mono<BookingRecord> validateAndBook(BookingCreateRequest request) {
return bookingSlotRepository.findByIdAndDeletedAtIsNull(request.getSlotId())
.switchIfEmpty(Mono.error(new BusinessException(ErrorCode.SLOT_NOT_FOUND, "时段不存在")))
.flatMap(slot -> {
if (!"AVAILABLE".equals(slot.getStatus())) {
return Mono.error(new BusinessException(ErrorCode.SLOT_NOT_AVAILABLE, "时段不可预约"));
}
if (slot.getBookedCount() >= slot.getMaxCapacity()) {
return Mono.error(new BusinessException(ErrorCode.SLOT_NOT_AVAILABLE, "时段已满"));
}
return bookingRecordRepository.findByMemberIdAndSlotIdAndDeletedAtIsNull(
request.getMemberId(), request.getSlotId()
).flatMap(existing -> Mono.<BookingRecord>error(
new BusinessException(ErrorCode.BOOKING_NOT_FOUND, "已预约该时段")
)).switchIfEmpty(createBookingRecord(request, slot));
});
}
private Mono<BookingRecord> createBookingRecord(BookingCreateRequest request, BookingSlot slot) {
return bookingSlotRepository.incrementBookedCount(request.getSlotId())
.flatMap(rows -> {
if (rows > 0) {
BookingRecord record = new BookingRecord();
record.setTenantId(slot.getTenantId());
record.setStoreId(slot.getStoreId());
record.setMemberId(request.getMemberId());
record.setSlotId(request.getSlotId());
record.setCoachId(slot.getCoachId());
record.setCourseName(slot.getCourseName());
record.setBookingTime(LocalDateTime.now());
record.setStatus("BOOKED");
record.setRemark(request.getRemark());
record.setCreatedAt(LocalDateTime.now());
record.setUpdatedAt(LocalDateTime.now());
return bookingRecordRepository.save(record);
} else {
return Mono.error(new BusinessException(ErrorCode.SLOT_NOT_AVAILABLE, "预约失败,请重试"));
}
});
}
public Mono<BookingRecordResponse> getBooking(Long id) {
log.info("查询预约: bookingId={}", id);
return bookingRecordRepository.findByIdAndDeletedAtIsNull(id)
.switchIfEmpty(Mono.error(new BusinessException(ErrorCode.BOOKING_NOT_FOUND, "预约不存在")))
.map(this::toBookingRecordResponse);
}
public Flux<BookingRecordResponse> listMemberBookings(Long memberId, int page, int size) {
log.info("查询会员预约列表: memberId={}", memberId);
return bookingRecordRepository.findByMemberIdAndDeletedAtIsNull(memberId,
org.springframework.data.domain.PageRequest.of(page, size))
.map(this::toBookingRecordResponse);
}
@Transactional
public Mono<Void> cancelBooking(Long id, String reason) {
log.info("取消预约: bookingId={}", id);
return bookingRecordRepository.findByIdAndDeletedAtIsNull(id)
.switchIfEmpty(Mono.error(new BusinessException(ErrorCode.BOOKING_NOT_FOUND, "预约不存在")))
.flatMap(record -> {
if ("CANCELLED".equals(record.getStatus())) {
return Mono.error(new BusinessException(ErrorCode.BOOKING_ALREADY_CANCELLED, "预约已取消"));
}
return bookingSlotRepository.decrementBookedCount(record.getSlotId())
.flatMap(rows -> {
record.setStatus("CANCELLED");
record.setCancelReason(reason);
record.setCancelTime(LocalDateTime.now());
record.setUpdatedAt(LocalDateTime.now());
return bookingRecordRepository.save(record);
});
})
.then();
}
private BookingRecordResponse toBookingRecordResponse(BookingRecord record) {
return BookingRecordResponse.builder()
.id(record.getId())
.memberId(record.getMemberId())
.slotId(record.getSlotId())
.coachId(record.getCoachId())
.courseName(record.getCourseName())
.bookingTime(record.getBookingTime())
.status(record.getStatus())
.cancelReason(record.getCancelReason())
.cancelTime(record.getCancelTime())
.checkinTime(record.getCheckinTime())
.remark(record.getRemark())
.createdAt(record.getCreatedAt())
.build();
}
}