Skip to content

Comments

[FIX] bom, material, part 이벤트 구독 수정#29

Merged
taemin3 merged 2 commits intomainfrom
SPM-458
Nov 8, 2025
Merged

[FIX] bom, material, part 이벤트 구독 수정#29
taemin3 merged 2 commits intomainfrom
SPM-458

Conversation

@taemin3
Copy link
Contributor

@taemin3 taemin3 commented Nov 7, 2025

📝 Summary

  • [FIX] bom, material, part 이벤트 구독 수정

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 자재·부품에 표준 수량(standardQuantity) 및 표준 총비용(standardTotalCost) 정보 추가
    • BOM에 항목별 및 전체 총비용(totalCost) 필드 추가로 비용 추적 강화
  • Bug Fixes

    • 수량을 소수로 표현해 소수점 단위 보존 지원
    • 생산·구매 계산에서 반올림 기반 수량 처리 및 표준단위 배치 적용으로 계산 정확도 및 리드타임 산정 개선

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

BOM, Material, Part 도메인에서 표준 수량/원가 필드를 추가하고 여러 수량 타입을 Integer/Long에서 Double로 변경했으며, 수량 계산에서 소수 보존과 반올림 로직(Math.round 등)을 도입해 관련 서비스·엔티티·DTO와 업데이트 메서드를 확장했습니다.

Changes

Cohort / File(s) Summary
BOM: DTO · Entity · Service
src/main/java/com/sampoom/factory/api/bom/dto/BomEventDto.java, src/main/java/com/sampoom/factory/api/bom/entity/BomMaterialProjection.java, src/main/java/com/sampoom/factory/api/bom/entity/BomProjection.java, src/main/java/com/sampoom/factory/api/bom/service/BomProjectionService.java
BomEventDto.Payload.totalCost: Double 추가, BomMaterialProjection.quantity 타입 Integer→Double 변경, BomProjection.totalCost: Double 추가 및 updateFromEvent(...) 시그니처와 호출 경로(생성/업데이트) 확장
Material: DTO · Entity · Service · Response · FactoryMaterial
src/main/java/com/sampoom/factory/api/material/dto/MaterialEventDto.java, src/main/java/com/sampoom/factory/api/material/entity/MaterialProjection.java, src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java, src/main/java/com/sampoom/factory/api/material/dto/MaterialResponseDto.java, src/main/java/com/sampoom/factory/api/material/entity/FactoryMaterial.java, src/main/java/com/sampoom/factory/api/purchase/service/PurchaseEventService.java
standardQuantity(Integer)standardTotalCost(Long) 필드 추가 (DTO · Projection), MaterialProjection.updateFromEvent(...) 시그니처 확장, MaterialResponseDto.quantity: Long→Double 변경, FactoryMaterial.quantity: Long→Double 및 increase/decrease 메서드 시그니처와 내부 연산을 Double로 변경, 구매/수령 흐름에서 Double 사용 반영
Part: DTO · Entity · Projection Service · 주문 서비스
src/main/java/com/sampoom/factory/api/part/dto/PartEventDto.java, src/main/java/com/sampoom/factory/api/part/entity/PartProjection.java, src/main/java/com/sampoom/factory/api/part/service/PartProjectionService.java, src/main/java/com/sampoom/factory/api/part/service/PartOrderService.java, src/main/java/com/sampoom/factory/api/part/service/PartOrderSchedulerService.java
PartPayloadstandardQuantity(Integer)·standardTotalCost(Long) 추가, PartProjection에 같은 필드 추가 및 updateFromEvent(...) 확장, 주문 계산부에서 수량 곱셈을 double로 처리하고 일부 경로에서 Math.round(...)로 반올림 적용, lead-time·구매 수량 계산 로직 수정 및 상세 로그 추가
Branch / Factory 초기화
src/main/java/com/sampoom/factory/api/factory/service/BranchProjectionService.java
FactoryMaterial 초기화에서 수량 리터럴을 Long(0L)→Double(0.0)로 변경하여 FactoryMaterial 타입 변경 반영

Sequence Diagram(s)

