-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature] 판매자 반품/교환 생성 API 추가 #608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.bbangle.bbangle.claim.repository; | ||
|
|
||
| import com.bbangle.bbangle.claim.domain.ExchangeRequest; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface ExchangeRequestRepository extends JpaRepository<ExchangeRequest, Long> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| package com.bbangle.bbangle.claim.seller.service; | ||
|
|
||
| import com.bbangle.bbangle.claim.domain.ExchangeRequest; | ||
| import com.bbangle.bbangle.claim.domain.constant.ExchangeRequestStatus; | ||
| import com.bbangle.bbangle.claim.repository.ExchangeRequestRepository; | ||
| import com.bbangle.bbangle.claim.seller.service.model.ExchangeCreateCommand; | ||
| import com.bbangle.bbangle.exception.BbangleErrorCode; | ||
| import com.bbangle.bbangle.exception.BbangleException; | ||
| import com.bbangle.bbangle.order.domain.Order; | ||
| import com.bbangle.bbangle.order.domain.OrderItem; | ||
| import com.bbangle.bbangle.order.domain.OrderItemHistory; | ||
| import com.bbangle.bbangle.order.repository.OrderItemHistoryRepository; | ||
| import com.bbangle.bbangle.order.repository.OrderItemRepository; | ||
| import com.bbangle.bbangle.order.repository.OrderRepository; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse.ExchangeContent; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse.ExchangeCreateResponse; | ||
| import com.bbangle.bbangle.seller.repository.SellerRepository; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Service | ||
| public class SellerExchangeService { | ||
|
|
||
| private final ExchangeRequestRepository exchangeRequestRepository; | ||
| private final OrderItemHistoryRepository orderItemHistoryRepository; | ||
| private final OrderRepository orderRepository; | ||
| private final OrderItemRepository orderItemRepository; | ||
| private final SellerRepository sellerRepository; | ||
|
|
||
| @Transactional | ||
| public ExchangeCreateResponse createExchange(ExchangeCreateCommand command) { | ||
| if (command.orderItemIds() == null || command.orderItemIds().isEmpty()) { | ||
| throw new BbangleException(BbangleErrorCode.ORDER_ITEM_NOT_FOUND); | ||
| } | ||
|
|
||
| List<Long> uniqueOrderItemIds = command.orderItemIds().stream() | ||
| .distinct() | ||
| .toList(); | ||
|
|
||
| int requestedCount = uniqueOrderItemIds.size(); | ||
|
|
||
| Order order = orderRepository.findById(command.orderId()) | ||
| .orElseThrow(() -> new BbangleException(BbangleErrorCode.ORDER_NOT_FOUND)); | ||
|
|
||
| Long storeId = getStoreIdOrThrow(command.sellerId()); | ||
|
|
||
| List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndIdIn( | ||
| order.getId(), | ||
| uniqueOrderItemIds | ||
| ); | ||
|
|
||
| Set<Long> foundIds = orderItems.stream() | ||
| .map(OrderItem::getId) | ||
| .collect(Collectors.toSet()); | ||
|
|
||
| if (!foundIds.isEmpty()) { | ||
| assertOwnedOrderItems(order.getId(), new ArrayList<>(foundIds), storeId); | ||
| } | ||
|
|
||
| List<Long> notFoundIds = uniqueOrderItemIds.stream() | ||
| .filter(id -> !foundIds.contains(id)) | ||
| .toList(); | ||
|
|
||
| List<Long> successOrderItemIds = new ArrayList<>(); | ||
| List<Long> failedOrderItemIds = new ArrayList<>(notFoundIds); | ||
| List<ExchangeRequest> exchangeRequestsToSave = new ArrayList<>(); | ||
| List<OrderItemHistory> historiesToSave = new ArrayList<>(); | ||
|
|
||
| for (OrderItem orderItem : orderItems) { | ||
| if (orderItem.requestExchange()) { | ||
| ExchangeRequest exchangeRequest = ExchangeRequest.builder() | ||
| .orderItem(orderItem) | ||
| .detailReason(command.reason()) | ||
| .sellerComment(command.sellerComment()) | ||
| .status(ExchangeRequestStatus.REQUESTED) | ||
| .build(); | ||
| exchangeRequestsToSave.add(exchangeRequest); | ||
| historiesToSave.add(OrderItemHistory.create(orderItem)); | ||
| successOrderItemIds.add(orderItem.getId()); | ||
| } else { | ||
| failedOrderItemIds.add(orderItem.getId()); | ||
| } | ||
| } | ||
|
|
||
| exchangeRequestRepository.saveAll(exchangeRequestsToSave); | ||
| orderItemHistoryRepository.saveAll(historiesToSave); | ||
|
|
||
| int successCount = successOrderItemIds.size(); | ||
| int failCount = failedOrderItemIds.size(); | ||
|
|
||
| SellerOrderResponse.Summary summary = | ||
| SellerOrderResponse.Summary.of(requestedCount, successCount, failCount); | ||
|
|
||
| ExchangeContent content = ExchangeContent.of( | ||
| order.getId(), | ||
| summary, | ||
| successOrderItemIds, | ||
| failedOrderItemIds | ||
| ); | ||
|
|
||
| return ExchangeCreateResponse.of(content); | ||
| } | ||
|
|
||
| private Long getStoreIdOrThrow(Long sellerId) { | ||
| Long storeId = sellerRepository.findStoreIdBySellerId(sellerId); | ||
| if (storeId == null) { | ||
| throw new BbangleException(BbangleErrorCode.SELLER_NOT_FOUND); | ||
| } | ||
| return storeId; | ||
| } | ||
|
|
||
| private void assertOwnedOrderItems(Long orderId, List<Long> orderItemIds, Long storeId) { | ||
| long ownedCount = orderItemRepository.countOwnedOrderItems(orderId, orderItemIds, storeId); | ||
| if (ownedCount != orderItemIds.size()) { | ||
| throw new BbangleException(BbangleErrorCode.ORDER_ACCESS_DENIED); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,25 @@ | |
|
|
||
| import com.bbangle.bbangle.claim.domain.ReturnRequest; | ||
| import com.bbangle.bbangle.claim.domain.constant.DecisionType; | ||
| import com.bbangle.bbangle.claim.domain.constant.ReturnRequestRequestStatus; | ||
| import com.bbangle.bbangle.claim.repository.ReturnRequestRepository; | ||
| import com.bbangle.bbangle.claim.seller.service.model.ReturnCreateCommand; | ||
| import com.bbangle.bbangle.exception.BbangleErrorCode; | ||
| import com.bbangle.bbangle.exception.BbangleException; | ||
| import com.bbangle.bbangle.order.domain.Order; | ||
| import com.bbangle.bbangle.order.domain.OrderItem; | ||
| import com.bbangle.bbangle.order.domain.OrderItemHistory; | ||
| import com.bbangle.bbangle.order.repository.OrderItemHistoryRepository; | ||
| import com.bbangle.bbangle.order.repository.OrderItemRepository; | ||
| import com.bbangle.bbangle.order.repository.OrderRepository; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse.ReturnContent; | ||
| import com.bbangle.bbangle.order.seller.controller.dto.response.SellerOrderResponse.ReturnCreateResponse; | ||
| import com.bbangle.bbangle.seller.repository.SellerRepository; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
@@ -19,6 +31,98 @@ public class SellerReturnService { | |
|
|
||
| private final ReturnRequestRepository returnRequestRepository; | ||
| private final OrderItemHistoryRepository orderItemHistoryRepository; | ||
| private final OrderRepository orderRepository; | ||
| private final OrderItemRepository orderItemRepository; | ||
| private final SellerRepository sellerRepository; | ||
|
|
||
| @Transactional | ||
| public ReturnCreateResponse createReturn(ReturnCreateCommand command) { | ||
| if (command.orderItemIds() == null || command.orderItemIds().isEmpty()) { | ||
| throw new BbangleException(BbangleErrorCode.ORDER_ITEM_NOT_FOUND); | ||
| } | ||
|
|
||
| List<Long> uniqueOrderItemIds = command.orderItemIds().stream() | ||
| .distinct() | ||
| .toList(); | ||
|
|
||
| int requestedCount = uniqueOrderItemIds.size(); | ||
|
|
||
| Order order = orderRepository.findById(command.orderId()) | ||
| .orElseThrow(() -> new BbangleException(BbangleErrorCode.ORDER_NOT_FOUND)); | ||
|
|
||
| Long storeId = getStoreIdOrThrow(command.sellerId()); | ||
|
|
||
| List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndIdIn( | ||
| order.getId(), | ||
| uniqueOrderItemIds | ||
| ); | ||
|
|
||
| Set<Long> foundIds = orderItems.stream() | ||
| .map(OrderItem::getId) | ||
| .collect(Collectors.toSet()); | ||
|
|
||
| if (!foundIds.isEmpty()) { | ||
| assertOwnedOrderItems(order.getId(), new ArrayList<>(foundIds), storeId); | ||
| } | ||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
요청된
🛡️ 제안: 아이템이 없을 때 early return if (!foundIds.isEmpty()) {
assertOwnedOrderItems(order.getId(), new ArrayList<>(foundIds), storeId);
+ } else {
+ return ReturnCreateResponse.of(ReturnContent.of(
+ order.getId(),
+ SellerOrderResponse.Summary.of(requestedCount, 0, requestedCount),
+ List.of(),
+ new ArrayList<>(uniqueOrderItemIds)
+ ));
}🤖 Prompt for AI Agents |
||
|
|
||
| List<Long> notFoundIds = uniqueOrderItemIds.stream() | ||
| .filter(id -> !foundIds.contains(id)) | ||
| .toList(); | ||
|
|
||
| List<Long> successOrderItemIds = new ArrayList<>(); | ||
| List<Long> failedOrderItemIds = new ArrayList<>(notFoundIds); | ||
| List<ReturnRequest> returnRequestsToSave = new ArrayList<>(); | ||
| List<OrderItemHistory> historiesToSave = new ArrayList<>(); | ||
|
|
||
| for (OrderItem orderItem : orderItems) { | ||
| if (orderItem.requestReturn()) { | ||
| ReturnRequest returnRequest = ReturnRequest.builder() | ||
| .orderItem(orderItem) | ||
| .detailReason(command.reason()) | ||
| .sellerComment(command.sellerComment()) | ||
| .status(ReturnRequestRequestStatus.REQUESTED) | ||
| .build(); | ||
| returnRequestsToSave.add(returnRequest); | ||
| historiesToSave.add(OrderItemHistory.create(orderItem)); | ||
| successOrderItemIds.add(orderItem.getId()); | ||
| } else { | ||
| failedOrderItemIds.add(orderItem.getId()); | ||
| } | ||
| } | ||
|
|
||
| returnRequestRepository.saveAll(returnRequestsToSave); | ||
| orderItemHistoryRepository.saveAll(historiesToSave); | ||
|
|
||
| int successCount = successOrderItemIds.size(); | ||
| int failCount = failedOrderItemIds.size(); | ||
|
|
||
| SellerOrderResponse.Summary summary = | ||
| SellerOrderResponse.Summary.of(requestedCount, successCount, failCount); | ||
|
|
||
| ReturnContent content = ReturnContent.of( | ||
| order.getId(), | ||
| summary, | ||
| successOrderItemIds, | ||
| failedOrderItemIds | ||
| ); | ||
|
|
||
| return ReturnCreateResponse.of(content); | ||
| } | ||
|
|
||
| private Long getStoreIdOrThrow(Long sellerId) { | ||
| Long storeId = sellerRepository.findStoreIdBySellerId(sellerId); | ||
| if (storeId == null) { | ||
| throw new BbangleException(BbangleErrorCode.SELLER_NOT_FOUND); | ||
| } | ||
| return storeId; | ||
| } | ||
|
|
||
| private void assertOwnedOrderItems(Long orderId, List<Long> orderItemIds, Long storeId) { | ||
| long ownedCount = orderItemRepository.countOwnedOrderItems(orderId, orderItemIds, storeId); | ||
| if (ownedCount != orderItemIds.size()) { | ||
| throw new BbangleException(BbangleErrorCode.ORDER_ACCESS_DENIED); | ||
| } | ||
| } | ||
|
|
||
| @Transactional | ||
| public void decision(List<Long> returnIds, Long sellerId, DecisionType decisionType, String reason) { | ||
|
|
@@ -28,28 +132,27 @@ public void decision(List<Long> returnIds, Long sellerId, DecisionType decisionT | |
| } | ||
|
|
||
| List<ReturnRequest> returnRequests = returnRequestRepository.findAllById(returnIds); | ||
| List<OrderItemHistory> historiesToSave = new ArrayList<>(); | ||
|
|
||
| for (ReturnRequest returnRequest : returnRequests) { | ||
| processDecision(returnRequest, decisionType, reason); | ||
| OrderItemHistory history = processDecision(returnRequest, decisionType, reason); | ||
| historiesToSave.add(history); | ||
| } | ||
| orderItemHistoryRepository.saveAll(historiesToSave); | ||
| } | ||
|
|
||
| private void processDecision(ReturnRequest returnRequest, DecisionType decisionType, String reason) { | ||
| private OrderItemHistory processDecision(ReturnRequest returnRequest, DecisionType decisionType, String reason) { | ||
| OrderItem orderItem = returnRequest.getOrderItem(); | ||
| switch (decisionType) { | ||
| case APPROVE -> { | ||
| returnRequest.approve(reason); | ||
| OrderItem orderItem = returnRequest.getOrderItem(); | ||
| orderItem.returnApprove(); | ||
| OrderItemHistory history = OrderItemHistory.create(orderItem); | ||
| orderItemHistoryRepository.save(history); | ||
| } | ||
| case REJECT -> { | ||
| returnRequest.reject(reason); | ||
| OrderItem orderItem = returnRequest.getOrderItem(); | ||
| orderItem.returnReject(); | ||
| OrderItemHistory history = OrderItemHistory.create(orderItem); | ||
| orderItemHistoryRepository.save(history); | ||
| } | ||
| } | ||
| return OrderItemHistory.create(orderItem); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.bbangle.bbangle.claim.seller.service.model; | ||
|
|
||
| import java.util.List; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder | ||
| public record ExchangeCreateCommand( | ||
| Long orderId, | ||
| List<Long> orderItemIds, | ||
| String reason, | ||
| String sellerComment, | ||
| Long sellerId | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.bbangle.bbangle.claim.seller.service.model; | ||
|
|
||
| import java.util.List; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder | ||
| public record ReturnCreateCommand( | ||
| Long orderId, | ||
| List<Long> orderItemIds, | ||
| String reason, | ||
| String sellerComment, | ||
| Long sellerId | ||
| ) { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 242
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 178
🏁 Script executed:
# Examine ExchangeRequest.java to see the actual field and parent class cat -n src/main/java/com/bbangle/bbangle/claim/domain/ExchangeRequest.javaRepository: eco-dessert-platform/backend
Length of output: 2675
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 430
🏁 Script executed:
# Check ReturnRequest.java for comparison cat -n src/main/java/com/bbangle/bbangle/claim/domain/ReturnRequest.javaRepository: eco-dessert-platform/backend
Length of output: 2697
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 1919
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 385
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 204
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 1942
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 1407
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 527
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 1790
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 54
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 392
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 54
🏁 Script executed:
Repository: eco-dessert-platform/backend
Length of output: 1816
exchange_request테이블에seller_comment컬럼이 누락되어 있습니다.ExchangeRequest.java의sellerComment필드(34줄)가 코드에서 사용 중이지만(approve/reject 메서드), 데이터베이스 스키마에서 해당 컬럼이 없습니다. V21에서exchange_request테이블이 생성될 때status컬럼만 포함되었으며, V33에서는seller_comment가return_request테이블에만 추가되었습니다. 이로 인해 엔티티 저장 시 컬럼 불일치 오류가 발생합니다.Flyway 마이그레이션 파일에 다음을 추가하여
exchange_request테이블에seller_comment컬럼을 추가해야 합니다:또한
sellerComment필드에@Column(name = "seller_comment")어노테이션을 명시적으로 추가하는 것이 권장됩니다.🤖 Prompt for AI Agents