# マスタードキュメント：トラクター作業記録機能

> **作成**: 2026-04-04
> **最終更新**: 2026-04-10
> **対象機能**: トラクター作業記録（畔塗・荒代掻き・植代掻き・耕耘）
> **実装状況**: 畔塗のみ実装済み。荒代掻き・植代掻き・耕耘は設計中（Issue #21）
> **対象 Issue**: `akira/keinasystem#21`

---

## 概要

農業生産者が、水稲作付け圃場に対して実施したトラクター作業を日付単位で記録する機能。
対象圃場をまとめて選択し、保存時に作業記録一覧へ自動反映する。

対象作業種別：

| 種別 | 日本語名 | 説明 |
|---|---|---|
| `levee_work` | 畔塗 | 畦畔の補修・造成 |
| `rough_harrowing` | 荒代掻き | 田植え前の粗い代掻き |
| `transplant_harrowing` | 植代掻き | 田植え直前の仕上げ代掻き |
| `cultivation` | 耕耘 | 土起こし・耕起 |

これらはいずれも**トラクターを用いた資材なし作業**であり、同一のデータモデルで管理する。

本機能は、施肥計画の散布実績と同様に
「作業本体を専用テーブルで持ち、作業記録一覧には索引を自動生成する」
という設計方針を採用する。

### 機能スコープ（IN / OUT）

| IN（本機能で扱う） | OUT（本機能では扱わない） |
|---|---|
| 日付単位の作業記録作成（全4種別） | 作業の工程管理 |
| 水稲作付け圃場の候補抽出 | 作業者別の工数集計 |
| 複数圃場の一括選択・保存 | 機械・資材の在庫管理 |
| 作業記録一覧（WorkRecord）への自動反映 | 写真添付 |
| 記録の編集・削除 | GPS軌跡連携 |
| 対象圃場一覧の参照画面 | 汎用作業日誌への完全統合 |

---

## 背景と目的

現状システムには畔塗の記録機能があるが、同じトラクター作業である荒代掻き・植代掻き・耕耘は登録できない。
これらは以下の共通点を持つため、統一モデルで管理する。

- 1日で複数圃場をまとめて実施することが多い
- 対象圃場は当年の作付け計画と密接に関係する
- 後から「いつ、どの圃場を実施したか」を一覧で見返したい
- 使用資材がない（施肥・農薬とは区別される）

---

## データモデル

### TractorWorkSession（トラクター作業記録本体）

日付単位のトラクター作業記録。

| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| work_type | varchar(30) | required | 作業種別（下記参照） |
| year | int | required | 年度フィルタ用。原則 `date.year` と一致させる |
| date | DateField | required | 作業日 |
| title | varchar(100) | required | 一覧表示タイトル。未指定時はサーバー側で work_type に応じたデフォルト値を補完する |
| notes | text | blank | 備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |

#### work_type の値とデフォルトタイトル

| work_type | 表示名 | デフォルトタイトル |
|---|---|---|
| `levee_work` | 畔塗 | 水稲畔塗 |
| `rough_harrowing` | 荒代掻き | 水稲荒代掻き |
| `transplant_harrowing` | 植代掻き | 水稲植代掻き |
| `cultivation` | 耕耘 | 水稲耕耘 |

- `year + date` の一意制約は付けない
- 同日に種別違い・地区違いで複数記録を持てるようにする

### TractorWorkSessionItem（対象圃場明細）

トラクター作業記録に紐づく対象圃場一覧。

| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| session | FK(TractorWorkSession) | CASCADE | 親の作業記録 |
| field | FK(fields.Field) | PROTECT | 対象圃場 |
| plan | FK(plans.Plan) | SET_NULL, nullable | 保存時点の作付け計画参照 |
| crop_name_snapshot | varchar(100) | required | 保存時点の作物名 |
| variety_name_snapshot | varchar(100) | blank | 保存時点の品種名 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |

- `unique_together = ['session', 'field']`
- 圃場名は `Field` を参照して表示する
- 作物・品種は履歴保全のためスナップショット保持

### WorkRecord（作業記録索引）

既存 `apps/workrecords` の `WorkRecord` をトラクター作業に対応させる。

