diff --git a/backend/devices/router.py b/backend/devices/router.py index 7508763..d5d9c83 100644 --- a/backend/devices/router.py +++ b/backend/devices/router.py @@ -10,6 +10,7 @@ from devices.models import ( DeviceCreate, DeviceUpdate, DeviceInDB, DeviceListResponse, DeviceUsersResponse, DeviceUserInfo, DeviceNoteCreate, DeviceNoteUpdate, + ResetStatsRequest, ResetStatsResult, ) from devices import service import database as mqtt_db @@ -65,7 +66,7 @@ async def create_device( ): device = service.create_device(body) await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "device", - device.device_id, device.device_name or device.device_id) + device.device_id, device.console_name or device.device_name or device.device_id) return device @@ -87,7 +88,7 @@ async def update_device( if "tags" in body.model_fields_set and (old.tags or []) != (device.tags or []): changes["tags"] = {"old": sorted(old.tags or []), "new": sorted(device.tags or [])} await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device", - device_id, device.device_name or device_id, changes=changes or None) + device_id, device.console_name or device.device_name or device_id, changes=changes or None) return device @@ -437,14 +438,14 @@ async def add_user_to_device( db: AsyncSession = Depends(get_pg_session), ): """Add a user reference to the device's user_list field.""" - db = get_firestore() - device_ref = db.collection("devices").document(device_id) + fs = get_firestore() + device_ref = fs.collection("devices").document(device_id) device_doc = device_ref.get() if not device_doc.exists: raise HTTPException(status_code=404, detail="Device not found") # Verify user exists - user_doc = db.collection("users").document(body.user_id).get() + user_doc = fs.collection("users").document(body.user_id).get() if not user_doc.exists: raise HTTPException(status_code=404, detail="User not found") @@ -461,7 +462,7 @@ async def add_user_to_device( existing_ids.add(entry.split("/")[-1]) if body.user_id not in existing_ids: - user_ref = db.collection("users").document(body.user_id) + user_ref = fs.collection("users").document(body.user_id) user_list.append(user_ref) device_ref.update({"user_list": user_list}) @@ -479,8 +480,8 @@ async def remove_user_from_device( db: AsyncSession = Depends(get_pg_session), ): """Remove a user reference from the device's user_list field.""" - db = get_firestore() - device_ref = db.collection("devices").document(device_id) + fs = get_firestore() + device_ref = fs.collection("devices").document(device_id) device_doc = device_ref.get() if not device_doc.exists: raise HTTPException(status_code=404, detail="Device not found") @@ -504,4 +505,75 @@ async def remove_user_from_device( await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device", device_id, device_id, meta={"action_detail": "user_removed", "user_id": user_id}) - return {"status": "removed", "user_id": user_id} + + +@router.post("/{device_id}/reset-stats", response_model=ResetStatsResult) +async def reset_device_stats( + device_id: str, + body: ResetStatsRequest, + _user: TokenPayload = Depends(require_permission("devices", "edit")), + db: AsyncSession = Depends(get_pg_session), +): + """Clears QA/bench test data accumulated before a device ships to a + customer. Only handles categories that don't require the device itself + to be online — Postgres history tables and Firestore device_stats + counters. The two firmware-side resets (telemetry.reset_boot_data, + logs.clear) are sent directly by the frontend over MQTT via the existing + command-ack flow, since they need the device online and this console + already has that round-trip built (see DeviceDetail.jsx's + sendMqttCommand) — duplicating it backend-side would just be a second, + inferior implementation of the same wait-for-device-reply logic. + + Nothing here touches device identity, customer assignment, notes, tags, + warranty/maintenance fields, or audit logs — see ResetStatsRequest's + field list for exactly what's in scope. + """ + device = service.get_device(device_id) + serial = device.serial_number or device.device_id + + result = ResetStatsResult() + + # Firestore device_stats — field-level only. DeviceStatistics mixes true + # counters with warranty/maintenance fields in one object; a blanket + # clear would destroy warranty data, so only the 4 named counter fields + # are ever sent here (deep-merge in service.update_device preserves + # every other key, including warrantyActive/warrantyStart/etc.). + stats_update = {} + if body.bell_stats: + stats_update["totalHammerStrikes"] = 0 + stats_update["perBellStrikes"] = [] + if body.playback_stats: + stats_update["totalPlaybacks"] = 0 + stats_update["totalWarningsGiven"] = 0 + if stats_update: + service.update_device(device_id, DeviceUpdate(device_stats=stats_update)) + if body.bell_stats: + result.bell_stats = True + if body.playback_stats: + result.playback_stats = True + + # Postgres — pure history/telemetry tables, per-device row deletes. + if body.device_logs: + result.device_logs = await mqtt_db.delete_device_logs(serial) + if body.heartbeats: + result.heartbeats = await mqtt_db.delete_device_heartbeats(serial) + if body.commands: + result.commands = await mqtt_db.delete_device_commands(serial) + if body.boot_events: + result.boot_events = await mqtt_db.delete_device_boot_events(serial) + if body.alert_history: + result.alert_history = await mqtt_db.delete_device_alert_events(serial) + if body.ping_samples: + result.ping_samples = await mqtt_db.delete_device_ping_samples(serial) + if body.diagnostics_reports: + result.diagnostics_reports = await mqtt_db.delete_device_diagnostics_reports(serial) + if body.reports: + result.reports = await mqtt_db.delete_device_reports(serial) + if body.current_alerts: + result.current_alerts = await mqtt_db.delete_device_current_alerts(serial) + + await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "device", + device_id, device.console_name or device.device_name or serial, + meta={"action_detail": "reset_stats", "categories": body.model_dump()}) + + return result diff --git a/frontend/src/modals/bellcloud/devices/AddDeviceUserModal.jsx b/frontend/src/modals/bellcloud/devices/AddDeviceUserModal.jsx index 28e2b27..0e061ed 100644 --- a/frontend/src/modals/bellcloud/devices/AddDeviceUserModal.jsx +++ b/frontend/src/modals/bellcloud/devices/AddDeviceUserModal.jsx @@ -172,7 +172,7 @@ export default function AddDeviceUserModal({ open, deviceId, existingUserIds = [ )} {/* Results */} -