Skip to content

feat: 상점의 발주 템플릿 목록 조회 기능 구현#58

Merged
JoonKyoLee merged 5 commits intomainfrom
feat/store-order-template-list
Nov 22, 2025
Merged

feat: 상점의 발주 템플릿 목록 조회 기능 구현#58
JoonKyoLee merged 5 commits intomainfrom
feat/store-order-template-list

Conversation

@JoonKyoLee
Copy link
Contributor

@JoonKyoLee JoonKyoLee commented Nov 22, 2025

✨ 작업 내용

  • 상점의 발주 템플릿 목록 조회 기능 구현

📝 적용 범위

  • /store

📌 참고 사항

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 주문 템플릿 조회 엔드포인트 추가: 페이지네이션 지원 및 활성화 상태별 필터링 기능 포함
    • 사용자가 자신의 매장 주문 템플릿을 효율적으로 조회하고 관리할 수 있음
  • 테스트

    • 주문 템플릿 조회 기능에 대한 다양한 시나리오 및 예외 처리에 대한 테스트 커버리지 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@JoonKyoLee JoonKyoLee self-assigned this Nov 22, 2025
@JoonKyoLee JoonKyoLee added the enhancement New feature or request label Nov 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

상점의 발주 템플릿 목록을 페이지네이션과 필터링(활성화 상태)을 지원하며 조회하는 기능을 구현합니다. 컨트롤러, 서비스, 레포지토리 계층에 새로운 메서드들을 추가하고 관련 테스트 케이스를 작성했습니다.

Changes

Cohort / File(s) 변경 요약
API 응답 메시지
src/main/java/com/almang/inventory/global/api/SuccessMessage.java
상점 발주 템플릿 조회 성공 메시지 GET_STORE_ORDER_TEMPLATE_SUCCESS enum 상수 추가
데이터 접근 계층
src/main/java/com/almang/inventory/order/template/repository/OrderTemplateRepository.java
스토어 ID 기반 페이지네이션 쿼리 3개 추가: 전체, 활성화됨, 비활성화됨 필터
컨트롤러 및 서비스 계층
src/main/java/com/almang/inventory/store/controller/StoreController.java, src/main/java/com/almang/inventory/store/service/StoreService.java
/api/v1/store/order-templates GET 엔드포인트 추가 및 getStoreOrderTemplateList() 서비스 메서드 구현, 페이지네이션 및 필터링 로직 포함
컨트롤러 단위 테스트
src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java
4개 테스트 케이스 추가: 정상 조회, 활성화=참/거짓 필터링, USER_NOT_FOUND 예외 처리
서비스 통합 테스트
src/test/java/com/almang/inventory/store/service/StoreServiceTest.java
Vendor/OrderTemplate 생성 헬퍼 메서드 및 4개 테스트 케이스 추가: 전체/활성화/비활성화 필터링, 예외 처리
기존 테스트 수정
src/test/java/com/almang/inventory/vendor/service/VendorServiceTest.java
테스트 사용자 username을 "template_viewer_store2""template_viewer"로 변경

Sequence Diagram

sequenceDiagram
    participant Client
    participant StoreController
    participant StoreService
    participant PaginationUtil
    participant OrderTemplateRepository
    participant Database

    Client->>StoreController: GET /api/v1/store/order-templates<br/>(page, size, activated)
    activate StoreController
    
    StoreController->>StoreService: getStoreOrderTemplateList<br/>(userId, page, size, activated)
    activate StoreService
    
    StoreService->>PaginationUtil: getPageRequest(page, size)
    activate PaginationUtil
    PaginationUtil-->>StoreService: PageRequest
    deactivate PaginationUtil
    
    alt activated == null
        StoreService->>OrderTemplateRepository: findAllByVendorStoreId(storeId, pageable)
    else activated == true
        StoreService->>OrderTemplateRepository: findAllByVendorStoreIdAndActivatedTrue(storeId, pageable)
    else activated == false
        StoreService->>OrderTemplateRepository: findAllByVendorStoreIdAndActivatedFalse(storeId, pageable)
    end
    
    activate OrderTemplateRepository
    OrderTemplateRepository->>Database: Query
    activate Database
    Database-->>OrderTemplateRepository: Page<OrderTemplate>
    deactivate Database
    deactivate OrderTemplateRepository
    
    StoreService->>StoreService: Map to OrderTemplateResponse
    StoreService->>StoreService: Wrap in PageResponse
    StoreService-->>StoreController: PageResponse<OrderTemplateResponse>
    deactivate StoreService
    
    StoreController-->>Client: ApiResponse<PageResponse<OrderTemplateResponse>><br/>(GET_STORE_ORDER_TEMPLATE_SUCCESS)
    deactivate StoreController
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

