This commit is contained in:
gsinghpal
2026-06-04 09:49:51 -04:00
parent 41ce3784d7
commit ba7c028c30
11 changed files with 3504 additions and 61 deletions

View File

@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import logging
import secrets
from odoo import api, fields, models

View File

@@ -4,7 +4,7 @@ import json
import logging
import time
import requests
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -338,7 +338,17 @@ class FusionCalendarAccount(models.Model):
updated = 0
deleted = 0
for event_data in all_events:
result = self._process_google_event(event_data)
# Per-row savepoint: one bad event must not abort the whole page
# (which would leave sync_token unadvanced and re-fail every cron).
try:
with self.env.cr.savepoint():
result = self._process_google_event(event_data)
except Exception as e:
_logger.warning(
"Skipping Google event %s on account %s: %s",
event_data.get('id'), self.id, e,
)
continue
if result == 'created':
created += 1
elif result == 'updated':
@@ -409,7 +419,15 @@ class FusionCalendarAccount(models.Model):
stop_val = vals.get('stop') or vals.get('stop_date')
if not (start_val and stop_val and vals.get('name')):
return None
domain = [('name', '=', vals['name']), ('active', 'in', [True, False])]
# Scope to THIS account's owner so a same-titled, same-time event that
# belongs to a DIFFERENT user is never merged in. Reuse only this
# account's own pulled events, or the user's native (sourceless) events.
domain = [
('name', '=', vals['name']),
('active', 'in', [True, False]),
('partner_ids', 'in', [self.x_fc_user_id.partner_id.id]),
('x_fc_source_account_id', 'in', [self.id, False]),
]
if vals.get('allday'):
domain += [('start_date', '=', start_val), ('stop_date', '=', stop_val)]
else:
@@ -417,20 +435,20 @@ class FusionCalendarAccount(models.Model):
return CalendarEvent.search(domain, limit=1)
def _upsert_event_link(self, EventLink, odoo_event_id, external_id, ical_uid):
"""Create or update a link between an Odoo event and an external event.
"""Create or update the link for this (account, external event).
If this account already has a link to the same Odoo event, update the
external_id rather than creating a duplicate link row. Returns the
link record.
Branches on the table's real UNIQUE key (account, external_id) so it can
never raise an IntegrityError; if the external event is already linked,
re-point it at the given Odoo event. Returns the link record.
"""
existing = EventLink.search([
('x_fc_account_id', '=', self.id),
('x_fc_event_id', '=', odoo_event_id),
('x_fc_external_id', '=', external_id),
], limit=1)
now = fields.Datetime.now()
if existing:
existing.write({
'x_fc_external_id': external_id,
'x_fc_event_id': odoo_event_id,
'x_fc_universal_id': ical_uid or existing.x_fc_universal_id,
'x_fc_last_synced': now,
})
@@ -481,7 +499,7 @@ class FusionCalendarAccount(models.Model):
existing_link = EventLink.search([
('x_fc_universal_id', '=', ical_uid),
('x_fc_universal_id', '!=', False),
('x_fc_account_id.x_fc_user_id', '=', self.x_fc_user_id.id),
], limit=1) if ical_uid else None
if existing_link and existing_link.x_fc_event_id:
@@ -527,8 +545,8 @@ class FusionCalendarAccount(models.Model):
start_dt = datetime.fromisoformat(start_str.replace('Z', '+00:00'))
end_dt = datetime.fromisoformat(end_str.replace('Z', '+00:00'))
# Convert to naive UTC for Odoo
start_utc = start_dt.astimezone(tz=None).replace(tzinfo=None) if start_dt.tzinfo else start_dt
end_utc = end_dt.astimezone(tz=None).replace(tzinfo=None) if end_dt.tzinfo else end_dt
start_utc = start_dt.astimezone(timezone.utc).replace(tzinfo=None) if start_dt.tzinfo else start_dt
end_utc = end_dt.astimezone(timezone.utc).replace(tzinfo=None) if end_dt.tzinfo else end_dt
except (ValueError, KeyError):
return None
vals = {
@@ -567,10 +585,12 @@ class FusionCalendarAccount(models.Model):
MICROSOFT_GRAPH_API, MICROSOFT_SELECT_FIELDS, start_dt, end_dt,
)
all_events = []
next_sync_token = self.x_fc_sync_token
page_num = 0
max_events = 5000 if self.x_fc_sync_token else 2000
created = 0
updated = 0
deleted = 0
processed = 0
while url:
page_num += 1
@@ -594,16 +614,28 @@ class FusionCalendarAccount(models.Model):
resp.raise_for_status()
data = resp.json()
# Process each page as it arrives — no unbounded accumulation and no
# event cap that would silently drop everything past the limit. Each
# event gets its own savepoint so one bad row can't abort the page.
page_events = data.get('value', [])
all_events.extend(page_events)
_logger.warning("MS sync account %s page %d: %d events (total %d)", self.id, page_num, len(page_events), len(all_events))
if len(all_events) >= max_events:
_logger.warning(
"MS sync account %s: hit event limit (%d/%d), stopping fetch",
self.id, len(all_events), max_events,
)
break
for event_data in page_events:
try:
with self.env.cr.savepoint():
result = self._process_microsoft_event(event_data)
except Exception as e:
_logger.warning(
"Skipping MS event %s on account %s: %s",
event_data.get('id'), self.id, e,
)
continue
if result == 'created':
created += 1
elif result == 'updated':
updated += 1
elif result == 'deleted':
deleted += 1
processed += 1
_logger.warning("MS sync account %s page %d: %d events (processed %d total)", self.id, page_num, len(page_events), processed)
url = data.get('@odata.nextLink')
if not url:
@@ -611,21 +643,6 @@ class FusionCalendarAccount(models.Model):
if '$deltatoken=' in delta_link:
next_sync_token = delta_link.split('$deltatoken=')[-1]
_logger.warning("MS sync account %s: processing %d events...", self.id, len(all_events))
created = 0
updated = 0
deleted = 0
for i, event_data in enumerate(all_events):
result = self._process_microsoft_event(event_data)
if result == 'created':
created += 1
elif result == 'updated':
updated += 1
elif result == 'deleted':
deleted += 1
if (i + 1) % 25 == 0:
_logger.warning("MS sync account %s: processed %d/%d events", self.id, i + 1, len(all_events))
self.sudo().write({
'x_fc_sync_token': next_sync_token,
'x_fc_last_sync': fields.Datetime.now(),
@@ -714,7 +731,7 @@ class FusionCalendarAccount(models.Model):
existing_link = EventLink.search([
('x_fc_universal_id', '=', ical_uid),
('x_fc_universal_id', '!=', False),
('x_fc_account_id.x_fc_user_id', '=', self.x_fc_user_id.id),
], limit=1) if ical_uid else None
if existing_link and existing_link.x_fc_event_id: