fix: send zigbee/zwave coordinator to pending inventory, not auto-canvas
The coordinator was special-cased in both mesh imports to auto-create a canvas Node, so it never appeared in the pending inventory and users could not approve/hide/type it like every other device. Remove the auto-placement: the coordinator now flows through the shared pending path (upsert into pending_devices with suggested_type zigbee_coordinator / zwave_coordinator). An already-approved coordinator Node still gets its properties refreshed on re-import via the shared approved-node path. The response's coordinator/coordinator_already_existed fields are retained (now always unset) for backward-compatible shape; the frontend already ignored them. Tests updated: coordinator lands in pending (counts include it), no Node auto-created, pending metadata carried, approved coordinator refresh + no pending re-list. ha-relevant: yes
This commit is contained in:
@@ -11,7 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.db.database import AsyncSessionLocal, get_db
|
||||
from app.db.models import Design, Node, PendingDevice, PendingDeviceLink, ScanRun
|
||||
from app.db.models import Node, PendingDevice, PendingDeviceLink, ScanRun
|
||||
from app.schemas.scan import ScanRunResponse
|
||||
from app.schemas.zigbee import (
|
||||
ZigbeeCoordinatorOut,
|
||||
@@ -143,10 +143,8 @@ async def _persist_pending_import(
|
||||
# the by-IEEE lookups below resolve to a single row.
|
||||
await dedupe_nodes_by_ieee(db)
|
||||
|
||||
# Determine target design (use first design as fallback)
|
||||
first_design = (await db.execute(select(Design).order_by(Design.created_at).limit(1))).scalar()
|
||||
default_design_id = first_design.id if first_design else None
|
||||
|
||||
# Coordinator is no longer auto-placed, so the response's coordinator fields
|
||||
# stay unset — retained for backward-compatible response shape.
|
||||
coordinator_out: ZigbeeCoordinatorOut | None = None
|
||||
coordinator_existed = False
|
||||
pending_created = 0
|
||||
@@ -160,44 +158,9 @@ async def _persist_pending_import(
|
||||
ieee, n.get("vendor"), n.get("model"), n.get("lqi")
|
||||
)
|
||||
|
||||
if n.get("device_type") == "Coordinator":
|
||||
# The coordinator may sit on several canvases (one Node per design).
|
||||
# Refresh props on every matching node, not just one.
|
||||
existing_nodes = (
|
||||
await db.execute(
|
||||
select(Node).where(Node.ieee_address == ieee).order_by(Node.id)
|
||||
)
|
||||
).scalars().all()
|
||||
if existing_nodes:
|
||||
for existing_node in existing_nodes:
|
||||
existing_node.properties = merge_zigbee_properties(
|
||||
existing_node.properties, props
|
||||
)
|
||||
first = existing_nodes[0]
|
||||
coordinator_out = ZigbeeCoordinatorOut(
|
||||
id=first.id,
|
||||
label=first.label,
|
||||
ieee_address=ieee,
|
||||
)
|
||||
coordinator_existed = True
|
||||
continue
|
||||
label = n.get("friendly_name") or ieee
|
||||
node = Node(
|
||||
label=label,
|
||||
type=n.get("type") or "zigbee_coordinator",
|
||||
status="online",
|
||||
check_method="none",
|
||||
ieee_address=ieee,
|
||||
services=[],
|
||||
properties=props,
|
||||
design_id=default_design_id,
|
||||
)
|
||||
db.add(node)
|
||||
await db.flush()
|
||||
coordinator_out = ZigbeeCoordinatorOut(
|
||||
id=node.id, label=label, ieee_address=ieee
|
||||
)
|
||||
continue
|
||||
# The coordinator is no longer auto-placed on the canvas — it flows to
|
||||
# the pending inventory like every other device, so the user approves it
|
||||
# explicitly. Only the shared paths below run for it.
|
||||
|
||||
# If the device has already been approved as a canvas Node, refresh
|
||||
# its properties on every canvas it sits on and skip creating a pending
|
||||
|
||||
Reference in New Issue
Block a user