From 0f1784121888e34894345e50ae18f3feb655697f Mon Sep 17 00:00:00 2001 From: serversdwn Date: Thu, 19 Feb 2026 18:57:59 +0000 Subject: [PATCH] feat: enhance project management by canceling pending actions for archived and on_hold projects --- backend/routers/dashboard.py | 9 ++++++ backend/routers/projects.py | 29 +++++++++++++++++++ backend/routers/recurring_schedules.py | 4 +++ .../services/recurring_schedule_service.py | 14 +++++++-- backend/services/scheduler.py | 9 ++++++ .../projects/recurring_schedule_list.html | 16 ++++++++-- .../partials/projects/schedule_list.html | 12 ++++++-- 7 files changed, 85 insertions(+), 8 deletions(-) diff --git a/backend/routers/dashboard.py b/backend/routers/dashboard.py index c9e61bb..4d36f52 100644 --- a/backend/routers/dashboard.py +++ b/backend/routers/dashboard.py @@ -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 diff --git a/backend/routers/projects.py b/backend/routers/projects.py index 4beec68..1316a79 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -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, }) diff --git a/backend/routers/recurring_schedules.py b/backend/routers/recurring_schedules.py index 9a0289b..ee019a0 100644 --- a/backend/routers/recurring_schedules.py +++ b/backend/routers/recurring_schedules.py @@ -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, }) diff --git a/backend/services/recurring_schedule_service.py b/backend/services/recurring_schedule_service.py index bce6f7b..8ef3f17 100644 --- a/backend/services/recurring_schedule_service.py +++ b/backend/services/recurring_schedule_service.py @@ -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: diff --git a/backend/services/scheduler.py b/backend/services/scheduler.py index 7b9da92..8e40e67 100644 --- a/backend/services/scheduler.py +++ b/backend/services/scheduler.py @@ -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() diff --git a/templates/partials/projects/recurring_schedule_list.html b/templates/partials/projects/recurring_schedule_list.html index c5279d8..6084598 100644 --- a/templates/partials/projects/recurring_schedule_list.html +++ b/templates/partials/projects/recurring_schedule_list.html @@ -5,7 +5,7 @@ {% if schedules %} {% for item in schedules %}
+ {% if project_status == 'on_hold' or not item.schedule.enabled %}opacity-60{% endif %}">
@@ -29,7 +29,15 @@ {% endif %} - {% if item.schedule.enabled %} + {% if project_status == 'on_hold' %} + + On Hold + + {% elif project_status == 'archived' %} + + Archived + + {% elif item.schedule.enabled %} Active @@ -98,7 +106,8 @@
- + + {% if project_status not in ('on_hold', 'archived') %}
{% if item.schedule.enabled %}
+ {% endif %}
{% endfor %} diff --git a/templates/partials/projects/schedule_list.html b/templates/partials/projects/schedule_list.html index 781fa17..46ed6db 100644 --- a/templates/partials/projects/schedule_list.html +++ b/templates/partials/projects/schedule_list.html @@ -19,7 +19,8 @@
{% for item in date_group.actions %} -
+
@@ -54,6 +55,11 @@ Pending + {% if project_status == 'on_hold' %} + + On Hold + + {% endif %} {% elif item.schedule.execution_status == 'completed' %} Completed @@ -157,7 +163,8 @@ {% endif %}
- + + {% if project_status not in ('on_hold', 'archived') %}
{% if item.schedule.execution_status == 'pending' %}
+ {% endif %}
{% endfor %}