Files
keinasystem/改善案/在庫管理機能実装案.md
2026-03-13 13:13:40 +09:00

16 KiB

在庫管理機能実装案

作成日: 2026-03-13 対象プロジェクト: keinasystem_t02 目的: 肥料と農薬を含む資材在庫管理機能を追加し、施肥計画から参照できるようにする


1. 結論

本プロジェクトの在庫管理機能は、以下の構成で実装することを推奨する。

  • 共通の資材マスタ Material を新設する
  • 肥料専用情報は FertilizerProfile として分離する
  • 農薬専用情報は PesticideProfile として分離する
  • 在庫数は共通の入出庫履歴 StockTransaction で管理する
  • 施肥計画は従来どおり「肥料のみ」を対象とし、農薬は混在させない

つまり、設計方針は「共通化するのは在庫管理の土台だけ、業務利用は肥料と農薬で分ける」である。


2. 背景

既存実装では、backend/apps/fertilizer/models.pyFertilizer モデルが以下の役割を持っている。

  • 肥料マスタ
  • 施肥計画エントリの参照先
  • 窒素量計算の対象
  • 分配計画の集計対象

特に、以下の点から Fertilizer はすでに肥料専用モデルになっている。

  • capacity_kg, nitrogen_pct, phosphorus_pct, potassium_pct を持つ
  • 施肥計画 FertilizationEntryFertilizer を直接参照している
  • 自動計算 API が Fertilizer の窒素成分を使っている

このため、既存の Fertilizer を単純に「資材」へ拡張すると、以下の問題が起きやすい。

  • 農薬に不要な肥料属性が大量にぶら下がる
  • 施肥画面で農薬が見えてしまう
  • 今後の農薬散布機能で肥料向けロジックが混ざる
  • フロントエンドと API の条件分岐が増える

3. 要件整理

今回の要件は大きく分けて次の2つである。

3.1 共通でほしいこと

  • 肥料と農薬を「在庫」という観点で一元管理したい
  • 入庫、出庫、棚卸、調整の履歴を残したい
  • 現在庫を一覧で見たい
  • 将来的に種苗など他資材にも拡張できるようにしたい

3.2 分けておきたいこと

  • 施肥計画では肥料だけを使いたい
  • 農薬散布計画や散布履歴では農薬だけを使いたい
  • 肥料特有の計算項目と農薬特有の項目は混在させたくない

4. 推奨アーキテクチャ

4.1 モデル分離の考え方

以下の3層で整理する。

  1. 共通資材層
  2. 種別別の詳細層
  3. 在庫履歴層

4.2 推奨モデル構成

Material

資材共通マスタ。在庫管理の主キーになる。

想定フィールド:

  • id
  • name 資材名
  • material_type 資材種別
  • maker メーカー
  • stock_unit 在庫単位
  • is_active 使用可否
  • notes 備考
  • created_at
  • updated_at

material_type の候補:

  • fertilizer
  • pesticide
  • seedling
  • other

stock_unit の候補:

  • bag
  • bottle
  • kg
  • liter
  • piece

FertilizerProfile

肥料専用の属性を持つ。

想定フィールド:

  • id
  • material OneToOneField(Material)
  • capacity_kg 1袋重量
  • nitrogen_pct
  • phosphorus_pct
  • potassium_pct
  • dilution_note 任意、必要なら

PesticideProfile

農薬専用の属性を持つ。

想定フィールド:

  • id
  • material OneToOneField(Material)
  • registration_no 農薬登録番号
  • formulation 剤型
  • usage_unit 使用単位
  • dilution_ratio 希釈倍率
  • active_ingredient 有効成分
  • category 殺菌剤、除草剤、殺虫剤など
  • remark_for_use 使用上メモ

StockTransaction

在庫の増減を管理する履歴テーブル。現在庫はこの集計で算出する。

想定フィールド:

  • id
  • material ForeignKey(Material)
  • transaction_type
  • quantity
  • unit
  • occurred_on
  • reference_type
  • reference_id
  • note
  • created_by
  • created_at

transaction_type の候補:

  • purchase
  • use
  • adjustment_plus
  • adjustment_minus
  • inventory_count
  • discard

