updated apps
This commit is contained in:
@@ -12,7 +12,9 @@ from ..const import (
|
||||
CONF_TASKS,
|
||||
MAX_CHECKLIST_ITEM_LENGTH,
|
||||
MAX_CHECKLIST_ITEMS,
|
||||
MAX_COST,
|
||||
MAX_DATE_LENGTH,
|
||||
MAX_DURATION_MINUTES,
|
||||
MAX_ID_LENGTH,
|
||||
MAX_TEXT_LENGTH,
|
||||
)
|
||||
@@ -26,6 +28,30 @@ from . import (
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _load_task_context(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
*,
|
||||
need_coordinator: bool = True,
|
||||
) -> tuple[Any, Any] | None:
|
||||
"""Resolve ``(runtime_data, entry)`` for a task action, or send the standard
|
||||
not-found error and return None.
|
||||
|
||||
Consolidates the identical prologue the task-action handlers copied inline
|
||||
(the copies had already drifted — e.g. snooze omitted the coordinator check).
|
||||
"""
|
||||
rd = _get_runtime_data(hass, msg["entry_id"])
|
||||
if need_coordinator and (rd is None or rd.coordinator is None):
|
||||
connection.send_error(msg["id"], "not_found", "Coordinator not found")
|
||||
return None
|
||||
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 None
|
||||
return rd, entry
|
||||
|
||||
|
||||
def _completion_blocked(rd: Any, task_id: str) -> bool:
|
||||
"""True iff the task's completion window forbids completing it right now.
|
||||
|
||||
@@ -48,8 +74,8 @@ def _completion_blocked(rd: Any, task_id: str) -> bool:
|
||||
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.Optional("notes"): vol.Any(vol.All(str, vol.Length(max=MAX_TEXT_LENGTH)), None),
|
||||
vol.Optional("cost"): vol.Any(vol.All(vol.Coerce(float), vol.Range(min=0, max=1_000_000)), None),
|
||||
vol.Optional("duration"): vol.Any(vol.All(vol.Coerce(int), vol.Range(min=0, max=525_600)), None),
|
||||
vol.Optional("cost"): vol.Any(vol.All(vol.Coerce(float), vol.Range(min=0, max=MAX_COST)), None),
|
||||
vol.Optional("duration"): vol.Any(vol.All(vol.Coerce(int), vol.Range(min=0, max=MAX_DURATION_MINUTES)), None),
|
||||
# Restrict checklist_state to {string-key (≤500): bool, ...} with
|
||||
# a hard cap on entries. Without this, attackers (or bad clients)
|
||||
# could inflate the per-task history with arbitrarily large dicts.
|
||||
@@ -67,6 +93,9 @@ def _completion_blocked(rd: Any, task_id: str) -> bool:
|
||||
# Meter readings (v2.20, #83): the recorded value for `reading` tasks.
|
||||
# Wide numeric bounds — meters count high, temperatures go negative.
|
||||
vol.Optional("reading_value"): vol.Any(vol.All(vol.Coerce(float), vol.Range(min=-1e12, max=1e12)), None),
|
||||
# Spare parts: on an auto-created "buy" task, how many units were
|
||||
# actually bought (dialog override of the part's restock_quantity).
|
||||
vol.Optional("restock_quantity"): vol.Any(vol.All(int, vol.Range(min=1, max=9999)), None),
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@@ -76,15 +105,10 @@ async def ws_complete_task(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Mark a task as completed."""
|
||||
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")
|
||||
ctx = _load_task_context(hass, connection, msg)
|
||||
if ctx is None:
|
||||
return
|
||||
rd, _entry = ctx
|
||||
|
||||
if _completion_blocked(rd, msg["task_id"]):
|
||||
connection.send_error(
|
||||
@@ -103,6 +127,7 @@ async def ws_complete_task(
|
||||
feedback=msg.get("feedback"),
|
||||
photo_doc_id=msg.get("photo_doc_id"),
|
||||
reading_value=msg.get("reading_value"),
|
||||
restock_quantity=msg.get("restock_quantity"),
|
||||
)
|
||||
connection.send_result(msg["id"], {"success": True})
|
||||
|
||||
@@ -186,15 +211,10 @@ async def ws_skip_task(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Skip the current maintenance cycle."""
|
||||
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")
|
||||
ctx = _load_task_context(hass, connection, msg)
|
||||
if ctx is None:
|
||||
return
|
||||
rd, _entry = ctx
|
||||
|
||||
await rd.coordinator.skip_maintenance(
|
||||
task_id=msg["task_id"],
|
||||
@@ -221,15 +241,10 @@ async def ws_reset_task(
|
||||
"""Reset the last performed date."""
|
||||
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")
|
||||
ctx = _load_task_context(hass, connection, msg)
|
||||
if ctx is None:
|
||||
return
|
||||
rd, _entry = ctx
|
||||
|
||||
reset_date = None
|
||||
if msg.get("date"):
|
||||
@@ -263,15 +278,10 @@ async def ws_postpone_task(
|
||||
"""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")
|
||||
ctx = _load_task_context(hass, connection, msg)
|
||||
if ctx is None:
|
||||
return
|
||||
rd, _entry = ctx
|
||||
|
||||
try:
|
||||
until = date_cls.fromisoformat(msg["until"])
|
||||
@@ -304,9 +314,7 @@ async def ws_snooze_task(
|
||||
"""
|
||||
from .. import DOMAIN, NOTIFICATION_MANAGER_KEY
|
||||
|
||||
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")
|
||||
if _load_task_context(hass, connection, msg, need_coordinator=False) is None:
|
||||
return
|
||||
|
||||
nm = hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
|
||||
|
||||
Reference in New Issue
Block a user