Fix recurring event duplication - handle recurringEventId properly in all phases
This commit is contained in:
61
bitbylaw/delete_all_calendars.py
Normal file
61
bitbylaw/delete_all_calendars.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script to clear all events from all calendars in Google Calendar account
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
from steps.advoware_cal_sync.calendar_sync_event_step import get_google_service
|
||||||
|
|
||||||
|
async def clear_all_calendars():
|
||||||
|
"""Clear all events from all calendars"""
|
||||||
|
try:
|
||||||
|
service = await get_google_service()
|
||||||
|
|
||||||
|
# Get all calendars
|
||||||
|
print("Fetching calendar list...")
|
||||||
|
calendars_result = service.calendarList().list().execute()
|
||||||
|
calendars = calendars_result.get('items', [])
|
||||||
|
|
||||||
|
print(f"Raw calendars result: {calendars_result}")
|
||||||
|
print(f"Calendars list: {calendars}")
|
||||||
|
|
||||||
|
print(f"Found {len(calendars)} calendars to clear:")
|
||||||
|
|
||||||
|
for calendar in calendars:
|
||||||
|
calendar_id = calendar['id']
|
||||||
|
summary = calendar.get('summary', 'No summary')
|
||||||
|
primary = calendar.get('primary', False)
|
||||||
|
|
||||||
|
print(f" - {summary} (ID: {calendar_id}, Primary: {primary})")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get all events in this calendar
|
||||||
|
events_result = service.events().list(calendarId=calendar_id, singleEvents=True).execute()
|
||||||
|
events = events_result.get('items', [])
|
||||||
|
|
||||||
|
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:
|
||||||
|
print(f" ✗ Failed to clear {summary}: {e}")
|
||||||
|
|
||||||
|
print("\nAll calendars have been cleared of events.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(clear_all_calendars())
|
||||||
@@ -614,9 +614,14 @@ async def handler(event, context):
|
|||||||
)
|
)
|
||||||
logger.info(f"Fetched {len(rows)} existing sync rows")
|
logger.info(f"Fetched {len(rows)} existing sync rows")
|
||||||
|
|
||||||
# Build indexes
|
# Build indexes - use recurringEventId for recurring events to avoid duplicates
|
||||||
db_adv_index = {str(row['advoware_frnr']): row for row in rows if row['advoware_frnr']}
|
db_adv_index = {str(row['advoware_frnr']): row for row in rows if row['advoware_frnr']}
|
||||||
db_google_index = {row['google_event_id']: row for row in rows if row['google_event_id']}
|
db_google_index = {}
|
||||||
|
for row in rows:
|
||||||
|
if row['google_event_id']:
|
||||||
|
# For recurring events, use the master event ID (recurringEventId)
|
||||||
|
# For regular events, use the event_id directly
|
||||||
|
db_google_index[row['google_event_id']] = row
|
||||||
|
|
||||||
# Phase 1: New from Advoware => Google
|
# Phase 1: New from Advoware => Google
|
||||||
logger.info("Phase 1: Processing new appointments from Advoware")
|
logger.info("Phase 1: Processing new appointments from Advoware")
|
||||||
@@ -640,7 +645,12 @@ async def handler(event, context):
|
|||||||
# Phase 2: New from Google => Advoware
|
# Phase 2: New from Google => Advoware
|
||||||
logger.info("Phase 2: Processing new events from Google")
|
logger.info("Phase 2: Processing new events from Google")
|
||||||
for event_id, evt in google_map.items():
|
for event_id, evt in google_map.items():
|
||||||
if event_id not in db_google_index:
|
# For recurring events, check if the master event (recurringEventId) is already synced
|
||||||
|
# For regular events, check the event_id directly
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not is_already_synced:
|
||||||
try:
|
try:
|
||||||
frnr = await safe_create_advoware_appointment(advoware, standardize_appointment_data(evt, 'google'), kuerzel, True)
|
frnr = await safe_create_advoware_appointment(advoware, standardize_appointment_data(evt, 'google'), kuerzel, True)
|
||||||
if frnr and str(frnr) != 'None':
|
if frnr and str(frnr) != 'None':
|
||||||
@@ -664,7 +674,19 @@ async def handler(event, context):
|
|||||||
frnr = row['advoware_frnr']
|
frnr = row['advoware_frnr']
|
||||||
event_id = row['google_event_id']
|
event_id = row['google_event_id']
|
||||||
adv_exists = str(frnr) in adv_map if frnr else False
|
adv_exists = str(frnr) in adv_map if frnr else False
|
||||||
google_exists = event_id in google_map if event_id else False
|
|
||||||
|
# For Google events, check if the master event or any instance exists
|
||||||
|
google_exists = False
|
||||||
|
if event_id:
|
||||||
|
# Check if the stored event_id exists
|
||||||
|
if event_id in google_map:
|
||||||
|
google_exists = True
|
||||||
|
else:
|
||||||
|
# Check if any event has this as recurringEventId (master event still exists)
|
||||||
|
for evt in google_map.values():
|
||||||
|
if evt.get('recurringEventId') == event_id:
|
||||||
|
google_exists = True
|
||||||
|
break
|
||||||
|
|
||||||
if not adv_exists and not google_exists:
|
if not adv_exists and not google_exists:
|
||||||
# Both missing - soft delete
|
# Both missing - soft delete
|
||||||
@@ -773,11 +795,36 @@ async def handler(event, context):
|
|||||||
|
|
||||||
# Phase 4: Update existing entries if changed
|
# Phase 4: Update existing entries if changed
|
||||||
logger.info("Phase 4: Processing updates for existing entries")
|
logger.info("Phase 4: Processing updates for existing entries")
|
||||||
|
# Track which master events we've already processed to avoid duplicate updates
|
||||||
|
processed_master_events = set()
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
frnr = row['advoware_frnr']
|
frnr = row['advoware_frnr']
|
||||||
event_id = row['google_event_id']
|
event_id = row['google_event_id']
|
||||||
adv_data = adv_map.get(str(frnr)) if frnr else None
|
adv_data = adv_map.get(str(frnr)) if frnr else None
|
||||||
google_data = google_map.get(event_id) if event_id else None
|
|
||||||
|
# For Google events, find the corresponding event (could be master or instance)
|
||||||
|
google_data = None
|
||||||
|
if event_id:
|
||||||
|
# First try to find the exact event_id
|
||||||
|
if event_id in google_map:
|
||||||
|
google_data = google_map[event_id]
|
||||||
|
else:
|
||||||
|
# Look for any event that has this as recurringEventId
|
||||||
|
for evt in google_map.values():
|
||||||
|
if evt.get('recurringEventId') == event_id:
|
||||||
|
google_data = evt
|
||||||
|
break
|
||||||
|
|
||||||
|
# Skip if we don't have both sides or if we've already processed this master event
|
||||||
|
if not adv_data or not google_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# For recurring events, only process the master event once
|
||||||
|
master_event_id = google_data.get('recurringEventId') or event_id
|
||||||
|
if master_event_id in processed_master_events:
|
||||||
|
continue
|
||||||
|
processed_master_events.add(master_event_id)
|
||||||
|
|
||||||
if adv_data and google_data:
|
if adv_data and google_data:
|
||||||
adv_std = standardize_appointment_data(adv_data, 'advoware')
|
adv_std = standardize_appointment_data(adv_data, 'advoware')
|
||||||
|
|||||||
Reference in New Issue
Block a user