Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[39] 주문 전 재고 체크 및 재고 감소 #42

Merged
merged 2 commits into from
Oct 18, 2024
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 @@ -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;
Expand All @@ -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<OrderDetailResponseDto.Get> orderDetails = orderDetailService.getOrderDetails(order.getId());
Address address = addressService.getAddress(dto.getAddressId(), user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public class OrderRequestDto {
@Getter
@Builder
public static class Create {
private Long userId;
private String userAuthId;
private BigDecimal totalAmount;
private List<OrderRequestDto.CreateDetail> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -46,8 +51,39 @@ public void delete(Product product) {
productRepository.delete(product);
}

@Transactional(readOnly = true)
public Page<ProductResponseDto.Get> getProductList(PageRequest of, String keyword, Long categoryId,
Long partnerId) {
return customProductRepository.getProducts(of, keyword, categoryId, partnerId);
}

@Transactional
public void updateStock(List<OrderRequestDto.CreateDetail> details) {
List<Long> productIds = details.stream()
.map(OrderRequestDto.CreateDetail::getProductId)
.toList();

List<Product> products = productRepository.findByIdInWithLock(productIds);

Map<Long, Product> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, "카테고리 정보를 찾을 수 없습니다.");
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/example/commerce_site/domain/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Product, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id IN :productIds")
List<Product> findByIdInWithLock(List<Long> productIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -54,15 +58,16 @@ public void create_ShouldCreateOrder() {
Address address = Address.builder().id(addressId).build();
List<OrderDetailResponseDto.Get> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
Loading