
이번 단계는 CoreERP가 단순 CRUD 프로젝트에서 벗어나 “재고가 변하는 구조”를 실제 ERP 형태로 고정한 구간이다.
특히 다음 두 가지를 명확히 확정했다.
이 시점부터 재고는 단순 수치가 아니라, “트랜잭션 기반으로 관리되는 데이터”가 되었다.
CoreERP의 재고 구조는 다음 원칙을 따른다.
현재 수량은 Inventory에만 존재하고, 왜 변했는지는 StockTx에 남긴다.
이 분리는 ERP에서 반드시 필요한 구조다. 조회 성능과 감사 추적을 동시에 확보하기 위해서다.
입고/출고 이전 단계에서도 실무에서는 재고 조정이 필요하다.
따라서 조정은 단순 수량 변경이 아니라, 합법적인 재고 변동 경로로 정의했다.
@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);
}
재고 변경과 원장 기록을 하나의 트랜잭션으로 묶었다.
성공하면 모두 반영, 실패하면 전부 롤백된다.
public void adjust(int delta) { int next = this.currentQty + delta; if (next < 0) { throw new InsufficientStockException("재고가 부족합니다."); } this.currentQty = next; } 재고 부족은 500이 아니라 409(CONFLICT)로 응답하도록 예외를 분리했다.
이로써 “서버 에러”와 “비즈니스 충돌”을 구분했다.
재고는 반드시 마스터 데이터 위에서만 동작해야 한다.
따라서 Warehouse CRUD를 최소 완성 기준으로 고정했다.
@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);
}
마스터는 단순 저장이 아니라, “중복 불가”라는 비즈니스 규칙을 먼저 고정했다.
이번 단계는 단순 기능 추가가 아니다.
CoreERP의 재고 시스템이 “수치 변경 프로그램”에서 “트랜잭션 기반 ERP 엔진”으로 전환된 시점이다.
이 기준을 고정했기 때문에, 향후 입고/출고/이동/발주가 붙어도 구조가 흔들리지 않는다.
CoreERP는 현재 다음 구조를 확보했다.
이제부터는 기능을 붙이는 것이 아니라, ERP 업무 흐름을 연결하는 단계로 들어간다.