Files
ha-freshharvest/custom_components/freshharvest/switch.py
T
flan 5e9d721a0f
Validate / hassfest (push) Failing after 8s
Validate / HACS (push) Failing after 17s
Validate / pytest (push) Failing after 8s
Add restore, and separate the produce box from add-ons
Restore is POST /s/submit/restore-delivery, wired to switch.turn_off. Its
popup only exists once an order is actually skipped, which is why it could not
be found earlier; verified end to end by skipping Aug 18 and restoring it.

Fixes two bugs found while testing that. async_fetch treated popup and AJAX
fragments as full pages, so the signed-in heuristic read them as logged out and
skip could never run. And the to-do list mixed box produce with add-ons, which
would have invited deletes with no endpoint behind them: only add-ons are
add/removable, so the entity is now scoped to those.
2026-08-03 20:20:15 +00:00

99 lines
3.6 KiB
Python

"""Skip control for the upcoming delivery.
A switch rather than a button: skipped/not-skipped is state the conversation
agent can read back and reverse, where a button would be fire-and-forget.
"""
from __future__ import annotations
import logging
from typing import Any
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import FreshHarvestConfigEntry
from .api import FreshHarvestError
from .const import DOMAIN, EVENT_ACTION
from .coordinator import FreshHarvestCoordinator
from .entity import FreshHarvestEntity
_LOGGER = logging.getLogger(__name__)
DESCRIPTION = SwitchEntityDescription(
key="skip_open_order",
translation_key="skip_open_order",
icon="mdi:calendar-remove",
)
async def async_setup_entry(
hass: HomeAssistant,
entry: FreshHarvestConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the switch platform."""
async_add_entities([FreshHarvestSkip(entry.runtime_data, entry)])
class FreshHarvestSkip(FreshHarvestEntity, SwitchEntity):
"""On means the next changeable delivery is skipped."""
entity_description = DESCRIPTION
def __init__(
self, coordinator: FreshHarvestCoordinator, entry: FreshHarvestConfigEntry
) -> None:
super().__init__(coordinator, entry, DESCRIPTION.key)
self._entry = entry
@property
def available(self) -> bool:
"""Only meaningful while there is an order that can still be changed."""
return super().available and self.coordinator.data.open_order is not None
@property
def is_on(self) -> bool:
"""A skipped order stops offering a shopping window."""
order = self.coordinator.data.open_order
return order is not None and not order.is_open
async def async_turn_on(self, **kwargs: Any) -> None:
"""Skip the next changeable delivery."""
order = self.coordinator.data.open_order
if order is None or order.delivery_date is None:
raise HomeAssistantError("no delivery is currently skippable")
try:
result = await self.coordinator.actions.async_skip(
order.delivery_date, dry_run=False
)
except FreshHarvestError as err:
self._fire("skip", False, str(order.delivery_date), str(err))
raise HomeAssistantError(f"could not skip: {err}") from err
self._fire("skip", True, str(order.delivery_date), result.detail)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Un-skip the delivery."""
order = self.coordinator.data.open_order
if order is None or order.delivery_date is None:
raise HomeAssistantError("no delivery to restore")
try:
result = await self.coordinator.actions.async_restore(
order.delivery_date, dry_run=False
)
except FreshHarvestError as err:
self._fire("restore", False, str(order.delivery_date), str(err))
raise HomeAssistantError(f"could not restore: {err}") from err
self._fire("restore", True, str(order.delivery_date), result.detail)
await self.coordinator.async_request_refresh()
def _fire(self, action: str, ok: bool, target: str, detail: str) -> None:
self.hass.bus.async_fire(
EVENT_ACTION,
{"domain": DOMAIN, "entry_id": self._entry.entry_id, "action": action,
"success": ok, "target": target, "detail": detail},
)