Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class Menu extends BaseEntity {
@Column(nullable = false)
private int salePrice;

@Setter
@Column(nullable = false)
private int quantity;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);}
}
Original file line number Diff line number Diff line change
@@ -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);}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +27,12 @@ List<Menu> findNearbyMenus(
double maxLng
);

/**
* ids에 포함된 Menu와 연관된 Store를 한번에 가져옴
*/
@Query(value = "SELECT m from Menu m join fetch m.store where m.id in :ids")
List<Menu> findAllByIdWithStore(@Param("ids") Collection<Long> ids);



}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public static Order toEntity(String pickUpCode, int price){
.pickedUp(false)
.build();
}

public void completeOrder(){
this.pickedUp = true;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -45,43 +48,47 @@ public CreateOrderRes orderMenus(CreateOrderReq orderReq) {
}

// 메뉴 id가 없을 경우 -> MenuNotFoundException
List<Menu> menuList = menuRepository.findAllById(orderList.keySet());
if(menuList.size() != orderList.keySet().size()) {
throw new MenuNotFoundException();
}
List<Menu> 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()<orderList.get(menu.getId())) throw new MenuOutOfStockException();

// 오버플로우 방지를 위한 addExact, multiplyExact 사용
totalPrice = Math.addExact(totalPrice, Math.multiplyExact(menu.getSalePrice(), orderList.get(menu.getId())));
}

// 픽업 코드를 통해 주문 생성
Order order = createOrderWithUniqueCode(totalPrice);

List<OrderItem> orderItemList = new ArrayList<>();
List<Long> storeIdList = new ArrayList<>();
Set<Long> 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();
}

/**
Expand All @@ -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();
}

/**
* 픽업 코드(영문 두자리 + 숫자 네자리) 생성 메서드
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,4 +23,10 @@ public ResponseEntity<SuccessResponse<CreateOrderRes>> createOrder(@RequestBody
CreateOrderRes createOrderRes = orderService.orderMenus(orderReq);
return ResponseEntity.status(HttpStatus.CREATED).body(SuccessResponse.of(createOrderRes, SuccessResponseCode.SUCCESS_CREATED));
}

@PutMapping("/complete")
public ResponseEntity<SuccessResponse<?>> completePickUp(@RequestBody @Valid CompleteOrderReq completeOrderReq){
orderService.completePickUp(completeOrderReq);
return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.empty());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}