Skip to content

Spm 375#38

Merged
vivivim merged 11 commits intomainfrom
SPM-375
Nov 5, 2025
Merged

Spm 375#38
vivivim merged 11 commits intomainfrom
SPM-375

Conversation

@vivivim
Copy link
Contributor

@vivivim vivivim commented Nov 5, 2025

📝 Summary

발주 테이블 생성 및 조회

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 구매 주문 관리: 생성 및 검색(필터·페이지네이션) 기능 추가
    • 출고 이력 조회 엔드포인트 추가(향후 활성화 예고)
  • Refactor

    • 자동 ROP 기반 흐름 개선: 부족 재고 자동 발주 생성 및 이벤트 페이로드 개선
    • 주문 상태 라벨 조정(배송/입고 표기 변경)
  • Bug Fixes

    • 페이징 집계에서 null 방지 처리
  • Chores

    • 저장소(리포지토리) 컴포넌트 등록 정비
    • 부품 코드에 대한 유니크 제약 추가

Admin added 3 commits November 4, 2025 18:22
재주문점 도달 시 발주 주문서 생성
조회: 창고별, 카테고리, 그룹, 상태 필터링, 주문넘버, 품목명, 품목코드 검색 + 페이지네이션
@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

구매주문(PurchaseOrder) 도메인(엔티티·리포지토리·서비스·컨트롤러) 추가, InventoryService의 ROP 기반 주문 로직 통합(구매주문 생성 호출 및 이벤트 페이로드 OrderToFactoryDto 사용), OutHistory 컨트롤러/DTO/리포지토리 추가 및 다수 리포지토리에 @Repository 주석과 일부 엔티티/DTO/열거형 변경이 적용되었습니다.

Changes

코호트 / 파일(s) 변경 요약
OutHistory 관련
src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java, src/main/java/com/sampoom/backend/api/inventory/dto/OutHistoryResDto.java, src/main/java/com/sampoom/backend/api/inventory/repository/OutHistoryRepository.java
OutHistory 컨트롤러(주석 처리된 엔드포인트 포함) 및 DTO 추가, 리포지토리에 @Repository 주석 적용
PurchaseOrder 엔티티 & 리포지토리
src/main/java/com/sampoom/backend/api/order/entity/PurchaseOrder.java, src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderRepository.java, src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepository.java, src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java
PurchaseOrder JPA 엔티티 추가, JpaRepository 및 커스텀 QueryDSL 기반 검색 리포지토리/구현 추가(동적 필터링·페이징)
PurchaseOrder DTO·컨트롤러·서비스
src/main/java/com/sampoom/backend/api/order/dto/POFilterDto.java, src/main/java/com/sampoom/backend/api/order/dto/POResDto.java, src/main/java/com/sampoom/backend/api/order/controller/PurchaseOrderController.java, src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java
PO 필터/응답 DTO(POResDto에 QueryDSL 프로젝션 포함) 추가, GET /po 컨트롤러와 PurchaseOrderService(생성(makePurchaseOrder), 조회(getPurchaseOrders), 주문명 생성, attachNames) 추가
InventoryService 변경
src/main/java/com/sampoom/backend/api/inventory/service/InventoryService.java
checkRop 흐름 변경: ROP 기반 조회 우선, 부족분에 대해 PurchaseOrderService.makePurchaseOrder 호출 및 PartDelta 기록, 비어있지 않은 부족 리스트에 대해 OrderToFactoryDto 직렬화로 이벤트 전송; PurchaseOrderService 의존성 추가
Repository @Repository 주석 추가
src/main/java/com/sampoom/backend/api/inventory/repository/InventoryRepository.java, src/main/java/com/sampoom/backend/api/inventory/repository/OutHistoryRepository.java, src/main/java/com/sampoom/backend/api/rop/repository/RopRepository.java
여러 리포지토리에 @Repository 주석 및 import 추가
ROP 쿼리 및 null-안전성
src/main/java/com/sampoom/backend/api/rop/repository/RopRepository.java, src/main/java/com/sampoom/backend/api/rop/repository/RopQueryRepositoryImpl.java
ROP에 findWithInventoryByAutoOrderStatusAndBranch_IdAndPart_IdIn 조인 페치 메서드 추가, count fetchOne 결과를 Optional로 감싸 기본값 0으로 처리
OrderStatus 및 DTO 변경
src/main/java/com/sampoom/backend/api/order/dto/OrderStatus.java, src/main/java/com/sampoom/backend/api/rop/dto/OrderToFactoryDto.java
OrderStatus enum 값 재정의(ARRIVED 추가, COMPLETED 레이블 변경) 및 @JsonValue 직렬화 메서드 추가, OrderToFactoryDto에 warehouseId 필드 추가
파트·엔티티 마이너 변경
src/main/java/com/sampoom/backend/api/part/entity/Part.java, src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java, src/main/java/com/sampoom/backend/api/inventory/entity/Inventory.java
Part.code에 unique=true 추가, PartRepository에 findByCode(String) 메서드 추가, Inventory 파일 주석(비기능적) 정리

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PurchaseOrderController
    participant PurchaseOrderService
    participant PurchaseOrderRepository
    Client->>PurchaseOrderController: GET /po (필터 + 페이징)
    PurchaseOrderController->>PurchaseOrderService: getPurchaseOrders(POFilterDto, page, size)
    PurchaseOrderService->>PurchaseOrderRepository: search(POFilterDto, Pageable)
    PurchaseOrderRepository-->>PurchaseOrderService: Page<POResDto>
    PurchaseOrderService->>PurchaseOrderService: attachNames(각 POResDto)
    PurchaseOrderService-->>PurchaseOrderController: Page<POResDto>
    PurchaseOrderController-->>Client: ApiResponse<Page<POResDto>>
