CHANGES
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user