217 files

This commit is contained in:
Home Assistant Version Control
2026-07-30 23:59:38 +00:00
parent d43a63ad29
commit 7b5e46e702
217 changed files with 15978 additions and 3912 deletions
@@ -7,6 +7,7 @@ from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from ..const import (
CONF_TASKS,
@@ -105,6 +106,11 @@ def _completion_blocked(rd: Any, task_id: str) -> bool:
vol.Schema(
{
vol.Required("part_id"): vol.All(str, vol.Length(max=MAX_ID_LENGTH)),
# #111: the pool may live on another object. The
# schema has to allow it or voluptuous rejects the
# completion before the handler (which already
# validates the reference) ever runs.
vol.Optional("entry_id"): vol.All(str, vol.Length(max=MAX_ID_LENGTH)),
vol.Optional("quantity", default=1): vol.All(
vol.Any(int, float), vol.Coerce(float), vol.Range(min=0.01, max=999)
),
@@ -143,21 +149,38 @@ async def ws_complete_task(
used_parts = msg.get("used_parts")
if used_parts is not None:
from ..helpers.parts import sanitize_consumes_parts
from . import foreign_part_resolver
used_parts = sanitize_consumes_parts(used_parts, set(_entry.data.get("parts") or {}))
used_parts = sanitize_consumes_parts(
used_parts,
set(_entry.data.get("parts") or {}),
foreign_part_ids=foreign_part_resolver(hass),
)
await rd.coordinator.complete_maintenance(
task_id=msg["task_id"],
notes=msg.get("notes"),
cost=msg.get("cost"),
duration=msg.get("duration"),
checklist_state=msg.get("checklist_state"),
feedback=msg.get("feedback"),
photo_doc_id=msg.get("photo_doc_id"),
reading_value=msg.get("reading_value"),
restock_quantity=msg.get("restock_quantity"),
used_parts=used_parts,
)
try:
await rd.coordinator.complete_maintenance(
task_id=msg["task_id"],
notes=msg.get("notes"),
cost=msg.get("cost"),
duration=msg.get("duration"),
checklist_state=msg.get("checklist_state"),
feedback=msg.get("feedback"),
photo_doc_id=msg.get("photo_doc_id"),
reading_value=msg.get("reading_value"),
restock_quantity=msg.get("restock_quantity"),
used_parts=used_parts,
# Who did it: taken from the authenticated connection, never from
# the payload — a client must not be able to credit someone else.
# This is also what feeds the `least_completed` rotation strategy
# and what satisfies a task requiring the "user" detail.
completed_by=connection.user.id if connection.user else None,
)
except ServiceValidationError as err:
# Required completion details are missing. The dialog normally
# prevents this, so reaching here means an older/cached frontend or a
# scripted call — answer with the field list rather than a traceback.
connection.send_error(msg["id"], "completion_details_required", str(err))
return
connection.send_result(msg["id"], {"success": True})
@@ -212,13 +235,20 @@ async def ws_quick_complete_task(
)
return
await rd.coordinator.complete_maintenance(
task_id=msg["task_id"],
notes=defaults.get("notes"),
cost=defaults.get("cost"),
duration=defaults.get("duration"),
feedback=defaults.get("feedback"),
)
try:
await rd.coordinator.complete_maintenance(
task_id=msg["task_id"],
notes=defaults.get("notes"),
cost=defaults.get("cost"),
duration=defaults.get("duration"),
feedback=defaults.get("feedback"),
completed_by=connection.user.id if connection.user else None,
)
except ServiceValidationError as err:
# The task demands details the quick-complete defaults do not cover —
# same fallback as `no_defaults`: the caller opens the full dialog.
connection.send_error(msg["id"], "completion_details_required", str(err))
return
connection.send_result(msg["id"], {"success": True, "via": "quick"})