Skip to content

[FEAT] 엑셀 upload로 소매 재고 관리 기능 구현 #149#155

Closed
Youngwave1128 wants to merge 3 commits intomainfrom
feat/reatil_management_by_excel
Closed

[FEAT] 엑셀 upload로 소매 재고 관리 기능 구현 #149#155
Youngwave1128 wants to merge 3 commits intomainfrom
feat/reatil_management_by_excel

Conversation

@Youngwave1128
Copy link
Contributor

@Youngwave1128 Youngwave1128 commented Nov 30, 2025

✨ 작업 내용


📝 적용 범위

  • product에 Okpos code, cafe24상품코드 관리

📌 참고 사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 소매 거래 Excel 파일 업로드 기능 추가
    • 제품 식별 코드를 Cafe24 코드와 POS 코드로 분리 관리
  • Chores

    • 고객 주문 처리 중 재고 감소 로직 미구현 상태로 표시
    • Excel 처리 라이브러리 추가

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

@Youngwave1128 Youngwave1128 linked an issue Nov 30, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2025

워크스루

상품 코드 체계를 단일 code에서 cafe24CodeposCode로 분리하고, Apache POI를 추가하여 소매 재고 데이터의 엑셀 업로드 기능을 구현합니다. 새로운 Retail 도메인 엔티티와 함께 완전한 업로드-파싱-저장 파이프라인을 도입합니다.

변경사항

응집 영역 / 파일(들) 변경 요약
의존성 관리
build.gradle
Apache POI 라이브러리(poi-ooxml:5.2.3) 추가로 Excel 파일 처리 지원
엑셀 샘플 생성 유틸
generate_sample_excel.py
Pandas를 사용한 Python 스크립트로 샘플 소매 데이터셋 생성 및 엑셀 파일 내보내기
상품 도메인 모델
src/main/java/com/almang/inventory/product/domain/Product.java
code 필드를 cafe24CodeposCode로 분리; name 컬럼 길이 30→255 확대; updateBasicInfo 메서드 서명 변경
상품 DTO(요청/응답)
src/main/java/com/almang/inventory/product/dto/request/CreateProductRequest.java, UpdateProductRequest.java, src/main/java/com/almang/inventory/product/dto/response/ProductResponse.java
모든 요청/응답 타입에서 codecafe24Code, posCode 변경; ProductResponse에 unitPerBox, unitWeightG, costPrice 필드 추가
상품 리포지토리
src/main/java/com/almang/inventory/product/repository/ProductRepository.java
findByCode() 제거; findByCafe24Code(), findByPosCode() 추가
상품 서비스
src/main/java/com/almang/inventory/product/service/ProductService.java
updateBasicInfo 호출 시 cafe24Code/posCode 파라미터 전달로 업데이트
인벤토리 응답 DTO
src/main/java/com/almang/inventory/inventory/dto/response/InventoryResponse.java
productCodecafe24Code, posCode 필드 변경
고객주문 서비스
src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java
findByCafe24Code 사용으로 상품 조회 변경; 재고 감소 로직을 TODO/경고 로그로 변경
소매 도메인 (신규)
src/main/java/com/almang/inventory/retail/domain/Retail.java
Store, Product 관계, quantity, appliedAt 필드를 가진 새로운 JPA 엔티티
소매 리포지토리 (신규)
src/main/java/com/almang/inventory/retail/repository/RetailRepository.java
Retail 엔티티용 Spring Data JpaRepository
소매 서비스 (신규)
src/main/java/com/almang/inventory/retail/service/RetailService.java
Excel 파일 파싱, 상품 조회, Retail 레코드 생성 및 인벤토리 업데이트 로직 구현
소매 컨트롤러 (신규)
src/main/java/com/almang/inventory/retail/controller/RetailController.java
POST /api/v1/retail/upload 엔드포인트로 multipart 파일 처리 및 RetailService 위임
테스트 파일
src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java, InventoryServiceTest.java, src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java, ProductServiceTest.java, src/test/java/com/almang/inventory/retail/service/RetailServiceTest.java
모든 테스트에서 cafe24Code/posCode 필드 반영; RetailServiceTest 신규 작성

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant RetailController
    participant RetailService
    participant ExcelParser as Excel Parser
    participant ProductRepo as ProductRepository
    participant RetailRepo as RetailRepository
    participant InventoryRepo as InventoryRepository
    participant DB as Database

    Client->>RetailController: POST /api/v1/retail/upload (multipart file)
    RetailController->>RetailController: 파일 유효성 검증
    RetailController->>RetailService: processRetailExcel(file)
    
    RetailService->>ExcelParser: 엑셀 파일 파싱
    ExcelParser->>ExcelParser: 헤더 스킵, 데이터 행 추출
    ExcelParser-->>RetailService: POS코드, 수량 배열
    
    loop 각 상품 행
        RetailService->>ProductRepo: findByPosCode(posCode)
        ProductRepo->>DB: SELECT * FROM product
        DB-->>ProductRepo: Product 객체
        ProductRepo-->>RetailService: Optional<Product>
        
        RetailService->>RetailService: Retail 레코드 생성
        RetailService->>InventoryRepo: findByProduct(product)
        InventoryRepo->>DB: SELECT * FROM inventory
        DB-->>InventoryRepo: Inventory 객체
        InventoryRepo-->>RetailService: Optional<Inventory>
        
        RetailService->>RetailService: displayStock 감소 (해당 시)
    end
    
    RetailService->>RetailRepo: saveAll(retailList)
    RetailRepo->>DB: INSERT INTO retails
    DB-->>RetailRepo: 저장 완료
    RetailRepo-->>RetailService: 저장됨
    
    RetailService-->>RetailController: 완료
    RetailController-->>Client: 200 OK / 4xx/5xx 에러
Loading

예상 코드 리뷰 난이도

🎯 4 (복잡함) | ⏱️ ~45-60분

중점 검토 영역

  • 도메인 모델 광범위 변경: Product의 codecafe24Code/posCode 분리는 전체 계층에 연쇄적으로 영향을 미칩니다. 데이터베이스 마이그레이션 전략(기존 code 데이터 매핑)이 명확한지 확인이 필수입니다.

  • CustomerOrderService의 TODO 섹션: 재고 감소 로직이 구현되지 않은 상태로 남아 있습니다. 이것이 의도된 것인지, 향후 PR에서 구현될 예정인지 명확히 할 필요가 있습니다. 현재 경고 로그만으로는 운영 환경에서 데이터 불일치를 야기할 수 있습니다.

  • Excel 파싱 안정성: RetailService의 processRetailExcel에서 예외 처리(빈 시트, 잘못된 셀 형식, Null 값)가 충분한지 검토가 필요합니다.

  • 트랜잭션 경계: 다중 저장소 작업(RetailRepository.saveAll, InventoryRepository 업데이트)이 트랜잭션 내에서 일관성 있게 처리되는지 확인하세요.

  • 테스트 커버리지: 새로운 Retail 기능에 대한 통합 테스트와 실패 경로(잘못된 POS 코드, 재고 부족 등) 테스트가 추가로 필요해 보입니다.