| 変更点 | 内容 |
|---|---|
| `work_type` enum | `TRACTOR_WORK = 'tractor_work'` を追加（`LEVEE_WORK` を置換） |
| FK | `tractor_work_session = OneToOneField('tractor_work.TractorWorkSession', ...)` に改名 |

制約:

- `on_delete=CASCADE`
- `null=True`, `blank=True`
- `related_name='work_record'`

一覧表示時の想定値:

| 項目 | 値 |
|---|---|
| 作業日 | 作業記録の日付 |
| 種別 | トラクター作業（work_type の日本語表示） |
| タイトル | session.title |
| 参照先 | 対象圃場一覧画面 |

---

## 候補圃場抽出ルール

候補は作付け計画 `Plan` から抽出する。

### 基本条件

- 指定年度の `Plan` であること
- `crop.name = "水稲"` の圃場であること

### 補足

- 品種未設定でも `crop=水稲` なら候補に含める
- 並び順は `field.display_order`, `field.id`

### 候補レスポンスで返す情報

| 項目 | 説明 |
|---|---|
| field_id | 圃場ID |
| field_name | 圃場名 |
| field_area_tan | 面積（反） |
| group_name | グループ名 |
| plan_id | 対応する作付け計画ID |
| crop_name | 作物名 |
| variety_name | 品種名 |
| selected | 初期選択状態（原則 `true`） |

---

## 画面仕様

### 画面の位置づけ

日付と作業種別を先に決めて対象圃場を選ぶ「日報型UI」。
1回の保存で複数圃場をまとめて記録する。

### 主要画面

#### 1. トラクター作業記録一覧画面（`/tractor-work`）

- 年度内の記録を一覧する
- 作業種別でフィルター可能
- 新規作成・既存記録の編集・削除

表示項目: 作業日 / 作業種別 / タイトル / 対象圃場数 / 面積合計 / 備考

#### 2. 作成・編集画面

入力項目:

- **作業種別**（畔塗 / 荒代掻き / 植代掻き / 耕耘）← 新規追加
- 日付
- タイトル（work_type に連動したデフォルト値を自動セット）
- 備考
- 対象圃場一覧（チェックボックス）

### 推奨UIイメージ

```text
トラクター作業記録作成

[作業種別    荒代掻き ▼]
[日付        2026-04-20]
[タイトル    水稲荒代掻き]
[備考        __________________ ]

対象圃場一覧
[全選択] [全解除]

☑ 田中上     1.2反  上エリア  コシヒカリ
☑ 田中下     0.8反  上エリア  あきたこまち
☐ 山の前     1.5反  南エリア  （未設定）

[保存]
```

---

## API エンドポイント

すべて JWT 認証必須。

### トラクター作業記録

| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/tractor-work/sessions/?year={year}` | 年度別一覧 |
| POST | `/api/tractor-work/sessions/` | 新規作成 |
| GET | `/api/tractor-work/sessions/{id}/` | 詳細取得 |
| PUT/PATCH | `/api/tractor-work/sessions/{id}/` | 更新 |
| DELETE | `/api/tractor-work/sessions/{id}/` | 削除 |

### 候補圃場取得

| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/tractor-work/candidates/?year={year}` | 水稲作付け圃場候補を返す |

### リクエスト例（新規作成）

```json
{
  "work_type": "rough_harrowing",
  "year": 2026,
  "date": "2026-04-20",
  "title": "水稲荒代掻き",
  "notes": "",
  "items": [
    { "field": 5, "plan": 12 },
    { "field": 6, "plan": 13 }
  ]
}
```

- `crop_name_snapshot` / `variety_name_snapshot` はクライアント送信不要。サーバーが `plan` から自動設定する
- `plan` が `null` の場合は `field` に対応する当年 `Plan` から補完を試みる

### レスポンス例（詳細）

```json
{
  "id": 3,
  "work_type": "rough_harrowing",
  "year": 2026,
  "date": "2026-04-20",
  "title": "水稲荒代掻き",
  "notes": "",
  "work_record_id": 15,
  "item_count": 2,
  "total_area_tan": "2.0000",
  "items": [
    {
      "id": 11,
      "field": 5,
      "field_name": "田中上",
      "plan": 12,
      "crop_name_snapshot": "水稲",
      "variety_name_snapshot": "コシヒカリ"
    }
  ],
  "created_at": "2026-04-20T08:00:00Z",
  "updated_at": "2026-04-20T08:00:00Z"
}
```

