식자재 재고를 다룰 때 의외로 발목을 잡는 건 계산이 아니라 단위예요. 박스로 사서 개수로 쓰고, kg으로 들여와 g으로 소진하죠. 이 어긋남을 그대로 두면 재고 숫자가 금방 신뢰를 잃어요. 그래서 케이터링 식자재 ERP에서는 재고 단위 환산을 데이터 설계의 중심에 뒀어요. 전체 그림은 케이터링 식자재 ERP 구축 사례에 있습니다.
재고 단위 환산이 없으면 무엇이 깨지나요
재고 단위 환산이 없으면 같은 품목이 화면마다 다른 숫자로 보여요. 식자재는 사는 단위와 쓰는 단위가 다른 경우가 흔해요. 예를 들어 음료는 "1박스(24개)"로 입고되지만 주문에는 "몇 개"로 나가요. 입고는 박스로, 출고는 개수로 기록되면 재고 숫자가 뒤죽박죽이 되죠.
이걸 사람이 매번 머릿속으로 "박스를 개수로" 바꾸면 실수가 쌓이고, 재고 장부는 결국 아무도 안 믿게 돼요. 그래서 우리는 품목마다 두 단위를 명시적으로 나눴어요.
- 재고 단위(stock_unit): 사고 보관하는 단위. 예: 박스, 포대
- 사용 단위(use_unit): 레시피·주문에서 쓰는 단위. 예: 개, g
- 환산율(conversion_rate): 재고 단위 1이 사용 단위로 얼마인지. 예: 1박스 = 24개
저장은 한 단위로, 입력은 편한 단위로
핵심 결정은 재고를 항상 '사용 단위' 하나로 저장하는 거예요. 화면에서는 박스로 넣든 개수로 넣든, DB의 현재고는 언제나 사용 단위로 통일돼 있어야 비교와 합산이 깨지지 않아요. 입고는 보통 재고 단위(박스)로 들어오니까, 저장 직전에 환산율을 곱해 사용 단위로 바꾸고, 화면에 박스로 보여줘야 할 때는 반대로 환산율로 나눠 표시해요.
입고는 환산율을 곱해 저장하고, 화면 표시는 환산율로 나눠 되돌립니다.
출고는 이미 사용 단위(개)로 나가니까 환산 없이 그대로 재고에서 빼요. 이렇게 "입력 단위는 자유롭게, 저장 단위는 하나로" 원칙을 지키면, 현재고는 언제나 한 기준으로 정리돼 있어요.
기능
입고·출고가 재고에 실시간 반영
입고를 등록하면 환산율로 계산된 사용 단위 수량만큼 현재고가 늘고, 출고는 사용 단위 그대로 빠집니다. 저장 단위를 하나로 통일해 어느 화면에서 봐도 재고가 같은 기준으로 보입니다.
입고·재고 실시간 반영 화면

환산 후 통일된 현재고 화면
수정·삭제까지 버텨야 진짜 무결성
환산만으로는 부족해요. 재고 무결성은 입고·출고를 나중에 고치거나 지울 때 진짜로 시험받아요. 입고 수량을 뒤늦게 수정하면 이미 반영된 재고와 새 값의 차이만큼만 다시 조정해야지, 통째로 다시 더하면 재고가 배로 튀어요. 그래서 갱신은 항상 "차이(diff)"로 처리했어요.
# 입고 수정: 이전 값과의 차이만큼만 재고 조정
diff = converted_quantity - converted_quantity_before_last_save
item.with_lock do
item.update_column(:current_stock, item.current_stock + diff)
end또 하나, 재고 갱신은 with_lock으로 감쌌어요. 여러 사람이 동시에 입출고를 등록해도 현재고 값이 서로 덮어써 꼬이지 않게, 갱신 구간을 잠금 안에서 처리한 거예요. 참고로 이 시스템은 마이너스 재고를 막지 않고 허용하되 경고로 표시해요. 현장에서는 전산보다 실물이 먼저 나가는 일이 있어서, 흐름을 막기보다 "지금 장부상 마이너스"라는 신호를 주는 쪽을 택했어요.
예제로 보는 환산의 흐름
숫자로 따라가 볼게요. 어떤 음료의 환산율이 "1박스 = 24개"라고 하죠. 담당자가 입고 화면에서 "10박스"를 넣으면, 저장 직전에 환산율이 곱해져 현재고에는 240개가 더해져요. 이후 주문으로 이 음료가 50개 나가면 현재고는 240개에서 50개를 빼 190개가 되고요. 입고는 박스로, 출고는 개수로 넣었지만 재고는 언제나 190개라는 한 숫자로 정리돼요.
화면마다 보여주는 단위가 다른 이유
같은 재고라도 화면 목적에 따라 보여주는 단위를 달리했어요. 입고 화면에서는 사람이 박스로 사니 재고 단위로 입력받는 게 자연스럽고, 재고 현황·부족 목록에서는 실제 소진 감각과 맞는 사용 단위로 보여주는 게 나아요. 저장은 사용 단위 하나로 통일해 두고, 표시 단계에서만 환산율로 앞뒤로 바꿔 주는 구조라 이게 가능해요. 저장 단위와 표시 단위를 분리한 덕에, 어느 화면에서도 숫자가 어긋나지 않으면서 각 화면은 가장 읽기 편한 단위를 쓸 수 있는 거죠. 소모품처럼 애초에 한 단위로만 다루는 품목은 환산율을 1로 두면 같은 코드가 그대로 동작해요. 특별한 예외 분기를 두지 않고 환산율이라는 하나의 값으로 단위가 같은 품목과 다른 품목을 함께 다루니, 품목이 늘어도 로직이 복잡해지지 않는 게 이 설계의 이점이에요.
이렇게 통일된 사용 단위 재고는 그다음 단계인 재고 소진 예측의 정확한 입력이 돼요. 단위가 흔들리면 예측도 흔들리니까요. 결국 재고 단위 환산은 화려한 기능이 아니라, 이후의 모든 계산이 딛고 설 바닥을 다지는 일이에요. 이 바닥이 튼튼해야 재고 현황도, 소진 예측도, 정산에 얽힌 매출 집계도 같은 숫자 위에서 돌아가요. 필요 재료가 어떤 단위로 계산되는지는 자재명세서(BOM) 필요재료 자동산출에서 이어집니다.
자주 묻는 질문
재고 단위와 사용 단위를 왜 나누나요?
식자재는 사는 단위(박스·포대)와 쓰는 단위(개·g)가 다른 경우가 많아서예요. 두 단위를 나누고 환산율로 이으면, 입고는 편한 단위로 넣으면서도 재고는 한 기준으로 통일돼 화면마다 숫자가 어긋나지 않습니다.
재고는 어느 단위로 저장하나요?
현재고는 항상 사용 단위 하나로 저장합니다. 입고가 재고 단위로 들어오면 환산율을 곱해 사용 단위로 바꿔 반영하고, 화면에 박스처럼 재고 단위로 보여줄 때만 역산해 표시해요. 저장 단위를 하나로 고정하는 게 무결성의 핵심이에요.
입고 수량을 나중에 수정하면 재고가 꼬이지 않나요?
꼬이지 않게 차이(diff)만큼만 조정합니다. 이전 반영값과 새 값의 차이를 계산해 그만큼만 현재고를 더하거나 빼고, 이 갱신을 잠금 안에서 처리해 동시 수정에도 값이 어긋나지 않도록 했어요.