관련 이슈

관련 PR

추천 라벨

enhancement

🧮 코드 분열의 시대,
단일 필드에서 쌍둥이 탄생 café와 POS!
엑셀의 마법으로 재고가 춤을 추고,
유닛당 무게와 비용도 함께 들어와,
소매의 꿈은 이제 업로드 한 번에 실현.✨


개선 제안

1. Product 도메인 마이그레이션 전략 명확화

현재 기존 code 컬럼 데이터를 어떻게 cafe24CodeposCode로 분리할 것인지 명시되지 않았습니다.

  • 권장: 데이터베이스 마이그레이션 SQL 스크립트를 준비하고, 기존 데이터의 일괄 처리 방안(예: 모든 기존 code를 cafe24Code로 매핑)을 문서화하세요.
  • 참고: Flyway 데이터베이스 마이그레이션 또는 Liquibase를 활용하여 버전 관리된 마이그레이션을 추천합니다.

2. CustomerOrderService의 미구현 재고 감소 로직

// 현재 코드
log.warn("Cafe24 주문[{}]의 상품[{}] 수량[{}]에 대해 재고 차감 로직이 미구현되었습니다.", 
         customerOrder.getId(), item.getProductName(), item.getQuantity());

이 접근은 운영 환경에서 재고 불일치를 야기할 수 있습니다.

  • 권장:
    1. 이 기능이 향후 PR에서 구현될 예정이라면, GitHub 이슈를 연결하고 PR 설명에 명시하세요.
    2. 또는 즉시 구현하되, 인벤토리 감소 로직을 별도 메서드로 추출하고 단위 테스트를 작성하세요.

3. Excel 파싱의 견고성 강화

RetailService의 processRetailExcel 메서드에서 다음을 검토하세요:

// 현재: 셀 값 추출 시 NPE 가능성
String posCode = getStringValue(row.getCell(0)); // null 체크?
BigDecimal quantity = getNumericValue(row.getCell(1));
  • 권장: 각 셀에 대해 null 체크 및 유효성 검증 추가:
if (cell == null || cell.getCellType() == CellType.BLANK) {
    throw new InvalidExcelFormatException("행 " + rowIndex + ": 필수 셀이 비어있습니다");
}

4. 트랜잭션 경계 명시

RetailService의 processRetailExcel@Transactional로 감싸세요. 부분 실패 시 롤백을 보장합니다:

`@Transactional`
public void processRetailExcel(MultipartFile file) {
    // ...
}

5. 테스트 커버리지 확대

현재 RetailServiceTest는 성공 경로만 다룹니다. 추가 필요:

  • ✅ POS 코드가 존재하지 않는 경우
  • ✅ 엑셀 형식 오류 (헤더 누락, 잘못된 데이터 타입)
  • ✅ 저장소(Store) 없음 에러
  • ✅ 빈 파일 처리

칭찬 포인트

이 PR은 소매 재고 시스템을 엑셀 기반으로 확장하면서도 기존 코드 패턴을 잘 따릅니다. 특히 Retail 도메인의 설계와 RetailService의 단일 책임 원칙 준수가 좋습니다. 👍

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.70% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive PR 설명이 매우 간략하며, 작업 범위가 불명확합니다. '제품 관리' 이상의 구체적인 기술 세부사항이 부족합니다. 작업 내용에 엑셀 파일 처리 로직, 새 엔티티/API 추가 등 기술적 변경사항을 구체적으로 기술하고, 적용 범위에 영향받는 모든 도메인과 API를 명시하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 엑셀 업로드를 통한 소매 재고 관리 기능 구현을 명확하게 요약하고 있으며, 이슈 번호 #149가 포함되어 있습니다.
✨ 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/reatil_management_by_excel

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java (1)

98-98: 디버그 아티팩트 제거가 필요합니다.

F_TEST 주석은 디버그/테스트 목적으로 남겨진 것으로 보입니다. 코드 정리를 위해 제거하세요.

-    // 추가적인 고객 주문 관련 비즈니스 로직은 여기에 구현될 수 있습니다. F_TEST
+    // 추가적인 고객 주문 관련 비즈니스 로직은 여기에 구현될 수 있습니다.
🧹 Nitpick comments (10)
src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java (1)

63-75: 미완성 재고 로직에 대한 추적이 필요합니다.

주석 처리된 코드와 TODO 플레이스홀더는 재고 감소 로직이 아직 구현되지 않았음을 명확히 보여줍니다. 이 부분이 프로덕션 배포 전에 완료되어야 하는지 확인하세요.

이 TODO를 추적하기 위한 이슈를 생성해 드릴까요?

src/main/java/com/almang/inventory/retail/service/RetailService.java (4)

39-41: 비효율적인 상점 조회 방식 개선이 필요합니다.

storeRepository.findAll()은 모든 상점을 메모리에 로드한 후 첫 번째만 사용합니다. 상점이 많아지면 성능 문제가 발생할 수 있습니다.

StoreRepositoryfindFirst() 또는 페이징을 사용하는 메서드를 추가하세요:

// StoreRepository에 추가
Optional<Store> findFirstBy();
-        Store store = storeRepository.findAll().stream().findFirst()
+        Store store = storeRepository.findFirstBy()
                 .orElseThrow(() -> new BaseException(ErrorCode.STORE_NOT_FOUND));

43-44: 파일 유효성 검증이 필요합니다.

Excel 파일 처리 전에 파일 확장자와 크기를 검증하면 잘못된 파일 업로드로 인한 오류를 조기에 방지할 수 있습니다. 또한, 악의적인 대용량 파일 업로드로 인한 DoS 공격을 방지할 수 있습니다.

// processRetailExcel 메서드 시작 부분에 추가
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

// 파일 검증 추가
if (file.isEmpty()) {
    throw new BaseException(ErrorCode.INVALID_FILE, "파일이 비어있습니다.");
}
if (file.getSize() > MAX_FILE_SIZE) {
    throw new BaseException(ErrorCode.FILE_TOO_LARGE, "파일 크기가 10MB를 초과합니다.");
}
String filename = file.getOriginalFilename();
if (filename == null || !filename.toLowerCase().endsWith(".xlsx")) {
    throw new BaseException(ErrorCode.INVALID_FILE_FORMAT, "xlsx 파일만 지원합니다.");
}

91-94: 예외 처리 개선이 필요합니다.

RuntimeException으로 감싸면 원본 예외의 컨텍스트가 손실될 수 있습니다. 커스텀 예외를 사용하면 API 응답에서 더 명확한 에러 메시지를 제공할 수 있습니다.

         } catch (IOException e) {
             log.error("Failed to parse Excel file", e);
-            throw new RuntimeException("Failed to parse Excel file", e);
+            throw new BaseException(ErrorCode.EXCEL_PARSE_ERROR, "Excel 파일 파싱 실패: " + e.getMessage());
         }