Loading
sequenceDiagram
    participant InventoryService
    participant RopRepository
    participant PurchaseOrderService
    participant EventService
    Note over InventoryService: checkRop 실행 (ROP 기반 조회)
    InventoryService->>RopRepository: findWithInventoryByAutoOrderStatusAndBranch_IdAndPart_IdIn(...)
    RopRepository-->>InventoryService: List<Rop> (with Inventory)
    loop 각 Rop·Inventory (quantity ≤ rop)
        InventoryService->>PurchaseOrderService: makePurchaseOrder(inventory, orderQuantity)
        PurchaseOrderService-->>InventoryService: 저장된 PurchaseOrder
        InventoryService->>InventoryService: PartDeltaDto 축적
    end
    alt 부족 아이템 존재
        InventoryService->>EventService: setEventOutBox(serialize(OrderToFactoryDto))
        EventService-->>InventoryService: 이벤트 등록 응답
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

주의 집중 파일/영역:

  • PurchaseOrderQueryRepositoryImpl.java — QueryDSL 동적 쿼리(joins, BooleanBuilder, 페이징) 정확성 및 성능
  • InventoryService.java — ROP 기반 로직 변경, PurchaseOrder 생성 호출, 이벤트 페이로드 형식 및 직렬화
  • OrderStatus.java — enum 값·직렬화 변경으로 인한 직렬화/역직렬화 영향 범위
  • PurchaseOrderService.java — 주문명 생성(타임존/포맷)과 attachNames의 예외 처리 경로

Possibly related PRs

  • Spm 268 #24 — OutHistory·ROP·Inventory 도메인 변경이 겹침(코드 수준 연관성 높음)
  • Dev #29 — OutHistory/Inventory/ROP 관련 추가 변경과 중복 영역 존재
  • Spm 371 #33 — OrderToFactoryDto 및 이벤트 페이로드 처리 관련 변경과 연관

Suggested labels

ready-to-merge

Suggested reviewers

  • Lee-Jong-Jin
  • taemin3
  • yangjiseonn
  • CHOOSLA

Poem

🐰 창고 한켠에서 풀을 뜯으며,
ROP를 보고 주문서를 살짝 적네,
번호는 톡, 이벤트는 삐삐삐,
OutHistory엔 발자국 남기고,
당근 하나로 오늘도 일하네 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive PR 제목 'Spm 375'는 매우 모호하며 변경 사항의 실질적인 내용을 설명하지 않습니다. 티켓 번호만 포함하고 있어 실제 변경 내용(발주 테이블 생성 및 조회)을 파악하기 어렵습니다. '발주 테이블 생성 및 조회 기능 추가' 또는 'Purchase Order 엔티티 및 조회 API 구현'과 같이 변경 사항을 명확하게 설명하는 제목으로 변경해주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 SPM-375

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 4

🧹 Nitpick comments (3)
src/main/java/com/sampoom/backend/api/inventory/repository/InventoryRepository.java (1)

10-10: @Repository 애노테이션은 선택적입니다.

