Fix recurring event duplication - handle recurringEventId properly in all phases

This commit is contained in:
root
2025-10-23 18:46:49 +00:00
parent da2b9960b0
commit db1206f91c
2 changed files with 51 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Script to clear all events from all calendars in Google Calendar account Script to delete all calendars from Google Calendar account
""" """
import asyncio import asyncio
import sys import sys
@@ -9,8 +9,8 @@ sys.path.append('.')
from steps.advoware_cal_sync.calendar_sync_event_step import get_google_service from steps.advoware_cal_sync.calendar_sync_event_step import get_google_service
async def clear_all_calendars(): async def delete_all_calendars():
"""Clear all events from all calendars""" """Delete all calendars from the Google account"""
try: try:
service = await get_google_service() service = await get_google_service()
@@ -22,7 +22,7 @@ async def clear_all_calendars():
print(f"Raw calendars result: {calendars_result}") print(f"Raw calendars result: {calendars_result}")
print(f"Calendars list: {calendars}") print(f"Calendars list: {calendars}")
print(f"Found {len(calendars)} calendars to clear:") print(f"Found {len(calendars)} calendars to delete:")
for calendar in calendars: for calendar in calendars:
calendar_id = calendar['id'] calendar_id = calendar['id']
@@ -31,31 +31,22 @@ async def clear_all_calendars():
print(f" - {summary} (ID: {calendar_id}, Primary: {primary})") print(f" - {summary} (ID: {calendar_id}, Primary: {primary})")
# Skip primary calendar if you want to keep it
if primary:
print(f" Skipping primary calendar: {summary}")
continue
try: try:
# Get all events in this calendar # Delete the calendar
events_result = service.events().list(calendarId=calendar_id, singleEvents=True).execute() service.calendars().delete(calendarId=calendar_id).execute()
events = events_result.get('items', []) print(f" ✓ Deleted calendar: {summary}")
print(f" Found {len(events)} events to delete")
# Delete each event
deleted_count = 0
for event in events:
try:
service.events().delete(calendarId=calendar_id, eventId=event['id']).execute()
deleted_count += 1
except Exception as e:
print(f" Failed to delete event {event['id']}: {e}")
print(f" ✓ Deleted {deleted_count} events from: {summary}")
except Exception as e: except Exception as e:
print(f" ✗ Failed to clear {summary}: {e}") print(f" ✗ Failed to delete calendar {summary}: {e}")
print("\nAll calendars have been cleared of events.") print("\nAll non-primary calendars have been deleted.")
except Exception as e: except Exception as e:
print(f"Error: {e}") print(f"Error: {e}")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(clear_all_calendars()) asyncio.run(delete_all_calendars())

View File

@@ -255,6 +255,42 @@ Cron-Step für regelmäßige Ausführung.
- Recurring-Events: Begrenzte Unterstützung; Advoware hat keine RRULE. - Recurring-Events: Begrenzte Unterstützung; Advoware hat keine RRULE.
- Timestamps: Fehlende in Google können zu Fallback führen. - Timestamps: Fehlende in Google können zu Fallback führen.
- Performance: Bei vielen Terminen könnte Paginierung helfen. - Performance: Bei vielen Terminen könnte Paginierung helfen.
- **Single Events Expansion**: `singleEvents=true` in `fetch_google_events()` expandiert wiederkehrende Events in einzelne Instanzen, was zu Duplizierungsproblemen führt, wenn nicht korrekt behandelt.
## Kritischer Bugfix: Duplizierung wiederkehrender Termine
### Problemstellung
Bei wiederkehrenden Terminen (`dauertermin=1`) wurden Termine bei jedem Sync dupliziert, weil `fetch_google_events()` mit `singleEvents=true` arbeitet:
1. **Google Calendar erstellt Master-Event** mit RRULE und `event_id` (z.B. `"abc123"`)
2. **`fetch_google_events()` expandiert** das Event in einzelne Instanzen mit IDs wie `"abc123_20251024"`, `"abc123_20251031"`, etc.
3. **Jede Instanz wird als "neu" behandelt** und erstellt einen separaten Advoware-Termin
4. **Ergebnis:** 1 wiederkehrender Advoware-Termin → N duplizierte Advoware-Termine
### Lösung
**RecurringEventId-basierte Erkennung** in allen Phasen:
- **DB-Indizes:** Verwenden weiterhin die gespeicherten `event_id` (Master-ID)
- **Phase 2:** Prüfe sowohl `event_id` als auch `recurringEventId` gegen DB-Index
- **Phase 3:** Berücksichtige `recurringEventId` bei Existenzprüfungen
- **Phase 4:** Verarbeite nur Master-Events einmal, nicht jede Instanz
**Code-Änderungen:**
```python
# Phase 2: Prüfe Master-Event
recurring_master_id = evt.get('recurringEventId')
is_already_synced = event_id in db_google_index or (recurring_master_id and recurring_master_id in db_google_index)
# Phase 4: Verarbeite nur Master-Events einmal
master_event_id = google_data.get('recurringEventId') or event_id
if master_event_id in processed_master_events:
continue
```
### Auswirkung
- Wiederkehrende Termine werden nicht mehr dupliziert
- Bidirektionale Sync funktioniert korrekt für alle Event-Typen
- Performance-Verbesserung durch weniger redundante Verarbeitung
## Korrekter Umgang mit Advoware-Timestamps ## Korrekter Umgang mit Advoware-Timestamps