受注時の個別在庫移動を廃止し、リクエスト → 承認 → 出庫 → 入庫 の業務フローに載せ替える。あわせて、出荷ペースから「あと何日もつか」を機械的に出し、在庫が薄くなった倉庫へ人の判断で在庫を寄せられる状態を目指す。
受注を打つタイミングで個別に倉庫移動ができるが、なぜ個別に行うのかが不明。
調査結果: 受注画面の在庫移動は、伝票を draft で作った直後に出庫確定と入庫完了を同一リクエスト内で連続実行していた。in_transit(移動中)を一切経由せず完了まで飛ぶ。
$transfer = WarehouseTransfer::create([...'status' => 'draft']);
WarehouseTransferItem::create([...]);
$transfer->confirmToInTransit($userId); // 即
$transfer->confirmToCompleted($userId); // 即
承認も現場の出庫作業も介在しない、正式フローの裏口になっている。倉庫移動というより即時の在庫付け替え。「元に戻す」も取消ではなく逆方向の移動を新規作成する実装で、伝票が2件残る。
一括受注確定画面に出るのは、その受注の出荷倉庫の有効残数のみ。他倉庫から寄せられる可能性があるかが分からない。
ただし本質は「今回の受注が足りるか」ではない。今日の受注は捌けたが、有効残数が 1000 → 200 になった。明日も受注は来る。この 200 では耐えられない ——この判断をして、あらかじめ在庫を寄せておきたい。
つまり不足が 0 でもリクエストは発生する。むしろ出荷できた商品ほど在庫が減っており、補充判断の対象になる。
draft → in_transit → completed は実装済み。shipped_by / received_by も記録されるfrom から減算、入庫時に to へ加算Stock::effectiveQuantity() = 実在庫 − 未消込outbound予約 + 未消込inbound予約。単一倉庫のみを対象InventoryReservation を一切作らないtotal-summary が既存。選択受注を商品×倉庫で集約し、過不足まで算出済み出庫で from から減り、入庫で to に入る。その間、在庫はどの倉庫にも存在しない。会社の総在庫が一時的に減る。
受け皿として「移動中」という倉庫が作られていたが、これは倉庫マスタに物理拠点と状態が混在する構造で、しかも中身がどこ行きなのか辿れない。今回これを廃止する。
「200 個が薄いかどうか」は商品ごとに違うため、数量では判断できない。出荷ペースで日数に変換する。
日次平均出荷 = 直近N日の shipment_out 合計 ÷ N ← 商品 × 倉庫ごと
必要在庫 = 日次平均出荷 × カバー日数
残り日数 = 有効残数 ÷ 日次平均出荷
危ない = 残り日数 < カバー日数
N と カバー日数 は環境変数で持つ(閾値は環境変数で管理する既存方針に揃える)。
products.safety_stock_months というカラムは存在するが、本番 9,241 商品すべて NULL(充足率 0%)。lead_time_days / lot_size も同様に 0%。
入れ忘れではない。移行元の WO 旧システム(販売大臣)の商品マスタに、安全在庫・発注点・リードタイムに当たる項目が存在しないため、埋めようがなかった。9,241 商品へ手入力する運用も現実的ではない。
出荷実績なら stock_ledgers に商品 × 倉庫 × 日付で自動的に積まれるため、マスタ入力ゼロで立ち上がる。これが唯一の現実解。
現在の本番にある出荷実績はテストデータのみで、意味のある日次平均が出せない。運用開始直後は全商品が「実績なし」表示となり、数週間動かして初めて日数が出る。
それでもマスタ入力を待つより確実に立ち上がる。マスタを 9,241 件埋める運用は続かない。
商品A × 会津
合計受注 1000 / 出荷処理 800 / 有効残数 200
日次平均 40個 → 残り 5日分 ⚠ (14日分を切っています)
└ 他倉庫: 中部 500 / 東京 300 / 会津工場 120
└ 中部から 300 リクエスト →
商品B × 中部
合計受注 50 / 出荷処理 50 / 有効残数 1800
日次平均 3個 → 残り 600日分
有効残数の引き算は自動で効く。確定時に outbound 予約が張られるため、Stock::effectiveQuantity() を普通に呼ぶだけで出荷処理分が差し引かれた値が返る。特別なロジックは不要。
既存の total-summary は既に商品 × 倉庫で集約して過不足を出しているため、骨格はそのまま使える。追加するのは「出荷処理分」の列、日数判定、他倉庫の展開、リクエストボタン。
draft で作られる。同じ from / to のリクエストはまとめて1伝票にできる。却下も履歴に残る。
from に outbound 予約、to に inbound 予約が張られる。実在庫は動かさない。
移動中の在庫は from 倉庫に物理的に残したまま、予約で有効残数を動かす。「移動中」倉庫は使わない。
| 状態 | stocks(実在庫) | 予約 | 有効残数への影響 |
|---|---|---|---|
draft | 動かない | なし | なし |
in_transit | 動かない(from に置いたまま) | from に outbound to に inbound | from が減り、to が増える |
completed | from −出庫数 to +入庫数 | 両方とも消込 | 実在庫に置き換わる |
Stock::effectiveQuantity() は既に 実在庫 − 未消込outbound + 未消込inbound なので、計算式は無改修。source_type に warehouse_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伝票のままとする。
transfer_requests 新設、明細に出庫数 / 入庫数を追加、ステータスを定数化(現在は生文字列)confirmToInTransit() の意味が「在庫を減らす」から「予約を張る」に変わる。この領域は現在テストが1件も無いため、テストを先に置いてから着手する。total-summary に「出荷処理分」の列、出荷ペースからの日数判定、他倉庫の展開、リクエスト送信を追加。あわせて受注画面の個別即時移動を撤去。一括確定モーダル自体の改修は不要。| 論点 | 結論 | 理由 |
|---|---|---|
| リクエストの目的 | 不足の穴埋めではなく在庫水準の維持 | 今日捌けても明日の受注が来る。不足 0 でもリクエストは発生する |
| 判断の場 | 集約画面(total-summary)に一本化 | 商品 × 倉庫の集約単位で見れば依頼が1本にまとまる。受注ごとだと細切れの依頼が調達チームに大量に届く |
| 危険判定 | 出荷ペースから「残り日数」を出す | 数量では商品ごとの事情を判断できない。日数なら誰でも分かる。機械が出して人が決める |
| 判定のデータ源 | 出荷実績(安全在庫マスタは使わない) | WO 旧システムに安全在庫の項目が無く、9,241 商品への手入力は続かない。実績なら自動で貯まる |
| 移動中の表現 | 伝票ステータス | 倉庫マスタに物理拠点と状態が混在するのを避ける。from / to が伝票に載るので「どこからどこへ、いくつ」が辿れる |
| 移動中の在庫 | from に残して予約で引く | 会社の総在庫が移動中も正しい。既存の有効残数の式を変えずに済む |
| リクエストの持ち方 | 別テーブル | 複数リクエストを1伝票にまとめられる。却下も履歴に残る |
| 出庫 / 入庫の伝票 | 1伝票のまま | 現行が既に1レコード。ブレは明細カラムで表現できる |
| 個別即時移動 | 撤去 | 承認も出庫作業も経ない裏口。集約画面のリクエスト導線に置き換える |