JpaRepository를 확장하는 인터페이스는 Spring Data JPA에 의해 자동으로 감지되므로 @Repository 애노테이션이 기술적으로 필요하지 않습니다. 하지만 명시적인 표현으로 코드의 의도를 명확히 하는 장점이 있으므로, 팀 컨벤션에 따라 유지하셔도 좋습니다.

Also applies to: 17-17

src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java (1)

7-7: Optional<Part> 반환 타입 사용을 권장합니다.

findByCode 메서드가 Part를 직접 반환하므로 부품을 찾지 못할 경우 null을 반환할 수 있습니다. 이는 호출하는 코드에서 NullPointerException을 유발할 수 있습니다.

다음과 같이 수정하는 것을 권장합니다:

-    Part findByCode(String code);
+    Optional<Part> findByCode(String code);
src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java (1)

5-14: 사용하지 않는 import를 제거하세요.

Page, PageRequest, Pageable, ApiResponse import가 주석 처리된 코드에서만 참조되고 있습니다. 현재 활성화된 코드에서는 사용되지 않으므로 제거하는 것이 좋습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce1251 and 577677c.

📒 Files selected for processing (18)
  • src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/inventory/dto/OutHistoryResDto.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/inventory/entity/Inventory.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/inventory/repository/InventoryRepository.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/inventory/repository/OutHistoryRepository.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/inventory/service/InventoryService.java (4 hunks)
  • src/main/java/com/sampoom/backend/api/order/controller/PurchaseOrderController.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/dto/OrderStatus.java (3 hunks)
  • src/main/java/com/sampoom/backend/api/order/dto/POFilterDto.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/entity/PurchaseOrder.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepository.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderRepository.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/rop/dto/OrderToFactoryDto.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/rop/repository/RopRepository.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
src/main/java/com/sampoom/backend/api/order/controller/PurchaseOrderController.java (1)
src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java (1)
  • RestController (16-27)
src/main/java/com/sampoom/backend/api/inventory/repository/InventoryRepository.java (1)
src/main/java/com/sampoom/backend/api/inventory/repository/InventoryQueryRepositoryImpl.java (1)
  • Repository (18-73)
src/main/java/com/sampoom/backend/api/rop/repository/RopRepository.java (1)
src/main/java/com/sampoom/backend/api/rop/repository/RopQueryRepositoryImpl.java (1)
  • Repository (19-85)
src/main/java/com/sampoom/backend/api/order/entity/PurchaseOrder.java (2)
src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1)
  • Getter (8-60)
src/main/java/com/sampoom/backend/api/inventory/entity/Inventory.java (1)
  • Entity (10-50)
src/main/java/com/sampoom/backend/api/order/dto/POFilterDto.java (2)
src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1)
  • Getter (8-60)
src/main/java/com/sampoom/backend/api/rop/dto/OrderToFactoryDto.java (1)
  • Getter (12-20)
src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java (2)
src/main/java/com/sampoom/backend/api/order/controller/PurchaseOrderController.java (1)
  • RestController (16-38)
src/main/java/com/sampoom/backend/api/inventory/controller/InventoryController.java (1)
  • RestController (17-98)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderRepository.java (1)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)
  • Repository (22-91)
src/main/java/com/sampoom/backend/api/inventory/repository/OutHistoryRepository.java (1)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)
  • Repository (22-91)
src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (2)
src/main/java/com/sampoom/backend/common/exception/NotFoundException.java (1)
  • NotFoundException (5-13)
src/main/java/com/sampoom/backend/api/inventory/service/InventoryService.java (1)
  • Service (41-277)
src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1)
src/main/java/com/sampoom/backend/api/order/dto/POFilterDto.java (1)
  • Getter (8-18)
🔇 Additional comments (17)
src/main/java/com/sampoom/backend/api/inventory/entity/Inventory.java (1)

28-28: LGTM!

주석의 이모지 제거로 코드 일관성이 개선되었습니다.

src/main/java/com/sampoom/backend/api/inventory/dto/OutHistoryResDto.java (1)

12-14: DTO가 불완전해 보입니다.

OutHistoryResDtooutOrderNumber 필드만 포함하고 있습니다. 출고 이력 조회 응답에는 일반적으로 출고 날짜, 창고 정보, 수량, 품목 정보 등이 포함됩니다.

현재 구현이 의도적인 최소 구현인지, 아니면 추가 필드가 필요한지 확인해 주세요.