sequenceDiagram
    participant Event as Event Source
    participant Service as ProjectionService
    participant Entity as Projection Entity
    participant Repo as Repository

    Note over Event,Service: 도메인 이벤트 수신 (BOM/Material/Part)
    Event->>Service: Emit Created/Updated/Deleted(payload with new fields)
    Service->>Entity: builder()/updateFromEvent(..., standardQuantity, standardTotalCost, totalCost, ...)
    Entity->>Repo: save()
    Note right of Repo: 저장 완료
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 검토 시 주의할 파일/영역:
    • FactoryMaterial의 quantity 타입 변경과 increase/decrease(Double) 구현(경계값·동시성 검증)
    • PartOrderService/PartOrderSchedulerService의 반올림·lead-time 변경이 재고·주문 계산에 미치는 영향
    • 여러 updateFromEvent(...) 시그니처 변경에 따른 호출 일관성 및 마이그레이션(기존 데이터) 위험
    • BOM·Material·Part 도메인 전파 경로에서 새로운 필드(null/기본값 처리) 일관성

Possibly related PRs

Suggested labels

ready-to-merge

Suggested reviewers

  • Lee-Jong-Jin
  • CHOOSLA
  • yangjiseonn
  • vivivim

Poem

🐰
표준 수량과 원가 싣고 달려왔네,
소수점도 안심하고 안아주며,
반올림은 부드럽게, 계산은 정직히,
도메인들 하나로 잘 연결되었네,
당근 한 조각으로 축하할래요! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 BOM, 재료, 부품 이벤트 구독 수정에 관한 것으로, 변경 사항의 주요 내용과 부분적으로 관련이 있습니다. 그러나 구체적이지 않고 전체 변경 범위(필드 타입 변경, 새로운 필드 추가, 수량 계산 로직 개선 등)를 포괄하지 않습니다.
✨ 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-458

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
Member

@Sangyoon98 Sangyoon98 left a comment

Choose a reason for hiding this comment

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

진짜 밤새니 거기서...

@taemin3
Copy link
Contributor Author

taemin3 commented Nov 7, 2025

진짜 밤새니 거기서...

언제오시나요...

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

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/sampoom/factory/api/material/entity/MaterialProjection.java (1)

75-96: handleMaterialUpdatedhandleMaterialCreated에서 null 안전성 처리가 필요합니다.

standardQuantitystandardTotalCost는 이벤트 페이로드에서 직접 전달되며, null 체크 없이 builder에 전달됩니다. 구버전 이벤트나 불완전한 페이로드에서 이 필드들이 null이면 entity 컬럼 제약(@Column(nullable = false))을 위반할 수 있습니다.

권장사항: handleMaterialDeleted에서 사용한 패턴처럼 기존 값을 보존하거나, 명시적 null 체크와 기본값을 추가하세요.

// handleMaterialUpdated에서
StandardQuantity standardQuantity = payload.getStandardQuantity() != null 
    ? payload.getStandardQuantity() 
    : currentMaterial.getStandardQuantity();
StandardTotalCost standardTotalCost = payload.getStandardTotalCost() != null 
    ? payload.getStandardTotalCost() 
    : currentMaterial.getStandardTotalCost();

또는 handleMaterialDeleted처럼 기존 값을 그대로 사용하는 것이 더 간단합니다.

src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java (1)

57-76: 이벤트 페이로드의 null 안전성을 확인하세요.

검증 결과, 두 개의 새로 추가된 필드가 nullable 형식인 상태로 NOT NULL 제약 조건이 있는 엔티티에 직접 할당되고 있습니다:

  • standardQuantity (Integer, nullable) → MaterialProjection의 @Column(nullable = false)
  • standardTotalCost (Long, nullable) → MaterialProjection의 @Column(nullable = false)

MaterialEventDto의 Payload 클래스에는 이 필드들에 대한 유효성 검증 애너테이션이 없으며, MaterialProjectionService의 handleMaterialCreated 메서드에도 null 체크가 없습니다. 구버전 이벤트나 불완전한 페이로드에서 이 값들이 null인 경우 데이터베이스 제약 조건 위반이 발생할 수 있습니다.

권장사항:

  1. MaterialEventDto.Payload의 필드에 @NotNull 또는 @NonNull 검증 추가
  2. 또는 handleMaterialCreated에서 null 체크 및 기본값 처리 추가
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd248b0 and 97b16e5.

