Skip to content

Commit c47cb12

Browse files
authored
수정 (#217)
1 parent 1fe8786 commit c47cb12

File tree

13 files changed

+1685
-18
lines changed

13 files changed

+1685
-18
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.back.domain.order.common.service;
2+
3+
import com.back.domain.order.order.entity.Order;
4+
import com.back.domain.order.order.repository.OrderRepository;
5+
import com.back.domain.order.orderItem.entity.OrderItem;
6+
import com.back.domain.order.orderItem.repository.OrderItemRepository;
7+
import com.back.domain.user.entity.User;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.data.domain.Pageable;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.transaction.annotation.Transactional;
14+
15+
import java.util.List;
16+
17+
/**
18+
* 주문 관련 공통 서비스 (환불/교환 공통 로직)
19+
*/
20+
@Slf4j
21+
@Transactional(readOnly = true)
22+
public abstract class BaseOrderActionService<T, R, I> {
23+
24+
protected final OrderRepository orderRepository;
25+
protected final OrderItemRepository orderItemRepository;
26+
27+
protected BaseOrderActionService(OrderRepository orderRepository, OrderItemRepository orderItemRepository) {
28+
this.orderRepository = orderRepository;
29+
this.orderItemRepository = orderItemRepository;
30+
}
31+
32+
/**
33+
* 주문 조회 및 권한 체크 (공통 로직)
34+
*/
35+
protected Order validateOrderAndOwnership(Long orderId, User user) {
36+
log.info("주문 조회 및 권한 체크 - orderId: {}, userId: {}", orderId, user.getId());
37+
38+
Order order = orderRepository.findById(orderId)
39+
.orElseThrow(() -> new IllegalArgumentException("주문을 찾을 수 없습니다."));
40+
41+
if (!order.getUser().getId().equals(user.getId())) {
42+
throw new IllegalStateException("해당 주문에 대한 권한이 없습니다.");
43+
}
44+
45+
return order;
46+
}
47+
48+
/**
49+
* 주문상품 조회 및 검증 (공통 로직)
50+
*/
51+
protected List<OrderItem> validateOrderItems(List<Long> orderItemIds) {
52+
log.info("주문상품 조회 및 검증 - orderItemIds: {}", orderItemIds);
53+
54+
List<OrderItem> orderItems = orderItemRepository.findAllById(orderItemIds);
55+
if (orderItems.isEmpty()) {
56+
throw new IllegalArgumentException("해당 상품을 찾을 수 없습니다.");
57+
}
58+
59+
return orderItems;
60+
}
61+
62+
/**
63+
* 사용자별 목록 조회 (공통 패턴)
64+
*/
65+
public abstract Page<R> getItemsByUser(User user, Pageable pageable);
66+
67+
/**
68+
* 상세 조회 (공통 패턴)
69+
*/
70+
public abstract R getItem(Long itemId, User user);
71+
72+
/**
73+
* 승인 처리 (공통 패턴)
74+
*/
75+
@Transactional
76+
public abstract R approveItem(Long itemId, User admin);
77+
}

src/main/java/com/back/domain/order/exchange/entity/Exchange.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import lombok.Getter;
1010
import lombok.NoArgsConstructor;
1111

12+
import java.util.ArrayList;
13+
import java.util.List;
14+
1215
@Entity
1316
@Getter
1417
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -56,6 +59,9 @@ public class Exchange extends BaseEntity {
5659
@Column(length = 20)
5760
private String newRecipientPhone;
5861

62+
@OneToMany(mappedBy = "exchange", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
63+
private List<ExchangeItem> exchangeItems = new ArrayList<>();
64+
5965
@Builder
6066
public Exchange(Order order, User user, ExchangeStatus status, String reason,
6167
String detailReason, ExchangeMethod exchangeMethod, String attachmentFiles,
@@ -75,16 +81,43 @@ public Exchange(Order order, User user, ExchangeStatus status, String reason,
7581
this.newRecipientPhone = newRecipientPhone;
7682
}
7783

78-
// 교환 승인
79-
public void approve() {
80-
this.status = ExchangeStatus.COMPLETED;
84+
85+
// ==================== Factory Methods ====================
86+
87+
/**
88+
* 교환 엔티티 생성 팩토리 메서드
89+
*/
90+
public static Exchange createExchange(Order order, User user, String reason, String detailReason,
91+
ExchangeMethod exchangeMethod, String attachmentFiles,
92+
String newShippingAddress1, String newShippingAddress2,
93+
String newShippingZip, String newRecipientName, String newRecipientPhone) {
94+
return Exchange.builder()
95+
.order(order)
96+
.user(user)
97+
.status(ExchangeStatus.REQUESTED)
98+
.reason(reason)
99+
.detailReason(detailReason)
100+
.exchangeMethod(exchangeMethod)
101+
.attachmentFiles(attachmentFiles)
102+
.newShippingAddress1(newShippingAddress1)
103+
.newShippingAddress2(newShippingAddress2)
104+
.newShippingZip(newShippingZip)
105+
.newRecipientName(newRecipientName)
106+
.newRecipientPhone(newRecipientPhone)
107+
.build();
81108
}
82109

83-
// 교환 거부 (거부 시에도 COMPLETED로 처리)
84-
public void reject() {
110+
// ==================== Business Methods ====================
111+
112+
/**
113+
* 교환 승인 처리
114+
*/
115+
public void approve() {
85116
this.status = ExchangeStatus.COMPLETED;
86117
}
87118

119+
// ==================== Enums ====================
120+
88121
// 교환 상태 enum
89122
public enum ExchangeStatus {
90123
REQUESTED, // 교환 신청

src/main/java/com/back/domain/order/exchange/repository/ExchangeRepository.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import org.springframework.data.domain.Page;
77
import org.springframework.data.domain.Pageable;
88
import org.springframework.data.jpa.repository.JpaRepository;
9+
import org.springframework.data.jpa.repository.Query;
10+
import org.springframework.data.repository.query.Param;
911
import org.springframework.stereotype.Repository;
1012

1113
import java.util.List;
14+
import java.util.Optional;
1215

1316
@Repository
1417
public interface ExchangeRepository extends JpaRepository<Exchange, Long> {
@@ -24,10 +27,18 @@ public interface ExchangeRepository extends JpaRepository<Exchange, Long> {
2427

2528
// 사용자별 교환 목록 (페이징, 최신순)
2629
Page<Exchange> findByUserOrderByCreateDateDesc(User user, Pageable pageable);
27-
30+
2831
// 주문별 교환 목록 (페이징, 최신순)
2932
Page<Exchange> findByOrderOrderByCreateDateDesc(Order order, Pageable pageable);
30-
33+
3134
// 상태별 교환 목록 (페이징, 최신순)
3235
Page<Exchange> findByStatusOrderByCreateDateDesc(Exchange.ExchangeStatus status, Pageable pageable);
36+
37+
// N+1 문제 해결: 교환과 교환 아이템을 함께 조회
38+
@Query("SELECT e FROM Exchange e LEFT JOIN FETCH e.exchangeItems ei LEFT JOIN FETCH ei.orderItem oi LEFT JOIN FETCH oi.product p LEFT JOIN FETCH p.images WHERE e.id = :exchangeId")
39+
Optional<Exchange> findByIdWithItems(@Param("exchangeId") Long exchangeId);
40+
41+
// N+1 문제 해결: 사용자별 교환 목록과 아이템들을 함께 조회
42+
@Query("SELECT e FROM Exchange e LEFT JOIN FETCH e.exchangeItems ei LEFT JOIN FETCH ei.orderItem oi LEFT JOIN FETCH oi.product p LEFT JOIN FETCH p.images WHERE e.user = :user ORDER BY e.createDate DESC")
43+
List<Exchange> findByUserWithItems(@Param("user") User user);
3344
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.back.domain.order.exchange.util;
2+
3+
import com.back.domain.order.exchange.dto.response.ExchangeResponseDto;
4+
import com.back.domain.order.exchange.entity.Exchange;
5+
import com.back.domain.order.exchange.entity.ExchangeItem;
6+
import org.springframework.stereotype.Component;
7+
8+
import java.util.List;
9+
10+
@Component
11+
public class ExchangeConverter {
12+
13+
/**
14+
* Exchange 엔티티를 ResponseDto로 변환
15+
*/
16+
public ExchangeResponseDto toResponseDto(Exchange exchange, List<ExchangeItem> exchangeItems) {
17+
List<ExchangeResponseDto.ExchangeItemResponseDto> itemDtos = exchangeItems.stream()
18+
.map(item -> new ExchangeResponseDto.ExchangeItemResponseDto(
19+
item.getId(),
20+
item.getOrderItem().getId(),
21+
item.getOrderItem().getProduct().getName(),
22+
item.getOrderItem().getProduct().getImages().isEmpty() ? null :
23+
item.getOrderItem().getProduct().getImages().get(0).getFileUrl(),
24+
item.getQuantity(),
25+
item.getOrderItem().getOptionInfo()
26+
))
27+
.toList();
28+
29+
return new ExchangeResponseDto(
30+
exchange.getId(),
31+
exchange.getOrder().getId(),
32+
exchange.getOrder().getOrderNumber(),
33+
exchange.getStatus(),
34+
exchange.getReason(),
35+
exchange.getDetailReason(),
36+
exchange.getExchangeMethod(),
37+
exchange.getAttachmentFiles() != null ? List.of(exchange.getAttachmentFiles().split(",")) : List.of(),
38+
exchange.getNewShippingAddress1(),
39+
exchange.getNewShippingAddress2(),
40+
exchange.getNewShippingZip(),
41+
exchange.getNewRecipientName(),
42+
exchange.getNewRecipientPhone(),
43+
exchange.getCreateDate(),
44+
exchange.getModifyDate(),
45+
itemDtos
46+
);
47+
}
48+
}

0 commit comments

Comments
 (0)