Files
keinasystem/document/14_マスタードキュメント_分配計画編.md
Akira eba6267495 変更したドキュメント
ファイル	変更内容
14_マスタードキュメント_分配計画編.md	全面改訂: 旧「分配計画」→ 新「運搬計画」。データモデル5テーブル、API仕様、画面UI操作、PDFフォーマットを記載
CLAUDE.md	データモデル概要(Distribution* → Delivery* に差し替え)、実装状況セクション、更新履歴を更新
13_マスタードキュメント_施肥計画編.md	OUT スコープの「圃場への配置計画」を「運搬計画」への参照に修正
内容を確認して、問題なければ実装に進みます。
2026-03-16 16:05:46 +09:00

396 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# マスタードキュメント:運搬計画機能(旧・分配計画)
> **作成**: 2026-03-02
> **最終更新**: 2026-03-16
> **対象機能**: 運搬計画(施肥計画の肥料を軽トラで運ぶ単位で計画・記録する)
> **実装状況**: 再設計中(旧分配計画から運搬計画へ移行)
---
## 概要
施肥計画で決めた圃場ごとの肥料袋数を、**軽トラ1回分の積載単位**で運搬計画にまとめる機能。
実際の作業では一度に全部運べないため、「何回目にどのグループのどの肥料を何袋運ぶか」を計画・記録する。
### 旧設計(分配計画)からの変更理由
旧設計は「1つの施肥計画の圃場をグループ分けする」だけだった。
実運用で以下のギャップが判明2026-03-16
1. **複数の施肥計画が混在する** - 軽トラには品種をまたいで積む
2. **単一の施肥計画が分割される** - 1回で運びきれない
3. **全肥料を一度に運ぶわけではない** - 運ぶ肥料を指定する必要がある
4. **圃場単位の合計袋数は不要** - グループ×肥料の合計が重要
5. **同じグループの圃場を回ごとに分割する** - 載りきらないときは次の回に
6. **作業記録でもある** - 運搬した日付を記録したい
### 機能スコープ
| IN実装対象 | OUT対象外 |
|---|---|
| 年度単位の運搬計画作成 | 購入管理 |
| 配送先グループへの圃場割り当て | 肥料の在庫管理 |
| 運搬回ごとの圃場×肥料割り当て | ルート最適化 |
| 回ごとの積載合計リアルタイム表示 | |
| 圃場を回の間で移動する操作 | |
| 「残り全部」一括割り当て | |
| 回ごとの運搬日記録 | |
| PDF出力回ごとに1ページ | |
---
## データモデル
### 旧モデルからの移行
| 旧(削除) | 新(追加) | 備考 |
|---|---|---|
| DistributionPlan | DeliveryPlan | FK(FertilizationPlan) 廃止 → year ベース |
| DistributionGroup | DeliveryGroup | ほぼ同等 |
| DistributionGroupField | DeliveryGroupField | ほぼ同等 |
| (なし) | DeliveryTrip | 新規:運搬回 |
| (なし) | DeliveryTripItem | 新規:運搬明細(圃場×肥料単位) |
### DeliveryPlan運搬計画
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| year | int | required | 年度 |
| name | varchar(200) | required | 計画名 |
| created_at / updated_at | datetime | auto | |
- `ordering = ['-year', 'name']`
- 施肥計画への直接FK なし(年度ベースで全施肥計画を横断)
### DeliveryGroup配送先グループ
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| delivery_plan | FK(DeliveryPlan) | CASCADE | |
| name | varchar(100) | required | グループ名(例: キウイ, 足川北) |
| order | PositiveIntegerField | default=0 | 表示順 |
- `unique_together = [['delivery_plan', 'name']]`
- `ordering = ['order', 'id']`
### DeliveryGroupFieldグループ圃場割り当て
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| delivery_plan | FK(DeliveryPlan) | CASCADE | 一意制約用 |
| group | FK(DeliveryGroup) | CASCADE | |
| field | FK(fields.Field) | PROTECT | |
- `unique_together = [['delivery_plan', 'field']]` → 1圃場=1グループ/1計画
### DeliveryTrip運搬回
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| delivery_plan | FK(DeliveryPlan) | CASCADE | |
| order | PositiveIntegerField | default=0 | 何回目(表示順) |
| name | varchar(100) | blank | 任意の名前(例: "たちはるか電気炉さい" |
| date | DateField | nullable | 運搬日(デフォルト: 1回目の日付を引き継ぎ |
- `ordering = ['order', 'id']`
### DeliveryTripItem運搬明細
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| trip | FK(DeliveryTrip) | CASCADE | |
| field | FK(fields.Field) | PROTECT | |
| fertilizer | FK(Fertilizer) | PROTECT | |
| bags | Decimal(10,4) | required | 袋数 |
- `unique_together = [['trip', 'field', 'fertilizer']]`
- bags は施肥計画の FertilizationEntry から自動計算で初期値を設定するが、手動上書きも可能
### ER図概念
```
DeliveryPlan (運搬計画)
├── year, name
├── groups → DeliveryGroup (配送先グループ)
│ ├── name, order
│ └── fields → DeliveryGroupField → Field
└── trips → DeliveryTrip (運搬回)
├── order, name, date
└── items → DeliveryTripItem
├── field → Field
├── fertilizer → Fertilizer
└── bags
```
### 袋数の算出ルール
1. 運搬計画作成時、年度の全 FertilizationEntry を参照して「グループ×肥料→圃場×袋数」を自動算出
2. ユーザーが運搬回に圃場を割り当てると、該当する FertilizationEntry の bags が DeliveryTripItem.bags にコピーされる
3. 手動で bags を上書きすることも可能(施肥計画との差異は許容)
4. 「残り全部」操作: 施肥計画の合計 既に割り当て済みの回の合計 = 残り
---
## API エンドポイント
すべて JWT 認証(`Authorization: Bearer <token>`)。
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/fertilizer/delivery/?year={year}` | 一覧(年度フィルタ) |
| POST | `/api/fertilizer/delivery/` | 新規作成 |
| GET | `/api/fertilizer/delivery/{id}/` | 詳細groups/trips/items 込み) |
| PUT | `/api/fertilizer/delivery/{id}/` | 更新groups・trips 全置換) |
| DELETE | `/api/fertilizer/delivery/{id}/` | 削除 |
| GET | `/api/fertilizer/delivery/{id}/pdf/` | PDF出力 |
### 一覧レスポンス
```json
{
"id": 1,
"year": 2026,
"name": "2026春 肥料運搬",
"group_count": 5,
"trip_count": 3,
"created_at": "...",
"updated_at": "..."
}
```
### 詳細レスポンス
```json
{
"id": 1,
"year": 2026,
"name": "2026春 肥料運搬",
"groups": [
{
"id": 10,
"name": "キウイ",
"order": 0,
"fields": [
{"id": 5, "name": "キウイ畑1", "area_tan": "1.2000"}
]
}
],
"trips": [
{
"id": 1,
"order": 0,
"name": "1回目 たちはるか電気炉さい",
"date": "2026-03-16",
"items": [
{"field": 5, "fertilizer": 1, "bags": "4.00"}
]
}
],
"unassigned_fields": [],
"available_fertilizers": [
{"id": 1, "name": "電気炉さい"},
{"id": 2, "name": "ミネラルホウ素"}
]
}
```
- `available_fertilizers`: 該当年度の全施肥計画で使われている肥料の一覧
- `unassigned_fields`: グループに割り当てられていない圃場
### 書き込みリクエストPOST/PUT
```json
{
"year": 2026,
"name": "2026春 肥料運搬",
"groups": [
{"name": "キウイ", "order": 0, "field_ids": [5, 6]}
],
"trips": [
{
"order": 0,
"name": "1回目",
"date": "2026-03-16",
"items": [
{"field_id": 5, "fertilizer_id": 1, "bags": "4.00"}
]
}
]
}
```
PUT は groups・trips を全削除→再作成する全置換方式。
---
## フロントエンド画面
### 運搬計画一覧 `/distribution`
- 年度セレクタ(`localStorage distributionYear` で保持)
- テーブル: 計画名・グループ数・回数
- アクション: PDF・編集・削除
### 運搬計画編集 `/distribution/new` / `/distribution/[id]/edit`
#### 画面レイアウト
```
[計画名: ________________] [年度: 2026]
━━━ グループ定義 ━━━━━━━━━━━━━━━━━━━
(既存の方式: グループ追加・圃場割り当て・並び替え)
━━━ 対象肥料 ━━━━━━━━━━━━━━━━━━━━━
☑電気炉さい ☑ミネラルホウ素 ☐有機100号 ...
(年度の施肥計画に含まれる肥料をチェックボックスで選択)
━━━ 未割り当て ━━━━━━━━━━━━━━━━━━━━
★ キウイ (小計: 電気炉さい 4, ミネラルホウ素 5)
圃場A 電気炉さい:2 ミネラルホウ素:3 [→1回目 ▼]
圃場B 電気炉さい:2 ミネラルホウ素:2 [→1回目 ▼]
★ 足川北 (小計: 電気炉さい 12, ミネラルホウ素 6)
圃場D ...
━━━ 1回目 (2026-03-16) ━━━ 積載: 46袋 ━━━
日付: [2026-03-16] 名前: [たちはるか電気炉さい]
★ たちはるか (小計: 電気炉さい 46)
圃場X 電気炉さい:10 [←戻す]
圃場Y 電気炉さい:12 [←戻す]
...
━━━ 2回目 (2026-03-16) ━━━ 積載: 39袋 ━━━
日付: [2026-03-16] 名前: [____________]
★ キウイ (小計: 電気炉さい 4, ミネラルホウ素 5)
...
[+回を追加] [残り全部→新しい回] [保存]
```
#### 主要な操作
| 操作 | 方法 | 説明 |
|---|---|---|
| 圃場を回に割り当て | 圃場行の「→N回目」ドロップダウン | 未割り当て→指定回に移動 |
| 圃場を回から戻す | 圃場行の「←戻す」ボタン | 回→未割り当てに移動 |
| 圃場を別の回に移動 | 戻す→再割り当て、または直接ドロップダウン | 回の間で移動 |
| 残り全部を一括割り当て | 「残り全部→新しい回」ボタン | 未割り当て全圃場を新しい回に追加 |
| 回の追加 | 「+回を追加」ボタン | 空の回を追加 |
| 回の削除 | 回ヘッダーの「×」ボタン | 回を削除、中の圃場は未割り当てに戻る |
| 回の日付設定 | 日付入力フィールド | デフォルトは1回目の日付 |
| 対象肥料の絞り込み | チェックボックス | 選択した肥料だけ表示 |
#### 積載合計のリアルタイム表示
各回のヘッダーに、その回の肥料ごとの合計袋数と総袋数を表示。
圃場を追加・削除するたびに即時再計算(サーバー通信なし)。
---
## PDF 出力
`GET /api/fertilizer/delivery/{id}/pdf/`
### フォーマット
- WeasyPrint、A4横向き
- **回ごとに1ページ**1回目=1ページ目、2回目=2ページ目...
### 各ページの内容
```
━━━ 2回目 2026-03-16 ━━━━━━━━━━━━━━━
電気炉さい ミネラルホウ素
★ キウイ 4 5
圃場A 2 3
圃場B 2 2
★ 池田さんちの前 2 2
圃場C 2 2
★ 足川北 12 6
圃場D 4 2
圃場E 4 2
圃場F 4 2
★ 出祥邸 - 8
圃場G - 4
圃場H - 4
─────────────────────────────────────
合計 18 21
```
- ★行: グループ小計(肥料ごと)、太字・緑背景
- 圃場行: 各圃場の肥料ごとの袋数(**合計列なし**
- 最下行: 回全体の肥料ごと合計
- 日付を各ページのヘッダーに記載
- ファイル名: `delivery_{year}_{plan_id}.pdf`
---
## ファイル構成(予定)
### Backend
```
backend/apps/fertilizer/
├── models.py # DeliveryPlan/Group/GroupField/Trip/TripItem
├── serializers.py # Delivery* シリアライザ
├── views.py # DeliveryPlanViewSet
├── urls.py # router.register('delivery', ...)
├── admin.py # DeliveryPlan 等の admin 登録
├── migrations/
│ └── 000X_delivery_*.py # 旧Distribution → 新Delivery マイグレーション
└── templates/fertilizer/
└── delivery_pdf.html # 回ごと1ページ PDF テンプレート
```
### Frontend
```
frontend/src/app/distribution/
├── page.tsx # 一覧ページ
├── new/page.tsx # 新規作成(ラッパー)
├── [id]/edit/page.tsx # 編集(ラッパー)
└── _components/DeliveryEditPage.tsx # 編集共通コンポーネント
```
---
## マイグレーション方針
### 旧モデルDistribution*)の扱い
1. 新モデルDelivery*)を追加するマイグレーションを作成
2. 旧モデルDistribution*)は削除マイグレーションで除去
3. 旧データは少量のため、データ移行は行わない(手動で再作成)
### マイグレーション順序
1. `000X_add_delivery_models.py` - DeliveryPlan, DeliveryGroup, DeliveryGroupField, DeliveryTrip, DeliveryTripItem を追加
2. `000Y_remove_distribution_models.py` - DistributionPlan, DistributionGroup, DistributionGroupField を削除
---
## 注意点
### 施肥計画との関係
- 運搬計画は施肥計画への直接FKを持たない
- 年度ベースで、その年度の全 FertilizationEntry を参照して圃場×肥料の袋数を取得する
- 施肥計画を変更すると、未割り当ての圃場の袋数は自動で反映される
- 既に運搬回に割り当て済みの DeliveryTripItem.bags は変わらない(コピー済み)
### 集計はクライアントサイド計算
画面上の集計(グループ小計・回の積載合計)は API を呼ばずクライアントで計算。
PDF生成時のみサーバーサイドで同じ計算を実施。
### エラー表示方針
施肥計画機能と同じく alert/confirm 廃止・インラインバナーに統一。