ErrorCodeEXCEL_PARSE_ERROR를 추가해야 합니다.


103-107: 숫자 변환 시 정밀도 손실 가능성.

(long) cell.getNumericCellValue()는 큰 숫자나 소수점이 있는 값에서 정밀도가 손실될 수 있습니다. POS 코드가 긴 숫자 문자열인 경우 문제가 될 수 있습니다.

         } else if (cell.getCellType() == CellType.NUMERIC) {
-            return String.valueOf((long) cell.getNumericCellValue());
+            // 정밀도 유지를 위해 BigDecimal 사용
+            return BigDecimal.valueOf(cell.getNumericCellValue()).toPlainString();
         } else {
src/main/java/com/almang/inventory/retail/domain/Retail.java (1)

12-42: 엔티티 구조가 깔끔합니다! 👍

기존 프로젝트 패턴(BaseTimeEntity 상속, Lombok 활용, LAZY fetch)을 잘 따르고 있습니다. updateQuantity 메서드는 서비스 레이어에서 검증 후 호출되므로 null 체크가 필요 없습니다(기존 학습 내용 기반).

선택적 개선 사항으로, JPA 엔티티에서 Set이나 Map 컬렉션 사용 시 예기치 않은 동작을 방지하기 위해 equals/hashCode 재정의를 고려해 보세요. Hibernate 공식 문서 참고.

src/test/java/com/almang/inventory/retail/service/RetailServiceTest.java (2)

53-109: 테스트 커버리지 확장을 권장합니다.

현재 성공 케이스만 테스트되어 있습니다. 다음 시나리오에 대한 테스트 추가를 고려해 주세요:

  1. 상품 코드가 존재하지 않는 경우
  2. 재고가 존재하지 않는 경우
  3. 잘못된 파일 형식 (예: CSV)
  4. 빈 데이터 행

또한 저장된 Retail 엔티티의 실제 값 검증과 재고 차감 로직 검증이 누락되어 있습니다:

// ArgumentCaptor를 활용한 저장 값 검증 예시
ArgumentCaptor<List<Retail>> captor = ArgumentCaptor.forClass(List.class);
verify(retailRepository).saveAll(captor.capture());
List<Retail> savedRetails = captor.getValue();
assertThat(savedRetails).hasSize(1);
assertThat(savedRetails.get(0).getQuantity()).isEqualByComparingTo(BigDecimal.valueOf(10));

70-92: 리소스 관리 개선을 위해 try-with-resources 사용을 고려하세요.

테스트 코드에서도 리소스 누수 방지는 좋은 습관입니다.

-        Workbook workbook = new XSSFWorkbook();
-        Sheet sheet = workbook.createSheet("Sheet1");
-        // ... header and data setup ...
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        workbook.write(bos);
-        workbook.close();
+        byte[] excelBytes;
+        try (Workbook workbook = new XSSFWorkbook();
+             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            Sheet sheet = workbook.createSheet("Sheet1");
+            // ... header and data setup ...
+            workbook.write(bos);
+            excelBytes = bos.toByteArray();
+        }
src/main/java/com/almang/inventory/product/dto/response/ProductResponse.java (2)

7-21: ProductResponse 레코드 필드 구조 변경 – API 스키마 영향 점검 제안

codecafe24Code, posCode 분리는 도메인과 외부 시스템을 명확히 나눠줘서 방향성은 좋습니다. 다만 public DTO 시그니처가 바뀌었기 때문에, 기존에 code 필드를 파싱하던 클라이언트(프론트, 외부 연동, 엑셀 템플릿 등)가 있다면 바로 깨질 수 있습니다.

  • 원인: 응답 스키마가 완전히 교체되었는데, 하위 호환 계층(예: deprecated 필드, v2 엔드포인트)이 없이 바로 교체되는 형태입니다.
  • 개선 방법:
    • 이미 모든 소비자가 이 변경을 따라가도록 동기화되었다면 문제 없지만, 아니라면
      • 일정 기간 동안 code를 남겨두고 @deprecated, @Schema(description = "... will be removed") 등으로 마이그레이션 기간을 두거나,
      • 최소한 API 문서/엑셀 포맷 스펙에 “code → cafe24Code/posCode로 분리됨”을 명시해 주는 것을 권장합니다.
    • cafe24Code, posCode가 선택 입력이라면, null 허용 여부를 도메인(@column(nullable = ...))과 DTO 문서(Swagger @Schema(required = ...))에서 명시해 두면 이후 유지보수 시 혼동이 줄어듭니다.

참고로 이런 응답 DTO 변경은 일반적으로 “API 버전업 또는 명시적인 스키마 변경 공지”와 함께 가는 것이 REST API 베스트 프랙티스로 많이 소개됩니다.


22-38: from(Product) 매핑 – 필드 정합성 OK, 매퍼 추출 고려

from(Product) 내부에서 새 필드들(cafe24Code, posCode, unitPerBox, unitWeightG, costPrice)을 record 파라미터 순서에 맞게 잘 매핑해 두셨습니다. 응답 필드와 도메인 필드 간 대응 관계가 직관적이라 읽기 좋네요.

다만 비슷한 Product → DTO 매핑(예: InventoryResponse, 다른 Product 관련 응답)이 점점 늘어날 가능성이 있어 보이므로, 다음을 한 번 고민해볼 만합니다.

  • 원인: 매핑 로직이 각 DTO의 from(...) 정적 메서드로 흩어지면, 필드가 추가/변경될 때마다 여러 곳을 동시에 수정해야 하고, 일부 DTO에서 누락/불일치가 발생할 수 있습니다.
  • 개선 방법:
    • ProductMapper 같은 전용 매퍼 클래스를 두거나, MapStruct 등 매핑 라이브러리를 도입해서 Product → 여러 Response DTO 매핑을 한곳에서 관리하는 패턴을 고려해볼 수 있습니다.
    • 특히 가격/단위 관련 필드는 다른 응답 객체에서도 재사용될 여지가 크므로, 공통 변환 로직을 한 곳으로 모아두면 테스트와 검증이 쉬워집니다.

추가로 product.getVendor().getId() 접근은 기존 학습 내용상 Service 단에서 vendor를 검증하고 컬럼도 nullable=false라 현재 형태로도 괜찮아 보입니다 (Based on learnings, ...).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a732986 and fc933de.

⛔ Files ignored due to path filters (1)
  • sample_retail.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (19)
  • build.gradle (1 hunks)
  • generate_sample_excel.py (1 hunks)
  • src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java (3 hunks)
  • src/main/java/com/almang/inventory/inventory/dto/response/InventoryResponse.java (1 hunks)
  • src/main/java/com/almang/inventory/product/domain/Product.java (2 hunks)
  • src/main/java/com/almang/inventory/product/dto/request/CreateProductRequest.java (1 hunks)
  • src/main/java/com/almang/inventory/product/dto/request/UpdateProductRequest.java (1 hunks)
  • src/main/java/com/almang/inventory/product/dto/response/ProductResponse.java (3 hunks)
  • src/main/java/com/almang/inventory/product/repository/ProductRepository.java (1 hunks)
  • src/main/java/com/almang/inventory/product/service/ProductService.java (6 hunks)
  • src/main/java/com/almang/inventory/retail/controller/RetailController.java (1 hunks)
  • src/main/java/com/almang/inventory/retail/domain/Retail.java (1 hunks)
  • src/main/java/com/almang/inventory/retail/repository/RetailRepository.java (1 hunks)
  • src/main/java/com/almang/inventory/retail/service/RetailService.java (1 hunks)
  • src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java (1 hunks)
  • src/test/java/com/almang/inventory/inventory/service/InventoryServiceTest.java (1 hunks)
  • src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java (1 hunks)
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java (1 hunks)
  • src/test/java/com/almang/inventory/retail/service/RetailServiceTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-20T10:43:47.502Z
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.502Z
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/inventory/service/InventoryServiceTest.java
  • src/main/java/com/almang/inventory/retail/domain/Retail.java
  • src/main/java/com/almang/inventory/product/domain/Product.java
  • src/main/java/com/almang/inventory/product/repository/ProductRepository.java
  • src/main/java/com/almang/inventory/product/service/ProductService.java
  • src/main/java/com/almang/inventory/product/dto/request/UpdateProductRequest.java
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
  • src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java
  • src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java
  • src/main/java/com/almang/inventory/product/dto/response/ProductResponse.java
📚 Learning: 2025-11-22T18:12:13.172Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 66
File: src/main/java/com/almang/inventory/order/domain/OrderItem.java:41-53
Timestamp: 2025-11-22T18:12:13.172Z
Learning: In the almang2/inventory-server repository, OrderItem entity update methods (updateQuantity, updatePrice in src/main/java/com/almang/inventory/order/domain/OrderItem.java) do not require null checks because OrderService will validate parameters before calling these update methods, following the same pattern as Product entity updates.
<!--

Applied to files:

  • src/test/java/com/almang/inventory/inventory/service/InventoryServiceTest.java
  • src/main/java/com/almang/inventory/retail/domain/Retail.java
  • src/main/java/com/almang/inventory/product/domain/Product.java
  • src/main/java/com/almang/inventory/product/repository/ProductRepository.java
  • src/main/java/com/almang/inventory/product/service/ProductService.java
  • src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java
  • src/main/java/com/almang/inventory/product/dto/request/UpdateProductRequest.java
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
  • src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java
🔇 Additional comments (21)
src/main/java/com/almang/inventory/customerorder/service/CustomerOrderService.java (1)

57-60: cafe24Code 기반 상품 조회로 변경 - 좋습니다! 👍

findByCode에서 findByCafe24Code로 변경한 것은 새로운 코드 모델과 일치합니다.

src/main/java/com/almang/inventory/retail/repository/RetailRepository.java (1)

1-7: 깔끔한 Repository 구현입니다.

JPA Repository 패턴을 따르고 있으며, 현재 요구사항에 충분합니다. 향후 조회 기능이 필요하면 findByProductAndStore, findByAppliedAtBetween 등의 쿼리 메서드를 추가할 수 있습니다.

src/main/java/com/almang/inventory/retail/service/RetailService.java (2)

69-71: 상품 미발견 시 전체 트랜잭션 실패 - 의도된 동작인지 확인 필요.

현재 구현에서는 하나의 POS 코드라도 찾지 못하면 전체 Excel 처리가 실패합니다. 부분 성공을 허용할지, 아니면 전체 실패(all-or-nothing)가 비즈니스 요구사항인지 확인하세요.

부분 성공을 허용한다면 다음과 같이 에러를 수집하고 로깅하는 방식을 고려하세요:

List<String> notFoundCodes = new ArrayList<>();
// ...
Product product = productRepository.findByPosCode(posCode).orElse(null);
if (product == null) {
    notFoundCodes.add(posCode);
    log.warn("POS code not found, skipping: {}", posCode);
    continue;
}
// 처리 후 notFoundCodes가 있으면 경고 또는 응답에 포함

82-85: 재고 감소 로직의 트랜잭션 일관성 검토 필요.

루프 내에서 inventory.decreaseDisplay(quantity)가 호출되지만, Retail 엔티티들은 루프 종료 후 saveAll()로 저장됩니다. 현재 @Transactional이 적용되어 있어 전체가 하나의 트랜잭션으로 처리되지만, saveAll() 이전에 예외가 발생하면 재고는 이미 감소된 상태가 될 수 있습니다.

JPA의 dirty checking으로 인해 트랜잭션 커밋 시점에 모든 변경이 함께 반영되므로 큰 문제는 아니지만, 명시적인 순서 보장을 위해 확인이 필요합니다.

공식 문서 참고: Spring Data JPA 트랜잭션 관리 (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions)

src/main/java/com/almang/inventory/product/dto/request/CreateProductRequest.java (1)

10-22: API 변경사항이 잘 반영되었습니다.

code 필드를 cafe24CodeposCode로 분리한 것은 각 채널별 코드 관리를 명확하게 합니다. @NotBlank 검증으로 필수값 처리도 적절합니다.

이 변경은 Breaking Change입니다. API를 사용하는 클라이언트(프론트엔드, 외부 시스템)에 변경사항을 공지했는지 확인하세요.

src/main/java/com/almang/inventory/product/service/ProductService.java (2)

63-63: updateBasicInfo 호출이 새로운 시그니처와 일치합니다.

cafe24CodeposCode를 전달하도록 업데이트되었습니다. 기존 learnings에 따르면 서비스에서 검증 후 엔티티 메서드를 호출하므로 추가 null 체크가 불필요합니다.


112-131: Product 엔티티 생성 로직이 새로운 필드 구조를 반영합니다.

toEntity 메서드가 cafe24CodeposCode를 사용하여 Product를 생성하도록 업데이트되었습니다. 빌더 패턴을 사용한 깔끔한 구현입니다.

src/test/java/com/almang/inventory/inventory/service/InventoryServiceTest.java (2)

83-100: newProduct 헬퍼 업데이트가 적절합니다.

cafe24CodeposCode 필드 추가에 맞춰 테스트 데이터 생성이 잘 업데이트되었습니다. "C-" + code 패턴으로 cafe24Code를 생성하여 두 코드 간 구분이 명확합니다.


137-138: 새 필드에 대한 assertion 추가 완료!

cafe24Code()posCode() 검증이 적절히 추가되었습니다.

src/main/java/com/almang/inventory/inventory/dto/response/InventoryResponse.java (2)

11-12: cafe24CodeposCode 분리가 적절합니다.

단일 productCode에서 두 개의 명시적 필드로 분리하여 각 코드의 용도가 명확해졌습니다. 이는 PR 목적(okpos code 및 cafe24상품코드 관리)과 일치합니다.


7-32: No action needed: cafe24Code and posCode fields are properly implemented and tested.

The commit "split pos code and cafe24 code" intentionally refactored the Product domain to distinguish between Cafe24 and POS system codes. The InventoryResponse was never using a combined productCode field—it currently exposes both cafe24Code and posCode as separate fields, which is the intended design. Tests throughout the codebase (InventoryControllerTest, InventoryServiceTest) already validate this structure and pass.

src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java (3)

45-61: LGTM! 테스트 설정이 깔끔하게 구성되었습니다.

@Autowired@MockitoBean 어노테이션을 활용한 DI 구성이 명확하고, 인증 헬퍼 메서드도 재사용 가능하게 잘 분리되어 있습니다. 👍


63-114: LGTM! cafe24Code와 posCode 필드가 올바르게 테스트되고 있습니다.

새로운 필드들에 대한 요청/응답 검증이 잘 구현되어 있습니다. 품목 등록 성공 시나리오가 완전하게 커버되어 있네요.


331-364: LGTM! 다른 상점 발주처 접근 제한 테스트가 잘 구현되었습니다.

권한 검증 로직에 대한 테스트가 명확하게 작성되어 있습니다. ErrorCode.VENDOR_ACCESS_DENIED 검증도 적절합니다.

src/test/java/com/almang/inventory/product/service/ProductServiceTest.java (2)

38-49: LGTM! 테스트 의존성 주입이 잘 구성되었습니다.

통합 테스트에 필요한 Repository들이 명확하게 주입되어 있습니다.


83-132: LGTM! 품목 생성 성공 테스트가 완벽하게 구현되었습니다.

cafe24CodeposCode 검증, 그리고 연관된 Inventory 자동 생성 검증까지 꼼꼼하게 테스트하고 있네요. 훌륭합니다! 🎉

src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java (4)

47-63: LGTM! 테스트 설정이 잘 구성되어 있습니다.

의존성 주입과 인증 헬퍼가 명확하게 분리되어 있습니다.


79-89: LGTM! InventoryResponsecafe24CodeposCode가 올바르게 추가되었습니다.

새로운 필드들이 응답 객체에 정확히 포함되어 있고, 테스트에서도 이를 검증하고 있습니다.


339-385: LGTM! 상점 재고 목록 조회 테스트가 새 필드들을 잘 검증하고 있습니다.

$.data.content[0].cafe24Code$.data.content[0].posCode 검증이 포함되어 API 응답 구조 변경이 올바르게 반영되었습니다.


387-427: LGTM! 재고 이동 테스트에서 새 필드 검증이 완벽합니다.

productName, cafe24Code, posCode 모두 검증되고 있어 응답 데이터 무결성이 보장됩니다. 👍

src/main/java/com/almang/inventory/product/domain/Product.java (1)

79-92: LGTM! updateBasicInfo 메서드가 새 필드들을 잘 처리합니다.

부분 업데이트 패턴이 일관되게 적용되어 있습니다. 기존 learnings에서 확인된 바와 같이 ProductService에서 검증 후 호출되므로 null 체크 로직이 적절합니다. Based on learnings, 서비스 레이어에서 사전 검증이 이루어집니다.

Comment on lines +54 to +55
// Excel (Apache POI)
implementation 'org.apache.poi:poi-ooxml:5.2.3'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

curl -s "https://search.maven.org/solrsearch/select?q=g:org.apache.poi+AND+a:poi-ooxml&rows=10&wt=json" | jq -r '.response.docs[] | "\(.v)"' | head -20

Repository: almang2/inventory-server

Length of output: 72


🌐 Web query:

Apache POI poi-ooxml 5.2.3 security vulnerabilities CVE

💡 Result:

Yes — poi-ooxml 5.2.3 is vulnerable to CVE-2025-31672 (Improper Input Validation: OOXML ZIP entries with duplicate names). The issue affects all poi-ooxml versions < 5.4.0; upgrade to >= 5.4.0 to fix it. [1][2][3]

Sources:

  • NVD entry for CVE-2025-31672. [1]
  • GitHub Advisory (Apache POI poi-ooxml < 5.4.0). [2]
  • Snyk vulnerability page / package details. [3]

🌐 Web query:

Apache POI latest version 2024 2025

💡 Result:

Latest Apache POI releases (relevant to 2024–2025):

  • 5.3.0 — released 2024-07-02. [1]
  • 5.4.0 — released 2025-01-08. [1]
  • 5.4.1 — released 2025-04-06. [1]
  • 5.5.0 — released 2025-11-15 (latest stable). [2][1]

Sources: Apache POI changelog and download page. [1] https://poi.apache.org/changes.html [2] https://poi.apache.org/download.cgi


Upgrade Apache POI to patch CVE-2025-31672.

poi-ooxml:5.2.3 is vulnerable to CVE-2025-31672 (improper input validation in OOXML ZIP entries with duplicate names). This affects all versions below 5.4.0. Upgrade to at least 5.4.0 (or 5.5.0, the current latest stable release).

🤖 Prompt for AI Agents
In build.gradle around lines 54-55 the Apache POI dependency is pinned to
vulnerable version 'org.apache.poi:poi-ooxml:5.2.3'; update the dependency
version to at least 5.4.0 (preferably 5.5.0) to address CVE-2025-31672 by
replacing the version string with 'org.apache.poi:poi-ooxml:5.5.0' (or '5.4.0'
if you prefer), then run a Gradle refresh/resolve to ensure the updated artifact
is fetched and the build still passes.

Comment on lines +4 to +8
data = {
'Product Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],
'Quantity': [10, 5, 20],
'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

샘플 데이터 컬럼 위치가 RetailService와 일치하지 않습니다.

RetailService에서는 Cell 1(인덱스 1)에서 POS 코드를, Cell 3(인덱스 3)에서 수량을 읽습니다. 현재 샘플 데이터는 인덱스 0, 1, 2에 데이터를 배치하므로 실제 업로드 시 파싱 오류가 발생할 수 있습니다.

다음과 같이 수정하여 컬럼 위치를 맞추세요:

 data = {
-    'Product Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],
-    'Quantity': [10, 5, 20],
-    'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']
+    'Empty': ['', '', ''],           # Cell 0
+    'POS Code': ['P00000LM000D', 'P00000WA000B', 'P00000IP000A'],  # Cell 1
+    'Empty2': ['', '', ''],          # Cell 2
+    'Quantity': [10, 5, 20],         # Cell 3
+    'Applied At': ['2023-12-01 10:00:00', '2023-12-01 11:00:00', '']  # Cell 4 (미사용)
 }
🤖 Prompt for AI Agents
In generate_sample_excel.py around lines 4 to 8, the sample data columns are
currently at indices 0,1,2 but RetailService expects the POS code at index 1 and
quantity at index 3; modify the data dict so there are placeholder columns to
align positions (e.g., add a first-column placeholder and a third-column
placeholder) and place 'Product Code' at index 1 and 'Quantity' at index 3 (keep
'Applied At' after those), ensuring each row has the same number of entries so
exported Excel columns match RetailService's expected cell indices.

Comment on lines +36 to +43
@Column(name = "name", length = 255, nullable = false)
private String name;

@Column(name = "code", length = 30, nullable = false)
private String code;
@Column(name = "cafe24_code", length = 50, nullable = false)
private String cafe24Code;

@Column(name = "pos_code", length = 50, nullable = false)
private String posCode;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n src/main/java/com/almang/inventory/product/domain/Product.java | head -100

Repository: almang2/inventory-server

Length of output: 3638


🏁 Script executed:

# Search for migration files and flyway/liquibase configuration
find . -type f \( -name "*.sql" -o -name "*.yaml" -o -name "*.yml" -o -name "application*.properties" \) | grep -E "(migration|db|flyway|liquibase)" | head -20

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Check for migration directories
find . -type d \( -name "migration*" -o -name "db" -o -name "flyway" -o -name "liquibase" \) 2>/dev/null

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for cafe24_code or pos_code references in SQL or migration files
rg -i "cafe24|pos_code" --type sql --type yaml -l 2>/dev/null || echo "No matches found"

Repository: almang2/inventory-server

Length of output: 94


🏁 Script executed:

cat docs/rds_create_tables.sql | grep -A 5 -B 5 "cafe24\|pos_code"

Repository: almang2/inventory-server

Length of output: 980


🏁 Script executed:

# Search for Flyway or Liquibase migration directories
find . -type d -name "db" -o -name "migration" 2>/dev/null | head -10

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Check for migration files in common locations
find . -path "*/db/migration*" -o -path "*/flyway*" -o -path "*/liquibase*" 2>/dev/null | head -20

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Look at the structure of migration/db directories if they exist
ls -la src/main/resources/ 2>/dev/null | grep -E "db|migration|flyway"

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

cat docs/rds_create_tables.sql | grep -n "CREATE TABLE.*products"

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

cat docs/rds_create_tables.sql

Repository: almang2/inventory-server

Length of output: 2017


🏁 Script executed:

# Search for any mention of cafe24_code or pos_code in the entire codebase
rg "cafe24_code|pos_code" --type sql

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for products table definition anywhere in codebase
rg "CREATE TABLE.*products" --type sql

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Find all SQL files
fd -e sql

Repository: almang2/inventory-server

Length of output: 94


🏁 Script executed:

# Search for any SQL files or migration-related files
find . -type f \( -name "*.sql" -o -name "*.ddl" \) 2>/dev/null

Repository: almang2/inventory-server

Length of output: 96


🏁 Script executed:

# Check if there's an existing products table schema somewhere else
rg "products" -t sql

Repository: almang2/inventory-server

Length of output: 175


🏁 Script executed:

# Look for any schema or DDL comments in Java files
rg "cafe24_code|pos_code" -A 3 -B 3

Repository: almang2/inventory-server

Length of output: 1003


Database migration script required for cafe24_code and pos_code columns.

The new cafe24Code and posCode fields are marked nullable = false in the entity, but no corresponding database migration script exists in the codebase. The docs/rds_create_tables.sql file does not contain definitions for these columns on the products table.

Before merging, create a database migration script that:

  • Adds cafe24_code (VARCHAR(50), NOT NULL) column
  • Adds pos_code (VARCHAR(50), NOT NULL) column
  • Provides default values or migration strategy for existing product records

Without this migration, the application will fail when Hibernate attempts to persist or update products.

Comment on lines 9 to +22
public record UpdateProductRequest(
@NotNull Long vendorId,
String name,
String code,
ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
Boolean isActivated,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice
) {}
@NotNull Long vendorId,
String name,
String cafe24Code,
String posCode,
ProductUnit unit,
BigDecimal boxWeightG,
@Positive Integer unitPerBox,
BigDecimal unitWeightG,
Boolean isActivated,
@Min(0) Integer costPrice,
@Min(0) Integer retailPrice,
@Min(0) Integer wholesalePrice) {
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine CreateProductRequest
echo "=== CreateProductRequest.java ==="
fd -t f "CreateProductRequest.java" --exec cat {}

# Find the test file and relevant test method
echo "=== ProductControllerTest - validation test ==="
fd -t f "ProductControllerTest.java" --exec wc -l {} \;

Repository: almang2/inventory-server

Length of output: 1069


🏁 Script executed:

#!/bin/bash
# Search for the specific test mentioned in the review (line 369-381 or 178-204)
echo "=== Test method searching for empty string validation ==="
rg -n -B 2 -A 15 "empty|Empty|빈.*문자" src/test/java/com/almang/inventory/product/controller/ --type java | head -100

Repository: almang2/inventory-server

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Check UpdateProductRequest current validation state more thoroughly
echo "=== Full UpdateProductRequest.java ==="
fd -t f "UpdateProductRequest.java" --exec cat {}

Repository: almang2/inventory-server

Length of output: 889


🏁 Script executed:

#!/bin/bash
# Find test methods in ProductControllerTest that test update product validation
echo "=== Test methods for update product ==="
rg -n "void.*update|updateProduct|PUT|patch" src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java -A 3

# Also search for test assertions with invalid input or validation
echo "=== Test validation assertions ==="
rg -n "INVALID_INPUT_VALUE|BindException|valid" src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java | head -30

Repository: almang2/inventory-server

Length of output: 4505


🏁 Script executed:

#!/bin/bash
# Read the test file to check for update-related tests
echo "=== ProductControllerTest content (lines 350-400) ==="
sed -n '350,400p' src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java

echo "=== ProductControllerTest content (lines 1-50) to find test class structure ==="
head -50 src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java

Repository: almang2/inventory-server

Length of output: 5370


UpdateProductRequest의 검증 어노테이션 누락 확인됨. name, cafe24Code, posCode 필드에 @NotBlank 추가 필요.

CreateProductRequest에는 이 세 필드에 @NotBlank 어노테이션이 있지만, UpdateProductRequest에는 누락되어 있습니다. 현재 테스트 "품목_수정_요청값_검증에_실패하면_예외가_발생한다()"(line 367-394)는 vendorId@NotNull 위반과 가격 필드의 @Min(0) 위반으로 인해 통과하고 있지만, 빈 문자열 값들은 검증되지 않고 있습니다.

일관성 유지와 입력값 검증을 위해 name, cafe24Code, posCode@NotBlank 어노테이션을 추가해주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/almang/inventory/product/dto/request/UpdateProductRequest.java
around lines 9 to 22, add @NotBlank to the parameters name, cafe24Code, and
posCode so they are validated like in CreateProductRequest; update imports to
include javax.validation.constraints.NotBlank if not already present, rebuild to
ensure the existing validation test now also fails on blank strings and adjust
any tests if necessary.

Comment on lines +28 to +32
// 상품 코드로 상품 찾기 (카페24 주문 처리용)
Optional<Product> findByCafe24Code(String cafe24Code);

// POS 코드로 상품 찾기 (소매 판매 엑셀 업로드용)
Optional<Product> findByPosCode(String posCode);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine ProductRepository.java
find . -type f -name "ProductRepository.java" | head -5

Repository: almang2/inventory-server

Length of output: 146


🏁 Script executed:

# Find and examine Product entity class
find . -type f -name "Product.java" | grep -E "entity|model" | head -5

Repository: almang2/inventory-server

Length of output: 50


🏁 Script executed:

# Search for cafe24Code and posCode field definitions
rg -n "cafe24Code|posCode" --type java -B2 -A2

Repository: almang2/inventory-server

Length of output: 24586


🏁 Script executed:

# Read the full Product entity class to check for unique constraints
cat -n src/main/java/com/almang/inventory/product/domain/Product.java | head -70

Repository: almang2/inventory-server

Length of output: 2557


Add unique constraints on cafe24Code and posCode fields in Product entity

The cafe24Code and posCode fields are currently missing unique constraints but are used as lookup keys in findByCafe24Code() and findByPosCode() queries. Without these constraints, duplicate codes could exist in the database, causing these repository methods to silently return only the first match. This creates a data integrity risk in order processing workflows (Cafe24 orders and retail Excel uploads).

Add unique = true to both @column annotations in the Product entity, or use @table(uniqueConstraints = {...}) if store-scoped uniqueness is required.

🤖 Prompt for AI Agents
In src/main/java/com/almang/inventory/product/repository/ProductRepository.java
around lines 28 to 32, the repository methods findByCafe24Code and findByPosCode
rely on fields that lack database uniqueness, risking duplicate entries and
ambiguous lookups; update the Product entity to enforce uniqueness by adding
unique = true to the @Column for cafe24Code and posCode, or if uniqueness must
be scoped (e.g., per store), add a @Table(uniqueConstraints =
{@UniqueConstraint(columnNames = {"store_id","cafe24_code"}),
@UniqueConstraint(columnNames = {"store_id","pos_code"})}) and adjust column
names accordingly so the database enforces the constraint.

Comment on lines +921 to +936
// 고무장갑 비활성 처리
productService.updateProduct(
willBeInactive.productId(),
new UpdateProductRequest(
vendor.getId(),
willBeInactive.name(),
willBeInactive.code(),
willBeInactive.unit(),
willBeInactive.boxWeightG(),
willBeInactive.unitPerBox(),
willBeInactive.unitWeightG(),
false,
willBeInactive.costPrice(),
willBeInactive.retailPrice(),
willBeInactive.wholesalePrice()),
user.getId());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

컴파일 에러: ProductResponse.code() 메서드가 존재하지 않습니다.

ProductResponse에서 code 필드가 cafe24CodeposCode로 분리되었으므로, .code() 호출은 컴파일 에러를 발생시킵니다. cafe24Code()posCode()를 사용해야 합니다.

 productService.updateProduct(
         willBeInactive.productId(),
         new UpdateProductRequest(
                 vendor.getId(),
                 willBeInactive.name(),
-                willBeInactive.code(),
+                willBeInactive.cafe24Code(),
+                willBeInactive.posCode(),
                 willBeInactive.unit(),
                 willBeInactive.boxWeightG(),
                 willBeInactive.unitPerBox(),
                 willBeInactive.unitWeightG(),
                 false,
                 willBeInactive.costPrice(),
                 willBeInactive.retailPrice(),
                 willBeInactive.wholesalePrice()),
         user.getId());

참고: DTO 필드 변경 시 IDE의 "Rename" 리팩토링 기능을 활용하면 누락을 방지할 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
around lines 921-936, the test calls willBeInactive.code() which no longer
exists because ProductResponse split the code field into cafe24Code and posCode;
update the UpdateProductRequest construction to pass willBeInactive.cafe24Code()
and willBeInactive.posCode() in place of the old .code() argument(s) so the
request matches the new DTO fields (adjust argument order to match
UpdateProductRequest's new signature).

Comment on lines +956 to +999
// 활성 + 이름 매칭
productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
user.getId());

// 비활성 + 이름 매칭
ProductResponse inactiveMatch = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고무장갑",
"P-002",
ProductUnit.EA,
null,
1,
null,
500,
800,
600),
user.getId());