src/main/java/com/sampoom/backend/api/rop/dto/OrderToFactoryDto.java (1)

17-17: LGTM!

warehouseId 필드 추가가 적절합니다. 이는 InventoryService에서 이벤트 페이로드에 창고 식별자를 포함하기 위한 것으로 보이며, 기존 warehouseName과 함께 명확한 창고 참조를 제공합니다.

src/main/java/com/sampoom/backend/api/inventory/repository/OutHistoryRepository.java (1)

8-8: @Repository 애노테이션은 선택적입니다.

JpaRepository를 확장하는 인터페이스는 Spring Data JPA에 의해 자동으로 감지되므로 @Repository 애노테이션이 기술적으로 필요하지 않습니다. 하지만 명시적인 표현으로 코드의 의도를 명확히 하며, PR 내 다른 repository와 일관성을 유지합니다.

Also applies to: 12-12

src/main/java/com/sampoom/backend/api/order/dto/OrderStatus.java (2)

16-17: 열거형 상수 변경에 대한 실제 영향이 없습니다.

검증 결과, 코드베이스에서 OrderStatus.COMPLETEDOrderStatus.ARRIVED는 전혀 사용되지 않고 있습니다.

  • 실제 사용 중인 상태: PENDING, CONFIRMED만 사용 중 (OrderController, OrderService, PurchaseOrder)
  • 데이터베이스: COMPLETED 상태로 저장된 레코드 없음
  • 상태 비교 로직: 사용 중인 로직에서 이 두 상태값 참조 없음

따라서 현재 코드의 동작에 영향을 주지 않습니다. 다만 향후 배송 관련 기능 개발 시 이들 상태가 사용되면 그때부터 의미가 적용될 것입니다.

Likely an incorrect or invalid review comment.


35-38: 해당 리뷰 의견은 재검토 필요합니다.

검증 결과, @JsonValue 추가는 의도하지 않은 부작용이 아니라 의도된 설계 변경입니다:

  • 의도된 변경: 커밋 메시지 aee33b0 "fix: 상태 한글로 응답"에서 한글 응답을 목표로 명확히 설정
  • 양방향 매핑 완성: @JsonCreator(역직렬화)와 @JsonValue(직렬화)를 함께 구현하여 클라이언트가 한글 상태명으로 송수신 가능하도록 설계
  • 일관된 패턴: POResDto에서도 getOrderStatus() 메서드를 추가해 명시적으로 toKorean() 호출, QuantityStatus도 동일 패턴 사용

이는 API 계약의 의도된 변경이므로, 기존 클라이언트 호환성 문제는 이미 고려된 아키텍처 결정입니다.

Likely an incorrect or invalid review comment.

src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepository.java (1)

8-10: 인터페이스 설계가 적절합니다.

표준 Spring Data 패턴을 따르며 페이지네이션을 적절히 지원합니다.

src/main/java/com/sampoom/backend/api/rop/repository/RopRepository.java (2)

7-7: @repository 어노테이션 추가가 적절합니다.

명시적인 컴포넌트 스캔 설정으로 코드 가독성이 향상됩니다.

Also applies to: 12-12


43-53: JOIN FETCH를 활용한 효율적인 쿼리입니다.

N+1 쿼리 문제를 방지하기 위해 관련 엔티티들을 미리 로딩하는 구조가 잘 구현되어 있습니다.

src/main/java/com/sampoom/backend/api/order/dto/POFilterDto.java (1)

8-18: 필터 DTO 구조가 적절합니다.

Lombok을 활용하여 보일러플레이트 코드를 줄이고, 필요한 필터 조건들을 명확하게 정의했습니다.

src/main/java/com/sampoom/backend/api/order/controller/PurchaseOrderController.java (1)

16-38: 컨트롤러 구현이 적절합니다.

REST API 설계가 명확하며, 필터링과 페이지네이션을 적절히 지원합니다. 서비스 레이어로의 책임 분리도 잘 되어 있습니다.

src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (2)

35-43: 발주 생성 로직이 적절합니다.

재고 정보와 수량을 기반으로 발주를 생성하고, 가격 계산도 정확하게 수행됩니다.


45-49: 발주 번호 생성 방식이 명확합니다.

날짜와 UUID를 조합하여 고유한 발주 번호를 생성하는 방식이 적절합니다.

src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderRepository.java (1)

7-9: 리포지토리 구조가 적절합니다.

