291 files
This commit is contained in:
@@ -46,6 +46,7 @@ from ..helpers.qr_generator import (
|
||||
generate_qr_svg,
|
||||
generate_qr_svg_data_uri,
|
||||
)
|
||||
from ..helpers.update_state import async_version_state
|
||||
from ..helpers.ws_errors import send_translated_error
|
||||
from ..websocket.tasks import _check_nfc_tag_duplicate, _validate_trigger_config
|
||||
from . import ID_FIELD, _get_object_entries, _load_object_entry, _load_object_task, _merge_global_options
|
||||
@@ -512,17 +513,17 @@ def _keep_fleet_part_id(is_fleet: bool, old_id: str) -> bool:
|
||||
@websocket_api.websocket_command({vol.Required("type"): f"{DOMAIN}/version"})
|
||||
@websocket_api.async_response
|
||||
async def ws_version(hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]) -> None:
|
||||
"""The installed integration version (manifest).
|
||||
"""The running integration version, and the installed one while an update waits for a restart.
|
||||
|
||||
Roadmap guard 2 — stale-bundle handshake: the panel compares this against
|
||||
the version esbuild stamped into its bundle and offers a reload when a
|
||||
cached old frontend is talking to a newer backend (HA's service worker
|
||||
updates stale-while-revalidate, so this happens routinely after updates).
|
||||
Roadmap guard 2 — stale-bundle handshake: the panel compares `version`
|
||||
against the version esbuild stamped into its bundle and offers a reload
|
||||
when a cached old frontend is talking to a newer backend (HA's service
|
||||
worker updates stale-while-revalidate, so this happens routinely after
|
||||
updates). `restart_pending` (with `installed_version`, the manifest on
|
||||
disk) says the files were updated but Home Assistant still runs the old
|
||||
code — the panel then asks for a restart instead (helpers/update_state).
|
||||
"""
|
||||
from homeassistant.loader import async_get_integration
|
||||
|
||||
integration = await async_get_integration(hass, DOMAIN)
|
||||
connection.send_result(msg["id"], {"version": integration.version})
|
||||
connection.send_result(msg["id"], await async_version_state(hass))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
|
||||
@@ -95,11 +95,12 @@ async def ws_create_part(
|
||||
|
||||
stock = msg.get("stock")
|
||||
if stock is not None:
|
||||
from ..parts_runtime import async_change_part_stock, async_set_part_restoring
|
||||
from ..parts_runtime import async_change_part_stock
|
||||
|
||||
await async_change_part_stock(hass, entry, part["id"], absolute=float(stock))
|
||||
if part.get("reuse") and msg.get("restoring"):
|
||||
await async_set_part_restoring(hass, entry, part["id"], float(msg["restoring"]))
|
||||
pile = msg.get("restoring") if part.get("reuse") else None
|
||||
await async_change_part_stock(
|
||||
hass, entry, part["id"], absolute=float(stock), restoring=float(pile) if pile is not None else None
|
||||
)
|
||||
# Reload so the part's stock sensor appears (entities are created at setup).
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
connection.send_result(msg["id"], {"part_id": part["id"]})
|
||||
@@ -139,24 +140,27 @@ async def ws_update_part(
|
||||
parts[part["id"]] = part
|
||||
_persist_parts(hass, entry, parts)
|
||||
|
||||
from ..parts_runtime import async_change_part_stock, async_set_part_restoring, schedule_buy_task_reconcile
|
||||
from ..parts_runtime import (
|
||||
async_change_part_stock,
|
||||
async_set_part_restoring,
|
||||
async_untrack_part_stock,
|
||||
schedule_buy_task_reconcile,
|
||||
)
|
||||
|
||||
# #208: the waiting pile goes with the part's reuse — set when given, and
|
||||
# gone when the part is no longer reusable.
|
||||
# gone when the part is no longer reusable. Booked together with the
|
||||
# stock: a first count creates the stock the pile lives next to.
|
||||
pile: float | None = None
|
||||
if not part.get("reuse") and existing.get("reuse"):
|
||||
await async_set_part_restoring(hass, entry, part["id"], 0)
|
||||
pile = 0.0
|
||||
elif part.get("reuse") and msg.get("restoring") is not None:
|
||||
await async_set_part_restoring(hass, entry, part["id"], float(msg["restoring"]))
|
||||
if "stock" in msg:
|
||||
stock = msg.get("stock")
|
||||
if stock is None:
|
||||
rd = _get_runtime_data(hass, entry.entry_id)
|
||||
if rd and rd.store:
|
||||
rd.store.set_part_stock(part["id"], None)
|
||||
await rd.store.async_save()
|
||||
schedule_buy_task_reconcile(hass, entry)
|
||||
else:
|
||||
await async_change_part_stock(hass, entry, part["id"], absolute=float(stock))
|
||||
pile = float(msg["restoring"])
|
||||
if "stock" in msg and msg.get("stock") is None:
|
||||
await async_untrack_part_stock(hass, entry, part["id"])
|
||||
elif "stock" in msg:
|
||||
await async_change_part_stock(hass, entry, part["id"], absolute=float(msg["stock"]), restoring=pile)
|
||||
elif pile is not None:
|
||||
await async_set_part_restoring(hass, entry, part["id"], pile)
|
||||
else:
|
||||
# Threshold/opt-in edits can change the desired buy-task set.
|
||||
schedule_buy_task_reconcile(hass, entry)
|
||||
|
||||
@@ -199,14 +199,19 @@ async def ws_adopt_problem_sensors(
|
||||
# dialog's suggestion (the target object/parts may have changed).
|
||||
from ..const import CONF_PARTS
|
||||
from ..helpers.parts import sanitize_consumes_parts
|
||||
from ..helpers.sanitize import sanitize_assignee_pool, seed_rotation_assignee
|
||||
|
||||
stashed = pop_stashed_config(hass, entity_id) or {}
|
||||
for field in ("notes", "responsible_user_id", "priority", "labels"):
|
||||
for field in ("notes", "responsible_user_id", "assignee_pool", "rotation_strategy", "priority", "labels"):
|
||||
if stashed.get(field):
|
||||
task_data[field] = stashed[field]
|
||||
# An explicit dialog pick wins over the stashed responsible user.
|
||||
if "assignee_pool" in task_data:
|
||||
task_data["assignee_pool"] = sanitize_assignee_pool(task_data["assignee_pool"])
|
||||
# An explicit dialog pick wins over the stashed responsible user;
|
||||
# a rotation keeps someone of its pool on duty.
|
||||
if sel.get("responsible_user_id"):
|
||||
task_data["responsible_user_id"] = sel["responsible_user_id"]
|
||||
seed_rotation_assignee(task_data)
|
||||
# Link the suggested spare part — or, absent one, the stashed link —
|
||||
# validated against the target object's parts (an unknown id is
|
||||
# silently dropped, same as the task-CRUD path).
|
||||
|
||||
@@ -17,6 +17,7 @@ from ..helpers.aggregate import get_coordinator_data, get_runtime_data, object_n
|
||||
from ..helpers.assignees import task_is_for
|
||||
from ..helpers.entry_tasks import write_task
|
||||
from ..helpers.permissions import require_write, user_may_write
|
||||
from ..helpers.sanitize import seed_rotation_assignee
|
||||
from . import (
|
||||
ID_FIELD,
|
||||
_build_task_summary,
|
||||
@@ -110,6 +111,10 @@ async def ws_assign_user(
|
||||
task.pop("responsible_user_id", None)
|
||||
else:
|
||||
task["responsible_user_id"] = user_id
|
||||
# The rotation rule of task/update: a rotation keeps someone of its pool
|
||||
# on duty (unassigning, or a user outside the pool, picks its first).
|
||||
seed_rotation_assignee(task)
|
||||
user_id = task.get("responsible_user_id")
|
||||
|
||||
# Patch only this task's key onto a fresh read — never write back a map
|
||||
# snapshot from before an await.
|
||||
|
||||
Reference in New Issue
Block a user