Files
windmill_workflow/docs/shiraou/19_windmill_通知ワークフロー連携仕様.md
2026-04-04 09:15:09 +09:00

310 lines
9.1 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.
# 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キーは定期的にローテーションすること変更時は統合システム側の環境変数も同時に更新