|
|
@@ -1,4 +1,5 @@
|
|
|
import asyncio
|
|
|
+import logging
|
|
|
from datetime import date, timedelta
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
@@ -9,6 +10,8 @@ from room_cal._utils import FetchResult, safe_json_loads, iter_dates, services a
|
|
|
if TYPE_CHECKING:
|
|
|
from room_cal.service_api import ServiceAPI
|
|
|
|
|
|
+_log = logging.getLogger(__name__)
|
|
|
+
|
|
|
ACTION_SPEC = ('fetch-reservations', [])
|
|
|
|
|
|
|
|
|
@@ -66,12 +69,39 @@ async def fetch_reservations(
|
|
|
if rid:
|
|
|
existing_by_id[rid] = (e, d)
|
|
|
|
|
|
- service_reservations = await api.get_reservations(start_date, end_date)
|
|
|
+ errored_services: set[str] = set()
|
|
|
+ service_reservations = await api.get_reservations(start_date, end_date, _out_errors=errored_services)
|
|
|
service_ids = {r['id'] for r in service_reservations}
|
|
|
|
|
|
+ # Per-service guard: don't delete a local reservation when its service threw an exception —
|
|
|
+ # the missing ID could be a transient failure, not a genuine cancellation.
|
|
|
+ returned_services = {r['service'] for r in service_reservations}
|
|
|
+ stored_by_service: dict[str, int] = {}
|
|
|
+ for data in (d for _, d in existing_by_id.values()):
|
|
|
+ svc = data.get('service', '')
|
|
|
+ if svc:
|
|
|
+ stored_by_service[svc] = stored_by_service.get(svc, 0) + 1
|
|
|
+ guarded_services: set[str] = set()
|
|
|
+ for svc, count in stored_by_service.items():
|
|
|
+ if svc in errored_services:
|
|
|
+ _log.error(
|
|
|
+ 'fetch_reservations: %r errored; skipping %d deletion(s) to avoid false purge',
|
|
|
+ svc, count,
|
|
|
+ )
|
|
|
+ guarded_services.add(svc)
|
|
|
+ elif svc not in returned_services:
|
|
|
+ _log.error(
|
|
|
+ 'fetch_reservations: %r returned 0 reservations but %d are stored; '
|
|
|
+ 'skipping deletion(s) — verify service is healthy',
|
|
|
+ svc, count,
|
|
|
+ )
|
|
|
+ guarded_services.add(svc)
|
|
|
+
|
|
|
changed = False
|
|
|
|
|
|
for rid, (event, data) in existing_by_id.items():
|
|
|
+ if data.get('service') in guarded_services:
|
|
|
+ continue
|
|
|
if rid not in service_ids:
|
|
|
changed = True
|
|
|
await res_cal.delete(event)
|