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:
@@ -14,6 +14,7 @@ from app.middleware.auth import (
|
||||
)
|
||||
from app.middleware.audit import get_audit_metadata
|
||||
from app.services.audit_service import AuditService
|
||||
from app.services.trigger_service import TriggerService
|
||||
|
||||
router = APIRouter(tags=["tasks"])
|
||||
|
||||
@@ -271,7 +272,7 @@ async def update_task(
|
||||
detail="Permission denied",
|
||||
)
|
||||
|
||||
# Capture old values for audit
|
||||
# Capture old values for audit and triggers
|
||||
old_values = {
|
||||
"title": task.title,
|
||||
"description": task.description,
|
||||
@@ -289,7 +290,7 @@ async def update_task(
|
||||
else:
|
||||
setattr(task, field, value)
|
||||
|
||||
# Capture new values for audit
|
||||
# Capture new values for audit and triggers
|
||||
new_values = {
|
||||
"title": task.title,
|
||||
"description": task.description,
|
||||
@@ -313,6 +314,10 @@ async def update_task(
|
||||
request_metadata=get_audit_metadata(request),
|
||||
)
|
||||
|
||||
# Evaluate triggers for priority changes
|
||||
if "priority" in update_data:
|
||||
TriggerService.evaluate_triggers(db, task, old_values, new_values, current_user)
|
||||
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
@@ -397,6 +402,9 @@ async def update_task_status(
|
||||
detail="Status not found in this project",
|
||||
)
|
||||
|
||||
# Capture old status for triggers
|
||||
old_status_id = task.status_id
|
||||
|
||||
task.status_id = status_data.status_id
|
||||
|
||||
# Auto-set blocker_flag based on status name
|
||||
@@ -405,6 +413,15 @@ async def update_task_status(
|
||||
else:
|
||||
task.blocker_flag = False
|
||||
|
||||
# Evaluate triggers for status changes
|
||||
if old_status_id != status_data.status_id:
|
||||
TriggerService.evaluate_triggers(
|
||||
db, task,
|
||||
{"status_id": old_status_id},
|
||||
{"status_id": status_data.status_id},
|
||||
current_user
|
||||
)
|
||||
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
@@ -460,6 +477,15 @@ async def assign_task(
|
||||
request_metadata=get_audit_metadata(request),
|
||||
)
|
||||
|
||||
# Evaluate triggers for assignee changes
|
||||
if old_assignee_id != assign_data.assignee_id:
|
||||
TriggerService.evaluate_triggers(
|
||||
db, task,
|
||||
{"assignee_id": old_assignee_id},
|
||||
{"assignee_id": assign_data.assignee_id},
|
||||
current_user
|
||||
)
|
||||
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user