WOaky / 設計メモ

倉庫移動の再設計

受注時の個別在庫移動を廃止し、リクエスト → 承認 → 出庫 → 入庫 の業務フローに載せ替える。あわせて「会社のどこに在庫があるか」を受注確定の場で見えるようにする。

背景

1. 個別の在庫移動が何のためか分からない

受注を打つタイミングで個別に倉庫移動ができるが、なぜ個別に行うのかが不明。

調査結果: 受注画面の在庫移動は、伝票を draft で作った直後に出庫確定と入庫完了を同一リクエスト内で連続実行していた。in_transit(移動中)を一切経由せず完了まで飛ぶ。

$transfer = WarehouseTransfer::create([...'status' => 'draft']);
WarehouseTransferItem::create([...]);
$transfer->confirmToInTransit($userId);   // 即
$transfer->confirmToCompleted($userId);   // 即

承認も現場の出庫作業も介在しない、正式フローの裏口になっている。倉庫移動というより即時の在庫付け替え。「元に戻す」も取消ではなく逆方向の移動を新規作成する実装で、伝票が2件残る。

2. 会社として在庫があるかが分からない

一括受注確定画面に出るのは、その受注の出荷倉庫の有効残数のみ。足りないときに「どこかから移動できる可能性があるか」が判断できない。

やりたいこと: 有効残数が足りない場合に限り、会社全体で在庫があるかを見せる。理想は「どの倉庫にいくつあるか」まで。

現状の実装

移動フロー
draftin_transitcompleted は実装済み。shipped_by / received_by も記録される
在庫の動き
出庫時に from から減算、入庫時に to へ加算
有効残数
Stock::effectiveQuantity() = 実在庫 − 未消込outbound予約 + 未消込inbound予約。単一倉庫のみを対象
予約
倉庫移動は InventoryReservation を一切作らない
テスト
倉庫移動の自動テストは0件。ステータス定数も無く生文字列で判定

設計上の穴: 移動中の在庫が宙に浮く

出庫で from から減り、入庫で to に入る。その間、在庫はどの倉庫にも存在しない。会社の総在庫が一時的に減る。

受け皿として「移動中」という倉庫が作られていたが、これは倉庫マスタに物理拠点と状態が混在する構造で、しかも中身がどこ行きなのか辿れない。今回これを廃止する。

新しいフロー

  1. 受注担当 在庫移動をリクエストする 一括受注確定画面で有効残数が足りない行から、他倉庫の在庫を見てその場で依頼を飛ばす。リクエストは溜まっていく。
  2. 調達チーム リクエストを見て承認する 溜まったリクエストを確認し、承認すると移動伝票が draft で作られる。同じ from / to のリクエストはまとめて1伝票にできる。却下も履歴に残る。
  3. 出荷現場 出庫処理を行う 伝票に対して出庫を確定する。実出庫数を記録する。
  4. システム 移動中になり、有効残数から引かれる from に outbound 予約、to に inbound 予約が張られる。実在庫は動かさない。
  5. 入庫者 受け取って入庫状況を入力する 出庫数に対してブレがないかを実入庫数で入力。完了すると実在庫が動き、予約が消し込まれて有効残数が復帰する。

在庫の持ち方

移動中の在庫は from 倉庫に物理的に残したまま、予約で有効残数を動かす。「移動中」倉庫は使わない。

状態stocks(実在庫)予約有効残数への影響
draft 動かない なし なし
in_transit 動かない(from に置いたまま) from に outbound
to に inbound
from が減り、to が増える
completed from −出庫数
to +入庫数
両方とも消込 実在庫に置き換わる

Stock::effectiveQuantity() は既に 実在庫 − 未消込outbound + 未消込inbound なので、計算式は無改修source_typewarehouse_transfer_item を足すだけで乗る。発注の入荷予約と全く同じ構造。

結果として、from の有効残数は出庫時点で即引かれ、to には「間もなく着く分」が見え、会社の総在庫は移動中も正しいままになる。

スキーマ

transfer_requests 新規

id
company_id
product_id
from_warehouse_id      移動元
to_warehouse_id        移動先
quantity               依頼数
requested_by           依頼者
sales_order_id         任意 — どの受注発の依頼か
status                 pending | approved | rejected
warehouse_transfer_id  承認後に紐づく(複数リクエスト → 1伝票)
reject_reason
timestamps

warehouse_transfer_items カラム追加

quantity           予定数(承認時に確定)
quantity_shipped   実出庫数    ← 新規
quantity_received  実入庫数    ← 新規

ブレ = quantity_shipped − quantity_received

この3カラム構成は、発注の quantity / quantity_received と同型。UIも実装も揃う。

出庫伝票と入庫伝票は分けない

現行は既に warehouse_transfers の1レコードをステータスで進める形になっており、伝票が2つ生成されているわけではない。ブレの記録は上記の明細カラムで足りるため、1伝票のままとする。

UI

受注画面(一括確定モーダル)

有効残数が不足している行にのみバッジを出し、クリックで倉庫別の在庫を展開する。足りている行には何も出さない。

商品A  受注100  有効残数 20 ⚠
        └ [他倉庫に 350 あり]   ←クリックで展開
           会津       200個  [リクエスト]
           中部工場   120個  [リクエスト]
           東京        30個  [リクエスト]

商品B  受注50   有効残数 800      ←足りているので何も出ない

既存の個別即時移動(在庫移動フォーム・元に戻すボタン)は撤去し、この導線に置き換える。

移動リクエスト承認画面 新規

調達チーム向け。pending のリクエスト一覧から、同じ from / to をまとめて選択し、1つの移動伝票(draft)として承認する。却下は理由とともに履歴に残す。

倉庫移動画面 改修

draft で出庫確定、in_transit で実入庫数を入力して完了。出庫数と入庫数にブレがある場合は明示する。

実装の分割

A
スキーマ
transfer_requests 新設、明細に出庫数 / 入庫数を追加、ステータスを定数化(現在は生文字列)
B
在庫ロジックを予約方式へ変更
最も慎重に進める箇所。confirmToInTransit() の意味が「在庫を減らす」から「予約を張る」に変わる。この領域は現在テストが1件も無いため、テストを先に置いてから着手する
C
受注画面のリクエスト導線
不足行の他倉庫展開とリクエスト送信。個別即時移動の撤去。
D
調達チームの承認画面
pending 一覧、まとめて承認して1伝票化、却下。
E
倉庫移動画面の入庫入力
実入庫数の入力とブレ表示。

決めたこと

論点結論理由
移動中の表現 伝票ステータス 倉庫マスタに物理拠点と状態が混在するのを避ける。from / to が伝票に載るので「どこからどこへ、いくつ」が辿れる
移動中の在庫 from に残して予約で引く 会社の総在庫が移動中も正しい。既存の有効残数の式を変えずに済む
リクエストの持ち方 別テーブル 複数リクエストを1伝票にまとめられる。却下も履歴に残る
出庫 / 入庫の伝票 1伝票のまま 現行が既に1レコード。ブレは明細カラムで表現できる
他倉庫の表示 不足時のみ展開 必要なときだけ情報が出る。集計も不足行だけで済む
個別即時移動 撤去 承認も出庫作業も経ない裏口。リクエスト導線に置き換える