diff --git a/src/main/java/org/example/commerce_site/application/order/OrderFacade.java b/src/main/java/org/example/commerce_site/application/order/OrderFacade.java index 8e2d048..d314ce8 100644 --- a/src/main/java/org/example/commerce_site/application/order/OrderFacade.java +++ b/src/main/java/org/example/commerce_site/application/order/OrderFacade.java @@ -5,6 +5,7 @@ import org.example.commerce_site.application.address.AddressService; import org.example.commerce_site.application.order.dto.OrderDetailResponseDto; import org.example.commerce_site.application.order.dto.OrderRequestDto; +import org.example.commerce_site.application.product.ProductService; import org.example.commerce_site.application.shipment.ShipmentService; import org.example.commerce_site.application.user.UserService; import org.example.commerce_site.domain.Address; @@ -23,12 +24,13 @@ public class OrderFacade { private final OrderDetailService orderDetailService; private final ShipmentService shipmentService; private final AddressService addressService; + private final ProductService productService; @Transactional public void create(OrderRequestDto.Create dto) { - User user = userService.getUser(dto.getUserId()); - //TODO 상품의 수량이 없어서 또는 상태 변경으로 구매하지 못할 경우에 대한 검증이 필요함 - Order order = orderService.createOrder(dto); + User user = userService.getUser(dto.getUserAuthId()); + productService.updateStock(dto.getDetails()); + Order order = orderService.createOrder(dto, user.getId()); orderDetailService.createOrderDetails(dto.getDetails(), order); List orderDetails = orderDetailService.getOrderDetails(order.getId()); Address address = addressService.getAddress(dto.getAddressId(), user); diff --git a/src/main/java/org/example/commerce_site/application/order/OrderService.java b/src/main/java/org/example/commerce_site/application/order/OrderService.java index aabd0c7..65a130d 100644 --- a/src/main/java/org/example/commerce_site/application/order/OrderService.java +++ b/src/main/java/org/example/commerce_site/application/order/OrderService.java @@ -16,8 +16,8 @@ public class OrderService { private final OrderRepository orderRepository; @Transactional - public Order createOrder(OrderRequestDto.Create dto) { - return orderRepository.save(OrderRequestDto.Create.toEntity(dto)); + public Order createOrder(OrderRequestDto.Create dto, Long userId) { + return orderRepository.save(OrderRequestDto.Create.toEntity(dto, userId)); } @Transactional(readOnly = true) diff --git a/src/main/java/org/example/commerce_site/application/order/dto/OrderRequestDto.java b/src/main/java/org/example/commerce_site/application/order/dto/OrderRequestDto.java index 62d6e9a..193f734 100644 --- a/src/main/java/org/example/commerce_site/application/order/dto/OrderRequestDto.java +++ b/src/main/java/org/example/commerce_site/application/order/dto/OrderRequestDto.java @@ -16,14 +16,14 @@ public class OrderRequestDto { @Getter @Builder public static class Create { - private Long userId; + private String userAuthId; private BigDecimal totalAmount; private List details; private Long addressId; - public static Order toEntity(OrderRequestDto.Create dto) { + public static Order toEntity(OrderRequestDto.Create dto, Long userId) { return Order.builder() - .userId(dto.getUserId()) + .userId(userId) .totalAmount(dto.getTotalAmount()) .status(OrderStatus.PENDING) .build(); diff --git a/src/main/java/org/example/commerce_site/application/product/ProductService.java b/src/main/java/org/example/commerce_site/application/product/ProductService.java index 79ab9a6..b8f7716 100644 --- a/src/main/java/org/example/commerce_site/application/product/ProductService.java +++ b/src/main/java/org/example/commerce_site/application/product/ProductService.java @@ -1,5 +1,10 @@ package org.example.commerce_site.application.product; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.example.commerce_site.application.order.dto.OrderRequestDto; import org.example.commerce_site.application.product.dto.ProductRequestDto; import org.example.commerce_site.application.product.dto.ProductResponseDto; import org.example.commerce_site.common.exception.CustomException; @@ -46,8 +51,39 @@ public void delete(Product product) { productRepository.delete(product); } + @Transactional(readOnly = true) public Page getProductList(PageRequest of, String keyword, Long categoryId, Long partnerId) { return customProductRepository.getProducts(of, keyword, categoryId, partnerId); } + + @Transactional + public void updateStock(List details) { + List productIds = details.stream() + .map(OrderRequestDto.CreateDetail::getProductId) + .toList(); + + List products = productRepository.findByIdInWithLock(productIds); + + Map productMap = products.stream() + .collect(Collectors.toMap(Product::getId, product -> product)); + + for (OrderRequestDto.CreateDetail detail : details) { + Product product = productMap.get(detail.getProductId()); + + if (product == null) { + throw new CustomException(ErrorCode.PRODUCT_NOT_FOUND); + } + + long newStockQuantity = product.getStockQuantity() - detail.getQuantity(); + + if (newStockQuantity < 0) { + throw new CustomException(ErrorCode.PRODUCT_OUT_OF_STOCK); + } + + product.updateQuantity(newStockQuantity); + } + + productRepository.saveAll(products); + } } diff --git a/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java b/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java index 40a323a..d7608f7 100644 --- a/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java +++ b/src/main/java/org/example/commerce_site/common/exception/ErrorCode.java @@ -37,6 +37,7 @@ public enum ErrorCode { //product PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "상품 정보를 찾을 수 없습니다."), PRODUCT_ACCESS_DENIED(HttpStatus.FORBIDDEN, 403, "상품 정보 수정에 대한 권한이 없습니다."), + PRODUCT_OUT_OF_STOCK(HttpStatus.CONFLICT, 409, "상품의 재고가 부족합니다"), //category CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "카테고리 정보를 찾을 수 없습니다."); diff --git a/src/main/java/org/example/commerce_site/domain/Product.java b/src/main/java/org/example/commerce_site/domain/Product.java index ac286b3..73c39e9 100644 --- a/src/main/java/org/example/commerce_site/domain/Product.java +++ b/src/main/java/org/example/commerce_site/domain/Product.java @@ -54,4 +54,8 @@ public void update(ProductRequestDto.Put dto, Category category) { this.isEnable = dto.getIsEnable() != null ? dto.getIsEnable() : this.isEnable; this.category = category != null ? category : this.category; } + + public void updateQuantity(Long stockQuantity) { + this.stockQuantity = stockQuantity; + } } diff --git a/src/main/java/org/example/commerce_site/infrastructure/product/ProductRepository.java b/src/main/java/org/example/commerce_site/infrastructure/product/ProductRepository.java index 92a1f00..0c5f623 100644 --- a/src/main/java/org/example/commerce_site/infrastructure/product/ProductRepository.java +++ b/src/main/java/org/example/commerce_site/infrastructure/product/ProductRepository.java @@ -1,9 +1,18 @@ package org.example.commerce_site.infrastructure.product; +import java.util.List; + import org.example.commerce_site.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import jakarta.persistence.LockModeType; + @Repository public interface ProductRepository extends JpaRepository { + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT p FROM Product p WHERE p.id IN :productIds") + List findByIdInWithLock(List productIds); } diff --git a/src/main/java/org/example/commerce_site/representation/order/OrderController.java b/src/main/java/org/example/commerce_site/representation/order/OrderController.java index ba51796..4efeba5 100644 --- a/src/main/java/org/example/commerce_site/representation/order/OrderController.java +++ b/src/main/java/org/example/commerce_site/representation/order/OrderController.java @@ -5,6 +5,7 @@ import org.example.commerce_site.representation.order.request.OrderRequest; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,11 +18,12 @@ public class OrderController { private final OrderFacade orderFacade; - @PostMapping("/{user_id}") - public ApiSuccessResponse createOrder(@PathVariable("user_id") long userId, + @PostMapping() + public ApiSuccessResponse createOrder( + @RequestAttribute("userId") String userAuthId, @RequestBody OrderRequest.Create request ) { - orderFacade.create(OrderRequest.Create.toDto(request, userId)); + orderFacade.create(OrderRequest.Create.toDto(request, userAuthId)); return ApiSuccessResponse.success(); } } diff --git a/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java b/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java index ffaab94..6188003 100644 --- a/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java +++ b/src/main/java/org/example/commerce_site/representation/order/request/OrderRequest.java @@ -18,9 +18,9 @@ public static class Create { @NotNull private Long addressId; - public static OrderRequestDto.Create toDto(OrderRequest.Create request, Long userId) { + public static OrderRequestDto.Create toDto(OrderRequest.Create request, String userId) { return OrderRequestDto.Create.builder() - .userId(userId) + .userAuthId(userId) .totalAmount(request.getTotalAmount()) .details(CreateDetail.toDtos(request.getDetails())) .addressId(request.getAddressId()) diff --git a/src/test/java/org/example/commerce_site/application/order/OrderFacadeTest.java b/src/test/java/org/example/commerce_site/application/order/OrderFacadeTest.java index b33de49..adf33a5 100644 --- a/src/test/java/org/example/commerce_site/application/order/OrderFacadeTest.java +++ b/src/test/java/org/example/commerce_site/application/order/OrderFacadeTest.java @@ -8,6 +8,7 @@ import org.example.commerce_site.application.address.AddressService; import org.example.commerce_site.application.order.dto.OrderDetailResponseDto; import org.example.commerce_site.application.order.dto.OrderRequestDto; +import org.example.commerce_site.application.product.ProductService; import org.example.commerce_site.application.shipment.ShipmentService; import org.example.commerce_site.application.user.UserService; import org.example.commerce_site.domain.Address; @@ -39,12 +40,15 @@ class OrderFacadeTest { @Mock private AddressService addressService; + @Mock + private ProductService productService; + @Test public void create_ShouldCreateOrder() { Long userId = 1L; Long addressId = 1L; OrderRequestDto.Create dto = OrderRequestDto.Create.builder() - .userId(userId) + .userAuthId("Test Auth") .addressId(addressId) .details(Collections.emptyList()) .build(); @@ -54,15 +58,16 @@ public void create_ShouldCreateOrder() { Address address = Address.builder().id(addressId).build(); List orderDetails = Collections.emptyList(); - when(userService.getUser(userId)).thenReturn(user); - when(orderService.createOrder(dto)).thenReturn(order); + when(userService.getUser(dto.getUserAuthId())).thenReturn(user); + when(orderService.createOrder(eq(dto), eq(userId))).thenReturn(order); when(addressService.getAddress(addressId, user)).thenReturn(address); when(orderDetailService.getOrderDetails(order.getId())).thenReturn(orderDetails); orderFacade.create(dto); - verify(userService).getUser(userId); - verify(orderService).createOrder(dto); + verify(userService).getUser(dto.getUserAuthId()); + verify(productService).updateStock(dto.getDetails()); + verify(orderService).createOrder(eq(dto), eq(userId)); // 매처 사용 verify(orderDetailService).createOrderDetails(dto.getDetails(), order); verify(orderDetailService).getOrderDetails(order.getId()); verify(addressService).getAddress(addressId, user); diff --git a/src/test/java/org/example/commerce_site/application/order/OrderServiceTest.java b/src/test/java/org/example/commerce_site/application/order/OrderServiceTest.java index afa0626..2aaa64e 100644 --- a/src/test/java/org/example/commerce_site/application/order/OrderServiceTest.java +++ b/src/test/java/org/example/commerce_site/application/order/OrderServiceTest.java @@ -35,11 +35,11 @@ void create_ShouldCreateOrder() { details.add(detailDto2); OrderRequestDto.Create createDto = OrderRequestDto.Create.builder() - .userId(1L).addressId(1L).totalAmount(new BigDecimal(10000)).details(details).build(); + .userAuthId("Test Auth").addressId(1L).totalAmount(new BigDecimal(10000)).details(details).build(); Order order = Order.builder().id(1L).userId(1L).build(); when(orderRepository.save(any(Order.class))).thenReturn(order); - orderService.createOrder(createDto); + orderService.createOrder(createDto, 1L); verify(orderRepository).save(any(Order.class)); }