導入(問題提起)

在庫が「あるはず」「ないはず」の“勘”で動いていませんか? 出荷直前に欠品が判明して駆け回る、棚卸のたびに差異が大量に出る、発注点があいまいで過剰在庫になる——その多くはExcel台帳による管理限界が原因です。Excelは単独作業には便利ですが、同時編集・履歴管理・権限・リアルタイム性・モバイル入力に弱く、現場とバックオフィス間のタイムラグが必ず発生します。小規模であっても、在庫は「トランザクションの積み上げでしか確定しない」性質を持つため、最初からWeb前提の設計に切り替えると損失が減ります。本記事では、最小構成から始める在庫のExcel Web化と、Pythonを用いた実装の具体像を解説します。キーワードは「Webシステム開発」「業務システム」「Python」「Excel Web化」です。

課題の詳細説明

Excel台帳運用で典型的に発生する課題は次のとおりです。

  • 記帳遅延と二重計上:現場は出荷を優先し、入力は後回し。結果、別担当が別シートに記入して数字が二重化。
  • 同時編集の上書き事故:共有Excelで先着順に保存された内容が正になるため、誰がどこを更新したか追えない。
  • ロット/期限/シリアルの抜け:品目の“数”だけを記録し、同一品番の異ロット混在に気づけない。
  • ロケーションの粒度不一致:倉庫/棚/番地の粒度が人によって違い、ピッキング導線が乱れる。
  • 棚卸差異の恒常化:理論在庫と実地棚卸の差異原因が不明で、再発防止策が立てられない。
  • 外部システム連携の欠如:販売・会計・ECとデータ連携できず、同じ内容を何度も手入力する。
  • KPIが見えない:欠品率、在庫回転日数、死蔵在庫などの指標が取れないため、判断が経験頼みになる。

解決方法

在庫は「イベント(入出庫)の履歴を正規化して集計する」ことで正しくなります。最小のWeb在庫システムは、以下の設計原則で実装します。

  1. データモデルの正規化:items(品目) / locations(ロケーション) / lots(ロット) / txns(入出庫) / stocks(スナップショット任意)
  2. トランザクション原則:在庫数はtxnsの集計で算出。直接在庫数を書き換えない(不整合の温床を断つ)。
  3. ロケーション階層:倉庫 > エリア > 棚 > 番地を正規化。ピッキングルート最適化の前提にする。
  4. バーコード/QR運用:入庫・出庫・棚卸でスキャン。スマホ(PWA)対応で現場入力を即時化。
  5. 権限と承認:出庫は承認制、棚卸期間はロック。ロットの手動編集は管理者限定。
  6. 監査ログ:誰が/いつ/どこで/何を/いくつ動かしたかを自動記録し、変更前後も保持(改ざん防止)。
  7. しきい値アラート:安全在庫割れ/過剰在庫/消費期限接近を定期ジョブで通知(メール/Slack/LINE)。
  8. 連携:販売管理・会計・ECへCSV/API連携。まずはCSVで始め、将来REST/Webhookに拡張。

具体例

UI/機能の最小セット例は以下です。

  • ダッシュボード:在庫サマリ(ロケーション×品目)、欠品アラート、死蔵在庫ランキング。
  • 入庫:発注番号/仕入先/数量/ロット/期限/ロケーションを登録。バーコードで品目とロットを紐づけ。
  • 出庫:ピッキングリストをもとにスキャン。ロットはFIFO/FEFO(期限優先)ポリシーを選択。
  • 棚卸:対象ロケーションをロックし、スキャン差分を自動抽出。差異の承認ワークフローを用意。
  • 移動:倉庫間/棚間移動を1イベントとして記録。人的移動と車両移動を区別できるメモ欄。
  • 帳票:入出庫履歴、ロットトレーサビリティ、棚卸差異報告をPDF/CSV出力。

技術的な解説(Python × 小規模Webアーキテクチャ)

最小構成は「Python + Bottle(またはFastAPI) + SQLite」。小規模では十分な性能を持ち、将来はPostgreSQLに移行できます。

  • データモデル(例:SQLite)

