diff --git a/src/main/java/com/example/Centralthon/domain/menu/entity/Menu.java b/src/main/java/com/example/Centralthon/domain/menu/entity/Menu.java index cb15f39..4ea7225 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/entity/Menu.java +++ b/src/main/java/com/example/Centralthon/domain/menu/entity/Menu.java @@ -30,6 +30,7 @@ public class Menu extends BaseEntity { @Column(nullable = false) private int salePrice; + @Setter @Column(nullable = false) private int quantity; diff --git a/src/main/java/com/example/Centralthon/domain/menu/exception/MenuErrorCode.java b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuErrorCode.java index 3e53d4c..9cfadb4 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/exception/MenuErrorCode.java +++ b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuErrorCode.java @@ -7,7 +7,9 @@ @Getter @AllArgsConstructor public enum MenuErrorCode implements BaseResponseCode { - MENU_NOT_FOUND("MENU_NOT_FOUND_404_1",404,"존재하지 않는 메뉴 입니다."); + MENU_EXPIRED("MENU_400_1", 400, "판매 마감된 메뉴입니다."), + MENU_OUT_OF_STOCK("MENU_400_2", 400, "재고가 부족합니다."), + MENU_NOT_FOUND("MENU_404_1",404,"존재하지 않는 메뉴 입니다."); private final String code; private final int httpStatus; diff --git a/src/main/java/com/example/Centralthon/domain/menu/exception/MenuExpiredException.java b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuExpiredException.java new file mode 100644 index 0000000..33acc1c --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuExpiredException.java @@ -0,0 +1,7 @@ +package com.example.Centralthon.domain.menu.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class MenuExpiredException extends BaseException { + public MenuExpiredException() {super(MenuErrorCode.MENU_EXPIRED);} +} diff --git a/src/main/java/com/example/Centralthon/domain/menu/exception/MenuOutOfStockException.java b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuOutOfStockException.java new file mode 100644 index 0000000..b5dd350 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/exception/MenuOutOfStockException.java @@ -0,0 +1,7 @@ +package com.example.Centralthon.domain.menu.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class MenuOutOfStockException extends BaseException { + public MenuOutOfStockException() {super(MenuErrorCode.MENU_OUT_OF_STOCK);} +} diff --git a/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java b/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java index 46b30a5..14c8eb6 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java +++ b/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.List; import java.time.LocalDateTime; @Repository @@ -26,6 +27,12 @@ List findNearbyMenus( double maxLng ); + /** + * ids에 포함된 Menu와 연관된 Store를 한번에 가져옴 + */ + @Query(value = "SELECT m from Menu m join fetch m.store where m.id in :ids") + List findAllByIdWithStore(@Param("ids") Collection ids); + } diff --git a/src/main/java/com/example/Centralthon/domain/order/entity/Order.java b/src/main/java/com/example/Centralthon/domain/order/entity/Order.java index 7b12690..f02ad88 100644 --- a/src/main/java/com/example/Centralthon/domain/order/entity/Order.java +++ b/src/main/java/com/example/Centralthon/domain/order/entity/Order.java @@ -33,4 +33,8 @@ public static Order toEntity(String pickUpCode, int price){ .pickedUp(false) .build(); } + + public void completeOrder(){ + this.pickedUp = true; + } } diff --git a/src/main/java/com/example/Centralthon/domain/order/exception/CodeNotCreatedException.java b/src/main/java/com/example/Centralthon/domain/order/exception/CodeNotCreatedException.java deleted file mode 100644 index 3d83bec..0000000 --- a/src/main/java/com/example/Centralthon/domain/order/exception/CodeNotCreatedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.Centralthon.domain.order.exception; - -import com.example.Centralthon.global.exception.BaseException; - -public class CodeNotCreatedException extends BaseException { - public CodeNotCreatedException() { - super(OrderErrorCode.CODE_NOT_CREATED); - } -} diff --git a/src/main/java/com/example/Centralthon/domain/order/exception/OrderCodeNotCreatedException.java b/src/main/java/com/example/Centralthon/domain/order/exception/OrderCodeNotCreatedException.java new file mode 100644 index 0000000..aa29144 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/order/exception/OrderCodeNotCreatedException.java @@ -0,0 +1,9 @@ +package com.example.Centralthon.domain.order.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class OrderCodeNotCreatedException extends BaseException { + public OrderCodeNotCreatedException() { + super(OrderErrorCode.ORDER_CODE_NOT_CREATED); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/order/exception/OrderErrorCode.java b/src/main/java/com/example/Centralthon/domain/order/exception/OrderErrorCode.java index bf14a67..b7a0d78 100644 --- a/src/main/java/com/example/Centralthon/domain/order/exception/OrderErrorCode.java +++ b/src/main/java/com/example/Centralthon/domain/order/exception/OrderErrorCode.java @@ -7,7 +7,9 @@ @Getter @AllArgsConstructor public enum OrderErrorCode implements BaseResponseCode { - CODE_NOT_CREATED("ORDER_500_1",500,"서버에서 유일한 픽업 코드를 생성하지 못했습니다."); + ORDER_EXPIRED("ORDER_400_1", 400, "만료된 주문입니다."), + ORDER_NOT_FOUND("ORDER_404_1", 404, "존재하지 않는 주문입니다."), + ORDER_CODE_NOT_CREATED("ORDER_500_1",500,"서버에서 유일한 픽업 코드를 생성하지 못했습니다."); private final String code; private final int httpStatus; diff --git a/src/main/java/com/example/Centralthon/domain/order/exception/OrderExpiredException.java b/src/main/java/com/example/Centralthon/domain/order/exception/OrderExpiredException.java new file mode 100644 index 0000000..97df1c1 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/order/exception/OrderExpiredException.java @@ -0,0 +1,9 @@ +package com.example.Centralthon.domain.order.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class OrderExpiredException extends BaseException { + public OrderExpiredException() { + super(OrderErrorCode.ORDER_EXPIRED); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/order/exception/OrderNotFoundException.java b/src/main/java/com/example/Centralthon/domain/order/exception/OrderNotFoundException.java new file mode 100644 index 0000000..a072988 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/order/exception/OrderNotFoundException.java @@ -0,0 +1,9 @@ +package com.example.Centralthon.domain.order.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class OrderNotFoundException extends BaseException { + public OrderNotFoundException() { + super(OrderErrorCode.ORDER_NOT_FOUND); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/order/service/OrderService.java b/src/main/java/com/example/Centralthon/domain/order/service/OrderService.java index 03c0e30..28a50a9 100644 --- a/src/main/java/com/example/Centralthon/domain/order/service/OrderService.java +++ b/src/main/java/com/example/Centralthon/domain/order/service/OrderService.java @@ -1,8 +1,10 @@ package com.example.Centralthon.domain.order.service; +import com.example.Centralthon.domain.order.web.dto.CompleteOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderRes; public interface OrderService { CreateOrderRes orderMenus(CreateOrderReq orderReq); + void completePickUp(CompleteOrderReq completeOrderReq); } diff --git a/src/main/java/com/example/Centralthon/domain/order/service/OrderServiceImpl.java b/src/main/java/com/example/Centralthon/domain/order/service/OrderServiceImpl.java index 105cc24..16a98e8 100644 --- a/src/main/java/com/example/Centralthon/domain/order/service/OrderServiceImpl.java +++ b/src/main/java/com/example/Centralthon/domain/order/service/OrderServiceImpl.java @@ -1,13 +1,18 @@ package com.example.Centralthon.domain.order.service; import com.example.Centralthon.domain.menu.entity.Menu; +import com.example.Centralthon.domain.menu.exception.MenuExpiredException; import com.example.Centralthon.domain.menu.exception.MenuNotFoundException; +import com.example.Centralthon.domain.menu.exception.MenuOutOfStockException; import com.example.Centralthon.domain.menu.repository.MenuRepository; import com.example.Centralthon.domain.order.entity.Order; import com.example.Centralthon.domain.order.entity.OrderItem; -import com.example.Centralthon.domain.order.exception.CodeNotCreatedException; +import com.example.Centralthon.domain.order.exception.OrderCodeNotCreatedException; +import com.example.Centralthon.domain.order.exception.OrderExpiredException; +import com.example.Centralthon.domain.order.exception.OrderNotFoundException; import com.example.Centralthon.domain.order.repository.OrderItemRepository; import com.example.Centralthon.domain.order.repository.OrderRepository; +import com.example.Centralthon.domain.order.web.dto.CompleteOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderRes; import com.example.Centralthon.domain.order.web.dto.OrderItemListReq; @@ -19,10 +24,8 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; @Service @@ -45,43 +48,47 @@ public CreateOrderRes orderMenus(CreateOrderReq orderReq) { } // 메뉴 id가 없을 경우 -> MenuNotFoundException - List menuList = menuRepository.findAllById(orderList.keySet()); - if(menuList.size() != orderList.keySet().size()) { - throw new MenuNotFoundException(); - } + List menuList = menuRepository.findAllByIdWithStore(orderList.keySet()); + if(menuList.size() != orderList.keySet().size()) throw new MenuNotFoundException(); // 최종 결제 금액 계산 int totalPrice = 0; + LocalDateTime now = LocalDateTime.now(); for (Menu menu : menuList) { + // 메뉴 마감 시간이 지났을 경우 -> MenuExpiredException + if(menu.getDeadline().isBefore(now)) throw new MenuExpiredException(); + + // 메뉴 재고가 부족할 경우 -> MenuOutOfStockException + if(menu.getQuantity() orderItemList = new ArrayList<>(); - List storeIdList = new ArrayList<>(); + Set storeIds = new LinkedHashSet<>(); for(Menu menu : menuList) { - OrderItem orderItem = OrderItem.toEntity(order, menu, orderList.get(menu.getId())); - orderItemList.add(orderItem); - if(!storeIdList.contains(menu.getStore().getId())) storeIdList.add(menu.getStore().getId()); + int count = orderList.get(menu.getId()); + orderItemList.add(OrderItem.toEntity(order, menu, count)); + storeIds.add(menu.getStore().getId()); // fetch join : 추가 쿼리 없음 + + // 재고 차감 + menu.setQuantity(menu.getQuantity()-count); } orderItemRepository.saveAll(orderItemList); - return CreateOrderRes.of(order, storeIdList); + return CreateOrderRes.of(order, new ArrayList<>(storeIds)); } - private Order createOrderWithUniqueCode(int totalPrice) { - for(int i = 0; i < 10; i++) { - String code = generatePickUpCode(); - try{ - // 프록시를 적용해주지 않으면, this.saveOrder가 실행되어 트랙잭션이 실행되지 않음 - return selfProxy.saveOrder(code, totalPrice); - } catch (DataIntegrityViolationException e) { - // 다음 실행 진행 - } - } - throw new CodeNotCreatedException(); + @Override + @Transactional + public void completePickUp(CompleteOrderReq completeOrderReq){ + Order order = orderRepository.findByPickUpCode(completeOrderReq.getCode()).orElseThrow(OrderNotFoundException::new); + if(order.isPickedUp()) throw new OrderExpiredException(); + order.completeOrder(); } /** @@ -96,6 +103,19 @@ public Order saveOrder(String code, int totalPrice){ return orderRepository.save(order); } + private Order createOrderWithUniqueCode(int totalPrice) { + for(int i = 0; i < 10; i++) { + String code = generatePickUpCode(); + try{ + // 프록시를 적용해주지 않으면, this.saveOrder가 실행되어 트랙잭션이 실행되지 않음 + return selfProxy.saveOrder(code, totalPrice); + } catch (DataIntegrityViolationException e) { + // 다음 실행 진행 + } + } + throw new OrderCodeNotCreatedException(); + } + /** * 픽업 코드(영문 두자리 + 숫자 네자리) 생성 메서드 */ diff --git a/src/main/java/com/example/Centralthon/domain/order/web/controller/OrderController.java b/src/main/java/com/example/Centralthon/domain/order/web/controller/OrderController.java index 94874b8..e8fedae 100644 --- a/src/main/java/com/example/Centralthon/domain/order/web/controller/OrderController.java +++ b/src/main/java/com/example/Centralthon/domain/order/web/controller/OrderController.java @@ -1,6 +1,7 @@ package com.example.Centralthon.domain.order.web.controller; import com.example.Centralthon.domain.order.service.OrderService; +import com.example.Centralthon.domain.order.web.dto.CompleteOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderReq; import com.example.Centralthon.domain.order.web.dto.CreateOrderRes; import com.example.Centralthon.global.response.SuccessResponse; @@ -22,4 +23,10 @@ public ResponseEntity> createOrder(@RequestBody CreateOrderRes createOrderRes = orderService.orderMenus(orderReq); return ResponseEntity.status(HttpStatus.CREATED).body(SuccessResponse.of(createOrderRes, SuccessResponseCode.SUCCESS_CREATED)); } + + @PutMapping("/complete") + public ResponseEntity> completePickUp(@RequestBody @Valid CompleteOrderReq completeOrderReq){ + orderService.completePickUp(completeOrderReq); + return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.empty()); + } } diff --git a/src/main/java/com/example/Centralthon/domain/order/web/dto/CompleteOrderReq.java b/src/main/java/com/example/Centralthon/domain/order/web/dto/CompleteOrderReq.java new file mode 100644 index 0000000..c61bfd8 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/order/web/dto/CompleteOrderReq.java @@ -0,0 +1,12 @@ +package com.example.Centralthon.domain.order.web.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CompleteOrderReq { + @NotBlank(message = "픽업 코드는 필수 값입니다.") + String code; +}