310 lines
8.8 KiB
Markdown
310 lines
8.8 KiB
Markdown
# Windmill 通知ワークフロー連携仕様
|
||
|
||
> **作成日**: 2026-02-21
|
||
> **対象システム**: 白皇集落営農組合 統合システム (`shiraou.keinafarm.net`)
|
||
> **目的**: 予約・実績の変更をLINEで管理者に通知するWindmillワークフローの実装仕様
|
||
|
||
---
|
||
|
||
## 1. 概要
|
||
|
||
統合システム側が「変更履歴取得API」を提供する。
|
||
Windmillは定期的にこのAPIをポーリングし、変更があればLINEへ通知する。
|
||
|
||
```
|
||
Windmill(定期実行)
|
||
└→ GET https://shiraou.keinafarm.net/reservations/api/changes/?since=<前回実行時刻>
|
||
└→ 変更一覧(予約・実績)を取得
|
||
└→ 変更があればLINE Messaging APIへ通知
|
||
└→ 前回実行時刻を更新
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 変更履歴取得API
|
||
|
||
### エンドポイント
|
||
|
||
```
|
||
GET https://shiraou.keinafarm.net/reservations/api/changes/
|
||
```
|
||
|
||
### 認証
|
||
|
||
`X-API-Key` ヘッダーにAPIキーを指定する(統合システム管理者から取得)。
|
||
|
||
```
|
||
X-API-Key: <NOTIFICATION_API_KEY>
|
||
```
|
||
|
||
APIキーが不正な場合は `401 Unauthorized` が返る。
|
||
|
||
### クエリパラメータ
|
||
|
||
| パラメータ | 型 | 必須 | 説明 |
|
||
|-----------|-----|------|------|
|
||
| `since` | ISO8601文字列 | 必須 | この日時以降の変更を取得する |
|
||
|
||
**`since` の形式例**:
|
||
- `2026-02-21T10:00:00` (ナイーブ、JSTとして扱われる)
|
||
- `2026-02-21T10:00:00+09:00` (タイムゾーン付き、推奨)
|
||
|
||
### レスポンス(200 OK)
|
||
|
||
```json
|
||
{
|
||
"checked_at": "2026-02-21T12:00:00+09:00",
|
||
"since": "2026-02-21T10:00:00+09:00",
|
||
"reservations": [
|
||
{
|
||
"operation": "create",
|
||
"reservation_id": 123,
|
||
"user_name": "田中太郎",
|
||
"machine_name": "トラクター",
|
||
"start_at": "2026-02-25T09:00:00+09:00",
|
||
"end_at": "2026-02-25T12:00:00+09:00",
|
||
"operated_at": "2026-02-21T11:30:00+09:00",
|
||
"operator_name": "田中太郎",
|
||
"reason": ""
|
||
},
|
||
{
|
||
"operation": "cancel",
|
||
"reservation_id": 120,
|
||
"user_name": "佐藤花子",
|
||
"machine_name": "田植機",
|
||
"start_at": "2026-02-22T08:00:00+09:00",
|
||
"end_at": "2026-02-22T17:00:00+09:00",
|
||
"operated_at": "2026-02-21T11:45:00+09:00",
|
||
"operator_name": "佐藤花子",
|
||
"reason": ""
|
||
}
|
||
],
|
||
"usages": [
|
||
{
|
||
"operation": "update",
|
||
"usage_id": 456,
|
||
"user_name": "山田次郎",
|
||
"machine_name": "コンバイン",
|
||
"amount": 4.0,
|
||
"unit": "時間",
|
||
"start_at": "2026-02-20T08:00:00+09:00",
|
||
"end_at": "2026-02-20T12:00:00+09:00",
|
||
"operated_at": "2026-02-21T11:55:00+09:00",
|
||
"operator_name": "管理者A",
|
||
"reason": "記録ミスのため修正"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### operation の値一覧
|
||
|
||
**予約(reservations)**:
|
||
|
||
| 値 | 意味 |
|
||
|----|------|
|
||
| `create` | 予約が作成された |
|
||
| `update` | 予約の日時・機械が変更された |
|
||
| `cancel` | 予約がキャンセルされた |
|
||
|
||
**実績(usages)**:
|
||
|
||
| 値 | 意味 |
|
||
|----|------|
|
||
| `create` | 実績が登録された |
|
||
| `update` | 実績が修正された |
|
||
| `delete` | 実績が削除された |
|
||
|
||
### 変更なしの場合
|
||
|
||
`reservations` と `usages` が両方空配列になる。通知は不要。
|
||
|
||
```json
|
||
{
|
||
"checked_at": "2026-02-21T12:05:00+09:00",
|
||
"since": "2026-02-21T12:00:00+09:00",
|
||
"reservations": [],
|
||
"usages": []
|
||
}
|
||
```
|
||
|
||
### エラーレスポンス
|
||
|
||
| ステータス | 原因 |
|
||
|-----------|------|
|
||
| `401 Unauthorized` | APIキーが不正または未設定 |
|
||
| `400 Bad Request` | `since` パラメータが欠落または不正な日時形式 |
|
||
|
||
---
|
||
|
||
## 3. Windmillワークフロー設計
|
||
|
||
### 3.1 スケジュール
|
||
|
||
- **実行間隔**: 5分毎(`*/5 * * * *`)
|
||
- 農業機械の予約という用途上、数分の遅延は許容範囲
|
||
|
||
### 3.2 状態管理(前回実行時刻)
|
||
|
||
Windmillの **Resource** または **State Variable** に前回実行時刻を保存する。
|
||
|
||
```
|
||
変数名: last_checked_at
|
||
初期値: (初回実行時は現在時刻 - 10分 を使用)
|
||
```
|
||
|
||
### 3.3 ワークフロー全体フロー(擬似コード)
|
||
|
||
```python
|
||
# 1. 前回実行時刻を取得
|
||
last_checked = get_state("last_checked_at") or (now() - 10 minutes)
|
||
|
||
# 2. 変更履歴を取得
|
||
response = GET "https://shiraou.keinafarm.net/reservations/api/changes/"
|
||
params: { since: last_checked.isoformat() }
|
||
headers: { "X-API-Key": NOTIFICATION_API_KEY }
|
||
|
||
# 3. 変更があればLINEに通知
|
||
if response.reservations or response.usages:
|
||
message = format_line_message(response)
|
||
send_line_message(LINE_CHANNEL_ACCESS_TOKEN, LINE_USER_ID, message)
|
||
|
||
# 4. 前回実行時刻を更新
|
||
set_state("last_checked_at", response.checked_at)
|
||
```
|
||
|
||
### 3.4 LINEメッセージのフォーマット例
|
||
|
||
```python
|
||
def format_line_message(data):
|
||
lines = ["📋 営農システム 変更通知\n"]
|
||
|
||
for r in data["reservations"]:
|
||
start = r["start_at"][:16].replace("T", " ")
|
||
end = r["end_at"][:16].replace("T", " ")
|
||
|
||
if r["operation"] == "create":
|
||
icon = "🟢"
|
||
label = "予約作成"
|
||
elif r["operation"] == "update":
|
||
icon = "🔵"
|
||
label = "予約変更"
|
||
elif r["operation"] == "cancel":
|
||
icon = "🔴"
|
||
label = "予約キャンセル"
|
||
|
||
lines.append(f"{icon} {label}")
|
||
lines.append(f" 機械: {r['machine_name']}")
|
||
lines.append(f" 利用者: {r['user_name']}")
|
||
lines.append(f" 日時: {start} 〜 {end}")
|
||
if r["reason"]:
|
||
lines.append(f" 理由: {r['reason']}")
|
||
lines.append("")
|
||
|
||
for u in data["usages"]:
|
||
start = u["start_at"][:16].replace("T", " ")
|
||
|
||
if u["operation"] == "create":
|
||
icon = "🟢"
|
||
label = "実績登録"
|
||
elif u["operation"] == "update":
|
||
icon = "🔵"
|
||
label = "実績修正"
|
||
elif u["operation"] == "delete":
|
||
icon = "🔴"
|
||
label = "実績削除"
|
||
|
||
lines.append(f"{icon} {label}")
|
||
lines.append(f" 機械: {u['machine_name']}")
|
||
lines.append(f" 利用者: {u['user_name']}")
|
||
lines.append(f" 利用量: {u['amount']}{u['unit']}")
|
||
lines.append(f" 日: {start[:10]}")
|
||
if u["reason"]:
|
||
lines.append(f" 理由: {u['reason']}")
|
||
lines.append("")
|
||
|
||
return "\n".join(lines).strip()
|
||
```
|
||
|
||
**出力例**:
|
||
```
|
||
📋 営農システム 変更通知
|
||
|
||
🟢 予約作成
|
||
機械: トラクター
|
||
利用者: 田中太郎
|
||
日時: 2026-02-25 09:00 〜 2026-02-25 12:00
|
||
|
||
🔴 予約キャンセル
|
||
機械: 田植機
|
||
利用者: 佐藤花子
|
||
日時: 2026-02-22 08:00 〜 2026-02-22 17:00
|
||
|
||
🔵 実績修正
|
||
機械: コンバイン
|
||
利用者: 山田次郎
|
||
利用量: 4.0時間
|
||
日: 2026-02-20
|
||
理由: 記録ミスのため修正
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Windmill側の環境変数(シークレット)
|
||
|
||
| 変数名 | 説明 | 設定場所 |
|
||
|--------|------|---------|
|
||
| `NOTIFICATION_API_KEY` | 統合システムのAPIキー | Windmill Secret |
|
||
| `LINE_CHANNEL_ACCESS_TOKEN` | LINE Messaging API チャネルアクセストークン | Windmill Secret |
|
||
| `LINE_USER_ID` / `LINE_GROUP_ID` | 通知先のユーザーIDまたはグループID | Windmill Secret |
|
||
|
||
---
|
||
|
||
## 5. 統合システム側の設定(django側の作業)
|
||
|
||
本番サーバーの `docker-compose.yml` に以下の環境変数を追加し、デプロイする。
|
||
|
||
```yaml
|
||
environment:
|
||
- NOTIFICATION_API_KEY=<任意の強いランダム文字列>
|
||
```
|
||
|
||
**APIキーの生成例**:
|
||
```bash
|
||
openssl rand -hex 32
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 動作確認方法
|
||
|
||
### curlで直接テスト
|
||
|
||
```bash
|
||
# 変更なし(直近1分)
|
||
curl -H "X-API-Key: <キー>" \
|
||
"https://shiraou.keinafarm.net/reservations/api/changes/?since=2026-02-21T11:59:00%2B09:00"
|
||
|
||
# 広い範囲で変更を取得(初期確認用)
|
||
curl -H "X-API-Key: <キー>" \
|
||
"https://shiraou.keinafarm.net/reservations/api/changes/?since=2026-01-01T00:00:00"
|
||
|
||
# APIキーなし → 401が返ることを確認
|
||
curl "https://shiraou.keinafarm.net/reservations/api/changes/?since=2026-01-01T00:00:00"
|
||
```
|
||
|
||
### ダッシュボードで変更確認
|
||
|
||
管理者アカウントで `https://shiraou.keinafarm.net/reservations/dashboard/` にアクセスすると、
|
||
直近30件の予約操作履歴・実績操作ログを確認できる。
|
||
Windmillワークフローのデバッグ時に「APIが何を返すべきか」の確認に使える。
|
||
|
||
---
|
||
|
||
## 7. 注意事項
|
||
|
||
- `since` に渡す時刻は **前回の `checked_at`** を使う(`since` ではなく `checked_at` を保存すること)
|
||
- 同一の変更が2回通知されないよう、状態管理を確実に行う
|
||
- ワークフローがエラーで終了した場合、`last_checked_at` を更新しないようにすること(次回実行時に再取得される)
|
||
- APIキーは定期的にローテーションすること(変更時は統合システム側の環境変数も同時に更新)
|