refactor: unify active assignment checks and add project-type guards
- Replace all UnitAssignment "active" checks from `status == "active"` to `assigned_until == None` in both project_locations.py and projects.py. This aligns with the canonical definition: active = no end date set. (status field is still set in sync, but is no longer the query criterion) - Add `_require_sound_project()` helper to both routers and call it at the top of every sound-monitoring-specific endpoint (FTP browser, FTP downloads, RND file viewer, all Excel report endpoints, combined report wizard, upload-all, NRL live status, NRL data upload). Vibration projects hitting these endpoints now receive a clear 400 instead of silently failing or returning empty results. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,19 @@ from backend.templates_config import templates
|
||||
router = APIRouter(prefix="/api/projects/{project_id}", tags=["project-locations"])
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Shared helpers
|
||||
# ============================================================================
|
||||
|
||||
def _require_sound_project(project) -> None:
|
||||
"""Raise 400 if the project is not a sound_monitoring project."""
|
||||
if not project or project.project_type_id != "sound_monitoring":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="This feature is only available for Sound Monitoring projects.",
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Session period helpers
|
||||
# ============================================================================
|
||||
@@ -98,11 +111,11 @@ async def get_project_locations(
|
||||
# Enrich with assignment info
|
||||
locations_data = []
|
||||
for location in locations:
|
||||
# Get active assignment
|
||||
# Get active assignment (active = assigned_until IS NULL)
|
||||
assignment = db.query(UnitAssignment).filter(
|
||||
and_(
|
||||
UnitAssignment.location_id == location.id,
|
||||
UnitAssignment.status == "active",
|
||||
UnitAssignment.assigned_until == None,
|
||||
)
|
||||
).first()
|
||||
|
||||
@@ -258,11 +271,11 @@ async def delete_location(
|
||||
if not location:
|
||||
raise HTTPException(status_code=404, detail="Location not found")
|
||||
|
||||
# Check if location has active assignments
|
||||
# Check if location has active assignments (active = assigned_until IS NULL)
|
||||
active_assignments = db.query(UnitAssignment).filter(
|
||||
and_(
|
||||
UnitAssignment.location_id == location_id,
|
||||
UnitAssignment.status == "active",
|
||||
UnitAssignment.assigned_until == None,
|
||||
)
|
||||
).count()
|
||||
|
||||
@@ -569,9 +582,9 @@ async def get_available_units(
|
||||
)
|
||||
).all()
|
||||
|
||||
# Filter out units that already have active assignments
|
||||
# Filter out units that already have active assignments (active = assigned_until IS NULL)
|
||||
assigned_unit_ids = db.query(UnitAssignment.unit_id).filter(
|
||||
UnitAssignment.status == "active"
|
||||
UnitAssignment.assigned_until == None
|
||||
).distinct().all()
|
||||
assigned_unit_ids = [uid[0] for uid in assigned_unit_ids]
|
||||
|
||||
@@ -747,6 +760,9 @@ async def upload_nrl_data(
|
||||
from datetime import datetime
|
||||
|
||||
# Verify project and location exist
|
||||
project = db.query(Project).filter_by(id=project_id).first()
|
||||
_require_sound_project(project)
|
||||
|
||||
location = db.query(MonitoringLocation).filter_by(
|
||||
id=location_id, project_id=project_id
|
||||
).first()
|
||||
@@ -925,15 +941,18 @@ async def get_nrl_live_status(
|
||||
Fetch cached status from SLMM for the unit assigned to this NRL and
|
||||
return a compact HTML status card. Used in the NRL overview tab for
|
||||
connected NRLs. Gracefully shows an offline message if SLMM is unreachable.
|
||||
Sound Monitoring projects only.
|
||||
"""
|
||||
import os
|
||||
import httpx
|
||||
|
||||
# Find the assigned unit
|
||||
_require_sound_project(db.query(Project).filter_by(id=project_id).first())
|
||||
|
||||
# Find the assigned unit (active = assigned_until IS NULL)
|
||||
assignment = db.query(UnitAssignment).filter(
|
||||
and_(
|
||||
UnitAssignment.location_id == location_id,
|
||||
UnitAssignment.status == "active",
|
||||
UnitAssignment.assigned_until == None,
|
||||
)
|
||||
).first()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user