updated apps

This commit is contained in:
2026-07-14 23:57:03 -04:00
parent 6cc7212cef
commit 010e828e9c
797 changed files with 45153 additions and 4246 deletions
@@ -28,8 +28,9 @@ def _export_documents(doc_store: Any, object_id: str) -> list[dict[str, Any]]:
File binaries are NOT in the JSON export (they live under /config and travel
via the HA backup). An import without a matching backup therefore recreates
file metadata pointing at a missing blob — the storage-hygiene repair issue
catches those as dangling. Web-links round-trip fully. task_ids are dropped:
tasks get fresh ids on import, so the links wouldn't resolve.
catches those as dangling. Web-links round-trip fully. ``task_ids`` are
carried too (as the OLD task ids); the importer remaps them onto the fresh
task ids so a doc's task links survive a backup/restore.
"""
out: list[dict[str, Any]] = []
for d in doc_store.for_object(object_id):
@@ -40,6 +41,8 @@ def _export_documents(doc_store: Any, object_id: str) -> list[dict[str, Any]]:
"url": d.get("url"),
"title": d.get("title"),
"tags": d.get("tags") or [],
"task_ids": d.get("task_ids") or [],
"part_ids": d.get("part_ids") or [],
}
)
else:
@@ -52,6 +55,8 @@ def _export_documents(doc_store: Any, object_id: str) -> list[dict[str, Any]]:
"mime": d.get("mime"),
"size": d.get("size"),
"tags": d.get("tags") or [],
"task_ids": d.get("task_ids") or [],
"part_ids": d.get("part_ids") or [],
}
)
return out
@@ -81,6 +86,14 @@ def _build_export_object(
"name": tdata.get("name", ""),
"type": tdata.get("type", "custom"),
"enabled": tdata.get("enabled", True),
# Provenance + lifecycle a backup must preserve: created_at is the
# next_due fallback anchor when last_performed is None (dropping it
# silently shifts due dates on restore), and the archived_* pair
# keeps a retired task retired — without them an archived task comes
# back ACTIVE after a restore.
"created_at": tdata.get("created_at"),
"archived_at": tdata.get("archived_at"),
"archived_reason": tdata.get("archived_reason"),
"schedule_type": sched["schedule_type"],
"interval_days": sched["interval_days"],
"interval_unit": sched["interval_unit"],
@@ -115,6 +128,9 @@ def _build_export_object(
"assignee_pool": tdata.get("assignee_pool") or [],
"rotation_strategy": tdata.get("rotation_strategy"),
"reading_unit": tdata.get("reading_unit"),
# Spare parts: consumption links + the auto-buy-task marker.
"consumes_parts": tdata.get("consumes_parts"),
"part_ref": tdata.get("part_ref"),
"status": ct.get("_status", "ok"),
"days_until_due": ct.get("_days_until_due"),
"next_due": ct.get("_next_due"),
@@ -168,20 +184,39 @@ def _build_export_object(
},
"tasks": tasks,
"documents": documents,
# Spare parts: full static definition + the tracked stock (dynamic,
# read from the Store) so a backup/restore keeps the shelf state.
"parts": [
{**part, "stock": store.get_part_stock(part["id"]) if store is not None else None}
for part in (entry.data.get("parts") or {}).values()
],
}
def object_entries(hass: HomeAssistant, entry_ids: set[str] | None = None) -> list[ConfigEntry]:
"""The maintenance OBJECT entries (never the global hub), optionally
narrowed to a selection. Shared by every exporter so JSON/YAML/CSV apply
the same selective-export filter. ``entry_ids=None`` means all objects."""
return [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.unique_id != GLOBAL_UNIQUE_ID and (entry_ids is None or entry.entry_id in entry_ids)
]
def build_export_data(
hass: HomeAssistant,
include_history: bool = True,
entry_ids: set[str] | None = None,
) -> dict[str, Any]:
"""Gather all maintenance data into a plain dict.
This must be called from the event loop (accesses HA APIs).
The returned dict contains no HA objects and is safe to
serialize in an executor thread.
serialize in an executor thread. ``entry_ids`` narrows the export to a
selection of objects (None = all).
"""
entries = [entry for entry in hass.config_entries.async_entries(DOMAIN) if entry.unique_id != GLOBAL_UNIQUE_ID]
entries = object_entries(hass, entry_ids)
objects = []
for entry in entries: