Skip to content

Commit 8f82c29

Browse files
committed
fix: 신청예매 타임테이블 자동 생성으로 구역 조회 실패 해결
1 parent c4bad31 commit 8f82c29

11 files changed

Lines changed: 167 additions & 29 deletions

File tree

src/main/java/com/back/b2st/domain/performanceschedule/service/PerformanceScheduleService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
import com.back.b2st.domain.performanceschedule.dto.response.PerformanceScheduleDetailRes;
1313
import com.back.b2st.domain.performanceschedule.dto.response.PerformanceScheduleListRes;
1414
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
15+
import com.back.b2st.domain.performanceschedule.entity.BookingType;
1516
import com.back.b2st.domain.performanceschedule.error.PerformanceScheduleErrorCode;
1617
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
18+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
1719
import com.back.b2st.domain.scheduleseat.entity.ScheduleSeat;
1820
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
1921
import com.back.b2st.domain.scheduleseat.repository.ScheduleSeatRepository;
@@ -33,6 +35,7 @@ public class PerformanceScheduleService {
3335

3436
private final SeatRepository seatRepository;
3537
private final ScheduleSeatRepository scheduleSeatRepository;
38+
private final PrereservationTimeTableService prereservationTimeTableService;
3639

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

6467
PerformanceSchedule saved = performanceScheduleRepository.save(schedule);
6568

69+
if (saved.getBookingType() == BookingType.PRERESERVE) {
70+
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(saved.getPerformanceScheduleId());
71+
}
72+
6673
createScheduleSeats(saved.getPerformanceScheduleId(), performance.getVenue().getVenueId());
6774

6875
return PerformanceScheduleCreateRes.from(saved);

src/main/java/com/back/b2st/domain/prereservation/booking/service/PrereservationHoldService.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22

33
import java.time.LocalDateTime;
44

5-
import org.springframework.stereotype.Service;
65
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.stereotype.Service;
77
import org.springframework.transaction.annotation.Transactional;
88

99
import com.back.b2st.domain.performanceschedule.entity.BookingType;
1010
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
1111
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
1212
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
1313
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
14+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
1415
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
16+
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
1517
import com.back.b2st.domain.seat.seat.entity.Seat;
1618
import com.back.b2st.domain.seat.seat.repository.SeatRepository;
1719
import com.back.b2st.domain.venue.section.entity.Section;
1820
import com.back.b2st.domain.venue.section.repository.SectionRepository;
19-
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
2021
import com.back.b2st.global.error.exception.BusinessException;
2122

2223
import lombok.RequiredArgsConstructor;
@@ -30,14 +31,15 @@ public class PrereservationHoldService {
3031
private final SectionRepository sectionRepository;
3132
private final PrereservationRepository prereservationRepository;
3233
private final PrereservationSlotService prereservationSlotService;
34+
private final PrereservationTimeTableService prereservationTimeTableService;
3335

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

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

40-
@Transactional(readOnly = true)
42+
@Transactional
4143
public void validateSeatHoldAllowed(Long memberId, Long scheduleId, Long seatId) {
4244
PerformanceSchedule schedule = performanceScheduleRepository.findById(scheduleId)
4345
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SCHEDULE_NOT_FOUND));
@@ -78,6 +80,8 @@ public void validateSeatHoldAllowed(Long memberId, Long scheduleId, Long seatId)
7880
return;
7981
}
8082

83+
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);
84+
8185
Section section = sectionRepository.findById(seatSectionId)
8286
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SECTION_NOT_FOUND));
8387

src/main/java/com/back/b2st/domain/prereservation/entry/controller/PrereservationController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public class PrereservationController {
4040
summary = "사전 구역 신청",
4141
description = """
4242
BookingType이 PRERESERVE(신청 예매)인 회차에 대해 예매 가능한 구역을 사전에 신청합니다.
43-
- 예매 오픈 시간(bookingOpenAt) 24시간 전부터 신청 가능
44-
- 예매 오픈 시간(bookingOpenAt) 이후 신청 불가
43+
- 예매 오픈 날짜 기준 전날 00:00부터 신청 가능
44+
- 예매 오픈 날짜 기준 당일 00:00 이후 신청 불가
4545
- 동일 회차/구역 중복 신청 불가
4646
"""
4747
)