quantity は正の数で持ち、増減方向は transaction_type で判定する方式を推奨する。

MaterialStockSnapshot

初期フェーズでは不要。パフォーマンス課題が出た場合のみ追加する。

  • 日次や月次の在庫スナップショット
  • 一覧高速化用の集計テーブル

5. Django モデル案

以下はイメージであり、実装時には既存 app 構成に合わせて調整する。

from django.conf import settings
from django.db import models


class Material(models.Model):
    class MaterialType(models.TextChoices):
        FERTILIZER = 'fertilizer', '肥料'
        PESTICIDE = 'pesticide', '農薬'
        SEEDLING = 'seedling', '種苗'
        OTHER = 'other', 'その他'

    class StockUnit(models.TextChoices):
        BAG = 'bag', '袋'
        BOTTLE = 'bottle', '本'
        KG = 'kg', 'kg'
        LITER = 'liter', 'L'
        PIECE = 'piece', '個'

    name = models.CharField(max_length=100, verbose_name='資材名')
    material_type = models.CharField(max_length=20, choices=MaterialType.choices, verbose_name='資材種別')
    maker = models.CharField(max_length=100, blank=True, null=True, verbose_name='メーカー')
    stock_unit = models.CharField(max_length=20, choices=StockUnit.choices, verbose_name='在庫単位')
    is_active = models.BooleanField(default=True, verbose_name='使用中')
    notes = models.TextField(blank=True, null=True, verbose_name='備考')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['material_type', 'name']
        constraints = [
            models.UniqueConstraint(fields=['material_type', 'name'], name='uniq_material_type_name'),
        ]


class FertilizerProfile(models.Model):
    material = models.OneToOneField(Material, on_delete=models.CASCADE, related_name='fertilizer_profile')
    capacity_kg = models.DecimalField(max_digits=8, decimal_places=3, blank=True, null=True)
    nitrogen_pct = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
    phosphorus_pct = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
    potassium_pct = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)


class PesticideProfile(models.Model):
    material = models.OneToOneField(Material, on_delete=models.CASCADE, related_name='pesticide_profile')
    registration_no = models.CharField(max_length=100, blank=True, null=True)
    formulation = models.CharField(max_length=100, blank=True, null=True)
    usage_unit = models.CharField(max_length=50, blank=True, null=True)
    dilution_ratio = models.CharField(max_length=100, blank=True, null=True)
    active_ingredient = models.CharField(max_length=200, blank=True, null=True)
    category = models.CharField(max_length=100, blank=True, null=True)