추가 주의 사항:

  • VendorServiceTest의 username 변경: "template_viewer_store2""template_viewer"로 변경된 이유를 확인하세요. 이것이 기존 테스트 케이스의 의도를 변경하거나 크로스스토어 접근 검증 로직에 영향을 미치는지 검증이 필요합니다.
  • 필터링 로직의 일관성: StoreService.findStoreOrderTemplatesByFilter() 헬퍼 메서드의 삼원 조건문이 모든 경우(null, true, false)를 처리하는지 확인하세요. 마진이 있는지 테스트 커버리지 검증이 권장됩니다.
  • 사용자-스토어 연계 검증: getStoreOrderTemplateList()에서 userId로부터 올바른 storeId를 조회하는 로직이 안전하게 구현되었는지 확인하세요. USER_NOT_FOUND 예외 처리가 적절히 이루어지고 있습니다.
  • 페이지네이션 기본값: PaginationUtil.getPageRequest()의 기본 정렬 방식과 페이지 크기 제한값이 명시되어 있는지 확인하세요.

Possibly related issues

Possibly related PRs

Poem

📋 발주 템플릿, 이제 목록을 볼 수 있네요
페이지마다 걸러내는 필터링의 마법 ✨
활성화 상태로 골라 담고,
테스트도 빈틈없이 덮었으니
상점은 이제 조회하고 또 조회하리! 🎯

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning VendorServiceTest에서 테스트 사용자명을 'template_viewer_store2'에서 'template_viewer'로 변경했는데, 이는 Issue #57의 요구사항과 무관한 범위 외 변경입니다. VendorServiceTest의 사용자명 변경이 정당한 이유가 있다면 설명하세요. 필요하지 않다면 별도 PR로 분리하거나 되돌려주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항인 '상점의 발주 템플릿 목록 조회 기능 구현'을 명확하고 간결하게 설명합니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(✨ 작업 내용, 📝 적용 범위, 📌 참고 사항)을 포함하고 있습니다.
Linked Issues check ✅ Passed PR 변경사항이 Issue #57의 요구사항을 완벽하게 충족합니다: 상점의 발주 템플릿 목록 조회 기능이 구현되었습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/store-order-template-list

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/main/java/com/almang/inventory/store/controller/StoreController.java (1)

50-67: 새 엔드포인트 구현 잘 되었습니다!

전반적으로 기존 패턴을 잘 따르고 있으며, 인증/로깅/응답 포맷이 일관성 있게 구현되어 있습니다.

다만, Swagger 문서화 개선을 위해 @Parameter 어노테이션을 추가하는 것을 권장합니다:

     @GetMapping("/order-templates")
     @Operation(summary = "상점 발주 템플릿 목록 조회", description = "현재 상점에 속한 모든 발주 템플릿을 조회합니다.")