📒 Files selected for processing (12)
  • src/main/java/com/sampoom/factory/api/bom/dto/BomEventDto.java (2 hunks)
  • src/main/java/com/sampoom/factory/api/bom/entity/BomMaterialProjection.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/bom/entity/BomProjection.java (2 hunks)
  • src/main/java/com/sampoom/factory/api/bom/service/BomProjectionService.java (2 hunks)
  • src/main/java/com/sampoom/factory/api/material/dto/MaterialEventDto.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/material/entity/MaterialProjection.java (3 hunks)
  • src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java (3 hunks)
  • src/main/java/com/sampoom/factory/api/part/dto/PartEventDto.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/part/entity/PartProjection.java (4 hunks)
  • src/main/java/com/sampoom/factory/api/part/service/PartOrderSchedulerService.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/part/service/PartOrderService.java (5 hunks)
  • src/main/java/com/sampoom/factory/api/part/service/PartProjectionService.java (3 hunks)
⏰ 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 (12)
src/main/java/com/sampoom/factory/api/material/dto/MaterialEventDto.java (1)

28-33: 새 필드 추가가 적절하게 구현되었습니다.

두 개의 새 필드(standardQuantity, standardTotalCost)가 이벤트 페이로드에 추가되었습니다. 타입 선택(수량은 Integer, 비용은 Long)이 적절합니다.

다만, AI 요약에서는 "수량 타입을 Integer에서 Double로 변경"한다고 언급되어 있으나, 실제 코드에서는 standardQuantity가 여전히 Integer 타입으로 선언되어 있습니다. 소수점 계산이 필요한 경우 이 부분을 재검토해야 할 수 있습니다.

src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java (3)

73-74: 프로젝션 타임스탬프 필드 초기화가 올바르게 추가되었습니다.

updatedAtsourceUpdatedAt 필드 초기화가 MaterialCreated 핸들러에 추가되었습니다. 이는 프로젝션의 시간 추적을 위한 중요한 개선사항입니다.


127-141: MaterialUpdated 핸들러가 새 필드를 올바르게 전파합니다.

standardQuantity(line 132)와 standardTotalCost(line 135)가 업데이트 경로를 통해 적절히 전파되고 있습니다.

다만 MaterialCreated와 동일하게 페이로드 값이 null일 가능성에 대한 검증이 필요합니다.


159-173: MaterialDeleted 핸들러 구현이 안전합니다.

삭제 시 currentMaterial의 기존 필드 값들을 사용하여 업데이트하므로(lines 164, 167), null 안전성이 보장됩니다. 이는 올바른 소프트 삭제 패턴입니다.

src/main/java/com/sampoom/factory/api/material/entity/MaterialProjection.java (1)

41-42: 마이그레이션 전략 존재 여부 수동 확인 필요

저장소 내에서 데이터베이스 마이그레이션 인프라를 찾을 수 없습니다. 다음을 수동으로 확인하세요:

  • SQL 마이그레이션 스크립트 존재 여부 (Flyway/Liquibase 등)
  • application.properties 또는 application.yml에서 spring.jpa.hibernate.ddl-auto 또는 마이그레이션 도구 설정 여부
  • 새로 추가된 standardQuantity(41-42줄)와 standardTotalCost(50-51줄) 필드를 처리하는 마이그레이션 로직 존재 여부

기존 데이터가 있는 테이블에 nullable = false인 컬럼을 추가할 경우, 마이그레이션 실패를 방지하려면 반드시 기본값 설정 또는 데이터 백필 로직이 필요합니다.

src/main/java/com/sampoom/factory/api/bom/dto/BomEventDto.java (1)

32-45: 이벤트 스키마 확장 확인했습니다.
BOM 이벤트 페이로드에 totalCost 추가와 수량의 Double 전환이 프로젝션·서비스 층과 정합하게 맞춰져 있어 새 필드를 안정적으로 흘려보낼 수 있겠습니다.

src/main/java/com/sampoom/factory/api/bom/entity/BomProjection.java (1)

44-72: 프로젝션 필드 추가가 DTO 변경과 정합합니다.
totalCost를 컬럼으로 유지하면서 updateFromEvent에서 빌더에 실어 보내도록 한 덕분에 이벤트·프로젝션 사이 스키마가 깨지지 않습니다.

src/main/java/com/sampoom/factory/api/part/dto/PartEventDto.java (1)

30-37: Part 이벤트 페이로드 확장 잘 반영되었습니다.
standardQuantity와 standardTotalCost를 공개해 프로젝션 서비스가 새 기준값을 즉시 소비할 수 있게 되어 흐름이 자연스럽습니다.

src/main/java/com/sampoom/factory/api/part/entity/PartProjection.java (1)

