Updated Scheduler and Maintainance Apps

This commit is contained in:
2026-07-08 12:00:20 -04:00
parent fefc2c8b5c
commit 0f7585754d
227 changed files with 2877 additions and 958 deletions
@@ -180,6 +180,9 @@ def _build_task_summary(
# local state for the action fields is empty (writes back null).
"on_complete_action": task_data.get("on_complete_action"),
"quick_complete_defaults": task_data.get("quick_complete_defaults"),
# Per-occurrence postpone: the active override date (or null) so the
# panel can badge "postponed to …" and offer to clear it.
"due_override": task_data.get("due_override"),
# Computed fields from coordinator
"status": ct.get("_status", "ok"),
"is_done": ct.get("_is_done", False),
@@ -439,6 +442,7 @@ def async_register_commands(hass: HomeAssistant) -> None:
ws_delete_task,
ws_duplicate_task,
ws_list_tasks,
ws_postpone_task,
ws_quick_complete_task,
ws_reset_task,
ws_skip_task,
@@ -481,6 +485,7 @@ def async_register_commands(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, ws_skip_task)
websocket_api.async_register_command(hass, ws_reset_task)
websocket_api.async_register_command(hass, ws_snooze_task)
websocket_api.async_register_command(hass, ws_postpone_task)
websocket_api.async_register_command(hass, ws_update_history_entry)
websocket_api.async_register_command(hass, ws_get_templates)
websocket_api.async_register_command(hass, ws_export_data)
@@ -69,6 +69,7 @@ from ..const import (
DEFAULT_WARNING_DAYS,
DEFAULT_WARRANTY_REMINDER_DAYS,
DOMAIN,
GLOBAL_UNIQUE_ID,
KNOWN_OBJECT_TABLE_COLUMNS,
MAX_PANEL_TITLE_LENGTH,
MAX_REMINDER_LEADS,
@@ -242,9 +243,25 @@ async def ws_get_statistics(
# Counts come from the shared aggregator (single source of truth) so the
# panel/card chips and the global summary sensors can never diverge.
counts = compute_status_counts(hass)
# (#86) Registry-resolved entity_ids of the four summary sensors. The
# dashboard strategy's KPI chips used to hardcode
# sensor.maintenance_supporter_<key>, which breaks whenever the actual id
# differs (renamed by the user, a _2 collision suffix, or a pre-pinning
# install that registered localized ids) — the chips then read "unknown"
# forever. unique_ids are stable, so resolve through the registry.
from homeassistant.helpers import entity_registry as er
ent_reg = er.async_get(hass)
summary_entity_ids = {
key: ent_reg.async_get_entity_id("sensor", DOMAIN, f"{GLOBAL_UNIQUE_ID}_summary_{key}")
for key in ("overdue", "due_soon", "triggered", "ok")
}
connection.send_result(
msg["id"],
{
"summary_entity_ids": summary_entity_ids,
"total_objects": counts["total_objects"],
"total_tasks": counts["total_tasks"],
"overdue": counts["overdue"],
@@ -374,6 +374,8 @@ async def ws_import_json(
"due_date",
"interval_anchor",
"last_planned_due",
# per-occurrence postpone (round-trips like last_planned_due)
"due_override",
# nested recurrence (calendar kinds) — config-flow normalize
# treats it as authoritative when present.
"schedule",
@@ -388,6 +390,16 @@ async def ws_import_json(
"adaptive_config",
"checklist",
"schedule_time",
# v2.17+ / #83 fields — mirror the export builder so a JSON
# backup round-trips them (validated/clamped just below).
"priority",
"labels",
"earliest_completion_days",
"on_complete_action",
"quick_complete_defaults",
"assignee_pool",
"rotation_strategy",
"reading_unit",
):
val = task_entry.get(key)
if val is not None:
@@ -9,6 +9,7 @@ production modules. ``__all__`` makes the re-exports explicit for mypy --strict.
from .tasks_actions import (
ws_complete_task,
ws_postpone_task,
ws_quick_complete_task,
ws_reset_task,
ws_skip_task,
@@ -62,6 +63,7 @@ __all__ = [
"ws_delete_task",
"ws_duplicate_task",
"ws_list_tasks",
"ws_postpone_task",
"ws_quick_complete_task",
"ws_reset_task",
"ws_skip_task",
@@ -246,6 +246,43 @@ async def ws_reset_task(
connection.send_result(msg["id"], {"success": True})
@websocket_api.websocket_command(
{
vol.Required("type"): "maintenance_supporter/task/postpone",
vol.Required("entry_id"): vol.All(str, vol.Length(max=MAX_ID_LENGTH)),
vol.Required("task_id"): vol.All(str, vol.Length(max=MAX_ID_LENGTH)),
vol.Required("until"): vol.All(str, vol.Length(max=MAX_DATE_LENGTH)),
}
)
@websocket_api.async_response
async def ws_postpone_task(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Postpone the current occurrence to a chosen date (per-occurrence defer)."""
from datetime import date as date_cls
rd = _get_runtime_data(hass, msg["entry_id"])
if rd is None or rd.coordinator is None:
connection.send_error(msg["id"], "not_found", "Coordinator not found")
return
entry = hass.config_entries.async_get_entry(msg["entry_id"])
if entry is None or msg["task_id"] not in entry.data.get(CONF_TASKS, {}):
connection.send_error(msg["id"], "not_found", "Task not found")
return
try:
until = date_cls.fromisoformat(msg["until"])
except ValueError:
connection.send_error(msg["id"], "invalid_date", "Invalid date format")
return
await rd.coordinator.async_postpone_task(msg["task_id"], until)
connection.send_result(msg["id"], {"success": True})
@websocket_api.websocket_command(
{
vol.Required("type"): "maintenance_supporter/task/snooze",