표준 JPA 리포지토리를 확장하고 커스텀 쿼리 인터페이스를 결합하는 구조가 잘 구현되어 있습니다.

src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1)

29-55: QueryProjection 생성자가 적절합니다.

QueryDSL 프로젝션을 위한 생성자가 명확하게 정의되어 있으며, 필요한 필드들을 적절히 매핑합니다.

src/main/java/com/sampoom/backend/api/inventory/service/InventoryService.java (2)

50-50: 발주 서비스 통합이 적절합니다.

재고 서비스에 발주 기능을 추가하여 ROP 체크 시 자동으로 발주를 생성하는 구조가 잘 설계되었습니다.


199-235: ROP 체크 로직이 개선되었습니다.

새로운 ROP 기반 조회 방식을 사용하여 효율성이 향상되었으며, 발주 생성 및 이벤트 발행 로직이 명확하게 구현되어 있습니다. Line 208에서 빈 리스트를 체크하므로 Line 210-211의 get(0) 호출은 안전합니다.

Copy link

@taemin3 taemin3 left a comment

Choose a reason for hiding this comment

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

확인했습니다

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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 577677c and 198a5fd.

📒 Files selected for processing (4)
  • src/main/java/com/sampoom/backend/api/order/dto/POResDto.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/sampoom/backend/api/order/dto/POResDto.java
  • src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java
🔇 Additional comments (2)
src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java (1)

6-7: LGTM!

새 메서드의 반환 타입에 필요한 표준 import입니다.

src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)

82-89: 이전 리뷰 지적사항이 적절히 수정되었습니다.

fetchOne()이 null을 반환할 때 Optional.ofNullable().orElse(0L)로 안전하게 처리하여 NPE 위험이 해결되었습니다.

Admin added 3 commits November 5, 2025 18:08
널러블 = 거짓
내 서비스의 무결성은 내가 지킨다
inventory - rop는 일대일 관계지만 여러 예외를 고려해서...
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: 1

♻️ Duplicate comments (1)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)

75-75: ROP inner join이 발주 데이터를 누락시킬 수 있는지 재확인하세요.

Line 75와 87에서 ROP를 inner join하고 있습니다. 이전 리뷰 코멘트에서 지적된 대로, ROP 레코드가 없는 inventory의 발주는 조회 결과에서 제외됩니다.

현재 코드 검토 결과:

  • Rop.javainventory 관계는 nullable = false로 설정되어 있어 모든 ROP는 inventory를 가져야 함
  • 하지만 역방향 관계(Inventory → ROP)가 항상 존재하는지는 확인되지 않음
  • InventoryService.checkRop() 메서드는 ROP 목록을 조회하지만, ROP가 없는 경우 빈 리스트를 반환하고 발주를 생성하지 않음

만약 ROP 없이 발주가 생성될 수 있다면, leftJoin(rop)으로 변경해야 합니다.

다음 스크립트로 ROP 없이 PurchaseOrder가 생성될 수 있는지 확인하세요:

#!/bin/bash
# Description: Verify if PurchaseOrder can be created without ROP

# Check if makePurchaseOrder validates ROP existence
ast-grep --pattern $'makePurchaseOrder($$$) {
  $$$
}'

# Check all call sites of makePurchaseOrder to see if ROP is validated
rg -n "makePurchaseOrder" --type java -B3 -A3

Also applies to: 87-87

🧹 Nitpick comments (3)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)

38-38: 불필요한 조건입니다.

Line 38의 builder.and(po.inventory.eq(inventory))는 불필요합니다. Line 73에서 이미 po.inventoryinventory를 조인하고 있으므로 이 조건은 자동으로 충족됩니다.

다음 diff를 적용하여 제거하세요:

 builder.and(inventory.branch.id.eq(req.getWarehouseId()));
-builder.and(po.inventory.eq(inventory));
src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (2)

45-49: 주문 번호 고유성을 고려하세요.

makeOrderName()은 날짜(8자)와 UUID의 앞 4자리만 사용하여 주문 번호를 생성합니다. 하루에 동일한 4자리 UUID가 생성될 확률은 낮지만 완전히 배제할 수는 없습니다(충돌 확률: ~1/65536).