44-105: 새 필드의 널 처리 확인 부탁드립니다.
standardQuantitystandardTotalCostnullable = false로 두었는데, 이벤트 페이로드는 Lombok 기본값이라 누락되면 null이 그대로 전달됩니다. 기존 이벤트 재생성이나 레거시 발행자가 아직 해당 필드를 채우지 않는 경우 ConstraintViolationException이 날 수 있으니, 발행 측에서 항상 값을 보내는지 한 번 더 점검하거나 디폴트/방어 로직을 추가해 주세요.

src/main/java/com/sampoom/factory/api/bom/entity/BomMaterialProjection.java (1)

37-45: Double 전환으로 이벤트 데이터를 충분히 담을 수 있게 되었습니다
수량을 Double로 전환하고 이벤트 업데이트 경로까지 일관되게 맞춰 주신 덕분에 소수점 데이터를 잃지 않고 반영할 수 있습니다. 👍

src/main/java/com/sampoom/factory/api/bom/service/BomProjectionService.java (1)

76-125: totalCost 전파가 전체 이벤트 흐름에 잘 맞춰졌습니다
생성/수정 경로 모두에서 totalCost와 이벤트 메타데이터를 일관되게 넘겨 주셔서 추후 감사나 재처리에도 안전하게 사용할 수 있겠습니다.

src/main/java/com/sampoom/factory/api/part/service/PartProjectionService.java (1)

62-137: standard 필드 전파가 균일하게 처리되었습니다*
생성·수정·삭제 전 경로에서 standardQuantitystandardTotalCost를 잃지 않도록 잘 반영해 두셔서, projection 일관성이 유지됩니다.

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

♻️ Duplicate comments (2)
src/main/java/com/sampoom/factory/api/part/service/PartOrderService.java (2)

122-124: Math.round 사용으로 부족량이 계속 누락됩니다

이전 리뷰에서 동일한 문제를 지적드렸지만 아직 Math.round가 남아 있어 1.2 → 1처럼 필요한 자재가 부족하다고 판단되지 않습니다. 생산·구매 전 과정에서 부정확한 수요가 이용되므로 반드시 올림(ceil)으로 교체해 주세요.

-                Long requiredQuantity = Math.round(bomMaterial.getQuantity() * item.getQuantity()); // Double에서 Long으로 변환
+                long requiredQuantity = (long) Math.ceil(bomMaterial.getQuantity() * item.getQuantity());

위 패턴을 이 파일 내 다른 동일 구간에도 적용해 주세요.

Also applies to: 139-141


291-309: Math.round로 부족량이 0이 되어 구매요청이 빠집니다

Math.round로 재고를 정수 반올림하면서 factoryMaterial.getQuantity() < required는 true인데 shortageAmount는 0이 되어 실제로 구매요청이 발생하지 않는 케이스가 그대로 남아 있습니다. 예) 필요량 1.6, 재고 1.6 → required=2(ceil 적용 시), 하지만 Math.round(1.6)=2라 shortageAmount=0이 되어 주문이 생략됩니다. 이전 코멘트에서 안내드린 Math.ceil 전환과 함께, 부족량은 실수 차이를 기준으로 올림해 정수화해야 안전합니다.

-                long required = Math.round(bomMaterial.getQuantity() * item.getQuantity()); // Double에서 long으로 변환
+                double requiredExact = bomMaterial.getQuantity() * item.getQuantity();
+                long required = (long) Math.ceil(requiredExact);
...
-                    long shortageAmount = required - (factoryMaterial != null ? Math.round(factoryMaterial.getQuantity()) : 0);
+                    double currentStock = factoryMaterial != null ? factoryMaterial.getQuantity() : 0d;
+                    long shortageAmount = (long) Math.max(0, Math.ceil(requiredExact - currentStock));
...
-                long required = Math.round(bomMaterial.getQuantity() * item.getQuantity()); // Double에서 long으로 변환
-                long currentStock = factoryMaterial != null ? Math.round(factoryMaterial.getQuantity()) : 0; // Double을 long으로 변환
+                double requiredExact = bomMaterial.getQuantity() * item.getQuantity();
+                long required = (long) Math.ceil(requiredExact);
+                double currentStock = factoryMaterial != null ? factoryMaterial.getQuantity() : 0d;
...
-                    long shortageAmount = required - currentStock;
+                    long shortageAmount = (long) Math.max(0, Math.ceil(requiredExact - currentStock));

