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,82 @@
from datetime import datetime
from typing import Optional, List, Dict, Any
from pydantic import BaseModel, Field
class TriggerCondition(BaseModel):
field: str = Field(..., description="Field to check: status_id, assignee_id, priority")
operator: str = Field(..., description="Operator: equals, not_equals, changed_to, changed_from")
value: str = Field(..., description="Value to compare against")
class TriggerAction(BaseModel):
type: str = Field(default="notify", description="Action type: notify")
target: str = Field(default="assignee", description="Target: assignee, creator, project_owner, user:<id>")
template: Optional[str] = Field(None, description="Message template with variables")
class TriggerCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
trigger_type: str = Field(default="field_change")
conditions: TriggerCondition
actions: List[TriggerAction]
is_active: bool = Field(default=True)
class TriggerUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
conditions: Optional[TriggerCondition] = None
actions: Optional[List[TriggerAction]] = None
is_active: Optional[bool] = None
class TriggerUserInfo(BaseModel):
id: str
name: str
email: str
class Config:
from_attributes = True
class TriggerResponse(BaseModel):
id: str
project_id: str
name: str
description: Optional[str]
trigger_type: str
conditions: Dict[str, Any]
actions: List[Dict[str, Any]]
is_active: bool
created_by: Optional[str]
created_at: datetime
updated_at: datetime
creator: Optional[TriggerUserInfo] = None
class Config:
from_attributes = True
class TriggerListResponse(BaseModel):
triggers: List[TriggerResponse]
total: int
class TriggerLogResponse(BaseModel):
id: str
trigger_id: str
task_id: Optional[str]
executed_at: datetime
status: str
details: Optional[Dict[str, Any]]
error_message: Optional[str]
class Config:
from_attributes = True
class TriggerLogListResponse(BaseModel):
logs: List[TriggerLogResponse]
total: int