Conversation
재주문점 도달 시 발주 주문서 생성 조회: 창고별, 카테고리, 그룹, 상태 필터링, 주문넘버, 품목명, 품목코드 검색 + 페이지네이션
Walkthrough구매주문(PurchaseOrder) 도메인(엔티티·리포지토리·서비스·컨트롤러) 추가, InventoryService의 ROP 기반 주문 로직 통합(구매주문 생성 호출 및 이벤트 페이로드 OrderToFactoryDto 사용), OutHistory 컨트롤러/DTO/리포지토리 추가 및 다수 리포지토리에 Changes
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>>
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 주의 집중 파일/영역:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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,ApiResponseimport가 주석 처리된 코드에서만 참조되고 있습니다. 현재 활성화된 코드에서는 사용되지 않으므로 제거하는 것이 좋습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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가 불완전해 보입니다.
OutHistoryResDto가outOrderNumber필드만 포함하고 있습니다. 출고 이력 조회 응답에는 일반적으로 출고 날짜, 창고 정보, 수량, 품목 정보 등이 포함됩니다.현재 구현이 의도적인 최소 구현인지, 아니면 추가 필드가 필요한지 확인해 주세요.
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.COMPLETED와OrderStatus.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)호출은 안전합니다.
src/main/java/com/sampoom/backend/api/inventory/controller/OutHistoryController.java
Show resolved
Hide resolved
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/com/sampoom/backend/api/order/service/PurchaseOrderService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 위험이 해결되었습니다.
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java
Show resolved
Hide resolved
src/main/java/com/sampoom/backend/api/order/repository/PurchaseOrderQueryRepositoryImpl.java
Show resolved
Hide resolved
src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java
Show resolved
Hide resolved
널러블 = 거짓
내 서비스의 무결성은 내가 지킨다
inventory - rop는 일대일 관계지만 여러 예외를 고려해서...
There was a problem hiding this comment.
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.java의inventory관계는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 -A3Also 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.inventory와inventory를 조인하고 있으므로 이 조건은 자동으로 충족됩니다.다음 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
📒 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_id에nullable = 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 도입 또는 수동 마이그레이션 스크립트 작성
📝 Summary
발주 테이블 생성 및 조회
🙏 Question & PR point
📬 Reference
Summary by CodeRabbit
릴리스 노트
New Features
Refactor
Bug Fixes
Chores