Przeglądaj źródła

Add Override to Current button for on-site hold-over extensions

Lets ops convert the currently displayed Current record into an override
without changing it, so it can be patched (e.g. extending checkout) without
being overwritten by the next backend poll.
ZiB 3 miesięcy temu
rodzic
commit
040506a3df

+ 26 - 0
custom_components/room_cal_ha/button.py

@@ -26,6 +26,7 @@ async def async_setup_entry(
   async_add_entities([
     ForceRefreshButton(coordinator, entry, device_name),
     TestNextGuestButton(coordinator, entry, device_name),
+    OverrideCurrentButton(coordinator, entry, device_name),
     OverrideEmptyButton(coordinator, entry, device_name),
     GuestArrivedButton(coordinator, entry, device_name),
     GuestDepartedButton(coordinator, entry, device_name),
@@ -85,6 +86,31 @@ class TestNextGuestButton(CoordinatorEntity[RoomCalCoordinator], ButtonEntity):
     await self.coordinator.async_start_override(OverrideData(current=override_current))
 
 
+class OverrideCurrentButton(CoordinatorEntity[RoomCalCoordinator], ButtonEntity):
+  _attr_has_entity_name = True
+  _attr_translation_key = "override_current"
+  _attr_icon = "mdi:calendar-edit"
+
+  def __init__(
+    self,
+    coordinator: RoomCalCoordinator,
+    entry: ConfigEntry,
+    device_name: str,
+  ) -> None:
+    super().__init__(coordinator)
+    self._attr_unique_id = f"{entry.entry_id}_override_current"
+    self._attr_device_info = DeviceInfo(
+      identifiers={(DOMAIN, f"{entry.entry_id}_room_state")},
+      name=device_name,
+    )
+
+  async def async_press(self) -> None:
+    current = self.coordinator.data.current if self.coordinator.data else None
+    if current is None:
+      return
+    await self.coordinator.async_start_override(OverrideData(current=current))
+
+
 class OverrideEmptyButton(CoordinatorEntity[RoomCalCoordinator], ButtonEntity):
   _attr_has_entity_name = True
   _attr_translation_key = "override_empty"

+ 1 - 0
custom_components/room_cal_ha/strings.json

@@ -120,6 +120,7 @@
     "button": {
       "force_refresh": { "name": "Force Refresh" },
       "test_next_guest": { "name": "Override to Next" },
+      "override_current": { "name": "Override to Current" },
       "override_empty": { "name": "Override to Empty" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_departed": { "name": "Guest Departed" }

+ 1 - 0
custom_components/room_cal_ha/translations/en.json

@@ -120,6 +120,7 @@
     "button": {
       "force_refresh": { "name": "Force Refresh" },
       "test_next_guest": { "name": "Override to Next" },
+      "override_current": { "name": "Override to Current" },
       "override_empty": { "name": "Override to Empty" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_departed": { "name": "Guest Departed" }

+ 51 - 0
tests/test_buttons.py

@@ -10,6 +10,7 @@ import pytest
 
 from custom_components.room_cal_ha.button import (
   ForceRefreshButton,
+  OverrideCurrentButton,
   OverrideEmptyButton,
   TestNextGuestButton,
 )
@@ -119,6 +120,56 @@ async def test_test_next_guest_no_next_data_does_nothing():
   btn.coordinator.async_start_override.assert_not_awaited()
 
 
+# ---------------------------------------------------------------------------
+# OverrideCurrentButton
+# ---------------------------------------------------------------------------
+
+_CURRENT = ReservationData(
+  guest_name="Carol",
+  check_in_date=date(2026, 7, 2),
+  check_in_time=time(15, 0),
+  check_out_date=date(2026, 7, 6),
+  check_out_time=time(11, 0),
+)
+
+
+def _override_current_button(current=_CURRENT):
+  btn = object.__new__(OverrideCurrentButton)
+  btn.coordinator = MagicMock()
+  btn.coordinator.data = MagicMock(current=current) if current is not None else None
+  btn.coordinator.async_start_override = AsyncMock()
+  return btn
+
+
+async def test_override_current_calls_start_override():
+  btn = _override_current_button()
+  await btn.async_press()
+  btn.coordinator.async_start_override.assert_awaited_once()
+
+
+async def test_override_current_passes_unchanged_current_data():
+  btn = _override_current_button()
+  await btn.async_press()
+  override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
+  assert isinstance(override, OverrideData)
+  assert override.current == _CURRENT
+  assert override.next is None
+
+
+async def test_override_current_no_end_at():
+  btn = _override_current_button()
+  await btn.async_press()
+  kwargs = btn.coordinator.async_start_override.call_args[1]
+  assert "end_at" not in kwargs
+
+
+async def test_override_current_no_data_does_nothing():
+  btn = _override_current_button(current=None)
+  btn.coordinator.data = None
+  await btn.async_press()
+  btn.coordinator.async_start_override.assert_not_awaited()
+
+
 # ---------------------------------------------------------------------------
 # OverrideEmptyButton
 # ---------------------------------------------------------------------------