Преглед на файлове

Add door_code_changed to GuestStateDiff so lock slot updates on in-window code changes

Previously a door code change fetched from the backend was stored silently with
no re-push to the lock. door_code_changed mirrors wifi_key_changed and triggers
_async_run_check_in_actions, so the periodic sync picks up admin-initiated code
rotations automatically.
ZiB преди 4 месеца
родител
ревизия
ff46bce6ab
променени са 3 файла, в които са добавени 28 реда и са изтрити 5 реда
  1. 4 3
      custom_components/room_cal_ha/coordinator.py
  2. 4 1
      custom_components/room_cal_ha/models.py
  3. 20 1
      tests/test_models.py

+ 4 - 3
custom_components/room_cal_ha/coordinator.py

@@ -115,7 +115,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         diff = self._diff_with_unifi_check(old_current, backend_current, now_naive)
         if diff.checkout_needed:
           await self._async_run_check_out_actions(diff.departing)
-        if diff.checkin_needed or diff.wifi_key_changed:
+        if diff.checkin_needed or diff.wifi_key_changed or diff.door_code_changed:
           await self._async_run_check_in_actions(backend_current or self._empty_reservation())
 
     store_current = self._with_defaults(self.store.get(STORE_CURRENT))
@@ -447,7 +447,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
       diff = self._diff_with_unifi_check(self._override.current, self.store.get(STORE_CURRENT), now_naive)
       if diff.checkout_needed:
         await self._async_run_check_out_actions(diff.departing)
-      if diff.checkin_needed or diff.wifi_key_changed:
+      if diff.checkin_needed or diff.wifi_key_changed or diff.door_code_changed:
         store_current = self.store.get(STORE_CURRENT)
         await self._async_run_check_in_actions(store_current or self._empty_reservation())
     self._override = None
@@ -530,7 +530,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         diff = self._diff_with_unifi_check(stored, new_stored, now_naive)
         if diff.checkout_needed:
           await self._async_run_check_out_actions(diff.departing)
-        if diff.checkin_needed or diff.wifi_key_changed:
+        if diff.checkin_needed or diff.wifi_key_changed or diff.door_code_changed:
           await self._async_run_check_in_actions(new_stored)
 
   def _diff_with_unifi_check(
@@ -575,6 +575,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
       checkout_needed=diff.checkout_needed,
       checkin_needed=diff.checkin_needed or ssid_wrong,
       wifi_key_changed=diff.wifi_key_changed or key_wrong,
+      door_code_changed=diff.door_code_changed,
       departing=diff.departing,
     )
 

+ 4 - 1
custom_components/room_cal_ha/models.py

@@ -12,16 +12,18 @@ class GuestStateDiff:
   checkout_needed  — run check-out actions for `departing`
   checkin_needed   — run check-in actions for the incoming reservation
   wifi_key_changed — same reservation window, WiFi key changed (re-push key only)
+  door_code_changed — same reservation window, door code changed (re-set lock slot)
   departing        — the reservation being vacated (set when checkout_needed is True)
   """
   checkout_needed: bool = False
   checkin_needed: bool = False
   wifi_key_changed: bool = False
+  door_code_changed: bool = False
   departing: ReservationData | None = None
 
   @property
   def has_changes(self) -> bool:
-    return self.checkout_needed or self.checkin_needed or self.wifi_key_changed
+    return self.checkout_needed or self.checkin_needed or self.wifi_key_changed or self.door_code_changed
 
 
 @dataclass
@@ -122,6 +124,7 @@ def reservation_diff(
 
   return GuestStateDiff(
     wifi_key_changed=old.wifi_key != new.wifi_key,
+    door_code_changed=old.door_code != new.door_code,
   )
 
 

+ 20 - 1
tests/test_models.py

@@ -177,7 +177,7 @@ def test_override_data_with_next():
 # reservation_diff
 # ---------------------------------------------------------------------------
 
-def _res(checkin_h: int, checkout_h: int, *, wifi_key="key", wifi_ssid="ssid") -> ReservationData:
+def _res(checkin_h: int, checkout_h: int, *, wifi_key="key", wifi_ssid="ssid", door_code=None) -> ReservationData:
   """Build a reservation on 2026-07-01 with given check-in/out hours."""
   return ReservationData(
     guest_name="Guest",
@@ -187,6 +187,7 @@ def _res(checkin_h: int, checkout_h: int, *, wifi_key="key", wifi_ssid="ssid") -
     check_out_time=time(checkout_h, 0),
     wifi_ssid=wifi_ssid,
     wifi_key=wifi_key,
+    door_code=door_code,
   )
 
 
@@ -244,10 +245,28 @@ def test_diff_same_window_wifi_key_changed():
   new = _res(14, 20, wifi_key="new-key")
   diff = reservation_diff(old, new, _DURING)
   assert diff.wifi_key_changed
+  assert not diff.door_code_changed
   assert not diff.checkin_needed
   assert not diff.checkout_needed
 
 
+def test_diff_same_window_door_code_changed():
+  old = _res(14, 20, door_code="1234")
+  new = _res(14, 20, door_code="5678")
+  diff = reservation_diff(old, new, _DURING)
+  assert diff.door_code_changed
+  assert not diff.wifi_key_changed
+  assert not diff.checkin_needed
+  assert not diff.checkout_needed
+
+
+def test_diff_same_window_no_change():
+  old = _res(14, 20, wifi_key="key", door_code="1234")
+  new = _res(14, 20, wifi_key="key", door_code="1234")
+  diff = reservation_diff(old, new, _DURING)
+  assert not diff.has_changes
+
+
 def test_diff_different_window_both_active_returns_checkout_and_checkin():
   old = ReservationData(
     check_in_date=date(2026, 7, 1), check_in_time=time(14, 0),