이렇게 해야 부족량이 정밀하게 계산되고 표준수량 배수 로직도 정상적으로 동작합니다.

Also applies to: 813-879

🧹 Nitpick comments (1)
src/main/java/com/sampoom/factory/api/material/entity/FactoryMaterial.java (1)

36-48: 부동소수점 비교 시 엡실론 값 고려 권장

newQty < 0 직접 비교는 대부분의 경우 정상 동작하지만, 부동소수점 연산 오차로 인해 극히 작은 음수 값(예: -1e-15)이 발생할 수 있습니다. 더 안전한 비교를 위해 엡실론 값을 사용하는 것을 고려하세요.

다음과 같이 개선할 수 있습니다:

+    private static final double EPSILON = 1e-10;
+
     public void decreaseQuantity(Double amount) {
         if (amount == null || amount <= 0) {
             throw new BadRequestException(ErrorStatus.INVALID_QUANTITY);
         }
         if (this.quantity == null) {
             this.quantity = 0.0;
         }
         double newQty = this.quantity - amount;
-        if (newQty < 0) {
+        if (newQty < -EPSILON) {
             throw new BadRequestException(ErrorStatus.INSUFFICIENT_MATERIAL_QUANTITY);
         }
+        // 엡실론 범위 내의 음수는 0으로 처리
+        if (newQty < 0) {
+            newQty = 0.0;
+        }
         this.quantity = newQty;
     }

단, BigDecimal을 사용하면 이러한 문제를 근본적으로 해결할 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97b16e5 and 1f1e0be.

📒 Files selected for processing (7)
  • src/main/java/com/sampoom/factory/api/factory/service/BranchProjectionService.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/material/dto/MaterialResponseDto.java (2 hunks)
  • src/main/java/com/sampoom/factory/api/material/entity/FactoryMaterial.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java (4 hunks)
  • src/main/java/com/sampoom/factory/api/part/service/PartOrderSchedulerService.java (1 hunks)
  • src/main/java/com/sampoom/factory/api/part/service/PartOrderService.java (10 hunks)
  • src/main/java/com/sampoom/factory/api/purchase/service/PurchaseEventService.java (1 hunks)
⏰ 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 (5)
src/main/java/com/sampoom/factory/api/material/dto/MaterialResponseDto.java (1)

20-36: 소수 수량 대응 DTO 정합성 확보

quantity 필드와 withQuantity 메서드를 함께 Double로 맞춰주셔서 Projection/Service 전반의 소수 재고 흐름이 자연스럽게 이어지네요. 별도 캐스팅 부담 없이 그대로 직렬화할 수 있어 보입니다.

src/main/java/com/sampoom/factory/api/material/service/MaterialProjectionService.java (1)

65-175: standard 지표 및 타임스탬프 연동 반영 확인

standardQuantity/standardTotalCost를 생성·갱신·삭제 경로 모두에서 전달하고 updatedAt/sourceUpdatedAt까지 채워 주신 덕분에 이벤트 재생 시 Projection 정보가 완결되겠습니다. 초기 FactoryMaterial도 0.0으로 통일된 점이 전체 Double 전환과 잘 어울립니다.

src/main/java/com/sampoom/factory/api/part/service/PartOrderSchedulerService.java (1)

99-100: 소수 재고 차감 로직 개선

Math.round 대신 그대로 double을 넘겨주면서 소수 단위 BOM 사용량이 유실되지 않게 되었네요. 이전에 지적되었던 0으로 떨어지는 문제를 자연스럽게 해소한 것으로 보입니다.

src/main/java/com/sampoom/factory/api/factory/service/BranchProjectionService.java (1)

101-107: Double 초기값 정렬

factoryMaterial 초기화 수량을 0.0으로 전환해 다른 모듈과 타입 일관성을 맞춘 선택이네요. 추가 후처리 없이 바로 Double 연산이 들어갈 수 있겠습니다.

src/main/java/com/sampoom/factory/api/purchase/service/PurchaseEventService.java (1)

99-109: 입고 시 재고 누적 Double화

입고 처리에서 doubleValue를 사용해 FactoryMaterial 증가 로직과 정합성을 확보해 주셔서 Purchase 이벤트 흐름에서도 동일한 스케일을 유지할 수 있게 되었습니다.

@taemin3 taemin3 merged commit 134d59b into main Nov 8, 2025
6 checks passed
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