# 在庫管理機能実装案 > 作成日: 2026-03-13 > 対象プロジェクト: `keinasystem_t02` > 目的: 肥料と農薬を含む資材在庫管理機能を追加し、施肥計画から参照できるようにする --- ## 1. 結論 本プロジェクトの在庫管理機能は、以下の構成で実装することを推奨する。 - 共通の資材マスタ `Material` を新設する - 肥料専用情報は `FertilizerProfile` として分離する - 農薬専用情報は `PesticideProfile` として分離する - 在庫数は共通の入出庫履歴 `StockTransaction` で管理する - 施肥計画は従来どおり「肥料のみ」を対象とし、農薬は混在させない つまり、設計方針は「共通化するのは在庫管理の土台だけ、業務利用は肥料と農薬で分ける」である。 --- ## 2. 背景 既存実装では、`backend/apps/fertilizer/models.py` の `Fertilizer` モデルが以下の役割を持っている。 - 肥料マスタ - 施肥計画エントリの参照先 - 窒素量計算の対象 - 分配計画の集計対象 特に、以下の点から `Fertilizer` はすでに肥料専用モデルになっている。 - `capacity_kg`, `nitrogen_pct`, `phosphorus_pct`, `potassium_pct` を持つ - 施肥計画 `FertilizationEntry` が `Fertilizer` を直接参照している - 自動計算 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 構成に合わせて調整する。 ```python 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` モデルをすぐに削除したり全面改名したりしない。 理由: - 施肥計画機能がすでに本番稼働中 - `FertilizationEntry` が `Fertilizer` を直接参照している - 分配計画、PDF、計算 API まで連鎖的に影響する ### 6.2 移行方針 段階的には次のどちらかが現実的である。 #### 案A: 既存 `Fertilizer` を残しつつ `Material` を追加する 推奨度: 高 構成: - `Material` を新設 - 既存 `Fertilizer` に `material = OneToOneField(Material)` を追加 - 施肥計画は当面 `Fertilizer` を使い続ける - 在庫管理は `Material` を使う メリット: - 既存施肥機能への影響が最小 - 農薬機能をあとから追加しやすい - データ移行が安全 デメリット: - 一時的に `Material` と `Fertilizer` の二重管理に見える #### 案B: `Fertilizer` を廃止して `Material + FertilizerProfile` に完全移行する 推奨度: 中 構成: - `Fertilizer` の役割を `Material + FertilizerProfile` に移す - `FertilizationEntry` は `FertilizerProfile` または `Material` を参照するよう変更 メリット: - モデル構造がきれいになる デメリット: - 既存コード改修範囲が大きい - 移行難易度が高い - 本番稼働機能への影響が大きい 現時点では案Aを採用し、運用が安定してから必要なら案Bへ寄せるのがよい。 --- ## 7. 推奨する app 構成 在庫管理を `apps/fertilizer` に混ぜ込まず、新規 app として切り出すことを推奨する。 候補: - `backend/apps/materials` - `backend/apps/inventory` 推奨: - 共通マスタも在庫履歴も含めて `backend/apps/materials` 理由: - 肥料専用 app に農薬や種苗を混ぜないため - 将来の資材計画、散布履歴、購入管理の拡張がしやすいため 推奨ファイル構成: ```text 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}/` レスポンス例: ```json { "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` だけ返す - 必要に応じて現在庫も返す レスポンス例: ```json [ { "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` を追加 - `Material` と `StockTransaction` を作成 - 既存 `Fertilizer` 1件ごとに `Material(material_type='fertilizer')` を作成 - `Fertilizer.material` を紐づける migration を追加 ### フェーズ2 - 在庫一覧画面と入出庫登録画面を実装 - 施肥計画画面で肥料在庫参照を追加 ### フェーズ3 - `PesticideProfile` を追加 - 農薬マスタ管理画面を実装 - 将来の農薬散布計画または散布履歴と連携 ### データ移行時の注意 - `Fertilizer.name` と `Material.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 単一テーブル案 `Fertilizer` を `Material` に改名し、肥料も農薬も1テーブルで管理する案。 採用しない理由: - nullable 項目が増えすぎる - `material_type` 条件分岐が画面や API に広がる - 施肥機能に農薬が混ざりやすい - モデル名と実務概念がずれて保守性が落ちる ### 13.2 完全分離案 肥料在庫テーブルと農薬在庫テーブルを別々に作る案。 採用しない理由: - 入出庫、棚卸、在庫集計ロジックを二重実装することになる - 将来の資材種追加に弱い - 一覧画面や集計の統一感がなくなる --- ## 14. 推奨実装順 最も安全な進め方は以下である。 1. `apps/materials` を追加する 2. `Material` と `StockTransaction` を実装する 3. 既存 `Fertilizer` に `material` を紐づける 4. 肥料だけを対象に在庫一覧を表示する 5. 施肥計画画面に在庫参照を追加する 6. その後、農薬マスタと農薬在庫を追加する --- ## 15. 最終提案 本案件では、次の方針を正式案とすることを推奨する。 - 在庫管理の主軸は `Material` と `StockTransaction` - 肥料と農薬は詳細テーブルで分ける - 施肥計画は既存 `Fertilizer` 中心のまま維持する - まずは「在庫参照」まで実装し、自動出庫連携は後続フェーズに回す この方針であれば、既存の施肥計画機能を壊さず、農薬対応と将来の資材拡張に耐える構成になる。 --- ## 16. 次アクション案 次に着手するなら、以下の順で進めるとよい。 1. `apps/materials` の Django モデルを確定する 2. migration 方針を決める 3. API 設計を確定する 4. フロントの画面導線を決める 5. 施肥計画画面に出す在庫情報の粒度を決める 必要であれば次の段階として、上記案をベースに - Django モデル実装 - migration 作成方針 - API 詳細仕様 - 画面ワイヤー案 まで具体化できる。