CoreERP 재고 엔진 안정화 & Warehouse CRUD 작업 기록

최병현·2026년 2월 21일

coreerp project

목록 보기
8/44
post-thumbnail

1. 이번 단계의 관점

이번 단계는 CoreERP가 단순 CRUD 프로젝트에서 벗어나 “재고가 변하는 구조”를 실제 ERP 형태로 고정한 구간이다.

특히 다음 두 가지를 명확히 확정했다.

  • 재고 스냅샷(Inventory) + 원장(StockTx) 이중 구조
  • 마스터 데이터(Warehouse) API 안정화

이 시점부터 재고는 단순 수치가 아니라, “트랜잭션 기반으로 관리되는 데이터”가 되었다.


2. 재고 설계 기준 고정

CoreERP의 재고 구조는 다음 원칙을 따른다.

  • Inventory → 현재 재고 상태 (조회 최적화)
  • StockTx → 재고 변동 이력 (Audit Log)

현재 수량은 Inventory에만 존재하고, 왜 변했는지는 StockTx에 남긴다.

이 분리는 ERP에서 반드시 필요한 구조다. 조회 성능과 감사 추적을 동시에 확보하기 위해서다.


3. 재고 조정(Adjust) 로직 완성

입고/출고 이전 단계에서도 실무에서는 재고 조정이 필요하다.

  • 초기 세팅
  • 실사 반영
  • 불량/폐기
  • 오류 정정

따라서 조정은 단순 수량 변경이 아니라, 합법적인 재고 변동 경로로 정의했다.


3-1. 서비스 핵심 로직

@Transactional
public AdjustStockResponse adjust(AdjustStockRequest req) {

    Item item = itemRepository.findById(req.itemId())
            .orElseThrow(() ->
                    new IllegalArgumentException("존재하지 않는 품목입니다.")
            );

    Warehouse warehouse = warehouseRepository.findById(req.warehouseId())
            .orElseThrow(() ->
                    new IllegalArgumentException("존재하지 않는 창고입니다.")
            );

    Inventory inventory = inventoryRepository
            .findByItem_ItemIdAndWarehouse_WarehouseId(
                    req.itemId(),
                    req.warehouseId()
            )
            .orElseGet(() ->
                    Inventory.create(item, warehouse)
            );

    // 도메인 메서드로 수량 변경
    inventory.adjust(req.qtyDelta());

    // 원장 기록
    stockTxRepository.save(
            StockTx.createAdjust(
                    item,
                    warehouse,
                    req.qtyDelta(),
                    req.memo()
            )
    );

    return AdjustStockResponse.from(inventory);
}

재고 변경과 원장 기록을 하나의 트랜잭션으로 묶었다.

성공하면 모두 반영, 실패하면 전부 롤백된다.


3-2. 음수 재고 방지

 public void adjust(int delta) { int next = this.currentQty + delta; if (next < 0) { throw new InsufficientStockException("재고가 부족합니다."); } this.currentQty = next; } 

재고 부족은 500이 아니라 409(CONFLICT)로 응답하도록 예외를 분리했다.

이로써 “서버 에러”와 “비즈니스 충돌”을 구분했다.


4. Warehouse CRUD 안정화

재고는 반드시 마스터 데이터 위에서만 동작해야 한다.

따라서 Warehouse CRUD를 최소 완성 기준으로 고정했다.

  • 등록 (POST)
  • 목록 조회 (GET)
  • 상세 조회 (GET)
  • warehouseCode UNIQUE 검증

4-1. Service 핵심


@Transactional
public WarehouseResponse create(CreateWarehouseRequest req) {

    if (warehouseRepository.existsByWarehouseCode(req.warehouseCode())) {
        throw new IllegalArgumentException("이미 존재하는 창고 코드입니다.");
    }

    Warehouse saved = warehouseRepository.save(
            Warehouse.builder()
                    .warehouseCode(req.warehouseCode())
                    .warehouseName(req.warehouseName())
                    .isActive(true)
                    .build()
    );

    return WarehouseResponse.from(saved);
}

마스터는 단순 저장이 아니라, “중복 불가”라는 비즈니스 규칙을 먼저 고정했다.


5. 이번 단계의 의미

이번 단계는 단순 기능 추가가 아니다.

CoreERP의 재고 시스템이 “수치 변경 프로그램”에서 “트랜잭션 기반 ERP 엔진”으로 전환된 시점이다.

  • 재고는 반드시 원장을 남긴다
  • 재고는 음수가 될 수 없다
  • 마스터 없이는 재고도 없다

이 기준을 고정했기 때문에, 향후 입고/출고/이동/발주가 붙어도 구조가 흔들리지 않는다.


6. 다음 단계

  • Vendor CRUD 안정화
  • Inbound 확정 로직 구현
  • StockTx 이력 조회 API
  • Reason Code 체계 도입

정리

CoreERP는 현재 다음 구조를 확보했다.

  • 마스터 → 재고 스냅샷 → 원장 기록
  • 재고 조정 트랜잭션 완성
  • 409 비즈니스 예외 분리
  • Warehouse CRUD API 안정화

이제부터는 기능을 붙이는 것이 아니라, ERP 업무 흐름을 연결하는 단계로 들어간다.

profile
Develop

0개의 댓글