feat: implement automation module

- Event-based triggers (Phase 1):
  - Trigger/TriggerLog models with field_change type
  - TriggerService for condition evaluation and action execution
  - Trigger CRUD API endpoints
  - Task integration (status, assignee, priority changes)
  - Frontend: TriggerList, TriggerForm components

- Weekly reports (Phase 2):
  - ScheduledReport/ReportHistory models
  - ReportService for stats generation
  - APScheduler for Friday 16:00 job
  - Report preview/generate/history API
  - Frontend: WeeklyReportPreview, ReportHistory components

- Tests: 23 new tests (14 triggers + 9 reports)
- OpenSpec: add-automation change archived

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-12-29 22:59:00 +08:00
parent 3108fe1dff
commit 95c281d8e1
32 changed files with 3163 additions and 3 deletions

View File

@@ -0,0 +1,185 @@
## Context
自動化系統需要處理兩種類型的自動化:
1. 事件驅動 - 任務欄位變更時觸發
2. 時間驅動 - 排程執行(如週報)
## Goals / Non-Goals
**Goals:**
- 提供欄位變更觸發器status, assignee, priority
- 實作每週五 16:00 自動週報
- 整合現有通知系統
- 記錄所有觸發器執行日誌
**Non-Goals:**
- 複雜的工作流引擎
- 跨任務觸發器
- Email 發送(僅系統內通知)
## Decisions
### 1. 排程方案
**Decision:** 使用 APScheduler 而非 Celery
**Rationale:**
- 無需額外 worker 進程,減少部署複雜度
- 嵌入 FastAPI 應用內運行
- 足夠處理週報等簡單定時任務
- 未來如需擴展可遷移至 Celery
**Configuration:**
```python
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = AsyncIOScheduler()
scheduler.add_job(
generate_weekly_reports,
CronTrigger(day_of_week='fri', hour=16, minute=0),
id='weekly_report'
)
```
### 2. 觸發器評估策略
**Decision:** 同步評估,任務更新時直接執行
**Rationale:**
- 簡化實作,無需消息隊列
- 觸發器執行速度快(僅發送通知)
- 失敗可即時回報
**Flow:**
```
Task Update → Detect Changes → Find Matching Triggers → Execute Actions → Log Results
```
### 3. 條件定義格式
**Decision:** 使用 JSON 結構定義條件
```json
{
"field": "status_id",
"operator": "equals",
"value": "uuid-of-testing-status"
}
```
**Supported Operators:**
- `equals` - 等於
- `not_equals` - 不等於
- `changed_to` - 變更為特定值
- `changed_from` - 從特定值變更
### 4. 動作定義格式
**Decision:** 使用 JSON 陣列定義動作
```json
[
{
"type": "notify",
"target": "assignee",
"template": "任務 {task.title} 狀態已變更為 {new_value}"
}
]
```
**Supported Actions (Phase 1):**
- `notify` - 發送系統通知
**Target Types:**
- `assignee` - 任務指派者
- `creator` - 任務建立者
- `project_owner` - 專案擁有者
- `user:<user_id>` - 指定使用者
## Data Model
```sql
-- 觸發器表
pjctrl_triggers
├── id: UUID (PK)
├── project_id: UUID (FK -> projects)
├── name: VARCHAR(200)
├── description: TEXT
├── trigger_type: ENUM('field_change', 'schedule')
├── conditions: JSON
├── actions: JSON
├── is_active: BOOLEAN DEFAULT true
├── created_by: UUID (FK -> users)
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
-- 觸發器執行日誌
pjctrl_trigger_logs
├── id: UUID (PK)
├── trigger_id: UUID (FK -> triggers)
├── task_id: UUID (FK -> tasks, nullable)
├── executed_at: TIMESTAMP
├── status: ENUM('success', 'failed')
├── details: JSON
└── error_message: TEXT
-- 排程報告設定
pjctrl_scheduled_reports
├── id: UUID (PK)
├── report_type: ENUM('weekly')
├── recipient_id: UUID (FK -> users)
├── is_active: BOOLEAN DEFAULT true
├── last_sent_at: TIMESTAMP
└── created_at: TIMESTAMP
-- 報告歷史
pjctrl_report_history
├── id: UUID (PK)
├── report_id: UUID (FK -> scheduled_reports)
├── generated_at: TIMESTAMP
├── content: JSON
├── status: ENUM('sent', 'failed')
└── error_message: TEXT
```
## API Design
```
# Triggers
POST /api/projects/{project_id}/triggers # 建立觸發器
GET /api/projects/{project_id}/triggers # 列出專案觸發器
GET /api/triggers/{id} # 觸發器詳情
PUT /api/triggers/{id} # 更新觸發器
DELETE /api/triggers/{id} # 刪除觸發器
GET /api/triggers/{id}/logs # 觸發器執行日誌
# Reports
GET /api/reports/weekly/preview # 預覽週報
POST /api/reports/weekly/generate # 手動觸發週報
GET /api/reports/history # 報告歷史
```
## Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| 大量觸發器影響效能 | 限制每專案觸發器數量,建立索引 |
| APScheduler 單點故障 | 記錄 last_sent_at 防止重複,考慮多實例鎖 |
| 觸發器條件複雜度 | Phase 1 僅支援簡單條件,後續擴展 |
## Integration Points
1. **Task Update Hook**
-`tasks/router.py` 的 update_task 後調用 TriggerService.evaluate()
2. **Notification Integration**
- 使用現有 NotificationService.create_notification()
3. **Audit Integration**
- 觸發器執行記錄至 TriggerLog非 AuditLog
## Open Questions
- [ ] 是否需要支援 Email 發送?(目前僅系統內通知)
- [ ] 週報收件者如何設定?(主管自動訂閱 or 手動設定)