1+ package com .back .domain .order .exchange .service ;
2+
3+ import com .back .domain .order .common .service .BaseOrderActionService ;
4+ import com .back .domain .order .order .entity .Order ;
5+ import com .back .domain .order .order .repository .OrderRepository ;
6+ import com .back .domain .order .orderItem .entity .OrderItem ;
7+ import com .back .domain .order .orderItem .repository .OrderItemRepository ;
8+ import com .back .domain .order .exchange .dto .request .ExchangeRequestDto ;
9+ import com .back .domain .order .exchange .dto .response .ExchangeResponseDto ;
10+ import com .back .domain .order .exchange .entity .Exchange ;
11+ import com .back .domain .order .exchange .entity .ExchangeItem ;
12+ import com .back .domain .order .exchange .repository .ExchangeItemRepository ;
13+ import com .back .domain .order .exchange .repository .ExchangeRepository ;
14+ import com .back .domain .order .exchange .util .ExchangeConverter ;
15+ import com .back .domain .user .entity .User ;
16+ import lombok .RequiredArgsConstructor ;
17+ import lombok .extern .slf4j .Slf4j ;
18+ import org .springframework .data .domain .Page ;
19+ import org .springframework .data .domain .PageImpl ;
20+ import org .springframework .data .domain .Pageable ;
21+ import org .springframework .stereotype .Service ;
22+ import org .springframework .transaction .annotation .Transactional ;
23+
24+ import java .util .List ;
25+
26+ @ Slf4j
27+ @ Service
28+ @ Transactional (readOnly = true )
29+ public class ExchangeService extends BaseOrderActionService <Exchange , ExchangeResponseDto , ExchangeItem > {
30+
31+ private final ExchangeRepository exchangeRepository ;
32+ private final ExchangeItemRepository exchangeItemRepository ;
33+ private final ExchangeConverter exchangeConverter ;
34+
35+ public ExchangeService (OrderRepository orderRepository , OrderItemRepository orderItemRepository ,
36+ ExchangeRepository exchangeRepository , ExchangeItemRepository exchangeItemRepository ,
37+ ExchangeConverter exchangeConverter ) {
38+ super (orderRepository , orderItemRepository );
39+ this .exchangeRepository = exchangeRepository ;
40+ this .exchangeItemRepository = exchangeItemRepository ;
41+ this .exchangeConverter = exchangeConverter ;
42+ }
43+
44+ /**
45+ * 교환 신청
46+ */
47+ @ Transactional
48+ public ExchangeResponseDto createExchange (ExchangeRequestDto requestDto , User user ) {
49+ log .info ("교환 신청 시작 - 사용자: {}, 주문 ID: {}" , user .getId (), requestDto .orderId ());
50+
51+ // 1. 공통 검증 로직 사용
52+ Order order = validateOrderAndOwnership (requestDto .orderId (), user );
53+ List <OrderItem > orderItems = validateOrderItems (requestDto .orderItemIds ());
54+
55+ // 2. 교환 가능 여부 검증 (Order 엔티티에서 처리)
56+ order .requestExchange ();
57+
58+ // 3. 교환 엔티티 생성 (팩토리 메서드 사용)
59+ Exchange exchange = Exchange .createExchange (
60+ order ,
61+ user ,
62+ requestDto .reason (),
63+ requestDto .detailReason (),
64+ requestDto .exchangeMethod (),
65+ requestDto .attachmentFiles () != null ? String .join ("," , requestDto .attachmentFiles ()) : null ,
66+ requestDto .newShippingAddress1 (),
67+ requestDto .newShippingAddress2 (),
68+ requestDto .newShippingZip (),
69+ requestDto .newRecipientName (),
70+ requestDto .newRecipientPhone ()
71+ );
72+
73+ // 4. 교환 저장
74+ Exchange savedExchange = exchangeRepository .save (exchange );
75+
76+ // 5. 교환 아이템 생성 및 저장
77+ List <ExchangeItem > exchangeItems = orderItems .stream ()
78+ .map (orderItem -> ExchangeItem .builder ()
79+ .exchange (savedExchange )
80+ .orderItem (orderItem )
81+ .quantity (orderItem .getQuantity ())
82+ .build ())
83+ .toList ();
84+
85+ exchangeItemRepository .saveAll (exchangeItems );
86+
87+ log .info ("교환 신청 완료 - 교환 ID: {}" , savedExchange .getId ());
88+
89+ // 6. N+1 문제 해결: 한 번의 쿼리로 모든 데이터 조회
90+ Exchange exchangeWithItems = exchangeRepository .findByIdWithItems (savedExchange .getId ())
91+ .orElseThrow (() -> new IllegalArgumentException ("교환 정보를 찾을 수 없습니다." ));
92+
93+ return exchangeConverter .toResponseDto (exchangeWithItems , exchangeWithItems .getExchangeItems ());
94+ }
95+
96+ /**
97+ * 교환 상세 조회 (N+1 문제 해결)
98+ */
99+ @ Override
100+ public ExchangeResponseDto getItem (Long exchangeId , User user ) {
101+ log .info ("교환 상세 조회 - 교환 ID: {}, 사용자: {}" , exchangeId , user .getId ());
102+
103+ Exchange exchange = exchangeRepository .findByIdWithItems (exchangeId )
104+ .orElseThrow (() -> new IllegalArgumentException ("교환 정보를 찾을 수 없습니다." ));
105+
106+ if (!exchange .getUser ().getId ().equals (user .getId ())) {
107+ throw new IllegalStateException ("해당 교환에 대한 권한이 없습니다." );
108+ }
109+
110+ return exchangeConverter .toResponseDto (exchange , exchange .getExchangeItems ());
111+ }
112+
113+ /**
114+ * 사용자별 교환 목록 조회 (N+1 문제 해결)
115+ */
116+ @ Override
117+ public Page <ExchangeResponseDto > getItemsByUser (User user , Pageable pageable ) {
118+ log .info ("사용자별 교환 목록 조회 - 사용자: {}" , user .getId ());
119+
120+ // N+1 문제 해결: 한 번의 쿼리로 모든 데이터 조회
121+ List <Exchange > exchanges = exchangeRepository .findByUserWithItems (user );
122+
123+ // 수동으로 페이징 처리
124+ int start = (int ) pageable .getOffset ();
125+ int end = Math .min ((start + pageable .getPageSize ()), exchanges .size ());
126+ List <Exchange > pagedExchanges = start >= exchanges .size () ? List .of () : exchanges .subList (start , end );
127+
128+ List <ExchangeResponseDto > exchangeDtos = pagedExchanges .stream ()
129+ .map (exchange -> exchangeConverter .toResponseDto (exchange , exchange .getExchangeItems ()))
130+ .toList ();
131+
132+ return new PageImpl <>(exchangeDtos , pageable , exchanges .size ());
133+ }
134+
135+ /**
136+ * 교환 승인
137+ */
138+ @ Override
139+ @ Transactional
140+ public ExchangeResponseDto approveItem (Long exchangeId , User admin ) {
141+ log .info ("교환 승인 - 교환 ID: {}, 관리자: {}" , exchangeId , admin .getId ());
142+
143+ Exchange exchange = exchangeRepository .findById (exchangeId )
144+ .orElseThrow (() -> new IllegalArgumentException ("교환 정보를 찾을 수 없습니다." ));
145+
146+ if (exchange .getStatus () != Exchange .ExchangeStatus .REQUESTED ) {
147+ throw new IllegalStateException ("승인 대기 중인 교환만 승인할 수 있습니다." );
148+ }
149+
150+ // 교환 승인 처리
151+ exchange .approve ();
152+
153+ // 주문 상태 변경
154+ exchange .getOrder ().completeExchange ();
155+
156+ log .info ("교환 승인 완료 - 교환 ID: {}" , exchangeId );
157+
158+ // N+1 문제 해결: 한 번의 쿼리로 모든 데이터 조회
159+ Exchange exchangeWithItems = exchangeRepository .findByIdWithItems (exchangeId )
160+ .orElseThrow (() -> new IllegalArgumentException ("교환 정보를 찾을 수 없습니다." ));
161+
162+ return exchangeConverter .toResponseDto (exchangeWithItems , exchangeWithItems .getExchangeItems ());
163+ }
164+ }
0 commit comments