140 lines
6.4 KiB
Java
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();
|
|
}
|
|
}
|