From 4a66f3d951460a79ac2350473f1d52b34d58d9fd Mon Sep 17 00:00:00 2001 From: Yun024 Date: Tue, 17 Feb 2026 23:03:49 +0900 Subject: [PATCH 1/9] feat(#335):OrderContextDto-create --- .../dto/response/OrderContextDto.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java new file mode 100644 index 00000000..50de0609 --- /dev/null +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java @@ -0,0 +1,29 @@ +package com.example.Spot.order.presentation.dto.response; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; +import java.util.UUID; + +import com.example.Spot.global.feign.dto.MenuOptionResponse; +import com.example.Spot.global.feign.dto.MenuResponse; +import com.example.Spot.global.feign.dto.StoreResponse; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OrderContextDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private StoreResponse store; + private Map menuMap; + private Map optionMap; +} From 3198995c690b6b90767a97f88300dca962217233 Mon Sep 17 00:00:00 2001 From: Yun024 Date: Wed, 18 Feb 2026 02:12:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat(#335):createOrder-=EA=B0=84=EC=86=8C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kafka.sh | 9 +- .../global/feign/dto/MenuOptionResponse.java | 4 + .../Spot/global/feign/dto/MenuResponse.java | 5 + .../Spot/global/feign/dto/StoreResponse.java | 4 + .../application/service/OrderServiceImpl.java | 120 +++++++----------- .../Spot/order/domain/entity/OrderEntity.java | 20 ++- .../order/domain/entity/OrderItemEntity.java | 8 ++ .../domain/entity/OrderItemOptionEntity.java | 4 + .../temporal/activity/OrderActivity.java | 5 + .../temporal/activity/OrderActivityImpl.java | 98 +++++++++++++- .../temporal/workflow/OrderWorkflow.java | 4 +- .../temporal/workflow/OrderWorkflowImpl.java | 6 +- .../dto/response/OrderContextDto.java | 27 ++++ .../dto/response/OrderResponseDto.java | 15 +++ 14 files changed, 237 insertions(+), 92 deletions(-) diff --git a/kafka.sh b/kafka.sh index 8d273ced..cb838066 100755 --- a/kafka.sh +++ b/kafka.sh @@ -15,16 +15,11 @@ set -e # 에러 발생 시 즉시 중단 echo "=== 선택적 컨테이너 종료 (Order, Payment, Temporal) ===" -docker compose stop spot-order spot-payment temporal temporal-ui -docker compose rm -f spot-order spot-payment temporal temporal-ui +docker compose stop spot-order spot-payment +docker compose rm -f spot-order spot-payment echo "=== 핵심 서비스 빌드 (Order, Payment) ===" (cd spot-order && ./gradlew clean bootJar -x test) (cd spot-payment && ./gradlew clean bootJar -x test) -echo "=== 인프라 및 핵심 서비스 시작 ===" -docker compose up -d db temporal temporal-ui -echo ">> Temporal 안정화 대기 (5초)..." -sleep 5 - docker compose up --build -d spot-order spot-payment \ No newline at end of file diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java index bd9b4d57..d9869673 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java @@ -1,5 +1,7 @@ package com.example.Spot.global.feign.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.UUID; import lombok.AllArgsConstructor; @@ -11,6 +13,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class MenuOptionResponse { private UUID id; @@ -18,5 +21,6 @@ public class MenuOptionResponse { private String name; private String detail; private Integer price; + @JsonProperty("isDeleted") private boolean isDeleted; } diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java index 93fbd366..193ca1ec 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java @@ -1,5 +1,7 @@ package com.example.Spot.global.feign.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.UUID; import lombok.AllArgsConstructor; @@ -11,6 +13,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class MenuResponse { private UUID id; @@ -19,6 +22,8 @@ public class MenuResponse { private String description; private Integer price; private String imageUrl; + @JsonProperty("isHidden") private boolean isHidden; + @JsonProperty("isDeleted") private boolean isDeleted; } diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java index 3810000f..10f2f7df 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java @@ -1,5 +1,7 @@ package com.example.Spot.global.feign.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.UUID; import lombok.AllArgsConstructor; @@ -11,6 +13,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class StoreResponse { private UUID id; @@ -20,5 +23,6 @@ public class StoreResponse { private String phoneNumber; private String status; private Integer ownerId; + @JsonProperty("isDeleted") private boolean isDeleted; } diff --git a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java index 124b0e34..ed5eedae 100644 --- a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java @@ -1,8 +1,12 @@ package com.example.Spot.order.application.service; +import com.example.Spot.global.feign.MenuClient; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; +import java.awt.*; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -66,6 +70,7 @@ public class OrderServiceImpl implements OrderService { private final StoreClient storeClient; private final OrderEventProducer orderEventProducer; private final WorkflowClient workflowClient; + private final MenuClient menuClient; // ******* // // 주문 조회 // @@ -211,70 +216,22 @@ private void fetchOrderItemOptions(List orders) { @Transactional @ValidateStoreAndMenu public OrderResponseDto createOrder(OrderCreateRequestDto requestDto, Integer userId) { - - StoreResponse store = OrderValidationContext.getStoreResponse(); - - checkDuplicateOrder(userId, store.getId(), requestDto); - - String orderNumber = generateOrderNumber(); - OrderEntity order = OrderEntity.builder() - .storeId(store.getId()) - .userId(userId) - .orderNumber(orderNumber) - .pickupTime(requestDto.getPickupTime()) - .needDisposables(requestDto.getNeedDisposables()) - .request(requestDto.getRequest()) - .build(); - - for (OrderItemRequestDto itemDto : requestDto.getOrderItems()) { - MenuResponse menu = OrderValidationContext.getMenuResponse(itemDto.getMenuId()); - - OrderItemEntity orderItem = OrderItemEntity.builder() - .menuId(menu.getId()) - .menuName(menu.getName()) - .menuPrice(BigDecimal.valueOf(menu.getPrice())) - .quantity(itemDto.getQuantity()) - .build(); - - for (OrderItemOptionRequestDto optionDto : itemDto.getOptions()) { - MenuOptionResponse menuOption = OrderValidationContext.getMenuOptionResponse(optionDto.getMenuOptionId()); - - OrderItemOptionEntity orderItemOption = OrderItemOptionEntity.builder() - .menuOptionId(menuOption.getId()) - .optionName(menuOption.getName()) - .optionDetail(menuOption.getDetail()) - .optionPrice(BigDecimal.valueOf(menuOption.getPrice())) - .build(); - - orderItem.addOrderItemOption(orderItemOption); - } - - order.addOrderItem(orderItem); - } - - OrderEntity savedOrder = orderRepository.save(order); - OrderResponseDto responseDto = OrderResponseDto.from(savedOrder); - - orderEventProducer.reserveOrderCreated( - savedOrder.getId(), - userId, - responseDto.getTotalAmount().longValue() - ); - + + OrderContextDto contextDto = fetchOrderContext(requestDto); + checkDuplicateOrder(userId, contextDto.getStore().getId(), requestDto); + UUID orderId = UUID.randomUUID(); + BigDecimal totalAmount = contextDto.calculateTotalAmount(requestDto); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId(savedOrder.getId().toString()) + .setWorkflowId(orderId.toString()) .setTaskQueue(OrderConstants.ORDER_TASK_QUEUE) .build()); - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - @Override - public void afterCommit() { - WorkflowClient.start(workflow::processOrder, savedOrder.getId()); - } - }); - return responseDto; + WorkflowClient.start(workflow::processOrder, orderId, userId, requestDto, contextDto); + + return OrderResponseDto.of(orderId, userId, requestDto, contextDto, totalAmount); } @@ -498,23 +455,7 @@ private void checkDuplicateOrder(Integer userId, UUID storeId, OrderCreateReques } } } - - private String generateOrderNumber() { - String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); - String datePattern = "ORDER-" + date + "-%"; - - Optional lastOrderNumber = orderRepository.findTopOrderNumberByDatePattern(datePattern); - - int sequence = 1; - if (lastOrderNumber.isPresent()) { - String lastNumber = lastOrderNumber.get(); - String lastSeq = lastNumber.substring(lastNumber.lastIndexOf('-') + 1); - sequence = Integer.parseInt(lastSeq) + 1; - } - - return String.format("ORDER-%s-%04d", date, sequence); - } - + private LocalDateTime[] getDateRange(LocalDateTime date) { LocalDateTime startOfDay = date.toLocalDate().atStartOfDay(); LocalDateTime endOfDay = date.toLocalDate().atTime(23, 59, 59); @@ -569,4 +510,33 @@ public void afterCommit() { } }); } + + private OrderContextDto fetchOrderContext(OrderCreateRequestDto requestDto) { + StoreResponse store = storeClient.getStoreById(requestDto.getStoreId()); + if (store == null) throw new IllegalArgumentException("존재하지 않는 가게입니다."); + + Map menuMap = new HashMap<>(); + Map optionMap = new HashMap<>(); + + for (OrderItemRequestDto itemDto : requestDto.getOrderItems()) { + MenuResponse menu = menuClient.getMenuById(itemDto.getMenuId()); + if (menu == null || menu.isHidden() || menu.isDeleted()) { + throw new IllegalArgumentException("판매 불가 메뉴입니다: " + itemDto.getMenuId()); + } + menuMap.put(itemDto.getMenuId(), menu); + + for (OrderItemOptionRequestDto optionDto : itemDto.getOptions()) { + MenuOptionResponse option = menuClient.getMenuOptionById(optionDto.getMenuOptionId()); + if (option == null || option.isDeleted()) { + throw new IllegalArgumentException("판매 불가 옵션입니다."); + } + optionMap.put(optionDto.getMenuOptionId(), option); + } + } + return OrderContextDto.builder() + .store(store) + .menuMap(menuMap) + .optionMap(optionMap) + .build(); + } } diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java index 5cadc29b..e57c2988 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java @@ -1,5 +1,6 @@ package com.example.Spot.order.domain.entity; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -38,8 +39,6 @@ public class OrderEntity extends BaseEntity { // orderItems는 unique key에 포함할 수 없음. @Id - @GeneratedValue - @UuidGenerator @Column(columnDefinition = "UUID") private UUID id; @@ -103,8 +102,9 @@ public class OrderEntity extends BaseEntity { private List orderItems = new ArrayList<>(); @Builder - public OrderEntity(UUID storeId, Integer userId, String orderNumber, - String request, boolean needDisposables, LocalDateTime pickupTime) { + public OrderEntity(UUID id, UUID storeId, Integer userId, String orderNumber, + String request, boolean needDisposables, LocalDateTime pickupTime, + OrderStatus orderStatus) { if (storeId == null) { throw new IllegalArgumentException("가게 ID는 필수입니다."); } @@ -117,15 +117,15 @@ public OrderEntity(UUID storeId, Integer userId, String orderNumber, if (pickupTime == null) { throw new IllegalArgumentException("픽업 시간은 필수입니다."); } - + + this.id = id; this.storeId = storeId; this.userId = userId; this.orderNumber = orderNumber; this.request = request; this.needDisposables = needDisposables; this.pickupTime = pickupTime; - - this.orderStatus = OrderStatus.PAYMENT_PENDING; + this.orderStatus = orderStatus != null ? orderStatus : OrderStatus.PAYMENT_PENDING; } public void addOrderItem(OrderItemEntity orderItem) { @@ -186,6 +186,12 @@ public void cancelOrder(String reason, CancelledBy cancelledBy) { this.cancelledBy = cancelledBy; } + public BigDecimal getTotalAmount() { + return orderItems.stream() + .map(OrderItemEntity::getTotalPrice) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + public void startCooking() { validateStatusTransition(OrderStatus.COOKING); this.orderStatus = OrderStatus.COOKING; diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemEntity.java b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemEntity.java index 6e0d0070..9a023c9e 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemEntity.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemEntity.java @@ -88,4 +88,12 @@ public void addOrderItemOption(OrderItemOptionEntity orderItemOption) { protected void setOrder(OrderEntity order) { this.order = order; } + + public BigDecimal getTotalPrice() { + BigDecimal optionsSum = orderItemOptions.stream() + .map(OrderItemOptionEntity::getSubtotal) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + return this.menuPrice.multiply(BigDecimal.valueOf(this.quantity)).add(optionsSum); + } } diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemOptionEntity.java b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemOptionEntity.java index 79de9fd2..959b7126 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemOptionEntity.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderItemOptionEntity.java @@ -69,5 +69,9 @@ public OrderItemOptionEntity(UUID menuOptionId, String optionName, String option protected void setOrderItem(OrderItemEntity orderItem) { this.orderItem = orderItem; } + + public BigDecimal getSubtotal() { + return this.optionPrice; // 옵션 하나의 가격 + } } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java index 0782116c..60c4d90e 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java @@ -1,5 +1,7 @@ package com.example.Spot.order.infrastructure.temporal.activity; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.util.UUID; import com.example.Spot.order.domain.enums.OrderStatus; @@ -10,6 +12,9 @@ @ActivityInterface public interface OrderActivity { + @ActivityMethod + void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto); + @ActivityMethod OrderStatus getOrderStatus(UUID orderId); diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java index aff7fd79..f8a34225 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java @@ -1,7 +1,22 @@ package com.example.Spot.order.infrastructure.temporal.activity; +import com.example.Spot.global.feign.dto.MenuOptionResponse; +import com.example.Spot.global.feign.dto.MenuResponse; +import com.example.Spot.global.feign.dto.StoreResponse; +import com.example.Spot.order.domain.entity.OrderItemEntity; +import com.example.Spot.order.domain.entity.OrderItemOptionEntity; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.request.OrderItemOptionRequestDto; +import com.example.Spot.order.presentation.dto.request.OrderItemRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Optional; import java.util.UUID; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +40,72 @@ public class OrderActivityImpl implements OrderActivity { private final OrderRepository orderRepository; private final OrderEventProducer orderEventProducer; + @Override + public void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto) { + if (orderRepository.existsById(orderId)) { + return; + } + + String orderNumber = generateOrderNumber(); + BigDecimal totalAmount = BigDecimal.ZERO; + + OrderEntity order = OrderEntity.builder() + .id(orderId) + .storeId(contextDto.getStore().getId()) + .userId(userId) + .orderNumber(orderNumber) + .pickupTime(requestDto.getPickupTime()) + .needDisposables(requestDto.getNeedDisposables()) + .request(requestDto.getRequest()) + .orderStatus(OrderStatus.PAYMENT_PENDING) + .build(); + + for (OrderItemRequestDto itemDto : requestDto.getOrderItems()) { + MenuResponse menu = contextDto.getMenuMap().get(itemDto.getMenuId()); + BigDecimal itemPrice = BigDecimal.valueOf(menu.getPrice()); + + // 총액 합산 로직 + totalAmount = totalAmount.add(itemPrice.multiply(BigDecimal.valueOf(itemDto.getQuantity()))); + + OrderItemEntity orderItem = OrderItemEntity.builder() + .menuId(menu.getId()) + .menuName(menu.getName()) + .menuPrice(itemPrice) + .quantity(itemDto.getQuantity()) + .build(); + + for (OrderItemOptionRequestDto optionDto : itemDto.getOptions()) { + MenuOptionResponse menuOption = contextDto.getOptionMap().get(optionDto.getMenuOptionId()); + BigDecimal optionPrice = BigDecimal.valueOf(menuOption.getPrice()); + + // 옵션 총액 합산 + totalAmount = totalAmount.add(optionPrice); + + OrderItemOptionEntity orderItemOption = OrderItemOptionEntity.builder() + .menuOptionId(menuOption.getId()) + .optionName(menuOption.getName()) + .optionDetail(menuOption.getDetail()) + .optionPrice(optionPrice) + .build(); + + orderItem.addOrderItemOption(orderItemOption); + } + order.addOrderItem(orderItem); + } + + orderRepository.save(order); + + orderEventProducer.reserveOrderCreated( + orderId, + userId, + totalAmount.longValue() + ); + + log.info("주문 생성이 완료되었습니다. OrderID: {}, OrderNumber: {}", orderId, orderNumber); + } + + + @Override @Transactional public OrderStatus getOrderStatus(UUID orderId) { @@ -74,5 +155,20 @@ public void handleRefundTimeout(UUID orderId) { orderId, order.getOrderStatus()); orderRepository.save(order); } - + + private String generateOrderNumber() { + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String datePattern = "ORDER-" + date + "-%"; + + Optional lastOrderNumber = orderRepository.findTopOrderNumberByDatePattern(datePattern); + + int sequence = 1; + if (lastOrderNumber.isPresent()) { + String lastNumber = lastOrderNumber.get(); + String lastSeq = lastNumber.substring(lastNumber.lastIndexOf('-') + 1); + sequence = Integer.parseInt(lastSeq) + 1; + } + + return String.format("ORDER-%s-%04d", date, sequence); + } } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java index 7b1e3475..f6988ecf 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java @@ -1,5 +1,7 @@ package com.example.Spot.order.infrastructure.temporal.workflow; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.util.UUID; import com.example.Spot.order.domain.enums.OrderStatus; @@ -12,7 +14,7 @@ public interface OrderWorkflow { @WorkflowMethod - void processOrder(UUID orderId); + void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto); @SignalMethod void signalStatusChanged(OrderStatus nextStatus); diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 7f3758c7..c1257f0d 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -1,5 +1,7 @@ package com.example.Spot.order.infrastructure.temporal.workflow; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.time.Duration; import java.util.UUID; @@ -27,9 +29,11 @@ public class OrderWorkflowImpl implements OrderWorkflow { private boolean isRefundCompleted = false; @Override - public void processOrder(UUID orderId) { + public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto) { OrderActivity activities = Workflow.newActivityStub(OrderActivity.class, ACTIVITY_OPTIONS); + activities.createOrderInDb(orderId, userId, requestDto, contextDto); + boolean paidWithinTime = Workflow.await(Duration.ofMinutes(5), () -> currentStatus == OrderStatus.PENDING || currentStatus.isFinalStatus()); diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java index 50de0609..a7dd27fe 100644 --- a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java @@ -26,4 +26,31 @@ public class OrderContextDto implements Serializable { private StoreResponse store; private Map menuMap; private Map optionMap; + + public java.math.BigDecimal calculateTotalAmount(com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto request) { + java.math.BigDecimal total = java.math.BigDecimal.ZERO; + + if (request.getOrderItems() == null) return total; + + for (var item : request.getOrderItems()) { + // 1. 메뉴 가격 계산 + MenuResponse menu = this.menuMap.get(item.getMenuId()); + if (menu != null) { + java.math.BigDecimal itemSubtotal = java.math.BigDecimal.valueOf(menu.getPrice()) + .multiply(java.math.BigDecimal.valueOf(item.getQuantity())); + total = total.add(itemSubtotal); + } + + // 2. 옵션 가격 합산 + if (item.getOptions() != null) { + for (var opt : item.getOptions()) { + MenuOptionResponse option = this.optionMap.get(opt.getMenuOptionId()); + if (option != null) { + total = total.add(java.math.BigDecimal.valueOf(option.getPrice())); + } + } + } + } + return total; + } } diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java index a50a390a..e271ae58 100644 --- a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java @@ -1,5 +1,6 @@ package com.example.Spot.order.presentation.dto.response; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -113,5 +114,19 @@ public static OrderResponseDto from(OrderEntity entity, String storeName) { .totalAmount(dto.getTotalAmount()) .build(); } + public static OrderResponseDto of(UUID orderId, Integer userId, OrderCreateRequestDto request, OrderContextDto context, BigDecimal totalAmount) { + return OrderResponseDto.builder() + .id(orderId) + .userId(userId) + .storeId(request.getStoreId()) + .storeName(context.getStore().getName()) + .orderStatus(OrderStatus.PAYMENT_PENDING) // 시작 상태 + .pickupTime(request.getPickupTime()) + .needDisposables(request.getNeedDisposables()) + .request(request.getRequest()) + .totalAmount(totalAmount) + .createdAt(LocalDateTime.now()) + .build(); + } } From 778a2fc6ed4cd66c49dfd03d246dc9db2215fb3c Mon Sep 17 00:00:00 2001 From: Yun024 Date: Wed, 18 Feb 2026 02:17:04 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(#335):=EC=A3=BC=EB=AC=B8=EC=88=98?= =?UTF-8?q?=EB=9D=BD=EB=8C=80=EA=B8=B0=20=ED=83=80=EC=9E=84=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=8B=9C=20=EC=9E=90=EB=8F=99=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../temporal/workflow/OrderWorkflowImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index c1257f0d..e19ecda4 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -45,9 +45,12 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req } return; } - - Workflow.await(Duration.ofMinutes(10), () -> currentStatus == OrderStatus.ACCEPTED || isTrulyFinalStatus(currentStatus)); - if (handleCancelIfNecessary(orderId, activities)) { + + boolean acceptedWithinTime = Workflow.await(Duration.ofMinutes(10), + () -> currentStatus == OrderStatus.ACCEPTED || isTrulyFinalStatus(currentStatus)); + if (!acceptedWithinTime && currentStatus == OrderStatus.PENDING) { + activities.cancelOrder(orderId, "점주 미수락으로 인한 자동 취소 . 환불"); + waitForRefundAndFinalize(orderId, activities); return; } From f18b3a6e39f118192d392c29c5b7e50edf5c028e Mon Sep 17 00:00:00 2001 From: Yun024 Date: Wed, 18 Feb 2026 22:01:22 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat(#335):Order-Service=EC=9D=98=20DB?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=95=A1=ED=8B=B0?= =?UTF-8?q?=EB=B9=84=ED=8B=B0=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/OrderServiceImpl.java | 225 ++++-------------- .../temporal/activity/OrderActivity.java | 5 + .../temporal/activity/OrderActivityImpl.java | 36 ++- .../temporal/dto/OrderStatusUpdate.java | 15 ++ .../temporal/workflow/OrderWorkflow.java | 10 +- .../temporal/workflow/OrderWorkflowImpl.java | 91 +++---- .../dto/response/OrderResponseDto.java | 7 + 7 files changed, 158 insertions(+), 231 deletions(-) create mode 100644 spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java diff --git a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java index ed5eedae..da29dd8c 100644 --- a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java @@ -1,15 +1,16 @@ package com.example.Spot.order.application.service; import com.example.Spot.global.feign.MenuClient; +import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; +import com.example.Spot.order.infrastructure.temporal.workflow.OrderWorkflowImpl; import com.example.Spot.order.presentation.dto.response.OrderContextDto; +import io.temporal.client.WorkflowNotFoundException; import java.awt.*; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -18,26 +19,20 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; import com.example.Spot.global.feign.PaymentClient; import com.example.Spot.global.feign.StoreClient; import com.example.Spot.global.feign.dto.MenuOptionResponse; import com.example.Spot.global.feign.dto.MenuResponse; -import com.example.Spot.global.feign.dto.PaymentCancelRequest; -import com.example.Spot.global.feign.dto.PaymentResponse; import com.example.Spot.global.feign.dto.StoreResponse; import com.example.Spot.order.domain.entity.OrderEntity; import com.example.Spot.order.domain.entity.OrderItemEntity; import com.example.Spot.order.domain.entity.OrderItemOptionEntity; -import com.example.Spot.order.domain.enums.CancelledBy; + import com.example.Spot.order.domain.enums.OrderStatus; import com.example.Spot.order.domain.exception.DuplicateOrderException; -import com.example.Spot.order.domain.exception.InvalidOrderStatusTransitionException; import com.example.Spot.order.domain.repository.OrderItemOptionRepository; import com.example.Spot.order.domain.repository.OrderRepository; -import com.example.Spot.order.infrastructure.aop.OrderStatusChange; import com.example.Spot.order.infrastructure.aop.OrderValidationContext; import com.example.Spot.order.infrastructure.aop.StoreOwnershipRequired; import com.example.Spot.order.infrastructure.aop.ValidateStoreAndMenu; @@ -50,9 +45,6 @@ import com.example.Spot.order.presentation.dto.response.OrderResponseDto; import com.example.Spot.order.presentation.dto.response.OrderStatsResponseDto; -import io.github.resilience4j.bulkhead.annotation.Bulkhead; -import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; -import io.github.resilience4j.retry.annotation.Retry; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; import lombok.RequiredArgsConstructor; @@ -239,196 +231,81 @@ public OrderResponseDto createOrder(OrderCreateRequestDto requestDto, Integer us // 주문 상태 변경 // // *********** // @Override - @Transactional - @OrderStatusChange("ACCEPT") public OrderResponseDto acceptOrder(UUID orderId, Integer estimatedTime) { - OrderEntity order = OrderValidationContext.getCurrentOrder(); - order.acceptOrder(estimatedTime); - - // 주문 수락 이벤트 발행 - orderEventProducer.reserveOrderAccepted(order.getUserId(), order.getId(), estimatedTime); - sendSignalToWorkflow(orderId, OrderStatus.ACCEPTED); - - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.ACCEPTED, estimatedTime, null)); + return OrderResponseDto.fromId(orderId, OrderStatus.ACCEPTED); } @Override - @Transactional - @OrderStatusChange("REJECT") public OrderResponseDto rejectOrder(UUID orderId, String reason) { - - OrderEntity order = OrderValidationContext.getCurrentOrder(); - if (order.getOrderStatus() != OrderStatus.PENDING) { - throw new InvalidOrderStatusTransitionException(order.getOrderStatus(), OrderStatus.CANCEL_PENDING); - } - - order.initiateCancel(reason, null); - orderEventProducer.reserveOrderCancelled(order.getId(), reason); - sendSignalToWorkflow(orderId, OrderStatus.CANCEL_PENDING); - log.info("주문 거절 처리 시작 (환불 대기): orderId={}, reason={}", orderId, reason); - - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } @Override - @Transactional - @OrderStatusChange("COOKING") public OrderResponseDto startCooking(UUID orderId) { - - OrderEntity order = OrderValidationContext.getCurrentOrder(); - order.startCooking(); - sendSignalToWorkflow(orderId, OrderStatus.COOKING); - - return OrderResponseDto.from(order); + sendSignal(orderId, OrderStatus.COOKING); + return OrderResponseDto.fromId(orderId, OrderStatus.COOKING); } @Override - @Transactional - @OrderStatusChange("READY") public OrderResponseDto readyForPickup(UUID orderId) { - - OrderEntity order = OrderValidationContext.getCurrentOrder(); - order.readyForPickup(); - sendSignalToWorkflow(orderId, OrderStatus.READY); - - return OrderResponseDto.from(order); + sendSignal(orderId, OrderStatus.READY); + return OrderResponseDto.fromId(orderId, OrderStatus.READY); } @Override - @Transactional - @OrderStatusChange("COMPLETE") public OrderResponseDto completeOrder(UUID orderId) { - - OrderEntity order = OrderValidationContext.getCurrentOrder(); - order.completeOrder(); - sendSignalToWorkflow(orderId, OrderStatus.COMPLETED); - - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.COMPLETED, null, null)); + return OrderResponseDto.fromId(orderId, OrderStatus.COMPLETED); } // ******* // // 주문 취소 // // ******* // @Override - @Transactional - public void completeOrderCancellation(UUID orderId) { - OrderEntity order = orderRepository.findByIdWithLock(orderId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다.")); - - if (order.getOrderStatus() == OrderStatus.CANCEL_PENDING) { - order.finalizeCancel(); - log.info("[보상 트랜잭션 완료] 주문 ID {} 가 최종 확정되었습니다.", orderId); - sendSignalToWorkflow(orderId, order.getOrderStatus()); - } else { - log.warn("[무시됨] 주문 ID {} 는 현재 취소 대기 상태가 아닙니다. (현재 상태: {})", - orderId, order.getOrderStatus()); - } - } - - @Override - @Transactional public OrderResponseDto customerCancelOrder(UUID orderId, String reason) { - OrderEntity order = orderRepository.findByIdWithLock(orderId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다.")); - - order.initiateCancel(reason, CancelledBy.CUSTOMER); - // 주문 취소(거절) 이벤트 발행 - orderEventProducer.reserveOrderCancelled(order.getId(), reason); - sendSignalToWorkflow(orderId, OrderStatus.CANCEL_PENDING); - log.info("고객에 의한 취소 처리 시작 (환불 대기): orderId={}, reason={}", orderId, reason); - - // 결제 취소 처리 (Payment 서비스 호출) - 비동기 전환으로 인한 주석처리: 추후 삭제 afterDelete - // cancelPaymentIfExists(orderId, "고객 주문 취소: " + reason); - - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + log.info("고객 취소 시그널 전송 완료: orderId={}, reason={}", orderId, reason); + return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } @Override - @Transactional public OrderResponseDto storeCancelOrder(UUID orderId, String reason) { - OrderEntity order = orderRepository.findByIdWithLock(orderId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다.")); - - order.initiateCancel(reason, CancelledBy.STORE); - // 주문 취소(거절) 이벤트 발행 - orderEventProducer.reserveOrderCancelled(order.getId(), reason); - sendSignalToWorkflow(orderId, OrderStatus.CANCEL_PENDING); - log.info("가게에 의한 취소 처리 시작 (환불 대기): orderId={}, reason={}", orderId, reason); - - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + log.info("가게 취소 시그널 전송 완료: orderId={}, reason={}", orderId, reason); + return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } - - @CircuitBreaker(name = "payment_ready_create") - @Bulkhead(name = "payment_ready_create", type = Bulkhead.Type.SEMAPHORE) - @Retry(name = "payment_ready_create") - private void cancelPaymentIfExists(UUID orderId, String cancelReason) { - try { - // Payment 서비스에서 결제 정보 조회 - if (!paymentClient.existsActivePaymentByOrderId(orderId)) { - log.info("주문 ID {}에 대한 결제 정보가 없습니다. 결제 취소를 건너뜁니다.", orderId); - return; - } - - PaymentResponse payment = paymentClient.getPaymentByOrderId(orderId); - - log.info("결제 취소 요청 - 결제 ID: {}, 주문 ID: {}, 사유: {}", payment.getId(), orderId, cancelReason); - - // Payment 서비스에 결제 취소 요청 - PaymentCancelRequest cancelRequest = PaymentCancelRequest.builder() - .cancelReason(cancelReason) - .build(); - paymentClient.cancelPayment(payment.getId(), cancelRequest); - - log.info("결제 취소 완료 - 결제 ID: {}", payment.getId()); - - } catch (Exception e) { - log.error("결제 취소 실패 - 주문 ID: {}, 오류: {}", orderId, e.getMessage(), e); - throw new RuntimeException("결제 취소 중 오류가 발생했습니다: " + e.getMessage(), e); - } + + @Override + public void completeOrderCancellation(UUID orderId) { + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalRefundCompleted(); + log.info("환불 완료 시그널 전송 성공: orderId={}", orderId); } @Override - @Transactional - @OrderStatusChange("COMPLETE_PAYMENT") public OrderResponseDto completePayment(UUID orderId) { - OrderEntity order = orderRepository.findByIdWithLock(orderId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다.")); - - // 멱등성 보장 결제 처리 로직 - if (order.getOrderStatus() == OrderStatus.PENDING || - order.getOrderStatus().isFinalStatus()) { - if (order.getOrderStatus() == OrderStatus.CANCELLED) { - log.warn("[주문서비스] 이미 취소된 주문에 결제 성공 이벤트 수신. 환불을 예약합니다. orderId={}", orderId); - orderEventProducer.reserveOrderCancelled(orderId, "타임아웃 이후 결제 성공 발생"); - } else { - log.info("[멱등성처리] 이미 처리된 주문입니다. 스킵: orderId={}, status={}", orderId, order.getOrderStatus()); - } - return OrderResponseDto.from(order); + try { + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PENDING, null, null)); + log.info("결제 완료 시그널 전송: orderId={}", orderId); + } catch (WorkflowNotFoundException e) { + log.warn("이미 종료된 워크플로우입니다. orderId={}", orderId); } - - // 정상 결제 처리 로직 - log.info("결제 성공 이벤트 수신 - 주문 확정 처리 시작: orderId={}", orderId); - order.completePayment(); - orderEventProducer.reserveOrderPending(order.getStoreId(), order.getId()); - log.info("결제 처리 및 사장님 알림 이벤트 발행 완료: orderId={}", orderId); - sendSignalToWorkflow(orderId, OrderStatus.PENDING); - - return OrderResponseDto.from(order); + return OrderResponseDto.fromId(orderId, OrderStatus.PENDING); } @Override - @Transactional public OrderResponseDto failPayment(UUID orderId) { - OrderEntity order = orderRepository.findByIdWithLock(orderId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다.")); - - if (order.getOrderStatus() == OrderStatus.PAYMENT_FAILED || order.getOrderStatus().isFinalStatus()) { - log.info("[멱등성 처리] 이미 실패 처리되었거나 최종 상태인 주문입니다. 스킵: orderId={}", orderId); - return OrderResponseDto.from(order); - } - - order.failPayment(); - return OrderResponseDto.from(order); + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PAYMENT_FAILED, null, "결제 승인 거절")); + return OrderResponseDto.fromId(orderId, OrderStatus.PAYMENT_FAILED); } private void checkDuplicateOrder(Integer userId, UUID storeId, OrderCreateRequestDto requestDto) { @@ -493,22 +370,10 @@ public OrderStatsResponseDto getOrderStats() { .build(); } - private void sendSignalToWorkflow(UUID orderId, OrderStatus status) { - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - @Override - public void afterCommit() { - try { - OrderWorkflow workflow = workflowClient.newWorkflowStub( - OrderWorkflow.class, - orderId.toString() - ); - workflow.signalStatusChanged(status); - log.info("워크플로우 시그널 전송 성공: orderId={}, status={}", orderId, status); - } catch (Exception e) { - log.error("워크플로우 시그널 전송 실패: orderId={}, status={}", orderId, status, e); - } - } - }); + private void sendSignal(UUID orderId, OrderStatus status) { + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); + workflow.signalStatusChanged(new OrderStatusUpdate(status, null, null)); + log.info("시그널 전송 완료: orderId={}, status={}", orderId, status); } private OrderContextDto fetchOrderContext(OrderCreateRequestDto requestDto) { diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java index 60c4d90e..a2b2d652 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java @@ -1,5 +1,7 @@ package com.example.Spot.order.infrastructure.temporal.activity; +import com.example.Spot.order.domain.entity.OrderEntity; +import com.example.Spot.order.domain.repository.OrderRepository; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.util.UUID; @@ -15,6 +17,9 @@ public interface OrderActivity { @ActivityMethod void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto); + @ActivityMethod + void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason); + @ActivityMethod OrderStatus getOrderStatus(UUID orderId); diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java index f8a34225..93e18ad7 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java @@ -18,6 +18,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.example.Spot.order.domain.entity.OrderEntity; @@ -104,7 +105,40 @@ public void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto log.info("주문 생성이 완료되었습니다. OrderID: {}, OrderNumber: {}", orderId, orderNumber); } - + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) // 독립적인 트랜잭션 보장 + public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason) { + log.info("Activity: 주문 상태 변경 시작 - orderId={}, nextStatus={}", orderId, nextStatus); + + OrderEntity order = orderRepository.findByIdWithLock(orderId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다: " + orderId)); + + if (!order.getOrderStatus().canTransitionTo(nextStatus)) { + log.warn("Activity: 유효하지 않은 상태 전환 시도 - current={}, next={}", order.getOrderStatus(), nextStatus); + return; + } + + switch (nextStatus) { + case PENDING -> { + order.completePayment(); + orderEventProducer.reserveOrderPending(order.getStoreId(), order.getId()); + } + case ACCEPTED -> { + order.acceptOrder(estimatedTime); + orderEventProducer.reserveOrderAccepted(order.getUserId(), order.getId(), estimatedTime); + } + case COOKING -> order.startCooking(); + case READY -> order.readyForPickup(); + case COMPLETED -> order.completeOrder(); + case CANCEL_PENDING -> { + order.initiateCancel(reason, null); + orderEventProducer.reserveOrderCancelled(order.getId(), reason); + } + default -> log.info("기타 상태 변경: {}", nextStatus); + } + + log.info("Activity: 주문 상태 변경 완료 - orderId={}, changedStatus={}", orderId, order.getOrderStatus()); + } @Override @Transactional diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java new file mode 100644 index 00000000..30233973 --- /dev/null +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java @@ -0,0 +1,15 @@ +package com.example.Spot.order.infrastructure.temporal.dto; + +import com.example.Spot.order.domain.enums.OrderStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class OrderStatusUpdate { + private OrderStatus status; + private Integer estimatedTime; + private String reason; +} diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java index f6988ecf..982a3657 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflow.java @@ -1,10 +1,10 @@ package com.example.Spot.order.infrastructure.temporal.workflow; -import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; -import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.util.UUID; -import com.example.Spot.order.domain.enums.OrderStatus; +import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import io.temporal.workflow.SignalMethod; import io.temporal.workflow.WorkflowInterface; @@ -17,8 +17,8 @@ public interface OrderWorkflow { void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto); @SignalMethod - void signalStatusChanged(OrderStatus nextStatus); - + void signalStatusChanged(OrderStatusUpdate update); + @SignalMethod void signalRefundCompleted(); } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index e19ecda4..6c9a7f97 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -1,5 +1,6 @@ package com.example.Spot.order.infrastructure.temporal.workflow; +import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.time.Duration; @@ -20,58 +21,50 @@ @WorkflowImpl(taskQueues = OrderConstants.ORDER_TASK_QUEUE) public class OrderWorkflowImpl implements OrderWorkflow { + private OrderStatus currentStatus = OrderStatus.PAYMENT_PENDING; + private Integer estimatedTime; + private String reason; + private boolean isRefundCompleted = false; + private static final ActivityOptions ACTIVITY_OPTIONS = ActivityOptions.newBuilder() .setStartToCloseTimeout(Duration.ofSeconds(10)) .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(5).build()) .build(); - - private OrderStatus currentStatus = OrderStatus.PAYMENT_PENDING; - private boolean isRefundCompleted = false; - + @Override public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto) { OrderActivity activities = Workflow.newActivityStub(OrderActivity.class, ACTIVITY_OPTIONS); - activities.createOrderInDb(orderId, userId, requestDto, contextDto); - - boolean paidWithinTime = Workflow.await(Duration.ofMinutes(5), - () -> currentStatus == OrderStatus.PENDING || currentStatus.isFinalStatus()); - - if (!paidWithinTime && currentStatus == OrderStatus.PAYMENT_PENDING) { - activities.cancelOrder(orderId, "결제 시간 초과로 인한 자동 취소"); - if (currentStatus == OrderStatus.CANCEL_PENDING) { - waitForRefundAndFinalize(orderId, activities); - return; - } - return; - } - - boolean acceptedWithinTime = Workflow.await(Duration.ofMinutes(10), - () -> currentStatus == OrderStatus.ACCEPTED || isTrulyFinalStatus(currentStatus)); - if (!acceptedWithinTime && currentStatus == OrderStatus.PENDING) { - activities.cancelOrder(orderId, "점주 미수락으로 인한 자동 취소 . 환불"); - waitForRefundAndFinalize(orderId, activities); - return; - } - - Workflow.await(() -> currentStatus == OrderStatus.COOKING || isTrulyFinalStatus(currentStatus)); - if (handleCancelIfNecessary(orderId, activities)) { - return; - } - - Workflow.await(() -> currentStatus == OrderStatus.READY || isTrulyFinalStatus(currentStatus)); - if (handleCancelIfNecessary(orderId, activities)) { + + Workflow.await(Duration.ofMinutes(5), + () -> currentStatus == OrderStatus.PENDING || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); + if (currentStatus == OrderStatus.PENDING) { + activities.updateOrderStatusInDb(orderId, OrderStatus.PENDING, null, null); + } else { + handleCancelIfNecessary(orderId, activities, "결제 단계 취소/타임아웃"); return; } - - Workflow.await(() -> currentStatus == OrderStatus.COMPLETED || isTrulyFinalStatus(currentStatus)); - if (handleCancelIfNecessary(orderId, activities)) { + + Workflow.await(Duration.ofMinutes(10), + () -> currentStatus == OrderStatus.ACCEPTED || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); + + if (currentStatus == OrderStatus.ACCEPTED) { + activities.updateOrderStatusInDb(orderId, OrderStatus.ACCEPTED, this.estimatedTime, null); + } else { + handleCancelIfNecessary(orderId, activities, "점주 미수락"); return; } + + // 4. 조리 단계 (COOKING) + if (waitForStatusAndUpdate(orderId, OrderStatus.COOKING, activities)) return; + if (waitForStatusAndUpdate(orderId, OrderStatus.READY, activities)) return; + if (waitForStatusAndUpdate(orderId, OrderStatus.COMPLETED, activities)) return; } - - private boolean handleCancelIfNecessary(UUID orderId, OrderActivity activities) { - if (currentStatus == OrderStatus.CANCEL_PENDING) { + + private boolean handleCancelIfNecessary(UUID orderId, OrderActivity activities, String defaultReason) { + if (currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECTED) { + String finalReason = (this.reason != null) ? this.reason : defaultReason; + activities.cancelOrder(orderId, finalReason); waitForRefundAndFinalize(orderId, activities); return true; } @@ -87,17 +80,25 @@ private void waitForRefundAndFinalize(UUID orderId, OrderActivity activities) { } } + private boolean waitForStatusAndUpdate(UUID orderId, OrderStatus targetStatus, OrderActivity activities) { + Workflow.await(() -> currentStatus == targetStatus || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus.isFinalStatus()); + if (currentStatus == targetStatus) { + activities.updateOrderStatusInDb(orderId, targetStatus, null, null); + return false; + } + handleCancelIfNecessary(orderId, activities, "진행 중 취소"); + return true; + } + @Override - public void signalStatusChanged(OrderStatus nextStatus) { - this.currentStatus = nextStatus; + public void signalStatusChanged(OrderStatusUpdate update) { + this.currentStatus = update.getStatus(); + this.estimatedTime = update.getEstimatedTime(); + this.reason = update.getReason(); } @Override public void signalRefundCompleted() { this.isRefundCompleted = true; } - - private boolean isTrulyFinalStatus(OrderStatus status) { - return status == OrderStatus.COMPLETED || status == OrderStatus.CANCELLED || status == OrderStatus.REJECTED; - } } diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java index e271ae58..11c4d334 100644 --- a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java @@ -128,5 +128,12 @@ public static OrderResponseDto of(UUID orderId, Integer userId, OrderCreateReque .createdAt(LocalDateTime.now()) .build(); } + + public static OrderResponseDto fromId(UUID orderId, OrderStatus status) { + return OrderResponseDto.builder() + .id(orderId) + .orderStatus(status) + .build(); + } } From 6f9e87ed58c695869b3b197cac059e4b2469a930 Mon Sep 17 00:00:00 2001 From: Yun024 Date: Wed, 18 Feb 2026 22:53:46 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat(#335):cancelOrder=EC=97=90=EC=84=9C=20?= =?UTF-8?q?cancelledBy=20=EC=A3=BC=EC=B2=B4=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/OrderServiceImpl.java | 17 +++++++++-------- .../temporal/activity/OrderActivity.java | 3 ++- .../temporal/activity/OrderActivityImpl.java | 19 +++++++++++++------ .../temporal/dto/OrderStatusUpdate.java | 2 ++ .../temporal/workflow/OrderWorkflowImpl.java | 15 ++++++++++----- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java index da29dd8c..e2b4c752 100644 --- a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java @@ -1,6 +1,7 @@ package com.example.Spot.order.application.service; import com.example.Spot.global.feign.MenuClient; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.infrastructure.temporal.workflow.OrderWorkflowImpl; import com.example.Spot.order.presentation.dto.response.OrderContextDto; @@ -233,14 +234,14 @@ public OrderResponseDto createOrder(OrderCreateRequestDto requestDto, Integer us @Override public OrderResponseDto acceptOrder(UUID orderId, Integer estimatedTime) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.ACCEPTED, estimatedTime, null)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.ACCEPTED, estimatedTime, null, null)); return OrderResponseDto.fromId(orderId, OrderStatus.ACCEPTED); } @Override public OrderResponseDto rejectOrder(UUID orderId, String reason) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason, null)); return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } @@ -259,7 +260,7 @@ public OrderResponseDto readyForPickup(UUID orderId) { @Override public OrderResponseDto completeOrder(UUID orderId) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.COMPLETED, null, null)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.COMPLETED, null, null, null)); return OrderResponseDto.fromId(orderId, OrderStatus.COMPLETED); } @@ -269,7 +270,7 @@ public OrderResponseDto completeOrder(UUID orderId) { @Override public OrderResponseDto customerCancelOrder(UUID orderId, String reason) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason, CancelledBy.CUSTOMER)); log.info("고객 취소 시그널 전송 완료: orderId={}, reason={}", orderId, reason); return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } @@ -277,7 +278,7 @@ public OrderResponseDto customerCancelOrder(UUID orderId, String reason) { @Override public OrderResponseDto storeCancelOrder(UUID orderId, String reason) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason, CancelledBy.STORE)); log.info("가게 취소 시그널 전송 완료: orderId={}, reason={}", orderId, reason); return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); } @@ -293,7 +294,7 @@ public void completeOrderCancellation(UUID orderId) { public OrderResponseDto completePayment(UUID orderId) { try { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PENDING, null, null)); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PENDING, null, null, null)); log.info("결제 완료 시그널 전송: orderId={}", orderId); } catch (WorkflowNotFoundException e) { log.warn("이미 종료된 워크플로우입니다. orderId={}", orderId); @@ -304,7 +305,7 @@ public OrderResponseDto completePayment(UUID orderId) { @Override public OrderResponseDto failPayment(UUID orderId) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PAYMENT_FAILED, null, "결제 승인 거절")); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.PAYMENT_FAILED, null, "결제 승인 거절", null)); return OrderResponseDto.fromId(orderId, OrderStatus.PAYMENT_FAILED); } @@ -372,7 +373,7 @@ public OrderStatsResponseDto getOrderStats() { private void sendSignal(UUID orderId, OrderStatus status) { OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(status, null, null)); + workflow.signalStatusChanged(new OrderStatusUpdate(status, null, null, null)); log.info("시그널 전송 완료: orderId={}, status={}", orderId, status); } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java index a2b2d652..3a271dd0 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java @@ -1,6 +1,7 @@ package com.example.Spot.order.infrastructure.temporal.activity; import com.example.Spot.order.domain.entity.OrderEntity; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.repository.OrderRepository; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.response.OrderContextDto; @@ -18,7 +19,7 @@ public interface OrderActivity { void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto requestDto, OrderContextDto contextDto); @ActivityMethod - void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason); + void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason, CancelledBy actor); @ActivityMethod OrderStatus getOrderStatus(UUID orderId); diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java index 93e18ad7..ecccfbc2 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java @@ -107,17 +107,25 @@ public void createOrderInDb(UUID orderId, Integer userId, OrderCreateRequestDto @Override @Transactional(propagation = Propagation.REQUIRES_NEW) // 독립적인 트랜잭션 보장 - public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason) { - log.info("Activity: 주문 상태 변경 시작 - orderId={}, nextStatus={}", orderId, nextStatus); - + public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer estimatedTime, String reason, CancelledBy actor) { OrderEntity order = orderRepository.findByIdWithLock(orderId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 주문입니다: " + orderId)); - + if (!order.getOrderStatus().canTransitionTo(nextStatus)) { log.warn("Activity: 유효하지 않은 상태 전환 시도 - current={}, next={}", order.getOrderStatus(), nextStatus); return; } + if (nextStatus == OrderStatus.CANCEL_PENDING) { + if (actor == null && order.getOrderStatus() != OrderStatus.PENDING) { + log.warn("이미 수락/조리 중인 주문은 '거절'할 수 없습니다. 상태: {}", order.getOrderStatus()); + return; + } + + order.initiateCancel(reason, actor); + orderEventProducer.reserveOrderCancelled(order.getId(), reason); + } + switch (nextStatus) { case PENDING -> { order.completePayment(); @@ -131,8 +139,7 @@ public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer case READY -> order.readyForPickup(); case COMPLETED -> order.completeOrder(); case CANCEL_PENDING -> { - order.initiateCancel(reason, null); - orderEventProducer.reserveOrderCancelled(order.getId(), reason); + log.info("CANCEL_PENDING 처리 완료 (주체: {})", actor); } default -> log.info("기타 상태 변경: {}", nextStatus); } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java index 30233973..91804433 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java @@ -1,5 +1,6 @@ package com.example.Spot.order.infrastructure.temporal.dto; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,4 +13,5 @@ public class OrderStatusUpdate { private OrderStatus status; private Integer estimatedTime; private String reason; + private CancelledBy cancelledBy; } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 6c9a7f97..06deee81 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -1,5 +1,6 @@ package com.example.Spot.order.infrastructure.temporal.workflow; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.response.OrderContextDto; @@ -24,6 +25,7 @@ public class OrderWorkflowImpl implements OrderWorkflow { private OrderStatus currentStatus = OrderStatus.PAYMENT_PENDING; private Integer estimatedTime; private String reason; + private CancelledBy actor; private boolean isRefundCompleted = false; private static final ActivityOptions ACTIVITY_OPTIONS = ActivityOptions.newBuilder() @@ -39,7 +41,7 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req Workflow.await(Duration.ofMinutes(5), () -> currentStatus == OrderStatus.PENDING || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); if (currentStatus == OrderStatus.PENDING) { - activities.updateOrderStatusInDb(orderId, OrderStatus.PENDING, null, null); + activities.updateOrderStatusInDb(orderId, OrderStatus.PENDING, null, null, null); } else { handleCancelIfNecessary(orderId, activities, "결제 단계 취소/타임아웃"); return; @@ -49,7 +51,7 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req () -> currentStatus == OrderStatus.ACCEPTED || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); if (currentStatus == OrderStatus.ACCEPTED) { - activities.updateOrderStatusInDb(orderId, OrderStatus.ACCEPTED, this.estimatedTime, null); + activities.updateOrderStatusInDb(orderId, OrderStatus.ACCEPTED, this.estimatedTime, null, null); } else { handleCancelIfNecessary(orderId, activities, "점주 미수락"); return; @@ -62,9 +64,11 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req } private boolean handleCancelIfNecessary(UUID orderId, OrderActivity activities, String defaultReason) { - if (currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECTED) { + if (currentStatus == OrderStatus.CANCEL_PENDING) { String finalReason = (this.reason != null) ? this.reason : defaultReason; - activities.cancelOrder(orderId, finalReason); + CancelledBy finalActor = (this.actor != null) ? this.actor : CancelledBy.SYSTEM; + + activities.updateOrderStatusInDb(orderId, OrderStatus.CANCEL_PENDING, null, finalReason, finalActor); waitForRefundAndFinalize(orderId, activities); return true; } @@ -83,7 +87,7 @@ private void waitForRefundAndFinalize(UUID orderId, OrderActivity activities) { private boolean waitForStatusAndUpdate(UUID orderId, OrderStatus targetStatus, OrderActivity activities) { Workflow.await(() -> currentStatus == targetStatus || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus.isFinalStatus()); if (currentStatus == targetStatus) { - activities.updateOrderStatusInDb(orderId, targetStatus, null, null); + activities.updateOrderStatusInDb(orderId, targetStatus, null, null, null); return false; } handleCancelIfNecessary(orderId, activities, "진행 중 취소"); @@ -95,6 +99,7 @@ public void signalStatusChanged(OrderStatusUpdate update) { this.currentStatus = update.getStatus(); this.estimatedTime = update.getEstimatedTime(); this.reason = update.getReason(); + this.actor = update.getCancelledBy(); } @Override From c981e854beac6e309c2e29c7ede16b45c6f41a31 Mon Sep 17 00:00:00 2001 From: Yun024 Date: Thu, 19 Feb 2026 02:06:28 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat(#335):=EC=A3=BC=EB=AC=B8=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=20[=EA=B1=B0=EC=A0=88=EB=8C=80=EA=B8=B0?= =?UTF-8?q?=EC=A4=91]=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/OrderServiceImpl.java | 14 +++++++-- .../Spot/order/domain/entity/OrderEntity.java | 31 ++++++++++--------- .../Spot/order/domain/enums/OrderStatus.java | 13 ++++---- .../temporal/activity/OrderActivityImpl.java | 27 ++++++++-------- .../temporal/workflow/OrderWorkflowImpl.java | 30 ++++++++++++------ 5 files changed, 69 insertions(+), 46 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java index e2b4c752..5b2092b2 100644 --- a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java @@ -2,10 +2,12 @@ import com.example.Spot.global.feign.MenuClient; import com.example.Spot.order.domain.enums.CancelledBy; +import com.example.Spot.order.domain.exception.InvalidOrderStatusTransitionException; import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.infrastructure.temporal.workflow.OrderWorkflowImpl; import com.example.Spot.order.presentation.dto.response.OrderContextDto; import io.temporal.client.WorkflowNotFoundException; +import jakarta.persistence.EntityNotFoundException; import java.awt.*; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -227,7 +229,6 @@ public OrderResponseDto createOrder(OrderCreateRequestDto requestDto, Integer us return OrderResponseDto.of(orderId, userId, requestDto, contextDto, totalAmount); } - // *********** // // 주문 상태 변경 // // *********** // @@ -239,10 +240,17 @@ public OrderResponseDto acceptOrder(UUID orderId, Integer estimatedTime) { } @Override + @Transactional(readOnly = true) public OrderResponseDto rejectOrder(UUID orderId, String reason) { + OrderEntity order = orderRepository.findById(orderId) + .orElseThrow(() -> new EntityNotFoundException("주문을 찾을 수 없습니다.")); + if (order.getOrderStatus() != OrderStatus.PENDING) { + throw new InvalidOrderStatusTransitionException(order.getOrderStatus(), OrderStatus.REJECT_PENDING); + } + OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, orderId.toString()); - workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.CANCEL_PENDING, null, reason, null)); - return OrderResponseDto.fromId(orderId, OrderStatus.CANCEL_PENDING); + workflow.signalStatusChanged(new OrderStatusUpdate(OrderStatus.REJECT_PENDING, null, reason, null)); + return OrderResponseDto.fromId(orderId, OrderStatus.REJECT_PENDING); } @Override diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java index e57c2988..6c784469 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java @@ -209,6 +209,13 @@ public void completeOrder() { this.orderStatus = OrderStatus.COMPLETED; this.pickedUpAt = LocalDateTime.now(); } + + public void initiateReject(String reason) { + validateStatusTransition(OrderStatus.REJECT_PENDING); + this.orderStatus = OrderStatus.REJECT_PENDING; + this.reason = reason; + this.cancelledBy = null; + } public void initiateCancel(String reason, CancelledBy cancelledBy) { validateStatusTransition(OrderStatus.CANCEL_PENDING); @@ -217,26 +224,22 @@ public void initiateCancel(String reason, CancelledBy cancelledBy) { this.cancelledBy = cancelledBy; } + public void finalizeReject() { + validateStatusTransition(OrderStatus.REJECTED); + this.orderStatus = OrderStatus.REJECTED; + this.rejectedAt = LocalDateTime.now(); + } + public void finalizeCancel() { - OrderStatus finalStatus = (this.cancelledBy == null) - ? OrderStatus.REJECTED - : OrderStatus.CANCELLED; - - validateStatusTransition(finalStatus); - - this.orderStatus = finalStatus; - if (finalStatus == OrderStatus.REJECTED) { - this.rejectedAt = LocalDateTime.now(); - } else { - this.cancelledAt = LocalDateTime.now(); - } + validateStatusTransition(OrderStatus.CANCELLED); + this.orderStatus = OrderStatus.CANCELLED; + this.cancelledAt = LocalDateTime.now(); } public void markAsRefundError() { - if (this.orderStatus != OrderStatus.CANCEL_PENDING) { + if (this.orderStatus != OrderStatus.CANCEL_PENDING && this.orderStatus != OrderStatus.REJECT_PENDING) { throw new IllegalStateException("환불 대기 상태가 아닌 주문은 에러 처리를 할 수 없습니다."); } - this.orderStatus = OrderStatus.REFUND_ERROR; } diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/enums/OrderStatus.java b/spot-order/src/main/java/com/example/Spot/order/domain/enums/OrderStatus.java index 78ef8059..27035326 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/enums/OrderStatus.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/enums/OrderStatus.java @@ -5,6 +5,7 @@ public enum OrderStatus { PAYMENT_FAILED("결제 실패"), // 결제 실패 PENDING("주문 수락 대기"), // 결제 완료 후 점주 수락 대기 ACCEPTED("주문 수락"), + REJECT_PENDING("거절 처리 중"), REJECTED("주문 거절"), COOKING("조리중"), READY("픽업 대기"), @@ -26,22 +27,20 @@ public String getDescription() { public boolean isFinalStatus() { return this == COMPLETED || this == CANCELLED || this == REJECTED || this == PAYMENT_FAILED || this == REFUND_ERROR; } - - public boolean isPaid() { - return this == PENDING || this == ACCEPTED || this == COOKING || - this == READY || this == COMPLETED; - } // 상태 전환 가능 여부 검증 public boolean canTransitionTo(OrderStatus newStatus) { return switch (this) { case PAYMENT_PENDING -> newStatus == PENDING || newStatus == PAYMENT_FAILED || newStatus == CANCELLED; case PAYMENT_FAILED -> newStatus == PAYMENT_PENDING || newStatus == CANCELLED; // 재결제 시도 가능 - case PENDING -> newStatus == ACCEPTED || newStatus == CANCEL_PENDING; + + case PENDING -> newStatus == ACCEPTED || newStatus == REJECT_PENDING || newStatus == CANCEL_PENDING; case ACCEPTED -> newStatus == COOKING || newStatus == CANCEL_PENDING; case COOKING -> newStatus == READY || newStatus == CANCEL_PENDING; - case CANCEL_PENDING -> newStatus == CANCELLED || newStatus == REJECTED || newStatus == REFUND_ERROR; case READY -> newStatus == COMPLETED; + + case REJECT_PENDING -> newStatus == REJECTED || newStatus == REFUND_ERROR; + case CANCEL_PENDING -> newStatus == CANCELLED || newStatus == REFUND_ERROR; default -> false; }; } diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java index ecccfbc2..0850cf75 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java @@ -116,16 +116,6 @@ public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer return; } - if (nextStatus == OrderStatus.CANCEL_PENDING) { - if (actor == null && order.getOrderStatus() != OrderStatus.PENDING) { - log.warn("이미 수락/조리 중인 주문은 '거절'할 수 없습니다. 상태: {}", order.getOrderStatus()); - return; - } - - order.initiateCancel(reason, actor); - orderEventProducer.reserveOrderCancelled(order.getId(), reason); - } - switch (nextStatus) { case PENDING -> { order.completePayment(); @@ -138,10 +128,15 @@ public void updateOrderStatusInDb(UUID orderId, OrderStatus nextStatus, Integer case COOKING -> order.startCooking(); case READY -> order.readyForPickup(); case COMPLETED -> order.completeOrder(); + case REJECT_PENDING -> { + order.initiateReject(reason); + orderEventProducer.reserveOrderCancelled(order.getId(), reason); // 환불 프로세스 시작 + } case CANCEL_PENDING -> { - log.info("CANCEL_PENDING 처리 완료 (주체: {})", actor); + order.initiateCancel(reason, actor); + orderEventProducer.reserveOrderCancelled(order.getId(), reason); // 환불 프로세스 시작 } - default -> log.info("기타 상태 변경: {}", nextStatus); + default -> log.info("상태 변경: {}", nextStatus); } log.info("Activity: 주문 상태 변경 완료 - orderId={}, changedStatus={}", orderId, order.getOrderStatus()); @@ -182,7 +177,13 @@ public void finalizeOrder(UUID orderId) { OrderEntity order = orderRepository.findByIdWithLock(orderId) .orElseThrow(() -> new IllegalArgumentException("주문 없음: " + orderId)); - order.finalizeCancel(); + if (order.getOrderStatus() == OrderStatus.REJECT_PENDING) { + order.finalizeReject(); + log.info("주문 거절 확정 완료: {}", orderId); + } else if (order.getOrderStatus() == OrderStatus.CANCEL_PENDING) { + order.finalizeCancel(); + log.info("주문 취소 확정 완료: {}", orderId); + } } @Override diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 06deee81..94f7999d 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -43,17 +43,17 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req if (currentStatus == OrderStatus.PENDING) { activities.updateOrderStatusInDb(orderId, OrderStatus.PENDING, null, null, null); } else { - handleCancelIfNecessary(orderId, activities, "결제 단계 취소/타임아웃"); + handleCancelOrRejectIfNecessary(orderId, activities, "결제 단계 취소/타임아웃"); return; } Workflow.await(Duration.ofMinutes(10), - () -> currentStatus == OrderStatus.ACCEPTED || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); + () -> currentStatus == OrderStatus.ACCEPTED || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECT_PENDING || currentStatus.isFinalStatus()); if (currentStatus == OrderStatus.ACCEPTED) { activities.updateOrderStatusInDb(orderId, OrderStatus.ACCEPTED, this.estimatedTime, null, null); } else { - handleCancelIfNecessary(orderId, activities, "점주 미수락"); + handleCancelOrRejectIfNecessary(orderId, activities, "점주 미수락/거절"); return; } @@ -63,12 +63,15 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req if (waitForStatusAndUpdate(orderId, OrderStatus.COMPLETED, activities)) return; } - private boolean handleCancelIfNecessary(UUID orderId, OrderActivity activities, String defaultReason) { - if (currentStatus == OrderStatus.CANCEL_PENDING) { + private boolean handleCancelOrRejectIfNecessary(UUID orderId, OrderActivity activities, String defaultReason) { + if (currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECT_PENDING) { String finalReason = (this.reason != null) ? this.reason : defaultReason; - CancelledBy finalActor = (this.actor != null) ? this.actor : CancelledBy.SYSTEM; + CancelledBy finalActor = this.actor; + if (currentStatus == OrderStatus.CANCEL_PENDING && finalActor == null) { + finalActor = CancelledBy.SYSTEM; + } - activities.updateOrderStatusInDb(orderId, OrderStatus.CANCEL_PENDING, null, finalReason, finalActor); + activities.updateOrderStatusInDb(orderId, currentStatus, null, finalReason, finalActor); waitForRefundAndFinalize(orderId, activities); return true; } @@ -85,17 +88,26 @@ private void waitForRefundAndFinalize(UUID orderId, OrderActivity activities) { } private boolean waitForStatusAndUpdate(UUID orderId, OrderStatus targetStatus, OrderActivity activities) { - Workflow.await(() -> currentStatus == targetStatus || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus.isFinalStatus()); + Workflow.await(() -> currentStatus == targetStatus || currentStatus == OrderStatus.CANCEL_PENDING + || currentStatus.isFinalStatus()); if (currentStatus == targetStatus) { activities.updateOrderStatusInDb(orderId, targetStatus, null, null, null); return false; } - handleCancelIfNecessary(orderId, activities, "진행 중 취소"); + handleCancelOrRejectIfNecessary(orderId, activities, "진행 중 취소/거절"); return true; } @Override public void signalStatusChanged(OrderStatusUpdate update) { + if (this.currentStatus.isFinalStatus()) { + return; + } + if (update.getStatus() == OrderStatus.REJECT_PENDING) { + if (this.currentStatus != OrderStatus.PENDING) { + return; + } + } this.currentStatus = update.getStatus(); this.estimatedTime = update.getEstimatedTime(); this.reason = update.getReason(); From 6011e2efae1cc06ebdc3ffca98915357a90c7dcb Mon Sep 17 00:00:00 2001 From: Yun024 Date: Thu, 19 Feb 2026 02:20:24 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat(#335):=EC=88=98=EB=9D=BD=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=EC=95=84=EC=9B=83=20=EB=B0=9C=EC=83=9D=EC=8B=9C=20SYS?= =?UTF-8?q?TEM=EC=97=90=20=EC=9D=98=ED=95=9C=20=EC=9E=90=EB=8F=99=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../temporal/workflow/OrderWorkflowImpl.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 94f7999d..8c1c0c19 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -47,12 +47,17 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req return; } - Workflow.await(Duration.ofMinutes(10), + boolean isAccepted = Workflow.await(Duration.ofSeconds(15), () -> currentStatus == OrderStatus.ACCEPTED || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECT_PENDING || currentStatus.isFinalStatus()); - if (currentStatus == OrderStatus.ACCEPTED) { + if (isAccepted && currentStatus == OrderStatus.ACCEPTED) { activities.updateOrderStatusInDb(orderId, OrderStatus.ACCEPTED, this.estimatedTime, null, null); } else { + if (!isAccepted) { + this.currentStatus = OrderStatus.CANCEL_PENDING; + this.reason = "타임아웃으로 인한 자동취소"; + this.actor = CancelledBy.SYSTEM; + } handleCancelOrRejectIfNecessary(orderId, activities, "점주 미수락/거절"); return; } From 0637ae1cc27d6d362f9a938e931a6bd8d7dbf6ba Mon Sep 17 00:00:00 2001 From: Yun024 Date: Thu, 19 Feb 2026 04:41:53 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat(#335):Order-Payment=20=EB=B6=80?= =?UTF-8?q?=EB=AA=A8=EC=9E=90=EC=8B=9D=EA=B4=80=EA=B3=84=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../temporal/workflow/OrderWorkflowImpl.java | 30 ++++++++++++++++++- .../listener/PaymentListener.java | 7 ++--- .../workflow/PaymentApproveWorkflow.java | 2 +- .../workflow/PaymentApproveWorkflowImpl.java | 5 ++-- .../workflow/PaymentCancelWorkflowImpl.java | 1 - 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 8c1c0c19..32dd9eb3 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -4,6 +4,8 @@ import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.response.OrderContextDto; +import io.temporal.workflow.ChildWorkflowOptions; +import io.temporal.workflow.ChildWorkflowStub; import java.time.Duration; import java.util.UUID; @@ -38,6 +40,17 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req OrderActivity activities = Workflow.newActivityStub(OrderActivity.class, ACTIVITY_OPTIONS); activities.createOrderInDb(orderId, userId, requestDto, contextDto); + ChildWorkflowStub paymentStub = Workflow.newUntypedChildWorkflowStub("PaymentApproveWorkflow", + ChildWorkflowOptions.newBuilder() + .setWorkflowId("payment-wf-" + orderId) // Payment 서비스가 사용할 ID와 일치시켜야 함 + .setTaskQueue("PAYMENT_TASK_QUEUE") // PaymentConstants.PAYMENT_TASK_QUEUE 값과 일치해야 함 + .build()); + try { + paymentStub.execute(Void.class, orderId); + } catch (Exception e) { + // 결제 워크플로우 실패 시 예외가 이쪽으로 전파됩니다. + } + Workflow.await(Duration.ofMinutes(5), () -> currentStatus == OrderStatus.PENDING || currentStatus.isFinalStatus() || currentStatus == OrderStatus.CANCEL_PENDING); if (currentStatus == OrderStatus.PENDING) { @@ -47,7 +60,7 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req return; } - boolean isAccepted = Workflow.await(Duration.ofSeconds(15), + boolean isAccepted = Workflow.await(Duration.ofMinutes(10), () -> currentStatus == OrderStatus.ACCEPTED || currentStatus == OrderStatus.CANCEL_PENDING || currentStatus == OrderStatus.REJECT_PENDING || currentStatus.isFinalStatus()); if (isAccepted && currentStatus == OrderStatus.ACCEPTED) { @@ -77,6 +90,21 @@ private boolean handleCancelOrRejectIfNecessary(UUID orderId, OrderActivity acti } activities.updateOrderStatusInDb(orderId, currentStatus, null, finalReason, finalActor); + + ChildWorkflowStub cancelStub = Workflow.newUntypedChildWorkflowStub("PaymentCancelWorkflow", + ChildWorkflowOptions.newBuilder() + .setWorkflowId("cancel-wf-" + orderId) + .setTaskQueue("PAYMENT_TASK_QUEUE") // 반드시 결제 큐 지정 + .build()); + try { + // 리스너가 던지는 (orderId, reason) 파라미터와 형식을 맞춥니다. + cancelStub.execute(Void.class, orderId, finalReason); + // Workflow.getWorkflowExecution(cancelStub); // 만약 비동기로 넘기고 싶다면 이 방식 사용 + } catch (Exception e) { + // 이미 리스너가 해당 ID로 실행을 완료했거나 진행 중일 때 발생하는 에러는 + // 부모-자식 관계가 맺어졌다면 무시해도 무방합니다. + } + waitForRefundAndFinalize(orderId, activities); return true; } diff --git a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/listener/PaymentListener.java b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/listener/PaymentListener.java index c7c3a162..df072bd9 100644 --- a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/listener/PaymentListener.java +++ b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/listener/PaymentListener.java @@ -55,12 +55,11 @@ public void handleOrderCreated(String message, Acknowledgment ack) { WorkflowOptions options = WorkflowOptions.newBuilder() .setWorkflowId("payment-wf-" + event.getOrderId()) .setTaskQueue(PaymentConstants.PAYMENT_TASK_QUEUE) - .setWorkflowIdReusePolicy(WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE) + .setWorkflowIdReusePolicy(WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY) // 2. 정책 완화 추천 .build(); .build(); - try { PaymentApproveWorkflow workflow = workflowClient.newWorkflowStub(PaymentApproveWorkflow.class, options); - WorkflowClient.start(workflow::processApprove, paymentId); + WorkflowClient.start(workflow::processApprove, event.getOrderId()); log.info("[결제] 새 워크플로우 시작: orderId={}, paymentId={}", event.getOrderId(), paymentId); } catch (WorkflowExecutionAlreadyStarted e) { log.info("[결제] 이미 진행 중인 워크플로우입니다. 스킵: orderId={}", event.getOrderId()); @@ -84,7 +83,7 @@ public void handleOrderCancelled(String message, Acknowledgment ack) { WorkflowOptions options = WorkflowOptions.newBuilder() .setWorkflowId("cancel-wf-" + event.getOrderId()) .setTaskQueue(PaymentConstants.PAYMENT_TASK_QUEUE) - .setWorkflowIdReusePolicy(WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE) + .setWorkflowIdReusePolicy(WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY) .build(); try { diff --git a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflow.java b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflow.java index 89067176..4be5e884 100644 --- a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflow.java +++ b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflow.java @@ -8,5 +8,5 @@ @WorkflowInterface public interface PaymentApproveWorkflow { @WorkflowMethod - void processApprove(UUID paymentId); + void processApprove(UUID orderId); } diff --git a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflowImpl.java b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflowImpl.java index 819e09d5..ccef40f8 100644 --- a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflowImpl.java +++ b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentApproveWorkflowImpl.java @@ -19,7 +19,6 @@ public class PaymentApproveWorkflowImpl implements PaymentApproveWorkflow { private static final String[] DO_NOT_RETRY_EXCEPTIONS = { "com.example.Spot.global.presentation.advice.BillingKeyNotFoundException", - "com.example.Spot.global.presentation.advice.ResourceNotFoundException", "java.lang.IllegalArgumentException" }; @@ -37,8 +36,10 @@ public class PaymentApproveWorkflowImpl implements PaymentApproveWorkflow { .build()); @Override - public void processApprove(UUID paymentId) { + public void processApprove(UUID orderId) { Saga saga = new Saga(new Saga.Options.Builder().setContinueWithError(false).build()); + + UUID paymentId = activities.findActivePaymentIdByOrderId(orderId); try { activities.recordStatus(paymentId, "IN_PROGRESS"); diff --git a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentCancelWorkflowImpl.java b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentCancelWorkflowImpl.java index 5448e0c2..bad4b766 100644 --- a/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentCancelWorkflowImpl.java +++ b/spot-payment/src/main/java/com/example/Spot/payments/infrastructure/temporal/workflow/PaymentCancelWorkflowImpl.java @@ -19,7 +19,6 @@ public class PaymentCancelWorkflowImpl implements PaymentCancelWorkflow { private static final String[] DO_NOT_RETRY_EXCEPTIONS = { "com.example.Spot.global.presentation.advice.BillingKeyNotFoundException", - "com.example.Spot.global.presentation.advice.ResourceNotFoundException", "java.lang.IllegalArgumentException" }; From f1228ca2885894a7756948e60d323eebb30a9546 Mon Sep 17 00:00:00 2001 From: Yun024 Date: Thu, 19 Feb 2026 05:30:16 +0900 Subject: [PATCH 9/9] feat(#335):pr-check --- .../global/feign/dto/MenuOptionResponse.java | 3 ++- .../Spot/global/feign/dto/MenuResponse.java | 3 ++- .../Spot/global/feign/dto/StoreResponse.java | 3 ++- .../application/service/OrderServiceImpl.java | 21 ++++++++-------- .../Spot/order/domain/entity/OrderEntity.java | 3 --- .../temporal/activity/OrderActivity.java | 8 +++---- .../temporal/activity/OrderActivityImpl.java | 19 +++++++-------- .../temporal/dto/OrderStatusUpdate.java | 1 + .../temporal/workflow/OrderWorkflowImpl.java | 24 ++++++++++++------- .../dto/response/OrderContextDto.java | 4 +++- .../dto/response/OrderResponseDto.java | 2 +- 11 files changed, 47 insertions(+), 44 deletions(-) diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java index d9869673..0a4aa69c 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuOptionResponse.java @@ -1,8 +1,9 @@ package com.example.Spot.global.feign.dto; +import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java index 193ca1ec..fd6fa6f7 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/MenuResponse.java @@ -1,8 +1,9 @@ package com.example.Spot.global.feign.dto; +import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java b/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java index 10f2f7df..42baa38c 100644 --- a/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java +++ b/spot-order/src/main/java/com/example/Spot/global/feign/dto/StoreResponse.java @@ -1,8 +1,9 @@ package com.example.Spot.global.feign.dto; +import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java index 5b2092b2..89e9c2e7 100644 --- a/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/application/service/OrderServiceImpl.java @@ -1,14 +1,5 @@ package com.example.Spot.order.application.service; -import com.example.Spot.global.feign.MenuClient; -import com.example.Spot.order.domain.enums.CancelledBy; -import com.example.Spot.order.domain.exception.InvalidOrderStatusTransitionException; -import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; -import com.example.Spot.order.infrastructure.temporal.workflow.OrderWorkflowImpl; -import com.example.Spot.order.presentation.dto.response.OrderContextDto; -import io.temporal.client.WorkflowNotFoundException; -import jakarta.persistence.EntityNotFoundException; -import java.awt.*; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.HashMap; @@ -23,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.example.Spot.global.feign.MenuClient; import com.example.Spot.global.feign.PaymentClient; import com.example.Spot.global.feign.StoreClient; import com.example.Spot.global.feign.dto.MenuOptionResponse; @@ -31,9 +23,10 @@ import com.example.Spot.order.domain.entity.OrderEntity; import com.example.Spot.order.domain.entity.OrderItemEntity; import com.example.Spot.order.domain.entity.OrderItemOptionEntity; - +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; import com.example.Spot.order.domain.exception.DuplicateOrderException; +import com.example.Spot.order.domain.exception.InvalidOrderStatusTransitionException; import com.example.Spot.order.domain.repository.OrderItemOptionRepository; import com.example.Spot.order.domain.repository.OrderRepository; import com.example.Spot.order.infrastructure.aop.OrderValidationContext; @@ -41,15 +34,19 @@ import com.example.Spot.order.infrastructure.aop.ValidateStoreAndMenu; import com.example.Spot.order.infrastructure.producer.OrderEventProducer; import com.example.Spot.order.infrastructure.temporal.config.OrderConstants; +import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; import com.example.Spot.order.infrastructure.temporal.workflow.OrderWorkflow; import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import com.example.Spot.order.presentation.dto.request.OrderItemOptionRequestDto; import com.example.Spot.order.presentation.dto.request.OrderItemRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import com.example.Spot.order.presentation.dto.response.OrderResponseDto; import com.example.Spot.order.presentation.dto.response.OrderStatsResponseDto; import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowNotFoundException; import io.temporal.client.WorkflowOptions; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -387,7 +384,9 @@ private void sendSignal(UUID orderId, OrderStatus status) { private OrderContextDto fetchOrderContext(OrderCreateRequestDto requestDto) { StoreResponse store = storeClient.getStoreById(requestDto.getStoreId()); - if (store == null) throw new IllegalArgumentException("존재하지 않는 가게입니다."); + if (store == null) { + throw new IllegalArgumentException("존재하지 않는 가게입니다."); + } Map menuMap = new HashMap<>(); Map optionMap = new HashMap<>(); diff --git a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java index 6c784469..5f810899 100644 --- a/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java +++ b/spot-order/src/main/java/com/example/Spot/order/domain/entity/OrderEntity.java @@ -6,8 +6,6 @@ import java.util.List; import java.util.UUID; -import org.hibernate.annotations.UuidGenerator; - import com.example.Spot.global.common.BaseEntity; import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; @@ -18,7 +16,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java index 3a271dd0..b6b0184d 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivity.java @@ -1,13 +1,11 @@ package com.example.Spot.order.infrastructure.temporal.activity; -import com.example.Spot.order.domain.entity.OrderEntity; -import com.example.Spot.order.domain.enums.CancelledBy; -import com.example.Spot.order.domain.repository.OrderRepository; -import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; -import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.util.UUID; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java index 0850cf75..d607131e 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/activity/OrderActivityImpl.java @@ -1,32 +1,29 @@ package com.example.Spot.order.infrastructure.temporal.activity; -import com.example.Spot.global.feign.dto.MenuOptionResponse; -import com.example.Spot.global.feign.dto.MenuResponse; -import com.example.Spot.global.feign.dto.StoreResponse; -import com.example.Spot.order.domain.entity.OrderItemEntity; -import com.example.Spot.order.domain.entity.OrderItemOptionEntity; -import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; -import com.example.Spot.order.presentation.dto.request.OrderItemOptionRequestDto; -import com.example.Spot.order.presentation.dto.request.OrderItemRequestDto; -import com.example.Spot.order.presentation.dto.response.OrderContextDto; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Optional; import java.util.UUID; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import com.example.Spot.global.feign.dto.MenuOptionResponse; +import com.example.Spot.global.feign.dto.MenuResponse; import com.example.Spot.order.domain.entity.OrderEntity; +import com.example.Spot.order.domain.entity.OrderItemEntity; +import com.example.Spot.order.domain.entity.OrderItemOptionEntity; import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; import com.example.Spot.order.domain.repository.OrderRepository; import com.example.Spot.order.infrastructure.producer.OrderEventProducer; import com.example.Spot.order.infrastructure.temporal.config.OrderConstants; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.request.OrderItemOptionRequestDto; +import com.example.Spot.order.presentation.dto.request.OrderItemRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import io.temporal.spring.boot.ActivityImpl; import lombok.RequiredArgsConstructor; diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java index 91804433..89dd14e5 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/dto/OrderStatusUpdate.java @@ -2,6 +2,7 @@ import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java index 32dd9eb3..9d75fed6 100644 --- a/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java +++ b/spot-order/src/main/java/com/example/Spot/order/infrastructure/temporal/workflow/OrderWorkflowImpl.java @@ -1,23 +1,23 @@ package com.example.Spot.order.infrastructure.temporal.workflow; -import com.example.Spot.order.domain.enums.CancelledBy; -import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; -import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; -import com.example.Spot.order.presentation.dto.response.OrderContextDto; -import io.temporal.workflow.ChildWorkflowOptions; -import io.temporal.workflow.ChildWorkflowStub; import java.time.Duration; import java.util.UUID; import org.springframework.stereotype.Component; +import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; import com.example.Spot.order.infrastructure.temporal.activity.OrderActivity; import com.example.Spot.order.infrastructure.temporal.config.OrderConstants; +import com.example.Spot.order.infrastructure.temporal.dto.OrderStatusUpdate; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; +import com.example.Spot.order.presentation.dto.response.OrderContextDto; import io.temporal.activity.ActivityOptions; import io.temporal.common.RetryOptions; import io.temporal.spring.boot.WorkflowImpl; +import io.temporal.workflow.ChildWorkflowOptions; +import io.temporal.workflow.ChildWorkflowStub; import io.temporal.workflow.Workflow; @Component @@ -76,9 +76,15 @@ public void processOrder(UUID orderId, Integer userId, OrderCreateRequestDto req } // 4. 조리 단계 (COOKING) - if (waitForStatusAndUpdate(orderId, OrderStatus.COOKING, activities)) return; - if (waitForStatusAndUpdate(orderId, OrderStatus.READY, activities)) return; - if (waitForStatusAndUpdate(orderId, OrderStatus.COMPLETED, activities)) return; + if (waitForStatusAndUpdate(orderId, OrderStatus.COOKING, activities)) { + return; + } + if (waitForStatusAndUpdate(orderId, OrderStatus.READY, activities)) { + return; + } + if (waitForStatusAndUpdate(orderId, OrderStatus.COMPLETED, activities)) { + return; + } } private boolean handleCancelOrRejectIfNecessary(UUID orderId, OrderActivity activities, String defaultReason) { diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java index a7dd27fe..67c914f9 100644 --- a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderContextDto.java @@ -30,7 +30,9 @@ public class OrderContextDto implements Serializable { public java.math.BigDecimal calculateTotalAmount(com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto request) { java.math.BigDecimal total = java.math.BigDecimal.ZERO; - if (request.getOrderItems() == null) return total; + if (request.getOrderItems() == null) { + return total; + } for (var item : request.getOrderItems()) { // 1. 메뉴 가격 계산 diff --git a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java index 11c4d334..567e86d3 100644 --- a/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java +++ b/spot-order/src/main/java/com/example/Spot/order/presentation/dto/response/OrderResponseDto.java @@ -1,6 +1,5 @@ package com.example.Spot.order.presentation.dto.response; -import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -10,6 +9,7 @@ import com.example.Spot.order.domain.entity.OrderEntity; import com.example.Spot.order.domain.enums.CancelledBy; import com.example.Spot.order.domain.enums.OrderStatus; +import com.example.Spot.order.presentation.dto.request.OrderCreateRequestDto; import lombok.AccessLevel; import lombok.AllArgsConstructor;