다음 개선 방안을 고려하세요:

  • UUID 길이를 6-8자로 늘리거나
  • 데이터베이스 unique constraint와 재시도 로직을 추가하거나
  • 시퀀스 번호를 추가로 사용
 private String makeOrderName() {
-    String uuidPart = UUID.randomUUID().toString().substring(0, 4).toUpperCase();
+    String uuidPart = UUID.randomUUID().toString().substring(0, 8).toUpperCase();
     String today = LocalDate.now(ZoneOffset.ofHours(9)).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
     return "PO-" + today + "-" + uuidPart;
 }

57-69: N+1 쿼리 성능 이슈가 있습니다.

attachNames 메서드는 각 POResDto마다 3개의 추가 쿼리(Part, Category, PartGroup)를 실행합니다. 페이지당 20개의 결과가 있다면 총 60개의 추가 쿼리가 발생합니다.

다음 개선 방안을 고려하세요:

해결 방안 1 (권장): PurchaseOrderQueryRepositoryImpl의 쿼리에서 Category와 PartGroup을 조인하여 POResDto에 이름을 직접 포함시키세요.

해결 방안 2: 배치로 조회하도록 리팩토링:

private List<POResDto> attachNames(List<POResDto> poResDtoList) {
    Set<String> partCodes = poResDtoList.stream()
        .map(POResDto::getPartCode)
        .collect(Collectors.toSet());
    
    Map<String, Part> partMap = partRepository.findByCodeIn(partCodes).stream()
        .collect(Collectors.toMap(Part::getCode, Function.identity()));
    
    Set<Long> categoryIds = partMap.values().stream()
        .map(Part::getCategoryId)
        .collect(Collectors.toSet());
    Set<Long> groupIds = partMap.values().stream()
        .map(Part::getGroupId)
        .collect(Collectors.toSet());
    
    Map<Long, Category> categoryMap = categoryRepository.findAllById(categoryIds).stream()
        .collect(Collectors.toMap(Category::getId, Function.identity()));
    Map<Long, PartGroup> groupMap = partGroupRepository.findAllById(groupIds).stream()
        .collect(Collectors.toMap(PartGroup::getId, Function.identity()));
    
    poResDtoList.forEach(dto -> {
        Part part = partMap.get(dto.getPartCode());
        if (part != null) {
            dto.setCategoryName(categoryMap.get(part.getCategoryId()).getName());
            dto.setGroupName(groupMap.get(part.getGroupId()).getName());
        }
    });
    
    return poResDtoList;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 198a5fd and 2baa6e9.

📒 Files selected for processing (5)
  • src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/part/entity/Part.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/rop/entity/Rop.java (1 hunks)
  • src/main/java/com/sampoom/backend/api/rop/repository/RopQueryRepositoryImpl.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java (1)
src/main/java/com/sampoom/backend/api/rop/repository/RopQueryRepositoryImpl.java (1)
  • Repository (20-87)
src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java (2)
src/main/java/com/sampoom/backend/common/exception/NotFoundException.java (1)
  • NotFoundException (5-13)
src/main/java/com/sampoom/backend/api/inventory/service/InventoryService.java (1)
  • Service (41-277)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (3)
src/main/java/com/sampoom/backend/api/rop/entity/Rop.java (1)

20-22: LGTM!

inventory_idnullable = false 제약을 추가한 것은 ROP가 항상 Inventory와 연결되어야 한다는 비즈니스 로직을 명확히 합니다.

src/main/java/com/sampoom/backend/api/rop/repository/RopQueryRepositoryImpl.java (1)

76-83: LGTM!

Optional.ofNullable().orElse(0L)을 사용하여 count 쿼리 결과가 null일 때 NPE를 방지한 것은 올바른 방어적 코딩입니다.

src/main/java/com/sampoom/backend/api/part/entity/Part.java (1)

20-21: 프로덕션 데이터베이스의 기존 Part 테이블에서 code 컬럼의 중복 여부를 확인하세요.

코드베이스 내에서는 마이그레이션 도구(Liquibase/Flyway)가 없고 마이그레이션 SQL 파일도 없습니다. 스키마 변경이 코드 수준에서만 적용되어 있으므로, 프로덕션 환경의 기존 데이터에 중복된 code 값이 있으면 DB 마이그레이션 시 제약 조건 위반으로 실패할 수 있습니다.

다음을 수행하세요:

  • 프로덕션 DB에 존재하는 중복 code 값 확인
  • 마이그레이션 전 데이터 정제 계획 수립
  • Flyway/Liquibase 도입 또는 수동 마이그레이션 스크립트 작성

@vivivim vivivim merged commit d40401a into main Nov 5, 2025
6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants