feat: enhance project management by canceling pending actions for archived and on_hold projects

This commit is contained in:
serversdwn
2026-02-19 18:57:59 +00:00
parent 65362bab21
commit 0f17841218
7 changed files with 85 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
from fastapi import APIRouter, Request, Depends
from sqlalchemy.orm import Session
from sqlalchemy import and_
from datetime import datetime, timedelta
from backend.database import get_db
@@ -48,10 +49,18 @@ def dashboard_todays_actions(request: Request, db: Session = Depends(get_db)):
today_start_utc = today_start_local.astimezone(ZoneInfo("UTC")).replace(tzinfo=None)
today_end_utc = today_end_local.astimezone(ZoneInfo("UTC")).replace(tzinfo=None)
# Exclude actions from paused/removed projects
paused_project_ids = [
p.id for p in db.query(Project.id).filter(
Project.status.in_(["on_hold", "archived", "deleted"])
).all()
]
# Query today's actions
actions = db.query(ScheduledAction).filter(
ScheduledAction.scheduled_time >= today_start_utc,
ScheduledAction.scheduled_time < today_end_utc,
ScheduledAction.project_id.notin_(paused_project_ids),
).order_by(ScheduledAction.scheduled_time.asc()).all()
# Enrich with location/project info and parse results

View File

@@ -342,6 +342,14 @@ async def update_project(
project.description = data["description"]
if "status" in data:
project.status = data["status"]
# Cancel pending scheduled actions when archiving
if data["status"] == "archived":
db.query(ScheduledAction).filter(
and_(
ScheduledAction.project_id == project_id,
ScheduledAction.execution_status == "pending",
)
).update({"execution_status": "cancelled"})
if "client_name" in data:
project.client_name = data["client_name"]
if "site_address" in data:
@@ -374,6 +382,14 @@ async def delete_project(project_id: str, db: Session = Depends(get_db)):
project.deleted_at = datetime.utcnow()
project.updated_at = datetime.utcnow()
# Cancel all pending scheduled actions
db.query(ScheduledAction).filter(
and_(
ScheduledAction.project_id == project_id,
ScheduledAction.execution_status == "pending",
)
).update({"execution_status": "cancelled"})
db.commit()
return {"success": True, "message": "Project deleted. Data will be permanently removed after 60 days."}
@@ -414,6 +430,15 @@ async def hold_project(project_id: str, db: Session = Depends(get_db)):
project.status = "on_hold"
project.updated_at = datetime.utcnow()
# Cancel pending scheduled actions so they don't appear in dashboards or fire
db.query(ScheduledAction).filter(
and_(
ScheduledAction.project_id == project_id,
ScheduledAction.execution_status == "pending",
)
).update({"execution_status": "cancelled"})
db.commit()
return {"success": True, "message": "Project put on hold."}
@@ -672,10 +697,14 @@ async def get_project_schedules(
"result": result_data,
})
project = db.query(Project).filter_by(id=project_id).first()
project_status = project.status if project else "active"
return templates.TemplateResponse("partials/projects/schedule_list.html", {
"request": request,
"project_id": project_id,
"schedules_by_date": schedules_by_date,
"project_status": project_status,
})

View File

@@ -497,6 +497,9 @@ async def get_schedule_list_partial(
"""
Return HTML partial for schedule list.
"""
project = db.query(Project).filter_by(id=project_id).first()
project_status = project.status if project else "active"
schedules = db.query(RecurringSchedule).filter_by(
project_id=project_id
).order_by(RecurringSchedule.created_at.desc()).all()
@@ -515,4 +518,5 @@ async def get_schedule_list_partial(
"request": request,
"project_id": project_id,
"schedules": schedule_data,
"project_status": project_status,
})

View File

@@ -15,7 +15,7 @@ from zoneinfo import ZoneInfo
from sqlalchemy.orm import Session
from sqlalchemy import and_
from backend.models import RecurringSchedule, ScheduledAction, MonitoringLocation, UnitAssignment
from backend.models import RecurringSchedule, ScheduledAction, MonitoringLocation, UnitAssignment, Project
logger = logging.getLogger(__name__)
@@ -594,8 +594,16 @@ class RecurringScheduleService:
return self.db.query(RecurringSchedule).filter_by(project_id=project_id).all()
def get_enabled_schedules(self) -> List[RecurringSchedule]:
"""Get all enabled recurring schedules."""
return self.db.query(RecurringSchedule).filter_by(enabled=True).all()
"""Get all enabled recurring schedules for projects that are not on hold or deleted."""
active_project_ids = [
p.id for p in self.db.query(Project.id).filter(
Project.status.notin_(["on_hold", "archived", "deleted"])
).all()
]
return self.db.query(RecurringSchedule).filter(
RecurringSchedule.enabled == True,
RecurringSchedule.project_id.in_(active_project_ids),
).all()
def get_recurring_schedule_service(db: Session) -> RecurringScheduleService:

View File

@@ -107,10 +107,19 @@ class SchedulerService:
try:
# Find pending actions that are due
now = datetime.utcnow()
# Only execute actions for active/completed projects (not on_hold, archived, or deleted)
active_project_ids = [
p.id for p in db.query(Project.id).filter(
Project.status.notin_(["on_hold", "archived", "deleted"])
).all()
]
pending_actions = db.query(ScheduledAction).filter(
and_(
ScheduledAction.execution_status == "pending",
ScheduledAction.scheduled_time <= now,
ScheduledAction.project_id.in_(active_project_ids),
)
).order_by(ScheduledAction.scheduled_time).all()