|
1 | 1 | package com.back.b2st.domain.ticket.service; |
2 | 2 |
|
| 3 | +import java.util.HashMap; |
3 | 4 | import java.util.List; |
| 5 | +import java.util.Map; |
| 6 | +import java.util.Objects; |
| 7 | +import java.util.Set; |
| 8 | +import java.util.function.Function; |
4 | 9 | import java.util.stream.Collectors; |
5 | 10 |
|
6 | 11 | import org.springframework.dao.DataIntegrityViolationException; |
|
26 | 31 | import com.back.b2st.domain.ticket.error.TicketErrorCode; |
27 | 32 | import com.back.b2st.domain.ticket.repository.TicketRepository; |
28 | 33 | import com.back.b2st.domain.trade.entity.Trade; |
| 34 | +import com.back.b2st.domain.trade.entity.TradeType; |
29 | 35 | import com.back.b2st.domain.trade.entity.TradeStatus; |
30 | 36 | import com.back.b2st.domain.trade.repository.TradeRepository; |
31 | 37 | import com.back.b2st.global.error.exception.BusinessException; |
32 | 38 |
|
33 | 39 | import lombok.RequiredArgsConstructor; |
34 | | -import lombok.extern.slf4j.Slf4j; |
35 | 40 |
|
36 | 41 | @Service |
37 | 42 | @RequiredArgsConstructor |
38 | | -@Slf4j |
39 | 43 | public class TicketService { |
40 | 44 |
|
41 | 45 | private final TicketRepository ticketRepository; |
@@ -131,57 +135,101 @@ public Ticket restoreTicket(Long ticketId) { |
131 | 135 | return ticket; |
132 | 136 | } |
133 | 137 |
|
| 138 | + @Transactional |
| 139 | + public void ensureTicketsForReservation(Long reservationId) { |
| 140 | + Reservation reservation = reservationRepository.findById(reservationId).orElse(null); |
| 141 | + if (reservation == null) { |
| 142 | + return; |
| 143 | + } |
| 144 | + |
| 145 | + List<ReservationSeatInfo> seats = reservationSeatRepository.findSeatInfos(reservationId); |
| 146 | + if (seats.isEmpty()) { |
| 147 | + return; |
| 148 | + } |
| 149 | + |
| 150 | + for (ReservationSeatInfo seat : seats) { |
| 151 | + createTicket(reservationId, reservation.getMemberId(), seat.seatId()); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + private void ensureTicketsForCompletedReservations(Long memberId) { |
| 156 | + List<TicketRepository.MissingTicketKey> missingTickets = ticketRepository.findMissingTicketsForMember(memberId); |
| 157 | + for (TicketRepository.MissingTicketKey key : missingTickets) { |
| 158 | + createTicket(key.getReservationId(), key.getMemberId(), key.getSeatId()); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + @Transactional |
134 | 163 | public List<TicketRes> getMyTickets(Long memberId) { |
| 164 | + ensureTicketsForCompletedReservations(memberId); |
| 165 | + |
135 | 166 | List<Ticket> tickets = ticketRepository.findByMemberId(memberId); |
136 | 167 |
|
137 | 168 | // 구매자로 받은 완료된 거래 조회 (교환/양도) |
138 | 169 | List<Trade> completedTrades = tradeRepository.findAllByBuyerIdAndStatus(memberId, TradeStatus.COMPLETED); |
| 170 | + Map<Long, AcquisitionType> acquisitionTypeBySeatId = computeAcquisitionTypeBySeatId(completedTrades); |
139 | 171 |
|
140 | 172 | return tickets.stream() |
141 | | - .map(ticket -> { |
142 | | - Seat seat = seatRepository.findById(ticket.getSeatId()) |
143 | | - .orElseThrow(() -> new BusinessException(TicketErrorCode.TICKET_NOT_FOUND)); |
144 | | - PerformanceSchedule schedule = resolveScheduleForTicket(ticket); |
145 | | - |
146 | | - // 티켓 획득 경로 판단 |
147 | | - AcquisitionType acquisitionType = determineAcquisitionType(ticket, completedTrades); |
148 | | - |
149 | | - return TicketRes.builder() |
150 | | - .ticketId(ticket.getId()) |
151 | | - .reservationId(ticket.getReservationId()) |
152 | | - .seatId(ticket.getSeatId()) |
153 | | - .status(ticket.getStatus()) |
154 | | - .sectionName(seat.getSectionName()) |
155 | | - .rowLabel(seat.getRowLabel()) |
156 | | - .seatNumber(seat.getSeatNumber()) |
157 | | - .performanceId(schedule.getPerformance().getPerformanceId()) |
158 | | - .acquisitionType(acquisitionType) |
159 | | - .build(); |
160 | | - }) |
| 173 | + .map(ticket -> toTicketResOrNull(ticket, acquisitionTypeBySeatId)) |
| 174 | + .filter(Objects::nonNull) |
161 | 175 | .collect(Collectors.toList()); |
162 | 176 | } |
163 | 177 |
|
164 | | - /** |
165 | | - * 티켓의 획득 경로를 판단합니다. |
166 | | - * @param ticket 현재 티켓 |
167 | | - * @param completedTrades 구매자로 받은 완료된 거래 목록 |
168 | | - * @return AcquisitionType (RESERVATION, TRANSFER, EXCHANGE) |
169 | | - */ |
170 | | - private AcquisitionType determineAcquisitionType(Ticket ticket, List<Trade> completedTrades) { |
171 | | - // 완료된 거래 중에서 현재 티켓의 좌석과 일치하는 거래 찾기 |
| 178 | + private TicketRes toTicketResOrNull(Ticket ticket, Map<Long, AcquisitionType> acquisitionTypeBySeatId) { |
| 179 | + Seat seat = seatRepository.findById(ticket.getSeatId()).orElse(null); |
| 180 | + if (seat == null) { |
| 181 | + return null; |
| 182 | + } |
| 183 | + |
| 184 | + PerformanceSchedule schedule; |
| 185 | + try { |
| 186 | + schedule = resolveScheduleForTicket(ticket); |
| 187 | + } catch (BusinessException e) { |
| 188 | + return null; |
| 189 | + } |
| 190 | + |
| 191 | + AcquisitionType acquisitionType = |
| 192 | + acquisitionTypeBySeatId.getOrDefault(ticket.getSeatId(), AcquisitionType.RESERVATION); |
| 193 | + |
| 194 | + return TicketRes.builder() |
| 195 | + .ticketId(ticket.getId()) |
| 196 | + .reservationId(ticket.getReservationId()) |
| 197 | + .seatId(ticket.getSeatId()) |
| 198 | + .status(ticket.getStatus()) |
| 199 | + .sectionName(seat.getSectionName()) |
| 200 | + .rowLabel(seat.getRowLabel()) |
| 201 | + .seatNumber(seat.getSeatNumber()) |
| 202 | + .performanceId(schedule.getPerformance().getPerformanceId()) |
| 203 | + .acquisitionType(acquisitionType) |
| 204 | + .build(); |
| 205 | + } |
| 206 | + |
| 207 | + private Map<Long, AcquisitionType> computeAcquisitionTypeBySeatId(List<Trade> completedTrades) { |
| 208 | + if (completedTrades.isEmpty()) { |
| 209 | + return Map.of(); |
| 210 | + } |
| 211 | + |
| 212 | + Set<Long> tradeTicketIds = completedTrades.stream() |
| 213 | + .map(Trade::getTicketId) |
| 214 | + .collect(Collectors.toSet()); |
| 215 | + |
| 216 | + Map<Long, Ticket> originalTicketsById = ticketRepository.findAllById(tradeTicketIds).stream() |
| 217 | + .collect(Collectors.toMap(Ticket::getId, Function.identity(), (a, b) -> a)); |
| 218 | + |
| 219 | + Map<Long, AcquisitionType> acquisitionTypeBySeatId = new HashMap<>(); |
172 | 220 | for (Trade trade : completedTrades) { |
173 | | - // Trade의 원본 티켓 조회 |
174 | | - Ticket originalTicket = ticketRepository.findById(trade.getTicketId()).orElse(null); |
175 | | - if (originalTicket != null && originalTicket.getSeatId().equals(ticket.getSeatId())) { |
176 | | - // 같은 좌석이면 교환 또는 양도로 받은 티켓 |
177 | | - return trade.getType() == com.back.b2st.domain.trade.entity.TradeType.TRANSFER |
178 | | - ? AcquisitionType.TRANSFER |
179 | | - : AcquisitionType.EXCHANGE; |
| 221 | + Ticket originalTicket = originalTicketsById.get(trade.getTicketId()); |
| 222 | + if (originalTicket == null) { |
| 223 | + continue; |
180 | 224 | } |
| 225 | + |
| 226 | + acquisitionTypeBySeatId.putIfAbsent( |
| 227 | + originalTicket.getSeatId(), |
| 228 | + trade.getType() == TradeType.TRANSFER ? AcquisitionType.TRANSFER : AcquisitionType.EXCHANGE |
| 229 | + ); |
181 | 230 | } |
182 | 231 |
|
183 | | - // 거래 이력이 없으면 예매로 받은 티켓 |
184 | | - return AcquisitionType.RESERVATION; |
| 232 | + return acquisitionTypeBySeatId; |
185 | 233 | } |
186 | 234 |
|
187 | 235 | private PerformanceSchedule resolveScheduleForTicket(Ticket ticket) { |
|
0 commit comments