Fix recurring event duplication - handle recurringEventId properly in all phases
This commit is contained in:
@@ -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())
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user