src/main/java/com/back/b2st/domain/prereservation/entry/service/PrereservationApplyService.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package com.back.b2st.domain.prereservation.entry.service;
22

33
import java.time.LocalDateTime;
4+
import java.time.LocalTime;
45
import java.time.format.DateTimeFormatter;
56
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.Map;
89
import java.util.TreeMap;
910
import java.util.TreeSet;
1011

11-
import org.springframework.dao.DataIntegrityViolationException;
1212
import org.springframework.beans.factory.annotation.Value;
13+
import org.springframework.dao.DataIntegrityViolationException;
1314
import org.springframework.stereotype.Service;
1415
import org.springframework.transaction.annotation.Transactional;
1516

@@ -21,6 +22,7 @@
2122
import com.back.b2st.domain.prereservation.entry.entity.Prereservation;
2223
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
2324
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
25+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
2426
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
2527
import com.back.b2st.domain.venue.section.entity.Section;
2628
import com.back.b2st.domain.venue.section.repository.SectionRepository;
@@ -38,11 +40,15 @@ public class PrereservationApplyService {
3840
private final SectionRepository sectionRepository;
3941
private final PrereservationRepository prereservationRepository;
4042
private final PrereservationSlotService prereservationSlotService;
43+
private final PrereservationTimeTableService prereservationTimeTableService;
4144
private final EmailSender emailSender;
4245

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

49+
@Value("${prereservation.slot.strict:true}")
50+
private boolean slotStrict = true;
51+
4652
@Value("${app.frontend.my-page-url:https://doncrytt.vercel.app/my-page}")
4753
private String myPageUrl = "https://doncrytt.vercel.app/my-page";
4854

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

6268
if (applicationStrict) {
6369
LocalDateTime now = LocalDateTime.now();
64-
LocalDateTime applyOpenAt = bookingOpenAt.minusDays(1);
70+
LocalDateTime applyOpenAt = bookingOpenAt.toLocalDate().minusDays(1).atStartOfDay();
71+
LocalDateTime applyCloseAt = bookingOpenAt.toLocalDate().atTime(LocalTime.MIDNIGHT);
6572
if (now.isBefore(applyOpenAt)) {
6673
throw new BusinessException(PrereservationErrorCode.APPLICATION_NOT_OPEN);
6774
}
68-
if (!now.isBefore(bookingOpenAt)) {
75+
if (!now.isBefore(applyCloseAt)) {
6976
throw new BusinessException(PrereservationErrorCode.APPLICATION_CLOSED);
7077
}
7178
}
@@ -96,11 +103,27 @@ public void apply(Long scheduleId, Long memberId, String email, Long sectionId)
96103
}
97104

98105
if (email != null && !email.isBlank()) {
99-
var slot = prereservationSlotService.calculateSlotOrThrow(schedule, section);
106+
var slot = slotStrict
107+
? calculateSlotOrEnsure(scheduleId, schedule, section)
108+
: new PrereservationSlotService.Slot(
109+
schedule.getBookingOpenAt(),
110+
schedule.getBookingCloseAt() != null
111+
? schedule.getBookingCloseAt()
112+
: schedule.getBookingOpenAt().plusDays(30)
113+
);
100114
sendAppliedEmail(email, section.getSectionName(), slot.startAt(), slot.endAt());
101115
}
102116
}
103117