class StockTransaction(models.Model):
    class TransactionType(models.TextChoices):
        PURCHASE = 'purchase', '入庫'
        USE = 'use', '使用'
        ADJUSTMENT_PLUS = 'adjustment_plus', '棚卸増'
        ADJUSTMENT_MINUS = 'adjustment_minus', '棚卸減'
        INVENTORY_COUNT = 'inventory_count', '棚卸記録'
        DISCARD = 'discard', '廃棄'

    material = models.ForeignKey(Material, on_delete=models.PROTECT, related_name='stock_transactions')
    transaction_type = models.CharField(max_length=30, choices=TransactionType.choices)
    quantity = models.DecimalField(max_digits=10, decimal_places=3)
    occurred_on = models.DateField()
    note = models.TextField(blank=True, null=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    created_at = models.DateTimeField(auto_now_add=True)

6. 既存肥料機能とのつなぎ方

6.1 重要方針

既存の Fertilizer モデルをすぐに削除したり全面改名したりしない。

理由:

  • 施肥計画機能がすでに本番稼働中
  • FertilizationEntryFertilizer を直接参照している
  • 分配計画、PDF、計算 API まで連鎖的に影響する

6.2 移行方針

段階的には次のどちらかが現実的である。

案A: 既存 Fertilizer を残しつつ Material を追加する

推奨度: 高

構成:

  • Material を新設
  • 既存 Fertilizermaterial = OneToOneField(Material) を追加
  • 施肥計画は当面 Fertilizer を使い続ける
  • 在庫管理は Material を使う

メリット:

  • 既存施肥機能への影響が最小
  • 農薬機能をあとから追加しやすい
  • データ移行が安全

デメリット:

  • 一時的に MaterialFertilizer の二重管理に見える

案B: Fertilizer を廃止して Material + FertilizerProfile に完全移行する

推奨度: 中

構成:

  • Fertilizer の役割を Material + FertilizerProfile に移す
  • FertilizationEntryFertilizerProfile または Material を参照するよう変更

メリット:

  • モデル構造がきれいになる

デメリット:

  • 既存コード改修範囲が大きい
  • 移行難易度が高い
  • 本番稼働機能への影響が大きい

現時点では案Aを採用し、運用が安定してから必要なら案Bへ寄せるのがよい。


7. 推奨する app 構成

在庫管理を apps/fertilizer に混ぜ込まず、新規 app として切り出すことを推奨する。

候補:

  • backend/apps/materials
  • backend/apps/inventory

推奨:

  • 共通マスタも在庫履歴も含めて backend/apps/materials

理由:

  • 肥料専用 app に農薬や種苗を混ぜないため
  • 将来の資材計画、散布履歴、購入管理の拡張がしやすいため

推奨ファイル構成:

backend/apps/materials/
├── models.py
├── serializers.py
├── views.py
├── urls.py
├── admin.py
└── migrations/

8. API 実装案

8.1 資材マスタ API

  • GET /api/materials/materials/
  • POST /api/materials/materials/
  • GET /api/materials/materials/{id}/
  • PUT /api/materials/materials/{id}/
  • DELETE /api/materials/materials/{id}/

主なクエリ:

  • ?material_type=fertilizer
  • ?material_type=pesticide
  • ?active=true

8.2 在庫履歴 API

  • GET /api/materials/stock-transactions/
  • POST /api/materials/stock-transactions/
  • GET /api/materials/stock-transactions/{id}/

主なクエリ:

  • ?material_id=
  • ?material_type=
  • ?date_from=
  • ?date_to=

8.3 在庫集計 API

  • GET /api/materials/stock-summary/
  • GET /api/materials/stock-summary/{material_id}/

レスポンス例:

{
  "material_id": 12,
  "name": "コシヒカリ専用一発肥料",
  "material_type": "fertilizer",
  "stock_unit": "bag",
  "current_stock": "18.00",
  "last_transaction_date": "2026-03-10"
}

8.4 施肥計画からの参照 API

施肥計画画面では、以下のような肥料在庫参照 API を追加する。

  • GET /api/materials/fertilizer-stock-options/

用途:

  • 施肥計画で使う肥料一覧を返す
  • material_type=fertilizer だけ返す
  • 必要に応じて現在庫も返す

レスポンス例:

[
  {
    "material_id": 12,
    "fertilizer_id": 3,
    "name": "コシヒカリ専用一発肥料",
    "current_stock": "18.00",
    "stock_unit": "bag"
  }
]

ここで重要なのは、施肥計画画面に農薬を返さない専用 API を用意することである。


9. フロントエンド実装案

9.1 新規画面

  • /materials
    • 資材一覧
  • /materials/fertilizers
    • 肥料マスタ管理
  • /materials/pesticides
    • 農薬マスタ管理
  • /materials/stock
    • 在庫一覧
  • /materials/stock/new
    • 入出庫登録

9.2 一覧画面の基本列

  • 資材名
  • 種別
  • メーカー
  • 在庫単位
  • 現在庫
  • 最終入出庫日
  • 使用中フラグ

9.3 施肥計画画面での使い方

既存の肥料選択 UI は維持しつつ、以下の補助情報だけ追加する。

  • 肥料名の横に現在庫を表示
  • 在庫不足が見込まれる場合は注意表示
  • 必要であれば、計画合計と在庫差分を表示

例:

  • コシヒカリ専用一発肥料 在庫18袋
  • 計画必要数 24袋 / 不足 6袋

これにより、施肥計画の UX を壊さずに在庫参照を実現できる。


10. 在庫計算ロジック

10.1 基本ルール

現在庫は StockTransaction の累積で求める。

計算式:

現在庫 = 入庫合計 - 出庫合計 + 調整増合計 - 調整減合計

10.2 推奨ルール

  • 在庫は更新値を直接持たず、履歴を正とする
  • 誤登録時は履歴修正または取消履歴で対応する
  • 初期在庫は adjustment_plus で登録する
  • 棚卸差異は adjustment_plus / adjustment_minus で表現する

10.3 将来連携

将来的には以下と自動連携できる。

  • 施肥計画確定時に肥料必要量を引当
  • 施肥実績登録時に肥料を出庫
  • 農薬散布実績登録時に農薬を出庫

ただし初期フェーズでは「計画から参照のみ」に留め、在庫自動減算はまだ行わない方が安全である。


11. データ移行案

フェーズ1

  • apps/materials を追加
  • MaterialStockTransaction を作成
  • 既存 Fertilizer 1件ごとに Material(material_type='fertilizer') を作成
  • Fertilizer.material を紐づける migration を追加

フェーズ2

  • 在庫一覧画面と入出庫登録画面を実装
  • 施肥計画画面で肥料在庫参照を追加

フェーズ3

  • PesticideProfile を追加
  • 農薬マスタ管理画面を実装
  • 将来の農薬散布計画または散布履歴と連携

データ移行時の注意

  • Fertilizer.nameMaterial.name の不整合を防ぐ
  • 肥料削除制約と Material の削除制約を揃える
  • 既存 API を壊さない
  • 既存フロント型定義を急に置き換えない

12. 画面・業務フロー案

12.1 資材登録

  1. 資材種別を選ぶ
  2. 共通項目を入力する
  3. 肥料なら肥料専用項目を入力する
  4. 農薬なら農薬専用項目を入力する

12.2 在庫登録

  1. 資材を選ぶ
  2. 入庫 or 出庫 or 棚卸調整を選ぶ
  3. 数量と日付を入力する
  4. メモを残す

12.3 施肥計画での利用

  1. 従来どおり肥料を選ぶ
  2. 肥料ごとの在庫を横に表示する
  3. 計画全体の必要量と現在庫の差分を見る
  4. 必要なら購入判断につなげる

13. 採用しない案

13.1 単一テーブル案

FertilizerMaterial に改名し、肥料も農薬も1テーブルで管理する案。

採用しない理由:

  • nullable 項目が増えすぎる
  • material_type 条件分岐が画面や API に広がる
  • 施肥機能に農薬が混ざりやすい
  • モデル名と実務概念がずれて保守性が落ちる

13.2 完全分離案

肥料在庫テーブルと農薬在庫テーブルを別々に作る案。

採用しない理由:

  • 入出庫、棚卸、在庫集計ロジックを二重実装することになる
  • 将来の資材種追加に弱い
  • 一覧画面や集計の統一感がなくなる

14. 推奨実装順

最も安全な進め方は以下である。

  1. apps/materials を追加する
  2. MaterialStockTransaction を実装する
  3. 既存 Fertilizermaterial を紐づける
  4. 肥料だけを対象に在庫一覧を表示する
  5. 施肥計画画面に在庫参照を追加する
  6. その後、農薬マスタと農薬在庫を追加する

15. 最終提案

本案件では、次の方針を正式案とすることを推奨する。

  • 在庫管理の主軸は MaterialStockTransaction
  • 肥料と農薬は詳細テーブルで分ける
  • 施肥計画は既存 Fertilizer 中心のまま維持する
  • まずは「在庫参照」まで実装し、自動出庫連携は後続フェーズに回す

この方針であれば、既存の施肥計画機能を壊さず、農薬対応と将来の資材拡張に耐える構成になる。


16. 次アクション案

次に着手するなら、以下の順で進めるとよい。

  1. apps/materials の Django モデルを確定する
  2. migration 方針を決める
  3. API 設計を確定する
  4. フロントの画面導線を決める
  5. 施肥計画画面に出す在庫情報の粒度を決める

必要であれば次の段階として、上記案をベースに

  • Django モデル実装
  • migration 作成方針
  • API 詳細仕様
  • 画面ワイヤー案

まで具体化できる。