+    @Parameter(name = "page", description = "페이지 번호 (기본값: 1)", example = "1")
+    @Parameter(name = "size", description = "페이지 크기 (기본값: 20)", example = "20")
+    @Parameter(name = "activated", description = "활성화 상태 필터 (true: 활성, false: 비활성, null: 전체)", example = "true")
     public ResponseEntity<ApiResponse<PageResponse<OrderTemplateResponse>>> getStoreOrderTemplates(
             @AuthenticationPrincipal CustomUserPrincipal userPrincipal,
             @RequestParam(value = "page", required = false) Integer page,
             @RequestParam(value = "size", required = false) Integer size,
             @RequestParam(value = "activated", required = false) Boolean activated
     ) {

필요한 import:

import io.swagger.v3.oas.annotations.Parameter;
src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java (1)

130-229: 테스트 커버리지 훌륭합니다! 🎯

주요 시나리오(정상 조회, 활성/비활성 필터, 예외 처리)가 모두 커버되어 있습니다.

추가로 고려해볼 만한 엣지 케이스 테스트를 제안합니다 (선택사항):

  1. 빈 결과 처리: 템플릿이 없는 상점의 조회
  2. 페이지네이션 엣지 케이스:
    • 음수 페이지/사이즈 입력
    • 존재하지 않는 페이지 번호 (예: page=999)
  3. 필터 조합: 활성 필터 + 페이지네이션 조합

이러한 테스트들은 프로덕션 환경에서 발생할 수 있는 예상치 못한 동작을 사전에 검증하는 데 도움이 됩니다.

src/test/java/com/almang/inventory/store/service/StoreServiceTest.java (1)

261-345: 통합 테스트 시나리오 잘 구성되었습니다!

전체 조회, 활성/비활성 필터링, 예외 처리까지 핵심 시나리오가 모두 검증되고 있습니다. 페이지네이션 메타데이터(page, size, totalElements) 검증도 꼼꼼하게 되어 있네요.

추가로 보안 측면에서 교차 상점 격리 테스트를 권장합니다:

@Test
void 다른_상점의_발주_템플릿은_조회되지_않는다() {
    // given
    Store store1 = newStore();
    Store store2 = storeRepository.save(
            Store.builder()
                    .name("다른 상점")
                    .isActivate(true)
                    .defaultCountCheckThreshold(BigDecimal.valueOf(0.2))
                    .build()
    );
    
    User userOfStore1 = newUser(store1);
    Vendor vendorOfStore1 = newVendor(store1, "상점1 발주처");
    Vendor vendorOfStore2 = newVendor(store2, "상점2 발주처");
    
    newOrderTemplate(vendorOfStore1, "상점1 템플릿", true);
    newOrderTemplate(vendorOfStore2, "상점2 템플릿", true);
    
    // when
    PageResponse<OrderTemplateResponse> response =
            storeService.getStoreOrderTemplateList(userOfStore1.getId(), 1, 20, null);
    
    // then
    assertThat(response.content()).hasSize(1);
    assertThat(response.content().get(0).title()).isEqualTo("상점1 템플릿");
}

이 테스트는 다른 상점의 템플릿이 노출되지 않는지 검증하여 데이터 격리가 올바르게 작동하는지 확인합니다.

src/main/java/com/almang/inventory/order/template/repository/OrderTemplateRepository.java (1)

11-23: 발주처 기준 쿼리도 페이지네이션 고려해보세요

현재 상점 기준 쿼리는 페이지네이션을 지원하지만, 발주처 기준 쿼리(Lines 12-16)는 List를 반환합니다.

만약 한 발주처에 템플릿이 수백 개 이상 존재할 가능성이 있다면, 발주처 기준 쿼리도 페이지네이션을 지원하도록 오버로드 메서드를 추가하는 것을 고려해보세요:

// 발주처 기준 - 페이지네이션 지원
Page<OrderTemplate> findAllByVendorId(Long vendorId, Pageable pageable);
Page<OrderTemplate> findAllByVendorIdAndActivatedTrue(Long vendorId, Pageable pageable);
Page<OrderTemplate> findAllByVendorIdAndActivatedFalse(Long vendorId, Pageable pageable);

다만 현재 비즈니스 요구사항에서 필요하지 않다면 나중에 추가해도 무방합니다.

src/test/java/com/almang/inventory/vendor/service/VendorServiceTest.java (1)

806-814: 네이밍 규칙 일관성 검토 및 확인 완료

검증 결과, 데이터베이스 레벨에서 username 필드에 unique=true 제약조건이 설정되어 있으므로 사용자명 중복으로 인한 테스트 실패는 발생하지 않습니다. 각 테스트는 독립적인 상점과 사용자를 생성하므로 테스트 격리도 정상적으로 작동합니다.

다만, 기존 store2 관련 사용자들이 모두 "tester_store2", "detail_tester_store2", "template_tester2"처럼 "_store2" 접미사를 따르는 반면, "template_viewer"는 이 규칙을 벗어나 있습니다. 코드의 일관성을 위해 다른 store2 사용자들과 동일한 네이밍 패턴을 따르는 것을 권장합니다:

제안: "template_viewer_store2"로 유지하거나, 의도된 변경이라면 다른 store2 사용자명들도 동일하게 리팩토링하는 것이 좋습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4acd721 and 7caefe5.

📒 Files selected for processing (7)
  • src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1 hunks)
  • src/main/java/com/almang/inventory/order/template/repository/OrderTemplateRepository.java (1 hunks)
  • src/main/java/com/almang/inventory/store/controller/StoreController.java (2 hunks)
  • src/main/java/com/almang/inventory/store/service/StoreService.java (5 hunks)
  • src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/store/service/StoreServiceTest.java (4 hunks)
  • src/test/java/com/almang/inventory/vendor/service/VendorServiceTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-20T10:43:47.489Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 33
