[FIX] google_calendar: Avoid unexpected recurring event duplication

When following recurring meeting are imported through google calendar
synchronisation, it happens that a meeting get unexpectedly
duplicated in Odoo, what will in turn duplicate the event on google
and send invitations to all the existing attendees.

As calendar.recurrence.calendar_event_ids is a One2many field,
the update using self was setting the base_event_id's recurrence_id
field to the last record of self recordset. Due to this, the event
will not be considered to filter out the existing_ranges in
calendar.recurrence._reconcile_events function.
Therefore, a calendar.event will in turn be created using the same
start and end datetime as the base_event_id of the calendar.recurrence.

OPW-3143680

closes odoo/odoo#114392

X-original-commit: d1587758ac642b0181e660ea48222385d01098e3
Signed-off-by: Arnaud Joset <arj@odoo.com>
This commit is contained in:
Akim Juillerat
2023-03-07 02:00:09 +01:00
parent 406abe1f6e
commit eb55cbb244
2 changed files with 81 additions and 1 deletions
@@ -217,7 +217,7 @@ class RecurrenceRule(models.Model):
specific_values_creation = {}
for recurrence in self.filtered('base_event_id'):
self.calendar_event_ids |= recurrence.base_event_id
recurrence.calendar_event_ids |= recurrence.base_event_id
event = recurrence.base_event_id or recurrence._get_first_event(include_outliers=False)
duration = event.stop - event.start
if specific_values_creation:
@@ -742,6 +742,86 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
self.assertEqual(recurrence.event_tz, 'Pacific/Auckland', "The Google event Timezone should be saved on the recurrency")
self.assertGoogleAPINotCalled()
@patch_api
def test_recurrence_no_duplicate(self):
values = [
{
"attendees": [
{
"email": "myemail@exampl.com",
"responseStatus": "needsAction",
"self": True,
},
{"email": "jane.doe@example.com", "responseStatus": "needsAction"},
{
"email": "john.doe@example.com",
"organizer": True,
"responseStatus": "accepted",
},
],
"created": "2023-02-20T11:45:07.000Z",
"creator": {"email": "john.doe@example.com"},
"end": {"dateTime": "2023-02-25T16:20:00+01:00", "timeZone": "Europe/Zurich"},
"etag": '"4611038912699385"',
"eventType": "default",
"iCalUID": "9lxiofipomymx2yr1yt0hpep99@google.com",
"id": "9lxiofipomymx2yr1yt0hpep99",
"kind": "calendar#event",
"organizer": {"email": "john.doe@example.com"},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=SA"],
"reminders": {"useDefault": True},
"sequence": 0,
"start": {"dateTime": "2023-02-25T15:30:00+01:00", "timeZone": "Europe/Zurich"},
"status": "confirmed",
"summary": "Weekly test",
"updated": "2023-02-20T11:45:08.547Z",
},
{
"attendees": [
{
"email": "myemail@exampl.com",
"responseStatus": "needsAction",
"self": True,
},
{
"email": "jane.doe@example.com",
"organizer": True,
"responseStatus": "needsAction",
},
{"email": "john.doe@example.com", "responseStatus": "accepted"},
],
"created": "2023-02-20T11:45:44.000Z",
"creator": {"email": "john.doe@example.com"},
"end": {"dateTime": "2023-02-26T15:20:00+01:00", "timeZone": "Europe/Zurich"},
"etag": '"5534851880843722"',
"eventType": "default",
"iCalUID": "hhb5t0cffjkndvlg7i22f7byn1@google.com",
"id": "hhb5t0cffjkndvlg7i22f7byn1",
"kind": "calendar#event",
"organizer": {"email": "jane.doe@example.com"},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=SU"],
"reminders": {"useDefault": True},
"sequence": 0,
"start": {"dateTime": "2023-02-26T14:30:00+01:00", "timeZone": "Europe/Zurich"},
"status": "confirmed",
"summary": "Weekly test 2",
"updated": "2023-02-20T11:48:00.634Z",
},
]
google_events = GoogleEvent(values)
self.env['calendar.recurrence']._sync_google2odoo(google_events)
no_duplicate_gevent = google_events.filter(lambda e: e.id == "9lxiofipomymx2yr1yt0hpep99")
dt_start = datetime.fromisoformat(no_duplicate_gevent.start["dateTime"]).astimezone(pytz.utc).replace(tzinfo=None).replace(hour=0)
dt_end = datetime.fromisoformat(no_duplicate_gevent.end["dateTime"]).astimezone(pytz.utc).replace(tzinfo=None).replace(hour=23)
no_duplicate_event = self.env["calendar.event"].search(
[
("name", "=", no_duplicate_gevent.summary),
("start", ">=", dt_start),
("stop", "<=", dt_end,)
]
)
self.assertEqual(len(no_duplicate_event), 1)
@patch_api
def test_simple_event_into_recurrency(self):
""" Synched single events should be converted in recurrency without problems"""