diff --git a/backend/routers/pending_deployments.py b/backend/routers/pending_deployments.py index 7496d74..608c409 100644 --- a/backend/routers/pending_deployments.py +++ b/backend/routers/pending_deployments.py @@ -296,6 +296,12 @@ async def promote_pending( if not location: raise HTTPException(status_code=404, detail=f"Location {location_id!r} not found.") project_id = location.project_id + # Backfill the captured GPS onto the existing location if it doesn't + # have coordinates yet. (Previously the captured coords were dropped on + # the assign-to-existing path, so only create-new locations got a pin.) + # Don't clobber coordinates an operator already set. + if pd.coordinates and not (location.coordinates or "").strip(): + location.coordinates = pd.coordinates else: # Create-new path. Need a project (existing or new). project_id = payload.get("project_id") @@ -456,6 +462,50 @@ async def cancel_pending( return {"success": True, "cancelled_at": pd.cancelled_at.isoformat()} +@router.post("/pending/{pending_id}/resync-location") +async def resync_location(pending_id: str, db: Session = Depends(get_db)): + """Re-push a promoted capture's GPS onto its assigned location. + + Use when a capture's coordinates didn't land on the location (e.g. it was + assigned to a pre-existing location that had none). Unlike the auto-backfill + on classify, this is an explicit operator action and OVERWRITES the + location's coordinates with the captured GPS. + """ + pd = db.query(PendingDeployment).filter_by(id=pending_id).first() + if not pd: + raise HTTPException(status_code=404, detail="Pending deployment not found.") + if pd.status != "assigned" or not pd.resulting_assignment_id: + raise HTTPException(status_code=400, detail="Only a promoted (assigned) capture can be re-forwarded.") + if not (pd.coordinates or "").strip(): + raise HTTPException(status_code=400, detail="This capture has no GPS coordinates to forward.") + + asg = db.query(UnitAssignment).filter_by(id=pd.resulting_assignment_id).first() + if not asg: + raise HTTPException(status_code=404, detail="Resulting assignment not found.") + location = db.query(MonitoringLocation).filter_by(id=asg.location_id).first() + if not location: + raise HTTPException(status_code=404, detail="Assigned location not found.") + + old = location.coordinates + location.coordinates = pd.coordinates.strip() + + _record_history( + db, unit_id=pd.unit_id, + change_type="deployment_coords_reforwarded", + old_value=old, + new_value=location.coordinates, + notes=f"Re-forwarded capture GPS to location '{location.name}'", + ) + + db.commit() + return { + "success": True, + "location_id": location.id, + "location_name": location.name, + "coordinates": location.coordinates, + } + + # ── Helpers ─────────────────────────────────────────────────────────────────── def _to_dict(pd: PendingDeployment, unit: Optional[RosterUnit] = None, detail: bool = False) -> dict: diff --git a/templates/admin/pending_deployments.html b/templates/admin/pending_deployments.html index 04df4fe..edb1eda 100644 --- a/templates/admin/pending_deployments.html +++ b/templates/admin/pending_deployments.html @@ -207,9 +207,19 @@ function _renderPdCard(pd) { `; } else if (pd.status === 'assigned') { + const reforward = pd.coordinates + ? `` + : ''; footerActions = `