- 在庫管理機能実装案.md: セクション23(引当・散布確定ワークフロー)を追加 - CODEX.md: Phase 1完了を受け、Phase 1.5実装指示に全面書き換え Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 KiB
CODEX 実装指示書: 施肥計画連携・引当機能(Phase 1.5)
作成日: 2026-03-14 対象:
keinasystem_t02設計案:改善案/在庫管理機能実装案.md(セクション23が対象) 前提: Phase 1(セクション1〜16)は実装済み。apps/materialsが稼働中。
0. 実装の前提と絶対ルール
現在のプロジェクト構造(Phase 1 実装済み)
keinasystem_t02/
├── backend/
│ ├── keinasystem/
│ │ ├── settings.py # apps.materials 登録済み
│ │ └── urls.py # /api/materials/ 登録済み
│ └── apps/
│ ├── fields/ # 圃場管理(Field モデル)
│ ├── plans/ # 作付け計画(Crop, Variety モデル)
│ ├── fertilizer/ # 施肥計画(Fertilizer, FertilizationPlan, FertilizationEntry 等)
│ │ └── models.py # Fertilizer.material = OneToOneField(Material) 追加済み
│ └── materials/ # 在庫管理(Material, FertilizerProfile, PesticideProfile, StockTransaction)
│ └── models.py # Phase 1 で作成済み
└── frontend/
└── src/
├── types/index.ts # Material, StockTransaction, StockSummary 定義済み
├── lib/api.ts # axios インスタンス(変更不要)
├── components/
│ └── Navbar.tsx # 在庫管理メニュー追加済み
└── app/
├── fertilizer/ # 施肥計画(既存)← 今回変更対象
│ ├── page.tsx
│ ├── [id]/edit/page.tsx
│ └── _components/FertilizerEditPage.tsx
└── materials/ # 在庫管理(Phase 1 で作成済み)← 今回変更対象
├── page.tsx
└── _components/StockOverview.tsx
技術スタック
- Backend: Django 5.2 + Django REST Framework + PostgreSQL 16
- Frontend: Next.js 14 (App Router) + TypeScript strict + Tailwind CSS
- 認証: SimpleJWT(ヘッダー
Authorization: Bearer <token>) - Docker:
docker compose exec backend python manage.py ...
絶対ルール
- 既存の施肥計画 CRUD(作成・編集・削除・PDF)を壊さない
FertilizationEntry → Fertilizerの FK は変更しないFertilizerモデルは改名・削除しない- フロントエンドでは
alert()/confirm()を使わない(インラインバナーで表示) - TypeScript strict mode に従う
- Next.js 14 では
paramsは通常のオブジェクト(use(params)は使わない) - マイグレーションは段階的に。1つのマイグレーションで複数の大きな変更をしない
1. 実装スコープ(Phase 1.5)
やること
StockTransactionにreserveタイプ追加StockTransactionにfertilization_planFK 追加(マイグレーション)FertilizationPlanにis_confirmed/confirmed_at追加(マイグレーション)- 在庫集計 API に
reserved_stock/available_stock追加 - 施肥計画の保存時に引当(reserve)を自動作成
- 施肥計画の削除時に引当を自動解除
- 散布確定 API(
confirm_spreading) - 肥料在庫一覧 API(施肥計画画面用)
- フロントエンド: 在庫一覧に引当表示追加
- フロントエンド: 施肥計画編集に在庫参照追加
- フロントエンド: 散布確定画面
- フロントエンド: 施肥計画一覧に確定状態表示追加
やらないこと
- 公式データ同期(FAMIC、農水省)
- 別名辞書(MaterialAlias)
- LLM 調査支援
- 農薬散布計画の在庫連携
2. バックエンド: モデル変更
2.1 StockTransaction の変更 (backend/apps/materials/models.py)
現在のコード(変更が必要な箇所のみ抜粋):
class StockTransaction(models.Model):
class TransactionType(models.TextChoices):
PURCHASE = 'purchase', '入庫'
USE = 'use', '使用'
ADJUSTMENT_PLUS = 'adjustment_plus', '棚卸増'
ADJUSTMENT_MINUS = 'adjustment_minus', '棚卸減'
DISCARD = 'discard', '廃棄'
INCREASE_TYPES = {
TransactionType.PURCHASE,
TransactionType.ADJUSTMENT_PLUS,
}
DECREASE_TYPES = {
TransactionType.USE,
TransactionType.ADJUSTMENT_MINUS,
TransactionType.DISCARD,
}
変更後:
class StockTransaction(models.Model):
class TransactionType(models.TextChoices):
PURCHASE = 'purchase', '入庫'
USE = 'use', '使用'
RESERVE = 'reserve', '引当' # ← 追加
ADJUSTMENT_PLUS = 'adjustment_plus', '棚卸増'
ADJUSTMENT_MINUS = 'adjustment_minus', '棚卸減'
DISCARD = 'discard', '廃棄'
INCREASE_TYPES = {
TransactionType.PURCHASE,
TransactionType.ADJUSTMENT_PLUS,
}
DECREASE_TYPES = {
TransactionType.USE,
TransactionType.RESERVE, # ← 追加
TransactionType.ADJUSTMENT_MINUS,
TransactionType.DISCARD,
}
フィールド追加(既存フィールドの後に追加):
fertilization_plan = models.ForeignKey(
'fertilizer.FertilizationPlan',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='stock_reservations',
verbose_name='施肥計画',
)
2.2 FertilizationPlan の変更 (backend/apps/fertilizer/models.py)
フィールド追加(既存フィールドの後に追加):
is_confirmed = models.BooleanField(
default=False, verbose_name='散布確定済み'
)
confirmed_at = models.DateTimeField(
null=True, blank=True, verbose_name='散布確定日時'
)
2.3 マイグレーション
マイグレーション1: backend/apps/materials/migrations/0002_stocktransaction_fertilization_plan.py
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materials', '0001_initial'),
('fertilizer', '0005_fertilizer_material'),
]
operations = [
migrations.AddField(
model_name='stocktransaction',
name='fertilization_plan',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='stock_reservations',
to='fertilizer.fertilizationplan',
verbose_name='施肥計画',
),
),
]
注意: TransactionType の choices 変更はマイグレーション不要(Django は choices をDBレベルで強制しないため)。
マイグレーション2: backend/apps/fertilizer/migrations/0006_fertilizationplan_confirmation.py
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fertilizer', '0005_fertilizer_material'),
]
operations = [
migrations.AddField(
model_name='fertilizationplan',
name='is_confirmed',
field=models.BooleanField(default=False, verbose_name='散布確定済み'),
),
migrations.AddField(
model_name='fertilizationplan',
name='confirmed_at',
field=models.DateTimeField(
blank=True, null=True, verbose_name='散布確定日時'
),
),
]
3. バックエンド: 引当ロジック
3.1 引当の作成・解除ヘルパー関数
backend/apps/materials/stock_service.py を新規作成:
from django.db import transaction
from .models import StockTransaction
@transaction.atomic
def create_reserves_for_plan(plan):
"""施肥計画の全エントリについて引当トランザクションを作成する。
既存の引当は全削除してから再作成する(差分更新ではなく全置換)。
"""
# 既存の引当を全削除
StockTransaction.objects.filter(
fertilization_plan=plan,
transaction_type='reserve',
).delete()
# plan が確定済みなら引当を作らない(use が既にある)
if plan.is_confirmed:
return
for entry in plan.entries.select_related('fertilizer__material'):
material = getattr(entry.fertilizer, 'material', None)
if material is None:
# Fertilizer.material が未連携の場合はスキップ
continue
StockTransaction.objects.create(
material=material,
transaction_type='reserve',
quantity=entry.bags,
occurred_on=plan.updated_at.date() if plan.updated_at else plan.created_at.date(),
note=f'施肥計画「{plan.name}」からの引当',
fertilization_plan=plan,
)
@transaction.atomic
def delete_reserves_for_plan(plan):
"""施肥計画に紐づく全引当トランザクションを削除する。"""
StockTransaction.objects.filter(
fertilization_plan=plan,
transaction_type='reserve',
).delete()
@transaction.atomic
def confirm_spreading(plan, actual_entries):
"""散布確定: 引当を削除し、実績数量で use トランザクションを作成する。
actual_entries: list of dict
[{"field_id": int, "fertilizer_id": int, "actual_bags": Decimal}, ...]
actual_bags=0 の行は引当解除のみ(use を作成しない)
"""
from apps.fertilizer.models import Fertilizer
from django.utils import timezone
# 既存の引当を全削除
delete_reserves_for_plan(plan)
# 実績 > 0 の行について use トランザクションを作成
today = timezone.now().date()
for entry_data in actual_entries:
actual_bags = entry_data['actual_bags']
if actual_bags <= 0:
continue
try:
fertilizer = Fertilizer.objects.select_related('material').get(
id=entry_data['fertilizer_id']
)
except Fertilizer.DoesNotExist:
continue
material = getattr(fertilizer, 'material', None)
if material is None:
continue
StockTransaction.objects.create(
material=material,
transaction_type='use',
quantity=actual_bags,
occurred_on=today,
note=f'施肥計画「{plan.name}」散布確定',
fertilization_plan=plan,
)
# 計画を確定済みに更新
plan.is_confirmed = True
plan.confirmed_at = timezone.now()
plan.save(update_fields=['is_confirmed', 'confirmed_at'])
3.2 施肥計画 ViewSet の変更 (backend/apps/fertilizer/views.py)
既存の FertilizationPlanViewSet に以下の変更を加える。
保存時の引当自動作成
perform_create と perform_update をオーバーライドして、保存後に引当を作成する:
from apps.materials.stock_service import (
create_reserves_for_plan,
delete_reserves_for_plan,
confirm_spreading as confirm_spreading_service,
)
class FertilizationPlanViewSet(viewsets.ModelViewSet):
# ... 既存コード ...
def perform_create(self, serializer):
instance = serializer.save()
create_reserves_for_plan(instance)
def perform_update(self, serializer):
instance = serializer.save()
create_reserves_for_plan(instance)
def perform_destroy(self, instance):
delete_reserves_for_plan(instance)
instance.delete()
散布確定アクション
from rest_framework.decorators import action
from decimal import Decimal
class FertilizationPlanViewSet(viewsets.ModelViewSet):
# ... 既存コード ...
@action(detail=True, methods=['post'], url_path='confirm_spreading')
def confirm_spreading(self, request, pk=None):
plan = self.get_object()
if plan.is_confirmed:
return Response(
{'detail': 'この計画は既に散布確定済みです。'},
status=status.HTTP_400_BAD_REQUEST,
)
entries_data = request.data.get('entries', [])
if not entries_data:
return Response(
{'detail': '実績データが空です。'},
status=status.HTTP_400_BAD_REQUEST,
)
actual_entries = []
for entry in entries_data:
actual_entries.append({
'field_id': entry['field_id'],
'fertilizer_id': entry['fertilizer_id'],
'actual_bags': Decimal(str(entry.get('actual_bags', 0))),
})
confirm_spreading_service(plan, actual_entries)
serializer = self.get_serializer(plan)
return Response(serializer.data)
3.3 施肥計画 Serializer の変更 (backend/apps/fertilizer/serializers.py)
FertilizationPlanSerializer(読み取り用)に is_confirmed / confirmed_at を追加:
class FertilizationPlanSerializer(serializers.ModelSerializer):
# ... 既存フィールド ...
is_confirmed = serializers.BooleanField(read_only=True)
confirmed_at = serializers.DateTimeField(read_only=True)
class Meta:
model = FertilizationPlan
fields = [
# ... 既存フィールド ...,
'is_confirmed', 'confirmed_at',
]
4. バックエンド: 在庫集計 API の変更
4.1 StockSummarySerializer の変更 (backend/apps/materials/serializers.py)
class StockSummarySerializer(serializers.Serializer):
material_id = serializers.IntegerField()
name = serializers.CharField()
material_type = serializers.CharField()
material_type_display = serializers.CharField()
maker = serializers.CharField()
stock_unit = serializers.CharField()
stock_unit_display = serializers.CharField()
is_active = serializers.BooleanField()
current_stock = serializers.DecimalField(max_digits=10, decimal_places=3)
reserved_stock = serializers.DecimalField(max_digits=10, decimal_places=3) # ← 追加
available_stock = serializers.DecimalField(max_digits=10, decimal_places=3) # ← 追加
last_transaction_date = serializers.DateField(allow_null=True)
4.2 StockSummaryView の変更 (backend/apps/materials/views.py)
在庫集計のループ内で reserved_stock と available_stock を計算する:
for material in queryset:
transactions = list(material.stock_transactions.all())
increase = sum(
txn.quantity for txn in transactions
if txn.transaction_type in StockTransaction.INCREASE_TYPES
)
decrease = sum(
txn.quantity for txn in transactions
if txn.transaction_type in StockTransaction.DECREASE_TYPES
)
reserved = sum(
txn.quantity for txn in transactions
if txn.transaction_type == 'reserve'
)
last_date = max((txn.occurred_on for txn in transactions), default=None)
current = increase - decrease # 引当込みの在庫(引当分は既に引かれている)
results.append({
'material_id': material.id,
'name': material.name,
'material_type': material.material_type,
'material_type_display': material.get_material_type_display(),
'maker': material.maker,
'stock_unit': material.stock_unit,
'stock_unit_display': material.get_stock_unit_display(),
'is_active': material.is_active,
'current_stock': current + reserved, # 引当を戻した「物理的な在庫」
'reserved_stock': reserved, # 引当中の数量
'available_stock': current, # 利用可能在庫(引当済み分を除く)
'last_transaction_date': last_date,
})
在庫計算の定義:
current_stock: 物理的に倉庫にある数量(入庫 - 使用 - 廃棄 ± 調整)reserved_stock: そのうち施肥計画で引き当てられている数量available_stock: 新しい計画に使える数量(= current_stock - reserved_stock)
4.3 肥料在庫 API(施肥計画画面用)
backend/apps/materials/views.py に追加:
class FertilizerStockView(generics.ListAPIView):
"""施肥計画画面用: 肥料の在庫情報を返す"""
permission_classes = [IsAuthenticated]
serializer_class = StockSummarySerializer
def get_queryset(self):
return None
def list(self, request, *args, **kwargs):
queryset = Material.objects.filter(
material_type='fertilizer',
is_active=True,
).prefetch_related('stock_transactions')
results = []
for material in queryset:
transactions = list(material.stock_transactions.all())
increase = sum(
txn.quantity for txn in transactions
if txn.transaction_type in StockTransaction.INCREASE_TYPES
)
decrease = sum(
txn.quantity for txn in transactions
if txn.transaction_type in StockTransaction.DECREASE_TYPES
)
reserved = sum(
txn.quantity for txn in transactions
if txn.transaction_type == 'reserve'
)
current = increase - decrease
results.append({
'material_id': material.id,
'name': material.name,
'material_type': material.material_type,
'material_type_display': material.get_material_type_display(),
'maker': material.maker,
'stock_unit': material.stock_unit,
'stock_unit_display': material.get_stock_unit_display(),
'is_active': material.is_active,
'current_stock': current + reserved,
'reserved_stock': reserved,
'available_stock': current,
'last_transaction_date': max(
(t.occurred_on for t in transactions), default=None
),
})
serializer = StockSummarySerializer(results, many=True)
return Response(serializer.data)
backend/apps/materials/urls.py に追加:
urlpatterns = [
path('', include(router.urls)),
path('stock-summary/', views.StockSummaryView.as_view(), name='stock-summary'),
path('fertilizer-stock/', views.FertilizerStockView.as_view(), name='fertilizer-stock'), # ← 追加
]
5. フロントエンド: 型定義の変更
5.1 StockTransaction 型に reserve 追加 (frontend/src/types/index.ts)
変更前:
transaction_type: 'purchase' | 'use' | 'adjustment_plus' | 'adjustment_minus' | 'discard';
変更後:
transaction_type: 'purchase' | 'use' | 'reserve' | 'adjustment_plus' | 'adjustment_minus' | 'discard';
5.2 StockSummary 型に引当フィールド追加
変更前:
export interface StockSummary {
material_id: number;
name: string;
material_type: 'fertilizer' | 'pesticide' | 'seedling' | 'other';
material_type_display: string;
maker: string;
stock_unit: string;
stock_unit_display: string;
is_active: boolean;
current_stock: string;
last_transaction_date: string | null;
}
変更後:
export interface StockSummary {
material_id: number;
name: string;
material_type: 'fertilizer' | 'pesticide' | 'seedling' | 'other';
material_type_display: string;
maker: string;
stock_unit: string;
stock_unit_display: string;
is_active: boolean;
current_stock: string;
reserved_stock: string; // ← 追加
available_stock: string; // ← 追加
last_transaction_date: string | null;
}
5.3 FertilizationPlan 型に確定フィールド追加
既存の FertilizationPlan インターフェースに追加:
export interface FertilizationPlan {
// ... 既存フィールド ...
is_confirmed: boolean; // ← 追加
confirmed_at: string | null; // ← 追加
}
6. フロントエンド: 画面変更
6.1 在庫一覧の引当表示 (frontend/src/app/materials/_components/StockOverview.tsx)
現在庫の表示を変更:
変更前:
現在庫: 18
変更後:
在庫 18袋(引当 12袋)/ 利用可能 6袋
引当が0の場合は引当表示を省略する。
6.2 施肥計画編集画面の在庫参照 (frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx)
施肥計画の編集画面(マトリクス表)で、肥料列ヘッダーに在庫情報を表示する。
追加表示(肥料名の下に小さく):
仁井田米有機
在庫 18袋 / 計画計 24袋
計画合計が在庫を超える場合は赤文字で「不足 6袋」を表示する。
データ取得: ページ読み込み時に GET /api/materials/fertilizer-stock/ を呼び、
Fertilizer.material の OneToOne 経由で material_id と紐づける。
紐づけロジック:
GET /api/fertilizer/fertilizers/で肥料一覧を取得(既存)GET /api/materials/materials/?material_type=fertilizerで Material 一覧を取得Fertilizer.nameとMaterial.nameを突き合わせる(同名で作成されているため一致する)
または、Fertilizer の serializer に material_id を追加して直接紐づける(推奨)。
Fertilizer serializer への追加(backend/apps/fertilizer/serializers.py):
class FertilizerSerializer(serializers.ModelSerializer):
material_id = serializers.IntegerField(source='material.id', read_only=True, default=None)
class Meta:
model = Fertilizer
fields = [
# ... 既存フィールド ...,
'material_id',
]
6.3 施肥計画一覧の確定状態表示 (frontend/src/app/fertilizer/page.tsx)
各計画行に確定状態を表示:
- 未確定: 通常表示 + 「散布確定」ボタン
- 確定済み: 背景色変更(例: 薄い青)+ 「確定済み ✓」バッジ + 確定日時
6.4 散布確定画面
実装方法: モーダルまたは専用ページ。施肥計画一覧の「散布確定」ボタンから起動。
画面構成:
┌─ 散布確定: 「計画名」──────────────────────────────┐
│ │
│ 肥料: 仁井田米有機 │
│ ┌─────────────┬──────┬──────────┐ │
│ │ 圃場 │ 計画 │ 実績 │ │
│ ├─────────────┼──────┼──────────┤ │
│ │ 上の田 │ 3袋 │ [ 3 ] │ │
│ │ 下の田 │ 4袋 │ [ 3.5 ] │ │
│ │ 山の畑 │ 2袋 │ [ 0 ] │ │
│ └─────────────┴──────┴──────────┘ │
│ │
│ 肥料: 土佐勤農党 │
│ ┌─────────────┬──────┬──────────┐ │
│ │ 圃場 │ 計画 │ 実績 │ │
│ ├─────────────┼──────┼──────────┤ │
│ │ ... │ ... │ [ ... ] │ │
│ └─────────────┴──────┴──────────┘ │
│ │
│ [キャンセル] [一括確定] │
└─────────────────────────────────────────────────────┘
動作:
- 施肥計画のエントリを肥料ごとにグループ化して表示
- 「実績」列は計画値がプリセットされた数値入力欄
- 修正が必要な行だけ数値を変更する
- 実績を0にした行は「未散布」として引当解除される
- 「一括確定」で
POST /api/fertilizer/plans/{id}/confirm_spreading/を呼ぶ
API リクエスト:
{
"entries": [
{"field_id": 1, "fertilizer_id": 3, "actual_bags": 3.0},
{"field_id": 2, "fertilizer_id": 3, "actual_bags": 3.5},
{"field_id": 3, "fertilizer_id": 3, "actual_bags": 0}
]
}
7. API エンドポイント一覧(Phase 1.5 で追加・変更)
新規
| メソッド | パス | 認証 | 説明 |
|---|---|---|---|
| POST | /api/fertilizer/plans/{id}/confirm_spreading/ |
JWT | 散布確定(reserve→use変換) |
| GET | /api/materials/fertilizer-stock/ |
JWT | 肥料在庫一覧(施肥計画画面用) |
変更
| メソッド | パス | 変更内容 |
|---|---|---|
| POST/PUT | /api/fertilizer/plans/ |
保存後に reserve 自動作成 |
| DELETE | /api/fertilizer/plans/{id}/ |
削除前に reserve 自動削除 |
| GET | /api/fertilizer/plans/ |
レスポンスに is_confirmed, confirmed_at 追加 |
| GET | /api/fertilizer/fertilizers/ |
レスポンスに material_id 追加 |
| GET | /api/materials/stock-summary/ |
レスポンスに reserved_stock, available_stock 追加 |
8. 実装順序(厳守)
Step 1: バックエンド — モデル・マイグレーション
apps/materials/models.pyにreserveタイプ追加、DECREASE_TYPES更新、fertilization_planFK 追加apps/fertilizer/models.pyにis_confirmed,confirmed_at追加apps/materials/migrations/0002_stocktransaction_fertilization_plan.py作成apps/fertilizer/migrations/0006_fertilizationplan_confirmation.py作成
Step 2: バックエンド — ロジック・API
apps/materials/stock_service.py作成(引当作成・解除・散布確定ヘルパー)apps/fertilizer/views.pyのFertilizationPlanViewSetにperform_create,perform_update,perform_destroyオーバーライド追加apps/fertilizer/views.pyにconfirm_spreadingアクション追加apps/fertilizer/serializers.pyにis_confirmed,confirmed_at追加apps/fertilizer/serializers.pyのFertilizerSerializerにmaterial_id追加apps/materials/serializers.pyのStockSummarySerializerにreserved_stock,available_stock追加apps/materials/views.pyのStockSummaryViewで引当集計を追加apps/materials/views.pyにFertilizerStockView追加apps/materials/urls.pyにfertilizer-stock/パス追加
Step 3: フロントエンド
types/index.tsにreserveタイプ追加、StockSummaryに引当フィールド追加、FertilizationPlanに確定フィールド追加app/materials/_components/StockOverview.tsxに引当表示追加app/materials/page.tsxのStockTransactionFormにreserveオプション追加(手動引当は不要なら省略可)app/fertilizer/_components/FertilizerEditPage.tsxに在庫参照表示追加app/fertilizer/page.tsxに確定状態表示・散布確定ボタン追加app/fertilizer/_components/ConfirmSpreadingModal.tsx新規作成(散布確定モーダル)
9. テスト確認項目
バックエンド
- マイグレーション適用成功(materials 0002, fertilizer 0006)
- 施肥計画を保存すると、各エントリに対応する reserve トランザクションが作成される
- 施肥計画を更新すると、古い reserve が削除され新しい reserve が作成される
- 施肥計画を削除すると、reserve が全て削除される
GET /api/materials/stock-summary/でreserved_stockとavailable_stockが返る- 入庫10 → 引当3 →
current_stock=10,reserved_stock=3,available_stock=7 POST /api/fertilizer/plans/{id}/confirm_spreading/で reserve が use に変換される- 確定済み計画に再度 confirm_spreading すると 400 エラー
- actual_bags=0 の行は reserve 削除のみ(use は作成しない)
Fertilizer.materialが null の Fertilizer は引当をスキップする- 既存の施肥計画 CRUD(作成・編集・削除・PDF)が壊れていない
フロントエンド
- 在庫一覧に引当数量と利用可能在庫が表示される
- 施肥計画編集画面に肥料ごとの在庫情報が表示される
- 施肥計画一覧に確定状態(未確定/確定済み)が表示される
- 散布確定モーダルが開き、計画値がプリセットされる
- 実績を修正して一括確定できる
- 確定後、計画が「確定済み」表示に変わる
- 確定済みの計画には「散布確定」ボタンが表示されない
10. 既存コードへの変更一覧(影響範囲)
| ファイル | 変更内容 |
|---|---|
backend/apps/materials/models.py |
StockTransaction に reserve タイプ・fertilization_plan FK 追加 |
backend/apps/materials/serializers.py |
StockSummarySerializer に reserved_stock・available_stock 追加 |
backend/apps/materials/views.py |
StockSummaryView 集計変更、FertilizerStockView 追加 |
backend/apps/materials/urls.py |
fertilizer-stock/ パス追加 |
backend/apps/materials/stock_service.py |
新規作成 — 引当ロジック |
backend/apps/materials/migrations/0002_...py |
新規作成 — fertilization_plan FK |
backend/apps/fertilizer/models.py |
FertilizationPlan に is_confirmed・confirmed_at 追加 |
backend/apps/fertilizer/views.py |
perform_create/update/destroy オーバーライド、confirm_spreading アクション追加 |
backend/apps/fertilizer/serializers.py |
is_confirmed・confirmed_at・material_id 追加 |
backend/apps/fertilizer/migrations/0006_...py |
新規作成 — is_confirmed, confirmed_at |
frontend/src/types/index.ts |
reserve タイプ追加、引当フィールド追加、確定フィールド追加 |
frontend/src/app/materials/_components/StockOverview.tsx |
引当表示追加 |
frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx |
在庫参照表示追加 |
frontend/src/app/fertilizer/page.tsx |
確定状態表示・散布確定ボタン追加 |
frontend/src/app/fertilizer/_components/ConfirmSpreadingModal.tsx |
新規作成 — 散布確定モーダル |
11. 参照すべき既存コード(実装パターンの手本)
| 目的 | 参照先 |
|---|---|
| 施肥計画 ViewSet(perform_create の追加先) | backend/apps/fertilizer/views.py |
| 施肥計画 Serializer(フィールド追加先) | backend/apps/fertilizer/serializers.py |
| 施肥計画の @action パターン(PDF アクション) | backend/apps/fertilizer/views.py の pdf アクション |
| 在庫集計ロジック | backend/apps/materials/views.py の StockSummaryView |
| 施肥計画編集画面(マトリクス表) | frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx |
| 施肥計画一覧画面 | frontend/src/app/fertilizer/page.tsx |
| モーダルパターン | frontend/src/app/materials/_components/StockTransactionForm.tsx |
| 在庫一覧コンポーネント | frontend/src/app/materials/_components/StockOverview.tsx |