-- 品目
CREATE TABLE items (
  id INTEGER PRIMARY KEY,
  sku TEXT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  unit TEXT NOT NULL,
  safety_stock INTEGER DEFAULT 0
);
-- ロケーション(倉庫>エリア>棚>番地)
CREATE TABLE locations (
  id INTEGER PRIMARY KEY,
  code TEXT UNIQUE NOT NULL,
  parent_id INTEGER REFERENCES locations(id)
);
-- ロット
CREATE TABLE lots (
  id INTEGER PRIMARY KEY,
  item_id INTEGER NOT NULL REFERENCES items(id),
  lot_code TEXT NOT NULL,
  expiry DATE
);
-- 入出庫トランザクション(+入庫 / -出庫 / 0移動)
CREATE TABLE txns (
  id INTEGER PRIMARY KEY,
  ts DATETIME NOT NULL,
  type TEXT CHECK(type IN ('IN','OUT','MOVE')) NOT NULL,
  item_id INTEGER NOT NULL REFERENCES items(id),
  lot_id INTEGER REFERENCES lots(id),
  qty INTEGER NOT NULL,
  from_loc INTEGER REFERENCES locations(id),
  to_loc INTEGER REFERENCES locations(id),
  actor TEXT NOT NULL,
  note TEXT
);
CREATE INDEX idx_txns_item_loc ON txns(item_id, to_loc, from_loc);
  • ルーティング(例:Bottle)

from bottle import Bottle, request, response
import sqlite3

app = Bottle()

def db():
    return sqlite3.connect('inventory.db')

@app.post('/inbound')
def inbound():
    data = request.json
    with db() as con:
        con.execute('INSERT INTO txns(ts,type,item_id,lot_id,qty,to_loc,actor) VALUES(datetime("now"),"IN",?,?,?,?,?)',
                    (data['item_id'], data.get('lot_id'), data['qty'], data['to_loc'], data['actor']))
    return {'ok': True}

@app.post('/outbound')
def outbound():
    data = request.json
    with db() as con:
        # 在庫残の検証は集計で行い、残不足なら400を返す
        cur = con.execute('SELECT COALESCE(SUM(CASE WHEN type="IN" THEN qty WHEN type="OUT" THEN -qty ELSE 0 END),0)\
                           FROM txns WHERE item_id=? AND (to_loc=? OR from_loc=?)',
                          (data['item_id'], data['loc'], data['loc']))
        if cur.fetchone()[0] < data['qty']:
            response.status = 400
            return {'error': 'insufficient stock'}
        con.execute('INSERT INTO txns(ts,type,item_id,lot_id,qty,from_loc,actor) VALUES(datetime("now"),"OUT",?,?,?,?,?)',
                    (data['item_id'], data.get('lot_id'), data['qty'], data['loc'], data['actor']))
    return {'ok': True}
  • 権限・監査:JWTでユーザを識別し、actorに記録。管理者のみロット編集や差異調整が可能。
  • モバイル最適化:PWA化でホーム画面から起動、カメラでバーコード読取。
  • 拡張:需要予測や最適発注量(EOQ)計算は、まずCSVエクスポート→Pythonで試算→有効ならAPI化。

導入の流れ

  1. 現状棚卸とデータ標準化:SKU/単位/ロケーション命名を決め、Excelの項目を揃える。
  2. PoC(主要品目×単一倉庫):Pythonの最小Webを用意し、入出庫/棚卸だけを稼働させる。
  3. バーコード導入:既存JAN/自社コードのどちらかに統一。スマホ読取を現場で検証。
  4. 連携の着手:販売/会計のインポート形式を決め、まずはCSV出力で月次締めを省力化。
  5. 指標の定義:欠品率・回転日数・滞留在庫のKPIをダッシュボードで見える化。
  6. 拡張:マルチ倉庫・ロットトレーサビリティ・予約引当・入荷予定の取込を段階追加。

まとめ

在庫は「正しいイベントを、正しい粒度で、正しいときに記録」できれば必ず良くなります。Excelで始めた管理も、Web前提に切り替えるだけで二重計上・欠品・死蔵が目に見えて減少します。Pythonによる小規模なWebシステム開発であれば、コストを抑えながら現場に馴染むUIを素早く提供でき、業務システムとしての信頼性と拡張性を両立できます。まずは単拠点・主要品目から試行し、うまくいった方式を全体へ展開しましょう。

「Webシステム開発のご相談は monou まで」