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 @@ -41,6 +41,7 @@ public enum SuccessMessage {
GET_ORDER_TEMPLATE_DETAIL("발주 템플릿 상세 조회 성공"),
CREATE_ORDER_SUCCESS("발주 생성 성공"),
GET_ORDER_SUCCESS("발주 조회 성공"),
GET_ORDER_LIST_SUCCESS("발주 목록 조회 성공"),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.almang.inventory.order.controller;

import com.almang.inventory.global.api.ApiResponse;
import com.almang.inventory.global.api.PageResponse;
import com.almang.inventory.global.api.SuccessMessage;
import com.almang.inventory.global.security.principal.CustomUserPrincipal;
import com.almang.inventory.order.domain.OrderStatus;
import com.almang.inventory.order.dto.request.CreateOrderRequest;
import com.almang.inventory.order.dto.response.OrderResponse;
import com.almang.inventory.order.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
Expand All @@ -18,6 +21,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
Expand Down Expand Up @@ -58,4 +62,26 @@ public ResponseEntity<ApiResponse<OrderResponse>> getOrder(
ApiResponse.success(SuccessMessage.GET_ORDER_SUCCESS.getMessage(), response)
);
}

@GetMapping
@Operation(summary = "발주 목록 조회", description = "발주 목록을 페이지네이션, 발주처, 상태, 날짜 검색 조건과 함께 조회합니다.")
public ResponseEntity<ApiResponse<PageResponse<OrderResponse>>> getOrderList(
@AuthenticationPrincipal CustomUserPrincipal userPrincipal,
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "size", required = false) Integer size,
@RequestParam(value = "vendorId", required = false) Long vendorId,
@RequestParam(value = "orderStatus", required = false) OrderStatus status,
@RequestParam(value = "fromDate", required = false) LocalDate fromDate,
@RequestParam(value = "endDate", required = false) LocalDate endDate
) {
Long userId = userPrincipal.getId();
log.info("[OrderController] 발주 목록 조회 요청 - userId: {}, page: {}, size: {}, vendorId: {}, status: {}, fromDate: {}, endDate: {}",
userId, page, size, vendorId, status, fromDate, endDate);
PageResponse<OrderResponse> response =
orderService.getOrderList(userId, vendorId, page, size, status, fromDate, endDate);

return ResponseEntity.ok(
ApiResponse.success(SuccessMessage.GET_ORDER_LIST_SUCCESS.getMessage(), response)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@

public interface OrderRepository extends JpaRepository<Order, Long> {

// 상점 기준 발주 목록
Page<Order> findAllByStoreId(Long storeId, Pageable pageable);

// 상점 + 상태 기준
Page<Order> findAllByStoreIdAndStatus(Long storeId, OrderStatus status, Pageable pageable);

// 기간 + 상점 기준
// 필터 없음
Page<Order> findAllByStoreIdAndCreatedAtBetween(
Long storeId, LocalDateTime start, LocalDateTime end, Pageable pageable
);

// 상태 필터
Page<Order> findAllByStoreIdAndStatusAndCreatedAtBetween(
Long storeId, OrderStatus status, LocalDateTime start, LocalDateTime end, Pageable pageable
);

// 발주처 필터
Page<Order> findAllByStoreIdAndVendorIdAndCreatedAtBetween(
Long storeId, Long vendorId, LocalDateTime start, LocalDateTime end, Pageable pageable
);

// 발주처 + 상태 필터
Page<Order> findAllByStoreIdAndVendorIdAndStatusAndCreatedAtBetween(
Long storeId, Long vendorId, OrderStatus status, LocalDateTime start, LocalDateTime end, Pageable pageable
);
}
62 changes: 62 additions & 0 deletions src/main/java/com/almang/inventory/order/service/OrderService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.almang.inventory.order.service;

import com.almang.inventory.global.api.PageResponse;
import com.almang.inventory.global.exception.BaseException;
import com.almang.inventory.global.exception.ErrorCode;
import com.almang.inventory.global.util.PaginationUtil;
import com.almang.inventory.order.domain.Order;
import com.almang.inventory.order.domain.OrderItem;
import com.almang.inventory.order.domain.OrderStatus;
Expand All @@ -19,10 +21,14 @@
import com.almang.inventory.vendor.domain.Vendor;
import com.almang.inventory.vendor.repository.VendorRepository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -69,6 +75,23 @@ public OrderResponse getOrder(Long orderId, Long userId) {
return OrderResponse.of(order, order.getItems());
}

@Transactional(readOnly = true)
public PageResponse<OrderResponse> getOrderList(
Long userId, Long vendorId, Integer page, Integer size,
OrderStatus status, LocalDate fromDate, LocalDate toDate
) {
User user = findUserById(userId);
Store store = user.getStore();

log.info("[OrderService] 발주 목록 조회 요청 - userId: {}, storeId: {}", userId, store.getId());
PageRequest pageable = PaginationUtil.createPageRequest(page, size, "createdAt");
Page<Order> orderPage = findOrdersByFilter(store.getId(), vendorId, status, fromDate, toDate, pageable);
Page<OrderResponse> mapped = orderPage.map(order -> OrderResponse.of(order, order.getItems()));

log.info("[OrderService] 발주 목록 조회 성공 - userId: {}, storeId: {}", userId, store.getId());
return PageResponse.from(mapped);
}

private List<OrderItem> createOrderItems(List<CreateOrderItemRequest> requests, Store store) {
List<OrderItem> items = new ArrayList<>();

Expand Down Expand Up @@ -157,4 +180,43 @@ private Order findOrderByIdAndValidateAccess(Long orderId, Store store) {
}
return order;
}

private Page<Order> findOrdersByFilter(
Long storeId, Long vendorId, OrderStatus status, LocalDate fromDate, LocalDate toDate, Pageable pageable
) {
LocalDate startDate = fromDate != null ? fromDate : LocalDate.of(1970, 1, 1);
LocalDateTime start = startDate.atStartOfDay();

LocalDate endDate = toDate != null ? toDate : LocalDate.now();
LocalDateTime end = endDate.plusDays(1).atStartOfDay().minusNanos(1);

boolean hasVendor = vendorId != null;
boolean hasStatus = status != null;

// 1) 필터 없음
if (!hasVendor && !hasStatus) {
return orderRepository.findAllByStoreIdAndCreatedAtBetween(
storeId, start, end, pageable
);
}

// 2) 상태 필터
if (!hasVendor) {
return orderRepository.findAllByStoreIdAndStatusAndCreatedAtBetween(
storeId, status, start, end, pageable
);
}

// 3) 발주처 필터
if (!hasStatus) {
return orderRepository.findAllByStoreIdAndVendorIdAndCreatedAtBetween(
storeId, vendorId, start, end, pageable
);
}

// 4) 발주처 + 상태 필터
return orderRepository.findAllByStoreIdAndVendorIdAndStatusAndCreatedAtBetween(
storeId, vendorId, status, start, end, pageable
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.almang.inventory.global.api.PageResponse;
import com.almang.inventory.global.api.SuccessMessage;
import com.almang.inventory.global.config.TestSecurityConfig;
import com.almang.inventory.global.exception.BaseException;
Expand Down Expand Up @@ -314,4 +315,101 @@ private UsernamePasswordAuthenticationToken auth() {
.andExpect(jsonPath("$.message").value(ErrorCode.ORDER_ACCESS_DENIED.getMessage()))
.andExpect(jsonPath("$.data").doesNotExist());
}

@Test
void 발주_목록_조회에_성공한다() throws Exception {
// given
OrderResponse r1 = new OrderResponse(
1L,
10L,
10L,
"메시지1",
OrderStatus.REQUEST,
1,
LocalDate.now().plusDays(1),
null,
null,
true,
5000,
List.of()
);

OrderResponse r2 = new OrderResponse(
2L,
10L,
10L,
"메시지2",
OrderStatus.REQUEST,
2,
LocalDate.now().plusDays(2),
null,
null,
true,
9000,
List.of()
);

PageResponse<OrderResponse> pageResponse = new PageResponse<>(
List.of(r1, r2),
1,
20,
2L,
1,
true
);

when(orderService.getOrderList(anyLong(), any(), any(), any(), any(), any(), any()))
.thenReturn(pageResponse);

// when & then
mockMvc.perform(get("/api/v1/order")
.param("page", "1")
.param("size", "20")
.with(authentication(auth())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.message")
.value(SuccessMessage.GET_ORDER_LIST_SUCCESS.getMessage()))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.content[0].orderId").value(1))
.andExpect(jsonPath("$.data.content[1].orderId").value(2));
}

@Test
void 발주_목록_조회시_사용자가_존재하지_않으면_예외가_발생한다() throws Exception {
// given
when(orderService.getOrderList(anyLong(), any(), any(), any(), any(), any(), any()))
.thenThrow(new BaseException(ErrorCode.USER_NOT_FOUND));

// when & then
mockMvc.perform(get("/api/v1/order")
.param("page", "1")
.param("size", "20")
.with(authentication(auth())))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status")
.value(ErrorCode.USER_NOT_FOUND.getHttpStatus().value()))
.andExpect(jsonPath("$.message")
.value(ErrorCode.USER_NOT_FOUND.getMessage()))
.andExpect(jsonPath("$.data").doesNotExist());
}

@Test
void 발주_목록_조회시_상점_접근_권한이_없으면_예외가_발생한다() throws Exception {
// given
when(orderService.getOrderList(anyLong(), any(), any(), any(), any(), any(), any()))
.thenThrow(new BaseException(ErrorCode.ORDER_ACCESS_DENIED));

// when & then
mockMvc.perform(get("/api/v1/order")
.param("page", "1")
.param("size", "20")
.with(authentication(auth())))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.status")
.value(ErrorCode.ORDER_ACCESS_DENIED.getHttpStatus().value()))
.andExpect(jsonPath("$.message")
.value(ErrorCode.ORDER_ACCESS_DENIED.getMessage()))
.andExpect(jsonPath("$.data").doesNotExist());
}
}
Loading