v0.12.1 — Unit Swap wizard, editable timeline, roster/tz fixes #54
@@ -336,6 +336,36 @@ async def promote_pending(
|
|||||||
db.add(location)
|
db.add(location)
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
|
# If this location already has an active assignment, the /deploy
|
||||||
|
# capture means someone replaced that unit in the field — close the
|
||||||
|
# old assignment, break the outgoing unit's modem pairing, and bench
|
||||||
|
# it so the heartbeat / polling subsystem stops chasing it.
|
||||||
|
existing_active = db.query(UnitAssignment).filter(
|
||||||
|
UnitAssignment.location_id == location.id,
|
||||||
|
UnitAssignment.assigned_until == None, # noqa: E711
|
||||||
|
).first()
|
||||||
|
if existing_active and existing_active.unit_id != pd.unit_id:
|
||||||
|
existing_active.assigned_until = pd.captured_at
|
||||||
|
existing_active.status = "completed"
|
||||||
|
old_unit = db.query(RosterUnit).filter_by(id=existing_active.unit_id).first()
|
||||||
|
if old_unit:
|
||||||
|
if old_unit.deployed_with_modem_id:
|
||||||
|
old_modem = db.query(RosterUnit).filter_by(
|
||||||
|
id=old_unit.deployed_with_modem_id, device_type="modem"
|
||||||
|
).first()
|
||||||
|
if old_modem and old_modem.deployed_with_unit_id == old_unit.id:
|
||||||
|
old_modem.deployed_with_unit_id = None
|
||||||
|
old_unit.deployed_with_modem_id = None
|
||||||
|
if old_unit.deployed:
|
||||||
|
old_unit.deployed = False
|
||||||
|
_record_history(
|
||||||
|
db, unit_id=existing_active.unit_id,
|
||||||
|
change_type="assignment_swapped",
|
||||||
|
old_value=location.name,
|
||||||
|
new_value=f"superseded by /deploy capture → {pd.unit_id}",
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
|
||||||
# Create the assignment. assigned_at = pending capture time (so
|
# Create the assignment. assigned_at = pending capture time (so
|
||||||
# events emitted after the install are correctly attributed back
|
# events emitted after the install are correctly attributed back
|
||||||
# to this location).
|
# to this location).
|
||||||
@@ -354,6 +384,12 @@ async def promote_pending(
|
|||||||
db.add(assignment)
|
db.add(assignment)
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
|
# Incoming unit is in the field again — flip it back to deployed
|
||||||
|
# if it was on the bench (mirrors the swap endpoint).
|
||||||
|
incoming_unit = db.query(RosterUnit).filter_by(id=pd.unit_id).first()
|
||||||
|
if incoming_unit and not incoming_unit.deployed:
|
||||||
|
incoming_unit.deployed = True
|
||||||
|
|
||||||
# Promote the pending row.
|
# Promote the pending row.
|
||||||
pd.status = "assigned"
|
pd.status = "assigned"
|
||||||
pd.promoted_at = datetime.utcnow()
|
pd.promoted_at = datetime.utcnow()
|
||||||
|
|||||||
@@ -876,6 +876,20 @@ async def unassign_unit(
|
|||||||
assignment.status = "completed"
|
assignment.status = "completed"
|
||||||
assignment.assigned_until = datetime.utcnow()
|
assignment.assigned_until = datetime.utcnow()
|
||||||
|
|
||||||
|
# Unit is leaving the field — bench it so the heartbeat / polling
|
||||||
|
# subsystem stops chasing it. Also break the modem pairing both ways.
|
||||||
|
unit = db.query(RosterUnit).filter_by(id=assignment.unit_id).first()
|
||||||
|
if unit:
|
||||||
|
if unit.deployed_with_modem_id:
|
||||||
|
modem = db.query(RosterUnit).filter_by(
|
||||||
|
id=unit.deployed_with_modem_id, device_type="modem"
|
||||||
|
).first()
|
||||||
|
if modem and modem.deployed_with_unit_id == unit.id:
|
||||||
|
modem.deployed_with_unit_id = None
|
||||||
|
unit.deployed_with_modem_id = None
|
||||||
|
if unit.deployed:
|
||||||
|
unit.deployed = False
|
||||||
|
|
||||||
location = db.query(MonitoringLocation).filter_by(id=assignment.location_id).first()
|
location = db.query(MonitoringLocation).filter_by(id=assignment.location_id).first()
|
||||||
_record_assignment_history(
|
_record_assignment_history(
|
||||||
db,
|
db,
|
||||||
@@ -1320,13 +1334,18 @@ async def swap_unit_on_location(
|
|||||||
# deployed_with_modem_id / deployed_with_unit_id back-reference
|
# deployed_with_modem_id / deployed_with_unit_id back-reference
|
||||||
# doesn't orphan onto the unit that just left the field.
|
# doesn't orphan onto the unit that just left the field.
|
||||||
old_unit = db.query(RosterUnit).filter_by(id=current.unit_id).first()
|
old_unit = db.query(RosterUnit).filter_by(id=current.unit_id).first()
|
||||||
if old_unit and old_unit.deployed_with_modem_id:
|
if old_unit:
|
||||||
|
if old_unit.deployed_with_modem_id:
|
||||||
old_modem = db.query(RosterUnit).filter_by(
|
old_modem = db.query(RosterUnit).filter_by(
|
||||||
id=old_unit.deployed_with_modem_id, device_type="modem"
|
id=old_unit.deployed_with_modem_id, device_type="modem"
|
||||||
).first()
|
).first()
|
||||||
if old_modem and old_modem.deployed_with_unit_id == current.unit_id:
|
if old_modem and old_modem.deployed_with_unit_id == current.unit_id:
|
||||||
old_modem.deployed_with_unit_id = None
|
old_modem.deployed_with_unit_id = None
|
||||||
old_unit.deployed_with_modem_id = None
|
old_unit.deployed_with_modem_id = None
|
||||||
|
# Bench the outgoing unit — it's no longer in the field, so
|
||||||
|
# the heartbeat / polling subsystem should stop chasing it.
|
||||||
|
if old_unit.deployed:
|
||||||
|
old_unit.deployed = False
|
||||||
|
|
||||||
# Create new assignment
|
# Create new assignment
|
||||||
new_assignment = UnitAssignment(
|
new_assignment = UnitAssignment(
|
||||||
|
|||||||
Reference in New Issue
Block a user