118+
private PrereservationSlotService.Slot calculateSlotOrEnsure(
119+
Long scheduleId,
120+
PerformanceSchedule schedule,
121+
Section section
122+
) {
123+
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);
124+
return prereservationSlotService.calculateSlotOrThrow(schedule, section);
125+
}
126+
104127
@Transactional(readOnly = true)
105128
public PrereservationRes getMyApplications(Long scheduleId, Long memberId) {
106129
PerformanceSchedule schedule = getScheduleOrThrow(scheduleId);
@@ -159,10 +182,10 @@ private PerformanceSchedule getScheduleOrThrow(Long scheduleId) {
159182
private void sendAppliedEmail(String email, String sectionName, LocalDateTime startAt, LocalDateTime endAt) {
160183
String message = """
161184
신청 예매 사전 신청이 완료되었습니다.
162-
185+
163186
- 신청 구역: %s
164187
- 예매 가능 시간: %s ~ %s
165-
188+
166189
해당 시간 외에는 예매가 불가능합니다.
167190
""".formatted(
168191
sectionName,

src/main/java/com/back/b2st/domain/prereservation/entry/service/PrereservationSectionService.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.back.b2st.domain.prereservation.entry.entity.Prereservation;
1717
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
1818
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
19+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
1920
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
2021
import com.back.b2st.domain.venue.section.entity.Section;
2122
import com.back.b2st.domain.venue.section.repository.SectionRepository;
@@ -31,11 +32,12 @@ public class PrereservationSectionService {
3132
private final SectionRepository sectionRepository;
3233
private final PrereservationRepository prereservationRepository;
3334
private final PrereservationSlotService prereservationSlotService;
35+
private final PrereservationTimeTableService prereservationTimeTableService;
3436

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

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

61+
if (slotStrict) {
62+
prereservationTimeTableService.ensureDefaultTimeTablesIfMissing(scheduleId);
63+
}
64+
5965
return sections.stream()
6066
.map(section -> {
6167
var slot = slotStrict
6268
? prereservationSlotService.calculateSlotOrThrow(schedule, section)
6369
: new PrereservationSlotService.Slot(
64-
schedule.getBookingOpenAt(),
65-
schedule.getBookingCloseAt() != null
66-
? schedule.getBookingCloseAt()
67-
: schedule.getBookingOpenAt().plusDays(30)
68-
);
70+
schedule.getBookingOpenAt(),
71+
schedule.getBookingCloseAt() != null
72+
? schedule.getBookingCloseAt()
73+
: schedule.getBookingOpenAt().plusDays(30)
74+
);
6975
return new PrereservationSectionRes(
7076
section.getId(),
7177
section.getSectionName(),

src/main/java/com/back/b2st/domain/prereservation/policy/service/PrereservationTimeTableService.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.time.LocalDateTime;
44
import java.util.List;
55

6+
import org.springframework.dao.DataIntegrityViolationException;
67
import org.springframework.stereotype.Service;
78
import org.springframework.transaction.annotation.Transactional;
89

@@ -64,6 +65,47 @@ public void upsert(Long scheduleId, List<PrereservationTimeTableUpsertReq> items
6465
}
6566
}
6667

68+
@Transactional
69+
public void ensureDefaultTimeTablesIfMissing(Long scheduleId) {
70+
PerformanceSchedule schedule = validatePrereserveScheduleOrThrow(scheduleId);
71+
72+
Long venueId = schedule.getPerformance().getVenue().getVenueId();
73+
List<Section> sections = sectionRepository.findByVenueId(venueId).stream()
74+
.sorted(java.util.Comparator.comparing(Section::getSectionName))
75+
.toList();
76+
77+
if (sections.isEmpty()) {
78+
throw new BusinessException(PrereservationErrorCode.SECTION_NOT_FOUND);
79+
}
80+
81+
LocalDateTime bookingOpenAt = schedule.getBookingOpenAt();
82+
83+
for (int idx = 0; idx < sections.size(); idx++) {
84+
Section section = sections.get(idx);
85+
86+
if (prereservationTimeTableRepository.findByPerformanceScheduleIdAndSectionId(scheduleId, section.getId())
87+
.isPresent()) {
88+
continue;
89+
}
90+
91+
LocalDateTime startAt = bookingOpenAt.plusHours(idx);
92+
LocalDateTime endAt = startAt.plusHours(1).minusSeconds(1);
93+
94+
validateTimeRangeOrThrow(startAt, endAt);
95+
validateWithinScheduleOrThrow(schedule, startAt, endAt);
96+
97+
try {
98+
prereservationTimeTableRepository.save(PrereservationTimeTable.builder()
99+
.performanceScheduleId(scheduleId)
100+
.sectionId(section.getId())
101+
.bookingStartAt(startAt)
102+
.bookingEndAt(endAt)
103+
.build());
104+
} catch (DataIntegrityViolationException ignored) {
105+
}
106+
}
107+
}
108+
67109
private PerformanceSchedule validatePrereserveScheduleOrThrow(Long scheduleId) {
68110
PerformanceSchedule schedule = performanceScheduleRepository.findById(scheduleId)
69111
.orElseThrow(() -> new BusinessException(PrereservationErrorCode.SCHEDULE_NOT_FOUND));

src/test/java/com/back/b2st/domain/performanceschedule/service/PerformanceScheduleServiceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.back.b2st.domain.performanceschedule.entity.PerformanceSchedule;
2525
import com.back.b2st.domain.performanceschedule.error.PerformanceScheduleErrorCode;
2626
import com.back.b2st.domain.performanceschedule.repository.PerformanceScheduleRepository;
27+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
2728
import com.back.b2st.domain.scheduleseat.entity.ScheduleSeat;
2829
import com.back.b2st.domain.scheduleseat.repository.ScheduleSeatRepository;
2930
import com.back.b2st.domain.seat.seat.entity.Seat;
@@ -46,6 +47,9 @@ class PerformanceScheduleServiceTest {
4647
@Mock
4748
private ScheduleSeatRepository scheduleSeatRepository;
4849

50+
@Mock
51+
private PrereservationTimeTableService prereservationTimeTableService;
52+
4953
@InjectMocks
5054
private PerformanceScheduleService performanceScheduleService;
5155

src/test/java/com/back/b2st/domain/prereservation/booking/service/PrereservationHoldServiceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.back.b2st.domain.prereservation.entry.error.PrereservationErrorCode;
2020
import com.back.b2st.domain.prereservation.entry.repository.PrereservationRepository;
2121
import com.back.b2st.domain.prereservation.policy.service.PrereservationSlotService;
22+
import com.back.b2st.domain.prereservation.policy.service.PrereservationTimeTableService;
2223
import com.back.b2st.domain.seat.seat.entity.Seat;
2324
import com.back.b2st.domain.seat.seat.repository.SeatRepository;
2425
import com.back.b2st.domain.scheduleseat.error.ScheduleSeatErrorCode;
@@ -44,6 +45,9 @@ class PrereservationHoldServiceTest {
4445
@Mock
4546
private PrereservationSlotService prereservationSlotService;
4647

48+
@Mock
49+
private PrereservationTimeTableService prereservationTimeTableService;
50+
4751
@InjectMocks
4852
private PrereservationHoldService prereservationHoldService;
4953

src/test/java/com/back/b2st/domain/prereservation/entry/PrereservationInitDataFlowTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ void prereservation_flow_success() {
7171
.status(PerformanceStatus.ACTIVE)
7272
.build());
7373

74-
LocalDateTime bookingOpenAt = LocalDateTime.now().plusHours(1);
74+
LocalDateTime bookingOpenAt = LocalDateTime.now().plusDays(1).withHour(13).withMinute(0).withSecond(0).withNano(0);
7575
LocalDateTime bookingCloseAt = bookingOpenAt.plusHours(3);
7676

7777
PerformanceSchedule schedule = performanceScheduleRepository.save(PerformanceSchedule.builder()
@@ -129,4 +129,3 @@ void prereservation_flow_success() {
129129
.isFalse();
130130
}
131131
}
132-

0 commit comments

Comments
 (0)