diff --git a/src/main/java/com/almang/inventory/global/api/SuccessMessage.java b/src/main/java/com/almang/inventory/global/api/SuccessMessage.java index 39453456..c2dda2b5 100644 --- a/src/main/java/com/almang/inventory/global/api/SuccessMessage.java +++ b/src/main/java/com/almang/inventory/global/api/SuccessMessage.java @@ -43,6 +43,7 @@ public enum SuccessMessage { GET_ORDER_SUCCESS("발주 조회 성공"), GET_ORDER_LIST_SUCCESS("발주 목록 조회 성공"), UPDATE_ORDER_SUCCESS("발주 수정 성공"), + GET_ORDER_ITEM_SUCCESS("발주 아이템 조회 성공"), ; private final String message; diff --git a/src/main/java/com/almang/inventory/order/controller/OrderController.java b/src/main/java/com/almang/inventory/order/controller/OrderController.java index 31a969b9..6225a8c3 100644 --- a/src/main/java/com/almang/inventory/order/controller/OrderController.java +++ b/src/main/java/com/almang/inventory/order/controller/OrderController.java @@ -7,6 +7,7 @@ import com.almang.inventory.order.domain.OrderStatus; import com.almang.inventory.order.dto.request.CreateOrderRequest; import com.almang.inventory.order.dto.request.UpdateOrderRequest; +import com.almang.inventory.order.dto.response.OrderItemResponse; import com.almang.inventory.order.dto.response.OrderResponse; import com.almang.inventory.order.service.OrderService; import io.swagger.v3.oas.annotations.Operation; @@ -102,4 +103,19 @@ public ResponseEntity> updateOrder( ApiResponse.success(SuccessMessage.UPDATE_ORDER_SUCCESS.getMessage(), response) ); } + + @GetMapping("/item/{orderItemId}") + @Operation(summary = "발주 아이템 조회", description = "발주 아이템 조회합니다.") + public ResponseEntity> getOrderItem( + @PathVariable Long orderItemId, + @AuthenticationPrincipal CustomUserPrincipal userPrincipal + ) { + Long userId = userPrincipal.getId(); + log.info("[OrderController] 발주 아이템 조회 요청 - orderItemId: {}, userId: {}", orderItemId, userId); + OrderItemResponse response = orderService.getOrderItem(orderItemId, userId); + + return ResponseEntity.ok( + ApiResponse.success(SuccessMessage.GET_ORDER_ITEM_SUCCESS.getMessage(), response) + ); + } } diff --git a/src/main/java/com/almang/inventory/order/dto/response/OrderItemResponse.java b/src/main/java/com/almang/inventory/order/dto/response/OrderItemResponse.java index fb1cd4ad..91166496 100644 --- a/src/main/java/com/almang/inventory/order/dto/response/OrderItemResponse.java +++ b/src/main/java/com/almang/inventory/order/dto/response/OrderItemResponse.java @@ -10,7 +10,8 @@ public record OrderItemResponse( Long productId, Integer quantity, Integer unitPrice, - Integer amount + Integer amount, + String note ) { public static OrderItemResponse from(OrderItem orderItem) { if (orderItem.getOrder() == null || orderItem.getProduct() == null) { @@ -22,7 +23,8 @@ public static OrderItemResponse from(OrderItem orderItem) { orderItem.getProduct().getId(), orderItem.getQuantity(), orderItem.getUnitPrice(), - orderItem.getAmount() + orderItem.getAmount(), + orderItem.getNote() ); } } diff --git a/src/main/java/com/almang/inventory/order/service/OrderService.java b/src/main/java/com/almang/inventory/order/service/OrderService.java index b0402e31..e1966dbb 100644 --- a/src/main/java/com/almang/inventory/order/service/OrderService.java +++ b/src/main/java/com/almang/inventory/order/service/OrderService.java @@ -11,6 +11,7 @@ import com.almang.inventory.order.dto.request.CreateOrderRequest; import com.almang.inventory.order.dto.request.UpdateOrderItemRequest; import com.almang.inventory.order.dto.request.UpdateOrderRequest; +import com.almang.inventory.order.dto.response.OrderItemResponse; import com.almang.inventory.order.dto.response.OrderResponse; import com.almang.inventory.order.repository.OrderItemRepository; import com.almang.inventory.order.repository.OrderRepository; @@ -115,6 +116,19 @@ public OrderResponse updateOrder(Long orderId, UpdateOrderRequest request, Long return OrderResponse.of(order, order.getItems()); } + @Transactional(readOnly = true) + public OrderItemResponse getOrderItem(Long orderItemId, Long userId) { + User user = findUserById(userId); + Store store = user.getStore(); + + log.info("[OrderService] 발주 상세 조회 요청 - userId: {}, storeId: {}", userId, store.getId()); + OrderItem orderItem = findOrderItemById(orderItemId); + validateOrderItemAccess(orderItem, store); + + log.info("[OrderService] 발주 상세 조회 성공 - orderItemId: {}", orderItem.getId()); + return OrderItemResponse.from(orderItem); + } + private List createOrderItems(List requests, Store store) { List items = new ArrayList<>(); @@ -270,4 +284,15 @@ private void updateOrderItems(Order order, UpdateOrderRequest request) { } order.updateTotalPrice(calculateTotalPrice(order.getItems())); } + + private OrderItem findOrderItemById(Long orderItemId) { + return orderItemRepository.findById(orderItemId) + .orElseThrow(() -> new BaseException(ErrorCode.ORDER_ITEM_NOT_FOUND)); + } + + private void validateOrderItemAccess(OrderItem orderItem, Store store) { + if (!orderItem.getOrder().getStore().getId().equals(store.getId())) { + throw new BaseException(ErrorCode.ORDER_ITEM_ACCESS_DENIED); + } + } } diff --git a/src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java b/src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java index 8693862b..8508ea15 100644 --- a/src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java +++ b/src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java @@ -20,6 +20,7 @@ import com.almang.inventory.order.dto.request.CreateOrderRequest; import com.almang.inventory.order.dto.request.UpdateOrderItemRequest; import com.almang.inventory.order.dto.request.UpdateOrderRequest; +import com.almang.inventory.order.dto.response.OrderItemResponse; import com.almang.inventory.order.dto.response.OrderResponse; import com.almang.inventory.order.service.OrderService; @@ -563,4 +564,72 @@ private UsernamePasswordAuthenticationToken auth() { .value(ErrorCode.VENDOR_CHANGE_NOT_ALLOWED.getMessage())) .andExpect(jsonPath("$.data").doesNotExist()); } + + @Test + void 발주_아이템_조회에_성공한다() throws Exception { + // given + Long orderItemId = 1L; + + OrderItemResponse response = new OrderItemResponse( + orderItemId, + 100L, + 10L, + 5, + 1000, + 5000, + "비고입니다" + ); + + when(orderService.getOrderItem(anyLong(), anyLong())) + .thenReturn(response); + + // when & then + mockMvc.perform(get("/api/v1/order/item/{orderItemId}", orderItemId) + .with(authentication(auth()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value(200)) + .andExpect(jsonPath("$.message") + .value(SuccessMessage.GET_ORDER_ITEM_SUCCESS.getMessage())) + .andExpect(jsonPath("$.data.orderItemId").value(orderItemId)) + .andExpect(jsonPath("$.data.orderId").value(100L)) + .andExpect(jsonPath("$.data.productId").value(10L)); + } + + @Test + void 발주_아이템_조회시_존재하지_않으면_예외가_발생한다() throws Exception { + // given + Long notExistOrderItemId = 9999L; + + when(orderService.getOrderItem(anyLong(), anyLong())) + .thenThrow(new BaseException(ErrorCode.ORDER_ITEM_NOT_FOUND)); + + // when & then + mockMvc.perform(get("/api/v1/order/item/{orderItemId}", notExistOrderItemId) + .with(authentication(auth()))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.status") + .value(ErrorCode.ORDER_ITEM_NOT_FOUND.getHttpStatus().value())) + .andExpect(jsonPath("$.message") + .value(ErrorCode.ORDER_ITEM_NOT_FOUND.getMessage())) + .andExpect(jsonPath("$.data").doesNotExist()); + } + + @Test + void 발주_아이템_조회시_다른_상점의_아이템이면_접근_거부_예외가_발생한다() throws Exception { + // given + Long orderItemId = 1L; + + when(orderService.getOrderItem(anyLong(), anyLong())) + .thenThrow(new BaseException(ErrorCode.ORDER_ITEM_ACCESS_DENIED)); + + // when & then + mockMvc.perform(get("/api/v1/order/item/{orderItemId}", orderItemId) + .with(authentication(auth()))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.status") + .value(ErrorCode.ORDER_ITEM_ACCESS_DENIED.getHttpStatus().value())) + .andExpect(jsonPath("$.message") + .value(ErrorCode.ORDER_ITEM_ACCESS_DENIED.getMessage())) + .andExpect(jsonPath("$.data").doesNotExist()); + } } diff --git a/src/test/java/com/almang/inventory/order/service/OrderServiceTest.java b/src/test/java/com/almang/inventory/order/service/OrderServiceTest.java index 20f0f7e3..54383d26 100644 --- a/src/test/java/com/almang/inventory/order/service/OrderServiceTest.java +++ b/src/test/java/com/almang/inventory/order/service/OrderServiceTest.java @@ -13,6 +13,7 @@ import com.almang.inventory.order.dto.request.CreateOrderRequest; import com.almang.inventory.order.dto.request.UpdateOrderItemRequest; import com.almang.inventory.order.dto.request.UpdateOrderRequest; +import com.almang.inventory.order.dto.response.OrderItemResponse; import com.almang.inventory.order.dto.response.OrderResponse; import com.almang.inventory.order.repository.OrderRepository; import com.almang.inventory.product.domain.Product; @@ -888,4 +889,86 @@ private Product newProduct(Store store, Vendor vendor, String name, String code) .isInstanceOf(BaseException.class) .hasMessageContaining(ErrorCode.ORDER_ITEM_ACCESS_DENIED.getMessage()); } + + @Test + void 발주_상세_조회에_성공한다() { + // given + Store store = newStore("테스트 상점"); + User user = newUser(store, "order_item_reader"); + Vendor vendor = newVendor(store, "발주처1"); + Product product = newProduct(store, vendor, "상품1", "P001"); + + CreateOrderRequest createRequest = new CreateOrderRequest( + vendor.getId(), + "발주 메시지", + 3, + List.of(new CreateOrderItemRequest(product.getId(), 2, 1000, "비고")) + ); + + OrderResponse created = orderService.createOrder(createRequest, user.getId()); + Long orderId = created.orderId(); + + Order order = orderRepository.findById(orderId) + .orElseThrow(); + OrderItem orderItem = order.getItems().get(0); + Long orderItemId = orderItem.getId(); + + // when + OrderItemResponse response = orderService.getOrderItem(orderItemId, user.getId()); + + // then + assertThat(response).isNotNull(); + assertThat(response.orderItemId()).isEqualTo(orderItemId); + assertThat(response.productId()).isEqualTo(product.getId()); + assertThat(response.quantity()).isEqualTo(2); + assertThat(response.unitPrice()).isEqualTo(1000); + assertThat(response.amount()).isEqualTo(2 * 1000); + assertThat(response.note()).isEqualTo("비고"); + } + + @Test + void 발주_상세_조회시_발주_항목이_존재하지_않으면_예외가_발생한다() { + // given + Store store = newStore("테스트 상점"); + User user = newUser(store, "order_item_reader"); + Long notExistOrderItemId = 9999L; + + // when & then + assertThatThrownBy(() -> orderService.getOrderItem(notExistOrderItemId, user.getId())) + .isInstanceOf(BaseException.class) + .hasMessageContaining(ErrorCode.ORDER_ITEM_NOT_FOUND.getMessage()); + } + + @Test + void 발주_상세_조회시_다른_상점의_발주_항목이면_접근_거부_예외가_발생한다() { + // given + Store store1 = newStore("상점1"); + Store store2 = newStore("상점2"); + + User userOfStore1 = newUser(store1, "user1"); + User userOfStore2 = newUser(store2, "user2"); + + Vendor vendorOfStore2 = newVendor(store2, "상점2 발주처"); + Product productOfStore2 = newProduct(store2, vendorOfStore2, "상점2 상품", "P999"); + + OrderResponse created = orderService.createOrder( + new CreateOrderRequest( + vendorOfStore2.getId(), + "상점2 발주", + 2, + List.of(new CreateOrderItemRequest(productOfStore2.getId(), 3, 1000, "비고")) + ), + userOfStore2.getId() + ); + + Long orderId = created.orderId(); + Order order2 = orderRepository.findById(orderId) + .orElseThrow(); + Long orderItemId = order2.getItems().get(0).getId(); + + // when & then + assertThatThrownBy(() -> orderService.getOrderItem(orderItemId, userOfStore1.getId())) + .isInstanceOf(BaseException.class) + .hasMessageContaining(ErrorCode.ORDER_ITEM_ACCESS_DENIED.getMessage()); + } }