// 비활성 + 이름 미매칭
ProductResponse inactiveNonMatch = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"세제",
"P-003",
ProductUnit.ML,
BigDecimal.valueOf(1000.0),
5,
BigDecimal.valueOf(200.0),
3000,
4000,
3500),
user.getId());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

컴파일 에러: 동시 필터링 테스트에서 동일한 문제들이 반복됩니다.

CreateProductRequestcafe24Code 누락, 그리고 Lines 1007, 1023에서 .code() 호출 문제가 있습니다.

동일한 패턴의 수정이 필요합니다:

  1. CreateProductRequest 생성자에 cafe24Code 파라미터 추가
  2. .code().cafe24Code(), .posCode() 변경
🤖 Prompt for AI Agents
In src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
around lines 956 to 999, the CreateProductRequest constructions are missing the
new cafe24Code parameter and related accessor calls elsewhere use .code()
instead of the updated .cafe24Code() or .posCode(); update each
CreateProductRequest invocation within this block to supply the cafe24Code
argument in the correct position expected by the constructor, and replace any
.code() calls in this test (and the repeated patterns nearby at lines ~1007 and
~1023) with the appropriate .cafe24Code() or .posCode() accessor so the calls
match the new DTO signature.

Comment on lines +1046 to +1065
@Test
void 품목_삭제에_성공한다() {
// given
Store store = newStore();
Vendor vendor = newVendor(store);
User user = newUser(store);

ProductResponse created = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
user.getId());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