---

## 業務フロー

### 1. 新規作成

1. ユーザーが作業種別・年度・日付を選ぶ
2. システムが当年の水稲作付け圃場を候補表示する
3. ユーザーが対象圃場を選択する
4. 保存時に `TractorWorkSession` を作成する
5. 明細として `TractorWorkSessionItem` を一括作成する
6. 各明細の `crop_name_snapshot` / `variety_name_snapshot` をサーバー側で自動設定する
7. `WorkRecord` を自動生成する（`update_or_create`）

### 2. 編集

1. ユーザーが既存の作業記録を開く
2. 作業種別・日付・タイトル・備考・対象圃場を変更する
3. 保存時に明細を再構成する
4. `WorkRecord` 側の作業日・タイトルも同期更新する

### 3. 削除

1. ユーザーが作業記録を削除する
2. 紐づく `TractorWorkSessionItem` は `CASCADE` で削除される
3. 紐づく `WorkRecord` は `tractor_work_session` の `on_delete=CASCADE` により削除される

---

## 作業記録連携仕様

### 追加する種別

| enum値 | 表示名 |
|---|---|
| `tractor_work` | トラクター作業 |

### 自動生成ルール

- `work_date` = `session.date`
- `work_type` = `tractor_work`
- `title` = `session.title`（work_type 別デフォルトで補完済み）
- `year` = `session.year`
- `auto_created` = `True`
- `tractor_work_session` = 対応する作業記録

### 同期タイミング

- 作成時・更新時: `update_or_create`
- 削除時: `on_delete=CASCADE` により自動削除

---

## バリデーションルール

### 必須

- `work_type`
- `year`
- `date`
- `items`（1件以上）

### 保存時チェック

- 選択圃場が0件の保存を禁止する
- 同一セッション内で同じ圃場を重複登録しない
- `year` は原則 `date.year` と一致しなければならない
- `plan` が指定されている場合、`plan.field` と `field` は一致しなければならない
- `plan.year` は `session.year` と一致しなければならない

### 業務上の許容

- 品種未設定の水稲圃場は保存可
- 同日に別種別・別地区で複数記録を持てる
- 一度作業した圃場を別日に再度記録することは可

---

## 実装方針

### 移行方針（levee_work → tractor_work）

既存 `apps/levee_work` を `apps/tractor_work` にアプリごと改名する。

- Django の `RenameModel` migration でテーブルを改名する
- `work_type` フィールドを追加し、既存レコードは `levee_work` で埋める
- `workrecords` の FK名・enum値も migration で更新する
- API パスを `/levee-work/` → `/tractor-work/` に変更する
- フロントエンドの `app/levee-work/` → `app/tractor-work/` に移動する

### バックエンド

- `Session` / `SessionItem` 構成を維持する
- Serializer は `read` と `write` を分離する
- 候補取得 API は `Plan` を起点に組み立てる
- `sync_tractor_work_record(session)` で `WorkRecord` と同期する

### フロントエンド

- 既存の levee-work ページを tractor-work に移植する
- 作業種別セレクタを追加し、選択に応じてデフォルトタイトルを自動セットする

---

## ソースファイル構成

### バックエンド

```
backend/apps/tractor_work/
├── models.py       # TractorWorkSession, TractorWorkSessionItem
├── serializers.py
├── views.py
├── urls.py
├── admin.py
└── migrations/
    ├── 0001_initial.py          # （levee_work から移行）
    └── 0002_rename_and_add_work_type.py
```

変更ファイル:
- `backend/apps/workrecords/models.py` — FK名・enum更新
- `backend/apps/workrecords/services.py` — sync関数改名
- `backend/keinasystem/settings.py` — INSTALLED_APPS更新
- `backend/keinasystem/urls.py` — URLパス更新

### フロントエンド

```
frontend/src/app/tractor-work/
└── page.tsx
```

変更ファイル:
- `frontend/src/types/index.ts` — 型定義更新
- `frontend/src/components/Navbar.tsx` — リンク更新
- `frontend/src/app/workrecords/page.tsx` — 遷移先更新
