Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import com.back.b2st.domain.performanceschedule.dto.response.PerformanceScheduleDetailRes;
import com.back.b2st.domain.performanceschedule.dto.response.PerformanceScheduleListRes;
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
import com.back.b2st.domain.performanceschedule.entity.BookingType;
import com.back.b2st.domain.performanceschedule.error.PerformanceScheduleErrorCode;
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.scheduleseat.entity.ScheduleSeat;
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
import com.back.b2st.domain.scheduleseat.repository.ScheduleSeatRepository;
Expand All @@ -33,6 +35,7 @@ public class PerformanceScheduleService {

private final SeatRepository seatRepository;
private final ScheduleSeatRepository scheduleSeatRepository;
private final PrereservationTimeTableService prereservationTimeTableService;

/**
* 회차 생성 (관리자)
Expand Down Expand Up @@ -63,6 +66,10 @@ public PerformanceScheduleCreateRes createSchedule(Long performanceId, Performan

PerformanceSchedule saved = performanceScheduleRepository.save(schedule);

if (saved.getBookingType() == BookingType.PRERESERVE) {
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(saved.getPerformanceScheduleId());
}

createScheduleSeats(saved.getPerformanceScheduleId(), performance.getVenue().getVenueId());

return PerformanceScheduleCreateRes.from(saved);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.back.b2st.domain.performanceschedule.entity.BookingType;
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
import com.back.b2st.domain.seat.seat.entity.Seat;
import com.back.b2st.domain.seat.seat.repository.SeatRepository;
import com.back.b2st.domain.venue.section.entity.Section;
import com.back.b2st.domain.venue.section.repository.SectionRepository;
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
import com.back.b2st.global.error.exception.BusinessException;

import lombok.RequiredArgsConstructor;
Expand All @@ -30,14 +31,15 @@ public class PrereservationHoldService {
private final SectionRepository sectionRepository;
private final PrereservationRepository prereservationRepository;
private final PrereservationSlotService prereservationSlotService;
private final PrereservationTimeTableService prereservationTimeTableService;

@Value("${prereservation.booking.strict:true}")
private boolean bookingStrict = true;

@Value("${prereservation.slot.strict:true}")
private boolean slotStrict = true;

@Transactional(readOnly = true)
@Transactional
public void validateSeatHoldAllowed(Long memberId, Long scheduleId, Long seatId) {
PerformanceSchedule schedule = performanceScheduleRepository.findById(scheduleId)
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SCHEDULE_NOT_FOUND));
Expand Down Expand Up @@ -78,6 +80,8 @@ public void validateSeatHoldAllowed(Long memberId, Long scheduleId, Long seatId)
return;
}

prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);

Section section = sectionRepository.findById(seatSectionId)
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SECTION_NOT_FOUND));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public class PrereservationController {
summary = "사전 구역 신청",
description = """
BookingType이 PRERESERVE(신청 예매)인 회차에 대해 예매 가능한 구역을 사전에 신청합니다.
- 예매 오픈 시간(bookingOpenAt) 24시간 전부터 신청 가능
- 예매 오픈 시간(bookingOpenAt) 이후 신청 불가
- 예매 오픈 날짜 기준 전날 00:00부터 신청 가능
- 예매 오픈 날짜 기준 당일 00:00 이후 신청 불가
- 동일 회차/구역 중복 신청 불가
"""
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.back.b2st.domain.prereservation.entry.service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -21,6 +22,7 @@
import com.back.b2st.domain.prereservation.entry.entity.Prereservation;
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
import com.back.b2st.domain.venue.section.entity.Section;
import com.back.b2st.domain.venue.section.repository.SectionRepository;
Expand All @@ -38,11 +40,15 @@ public class PrereservationApplyService {
private final SectionRepository sectionRepository;
private final PrereservationRepository prereservationRepository;
private final PrereservationSlotService prereservationSlotService;
private final PrereservationTimeTableService prereservationTimeTableService;
private final EmailSender emailSender;

@Value("${prereservation.application.strict:true}")
private boolean applicationStrict = true;

@Value("${prereservation.slot.strict:true}")
private boolean slotStrict = true;

@Value("${app.frontend.my-page-url:https://doncrytt.vercel.app/my-page}")
private String myPageUrl = "https://doncrytt.vercel.app/my-page";

Expand All @@ -61,11 +67,12 @@ public void apply(Long scheduleId, Long memberId, String email, Long sectionId)

if (applicationStrict) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime applyOpenAt = bookingOpenAt.minusDays(1);
LocalDateTime applyOpenAt = bookingOpenAt.toLocalDate().minusDays(1).atStartOfDay();
LocalDateTime applyCloseAt = bookingOpenAt.toLocalDate().atTime(LocalTime.MIDNIGHT);
if (now.isBefore(applyOpenAt)) {
throw new BusinessException(PrereservationErrorCode.APPLICATION_NOT_OPEN);
}
if (!now.isBefore(bookingOpenAt)) {
if (!now.isBefore(applyCloseAt)) {
throw new BusinessException(PrereservationErrorCode.APPLICATION_CLOSED);
}
}
Expand Down Expand Up @@ -96,11 +103,27 @@ public void apply(Long scheduleId, Long memberId, String email, Long sectionId)
}

if (email != null && !email.isBlank()) {
var slot = prereservationSlotService.calculateSlotOrThrow(schedule, section);
var slot = slotStrict
? calculateSlotOrEnsure(scheduleId, schedule, section)
: new PrereservationSlotService.Slot(
schedule.getBookingOpenAt(),
schedule.getBookingCloseAt() != null
? schedule.getBookingCloseAt()
: schedule.getBookingOpenAt().plusDays(30)
);
sendAppliedEmail(email, section.getSectionName(), slot.startAt(), slot.endAt());
}
}

private PrereservationSlotService.Slot calculateSlotOrEnsure(
Long scheduleId,
PerformanceSchedule schedule,
Section section
) {
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);
return prereservationSlotService.calculateSlotOrThrow(schedule, section);
}

@Transactional(readOnly = true)
public PrereservationRes getMyApplications(Long scheduleId, Long memberId) {
PerformanceSchedule schedule = getScheduleOrThrow(scheduleId);
Expand Down Expand Up @@ -159,10 +182,10 @@ private PerformanceSchedule getScheduleOrThrow(Long scheduleId) {
private void sendAppliedEmail(String email, String sectionName, LocalDateTime startAt, LocalDateTime endAt) {
String message = """
신청 예매 사전 신청이 완료되었습니다.

- 신청 구역: %s
- 예매 가능 시간: %s ~ %s

해당 시간 외에는 예매가 불가능합니다.
""".formatted(
sectionName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.back.b2st.domain.prereservation.entry.entity.Prereservation;
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
import com.back.b2st.domain.venue.section.entity.Section;
import com.back.b2st.domain.venue.section.repository.SectionRepository;
Expand All @@ -31,11 +32,12 @@ public class PrereservationSectionService {
private final SectionRepository sectionRepository;
private final PrereservationRepository prereservationRepository;
private final PrereservationSlotService prereservationSlotService;
private final PrereservationTimeTableService prereservationTimeTableService;

@Value("${prereservation.slot.strict:true}")
private boolean slotStrict = true;

@Transactional(readOnly = true)
@Transactional
public List<PrereservationSectionRes> getSections(Long scheduleId, Long memberId) {
PerformanceSchedule schedule = performanceScheduleRepository.findById(scheduleId)
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SCHEDULE_NOT_FOUND));
Expand All @@ -56,16 +58,20 @@ public List<PrereservationSectionRes> getSections(Long scheduleId, Long memberId
.map(Prereservation::getSectionId)
.collect(Collectors.toSet());

if (slotStrict) {
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);
}

return sections.stream()
.map(section -> {
var slot = slotStrict
? prereservationSlotService.calculateSlotOrThrow(schedule, section)
: new PrereservationSlotService.Slot(
schedule.getBookingOpenAt(),
schedule.getBookingCloseAt() != null
? schedule.getBookingCloseAt()
: schedule.getBookingOpenAt().plusDays(30)
);
schedule.getBookingOpenAt(),
schedule.getBookingCloseAt() != null
? schedule.getBookingCloseAt()
: schedule.getBookingOpenAt().plusDays(30)
);
return new PrereservationSectionRes(
section.getId(),
section.getSectionName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.LocalDateTime;
import java.util.List;

import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -64,6 +65,47 @@ public void upsert(Long scheduleId, List<PrereservationTimeTableUpsertReq> items
}
}

@Transactional
public void ensureDefaultTimeTablesIfMissing(Long scheduleId) {
PerformanceSchedule schedule = validatePrereserveScheduleOrThrow(scheduleId);

Long venueId = schedule.getPerformance().getVenue().getVenueId();
List<Section> sections = sectionRepository.findByVenueId(venueId).stream()
.sorted(java.util.Comparator.comparing(Section::getSectionName))
.toList();

if (sections.isEmpty()) {
throw new BusinessException(PrereservationErrorCode.SECTION_NOT_FOUND);
}

LocalDateTime bookingOpenAt = schedule.getBookingOpenAt();

for (int idx = 0; idx < sections.size(); idx++) {
Section section = sections.get(idx);

if (prereservationTimeTableRepository.findByPerformanceScheduleIdAndSectionId(scheduleId, section.getId())
.isPresent()) {
continue;
}

LocalDateTime startAt = bookingOpenAt.plusHours(idx);
LocalDateTime endAt = startAt.plusHours(1).minusSeconds(1);

validateTimeRangeOrThrow(startAt, endAt);
validateWithinScheduleOrThrow(schedule, startAt, endAt);

try {
prereservationTimeTableRepository.save(PrereservationTimeTable.builder()
.performanceScheduleId(scheduleId)
.sectionId(section.getId())
.bookingStartAt(startAt)
.bookingEndAt(endAt)
.build());
} catch (DataIntegrityViolationException ignored) {
}
}
}

private PerformanceSchedule validatePrereserveScheduleOrThrow(Long scheduleId) {
PerformanceSchedule schedule = performanceScheduleRepository.findById(scheduleId)
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SCHEDULE_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
import com.back.b2st.domain.performanceschedule.error.PerformanceScheduleErrorCode;
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.scheduleseat.entity.ScheduleSeat;
import com.back.b2st.domain.scheduleseat.repository.ScheduleSeatRepository;
import com.back.b2st.domain.seat.seat.entity.Seat;
Expand All @@ -46,6 +47,9 @@ class PerformanceScheduleServiceTest {
@Mock
private ScheduleSeatRepository scheduleSeatRepository;

@Mock
private PrereservationTimeTableService prereservationTimeTableService;

@InjectMocks
private PerformanceScheduleService performanceScheduleService;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
import com.back.b2st.domain.seat.seat.entity.Seat;
import com.back.b2st.domain.seat.seat.repository.SeatRepository;
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
Expand All @@ -44,6 +45,9 @@ class PrereservationHoldServiceTest {
@Mock
private PrereservationSlotService prereservationSlotService;

@Mock
private PrereservationTimeTableService prereservationTimeTableService;

@InjectMocks
private PrereservationHoldService prereservationHoldService;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void prereservation_flow_success() {
.status(PerformanceStatus.ACTIVE)
.build());

LocalDateTime bookingOpenAt = LocalDateTime.now().plusHours(1);
LocalDateTime bookingOpenAt = LocalDateTime.now().plusDays(1).withHour(13).withMinute(0).withSecond(0).withNano(0);
LocalDateTime bookingCloseAt = bookingOpenAt.plusHours(3);

PerformanceSchedule schedule = performanceScheduleRepository.save(PerformanceSchedule.builder()
Expand Down Expand Up @@ -129,4 +129,3 @@ void prereservation_flow_success() {
.isFalse();
}
}

Loading