ソースを参照

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 ヶ月 前
親
コミット
040506a3df

+ 26 - 0
custom_components/room_cal_ha/button.py

@@ -26,6 +26,7 @@ async def async_setup_entry(
   async_add_entities([
   async_add_entities([
     ForceRefreshButton(coordinator, entry, device_name),
     ForceRefreshButton(coordinator, entry, device_name),
     TestNextGuestButton(coordinator, entry, device_name),
     TestNextGuestButton(coordinator, entry, device_name),
+    OverrideCurrentButton(coordinator, entry, device_name),
     OverrideEmptyButton(coordinator, entry, device_name),
     OverrideEmptyButton(coordinator, entry, device_name),
     GuestArrivedButton(coordinator, entry, device_name),
     GuestArrivedButton(coordinator, entry, device_name),
     GuestDepartedButton(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))
     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):
 class OverrideEmptyButton(CoordinatorEntity[RoomCalCoordinator], ButtonEntity):
   _attr_has_entity_name = True
   _attr_has_entity_name = True
   _attr_translation_key = "override_empty"
   _attr_translation_key = "override_empty"

+ 1 - 0
custom_components/room_cal_ha/strings.json

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

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

@@ -120,6 +120,7 @@
     "button": {
     "button": {
       "force_refresh": { "name": "Force Refresh" },
       "force_refresh": { "name": "Force Refresh" },
       "test_next_guest": { "name": "Override to Next" },
       "test_next_guest": { "name": "Override to Next" },
+      "override_current": { "name": "Override to Current" },
       "override_empty": { "name": "Override to Empty" },
       "override_empty": { "name": "Override to Empty" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_departed": { "name": "Guest Departed" }
       "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 (
 from custom_components.room_cal_ha.button import (
   ForceRefreshButton,
   ForceRefreshButton,
+  OverrideCurrentButton,
   OverrideEmptyButton,
   OverrideEmptyButton,
   TestNextGuestButton,
   TestNextGuestButton,
 )
 )
@@ -119,6 +120,56 @@ async def test_test_next_guest_no_next_data_does_nothing():
   btn.coordinator.async_start_override.assert_not_awaited()
   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
 # OverrideEmptyButton
 # ---------------------------------------------------------------------------
 # ---------------------------------------------------------------------------