File: src/main/java/com/almang/inventory/product/domain/Product.java:62-66
Timestamp: 2025-11-20T10:43:47.489Z
Learning: In the almang2/inventory-server repository, Product entity update methods (e.g., updateVendor in src/main/java/com/almang/inventory/product/domain/Product.java) do not require null checks on vendor parameters because ProductService validates vendor existence via findVendorByIdAndValidateAccess before calling update methods, and the vendor field has nullable=false constraint ensuring this.vendor is never null for persisted entities.

Applied to files:

  • src/test/java/com/almang/inventory/vendor/service/VendorServiceTest.java
  • src/test/java/com/almang/inventory/store/service/StoreServiceTest.java
  • src/main/java/com/almang/inventory/store/controller/StoreController.java
🧬 Code graph analysis (1)
src/main/java/com/almang/inventory/store/service/StoreService.java (1)
src/main/java/com/almang/inventory/global/util/PaginationUtil.java (1)
  • PaginationUtil (7-17)
🔇 Additional comments (5)
src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1)

12-12: 새 성공 메시지 추가 LGTM!

상점의 발주 템플릿 조회 성공 메시지가 적절히 추가되었습니다. 네이밍과 위치가 기존 패턴과 일관성 있게 유지되고 있습니다.

src/test/java/com/almang/inventory/store/service/StoreServiceTest.java (1)

62-84: 헬퍼 메서드 추가 깔끔합니다!

테스트 데이터 생성을 위한 newVendornewOrderTemplate 메서드가 재사용성 있게 잘 구현되었습니다. 매개변수를 받아 유연하게 사용할 수 있는 구조가 좋습니다.

src/main/java/com/almang/inventory/store/service/StoreService.java (2)

56-70: 서비스 메서드 구현 잘 되었습니다!

사용자 검증, 페이지네이션 처리, 엔티티-DTO 매핑, 로깅까지 모든 레이어가 깔끔하게 구현되었습니다. @Transactional(readOnly = true) 사용으로 읽기 최적화도 잘 되어 있네요.

한 가지 질문이 있습니다:

정렬 기준 선택에 대한 확인

Line 64에서 PaginationUtil.createPageRequest(page, size, "title")를 사용하여 title 기준 오름차순 정렬을 하고 있습니다.

발주 템플릿 목록 조회 시 정렬 기준으로 title이 적절한가요? 일반적으로는:

  • 생성일시 역순 (createdAt DESC): 최신 템플릿을 먼저 보여줌
  • 수정일시 역순 (updatedAt DESC): 최근 수정된 템플릿을 먼저 보여줌
  • 사용 빈도순: 자주 사용하는 템플릿을 먼저 보여줌

와 같은 기준이 사용자 경험(UX) 관점에서 더 유용할 수 있습니다. 제품 기획 의도를 확인해주세요.

만약 OrderTemplate 엔티티에 createdAt 필드가 있다면:

PageRequest pageable = PaginationUtil.createPageRequest(page, size, "createdAt");

그리고 PaginationUtil에서 Direction을 DESC로 변경하거나, createPageRequest 메서드에 Direction 파라미터를 추가하는 것을 고려해보세요.


90-103: 필터링 로직 명확하고 좋습니다!

null 체크와 Boolean.TRUE.equals() 사용으로 NPE를 방지하면서도 코드가 읽기 쉽게 작성되었습니다.

src/main/java/com/almang/inventory/order/template/repository/OrderTemplateRepository.java (1)

18-23: vendors 테이블의 store_id 컬럼에 인덱스를 추가하세요.

현재 Vendor.javastore_id FK에 명시적 인덱스 정의가 없으며, 리포지토리 코드베이스 전체에서도 @Index 어노테이션을 사용하지 않고 있습니다. findAllByVendorStoreId* 쿼리들이 대규모 데이터셋에서 Full Table Scan을 유발할 수 있습니다.

개선 방법:

Vendor 엔티티의 @Table 어노테이션에 인덱스를 정의하세요:

@Table(name = "vendors", indexes = {
    @Index(name = "idx_vendor_store_id", columnList = "store_id")
})

또는 개별 컬럼에 정의:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "store_id", nullable = false)
@Index(name = "idx_vendor_store_id")
private Store store;

참고:

  • Spring Data JPA @Index는 Hibernate가 자동으로 DDL에 포함시킵니다
  • FK 컬럼은 자동으로 인덱싱되지 않으므로 명시적 정의가 필요합니다
  • Hibernate 공식 문서 참조

@JoonKyoLee JoonKyoLee merged commit 9ba103f into main Nov 22, 2025
1 check passed
@JoonKyoLee JoonKyoLee deleted the feat/store-order-template-list branch November 23, 2025 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 상점의 발주 템플릿 목록 조회 기능 구현

1 participant