컴파일 에러: 삭제 테스트에서 cafe24Code 누락

 ProductResponse created = productService.createProduct(
         new CreateProductRequest(
                 vendor.getId(),
                 "고체치약",
+                "C-001",
                 "P-001",
                 ProductUnit.G,
                 BigDecimal.valueOf(900.0),
                 10,
                 BigDecimal.valueOf(90.0),
                 1000,
                 1500,
                 1200),
         user.getId());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
void 품목_삭제에_성공한다() {
// given
Store store = newStore();
Vendor vendor = newVendor(store);
User user = newUser(store);
ProductResponse created = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
user.getId());
@Test
void 품목_삭제에_성공한다() {
// given
Store store = newStore();
Vendor vendor = newVendor(store);
User user = newUser(store);
ProductResponse created = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고체치약",
"C-001",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
user.getId());
🤖 Prompt for AI Agents
In src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
around lines 1046-1065, the call to new CreateProductRequest is missing the
required cafe24Code parameter (causing a compile error); update the constructor
invocation to include a cafe24Code value (e.g., "C-001") in the correct position
(between the product name and productCode) so the argument list matches the
CreateProductRequest signature.

Comment on lines +1117 to +1129
ProductResponse productOfStore2 = productService.createProduct(
new CreateProductRequest(
vendor2.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
userOfStore2.getId());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

컴파일 에러: 다른 상점 품목 삭제 테스트에서 cafe24Code 누락

 ProductResponse productOfStore2 = productService.createProduct(
         new CreateProductRequest(
                 vendor2.getId(),
                 "고체치약",
+                "C-001",
                 "P-001",
                 ProductUnit.G,
                 BigDecimal.valueOf(900.0),
                 10,
                 BigDecimal.valueOf(90.0),
                 1000,
                 1500,
                 1200),
         userOfStore2.getId());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ProductResponse productOfStore2 = productService.createProduct(
new CreateProductRequest(
vendor2.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
userOfStore2.getId());
ProductResponse productOfStore2 = productService.createProduct(
new CreateProductRequest(
vendor2.getId(),
"고체치약",
"C-001",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
userOfStore2.getId());
🤖 Prompt for AI Agents
In src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
around lines 1117-1129, the CreateProductRequest instantiation for
productOfStore2 is missing the required cafe24Code parameter which causes a
compilation error; update the constructor call to include a valid cafe24Code
string (e.g., "C-001" or an appropriate test value) in the correct position to
match the CreateProductRequest signature, and adjust any other test
instantiations to the same constructor ordering if necessary.

Comment on lines +1138 to +1157
@Test
void 삭제된_품목은_목록_및_상세에서_조회되지_않는다() {
// given
Store store = newStore();
Vendor vendor = newVendor(store);
User user = newUser(store);

ProductResponse product = productService.createProduct(
new CreateProductRequest(
vendor.getId(),
"고체치약",
"P-001",
ProductUnit.G,
BigDecimal.valueOf(900.0),
10,
BigDecimal.valueOf(90.0),
1000,
1500,
1200),
user.getId());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

컴파일 에러: 삭제 후 조회 불가 테스트에서 cafe24Code 누락

 ProductResponse product = productService.createProduct(
         new CreateProductRequest(
                 vendor.getId(),
                 "고체치약",
+                "C-001",
                 "P-001",
                 ProductUnit.G,
                 BigDecimal.valueOf(900.0),
                 10,
                 BigDecimal.valueOf(90.0),
                 1000,
                 1500,
                 1200),
         user.getId());

정리: 총 10개 이상의 테스트에서 동일한 문제가 발생하고 있습니다. 일괄 수정이 필요합니다. 테스트 헬퍼 메서드(예: createDefaultProductRequest(Long vendorId))를 만들어 중복을 줄이는 것을 권장드립니다.

🤖 Prompt for AI Agents
In src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
around lines 1138-1157 the test fails to compile because the
CreateProductRequest used in tests is missing the required cafe24Code argument;
update the test to supply a valid cafe24Code value (e.g. "C24-001") when
constructing CreateProductRequest or add an overloaded constructor/utility
factory used by tests that fills cafe24Code by default; apply the same change
across the other ~10+ test cases and refactor by adding a test helper method
like createDefaultProductRequest(Long vendorId) to centralize defaults including
cafe24Code and reduce duplication.

@jeongsoyong
Copy link

@Youngwave1128 RetailService.java 코드를 살펴보았는데요, 개선했으면 하는 부분이 있어서 코멘트 남깁니다.

  1. processRetailExcel() 함수가 너무 많은 작업을 하는듯 해서 파싱 로직이라도 별도 컴포넌트로 분리했으면 합니다.
    (Excel 구조 파싱, 셀 값 파싱, 도메인 검증, Retail 생성, Inventory 차감, DB 저장)
    예: RetailExcelParser or ExcelRowMapper 형태

  2. 트랜잭션 범위가 너무 넓은데요, 성능/메모리 이슈 및 롤백 리스크가 있을거 같습니다.
    (@transactional 로 전체 Excel 처리 작업을 감싸고 있습니다.)
    예: 5000줄 Excel이면 Retail 엔티티도 5000개 생성되고, 메모리 이슈가 있을거 같고, 한개의 row라도 PRODUCT_NOT_FOUND 발생하면 전체 롤백될 수도 있을거 같습니다.
    수정 예시 안
    ➡ row 단위 처리로 변경하거나, 실패한 row만 별도 기록하는 구조 고려
    ➡ Batch 처리 또는 Chunk 단위 처리 권장
    ➡ 트랜잭션을 saveAll 수행하는 부분에만 최소 적용도 가능

  3. 예외 처리 개선 필요할 거 같습니다.
    엑셀 파싱 IOException → RuntimeException 발생하고 있지만, 하지만 프로젝트에서는 BaseException 을 사용하고 있습니다. 예외가 서비스 전체 에러 핸들링 규칙에 맞게 수정되면 좋을거 같습니다.
    예: throw new BaseException(ErrorCode.EXCEL_PARSE_ERROR) 로 통일.

  4. Excel 숫자 처리는 확인이 필요해 보여요.
    예: "00123" 같은 POS 코드가 숫자로 파싱되어 "123"으로 바뀌는 위험이 있어서, numeric 셀에서 long 캐스팅 시 소수점 버려질거 같습니다.
    실제 숫자표기를 어떻게 할지 약속이 되어있을까요?

  5. BigDecimal 파싱이 개선되면 좋을거 같습니다.
    예: new BigDecimal(cell.getNumericCellValue()).stripTrailingZeros()
    추가로 문자열에 콤마 포함되는 케이스도 고려 필요해 보여요.

  6. Inventory 차감 로직이 단순 loop 안에 들어가 있어서 성능/트랜잭션 문제
    inventoryRepository.findByProduct(product).ifPresent(inventory -> {
    inventory.decreaseDisplay(quantity);
    });
    문제: 1000개 row → 1000번 DB 조회 차감 후 dirty check 발생 → flush 시 비용 큼
    개선안: 한번에 productId 리스트 조회 → Map 으로 변환(in 절로 inventory 한번 조회 loop에서 O(1)로 접근) → 마지막에 한 번 flush

  7. Excel row 단위로 product 조회 시 N+1 성능 문제가 생길거 같습니다.
    현재: Product product = productRepository.findByPosCode(posCode)
    1000행 → 1000번 쿼리
    개선: 모든 posCode 먼저 수집 → 한번에 조회
    List codes = ...;
    Map<String, Product> productMap = productRepository.findByPosCodeIn(codes)
    .stream().collect(Collectors.toMap(Product::getPosCode, p -> p));

  8. 추후 유지보수를 위해 실패 row 작업에 대한 로그가 추가되면 좋을거 같습니다.

실제 사용성에 맞게 가정 하신 부분도 있으실거 같은데요, 제가 맞지 않을수도 있어서 확인해보시고 수정해주시면 좋을 거 같아 코멘트 남깁니다.

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.

[FEAT] 엑셀 upload로 소매 재고 관리 기능 구현

3 participants