Files
odoo_source/addons/microsoft_calendar/models/calendar_recurrence_rule.py
T
Rémy Baranx (bar) 2fbfbf0dce [FIX] calendar, microsoft_calendar: backport bugfixes from master
As several customers complained about lot of bugs in microsoft_calendar in 14.0,
It has been decided to backport bug fixes of the model layer from master to 14.0,
without the need of an upgrade script (no new field, ...).

In master, we use 2 ids (organizer event id + universal id) instead of only one,
to handle Odoo <-> Outlook sync correctly when several attendees sync their Outlook
calendar with their Odoo calendar. For that, we have added a new field.
To report this bug fix in 14.0, the existing field which stores the organizer event id,
is now a string storing both ids separated by a ':' as follow: 'organizer_event_id:universal_id'.

2 new compute fields have been added to be able to use these 2 ids more easily.

(all commits from the original PR have been squashed to ease forward-port)

closes odoo/odoo#95736

X-original-commit: 5e83318a7240585371efd31e407829793f3e732f
Signed-off-by: Arnaud Joset <arj@odoo.com>
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
2022-07-12 13:21:27 +02:00

185 lines
8.4 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class RecurrenceRule(models.Model):
_name = 'calendar.recurrence'
_inherit = ['calendar.recurrence', 'microsoft.calendar.sync']
# Don't sync by default. Sync only when the recurrence is applied
need_sync_m = fields.Boolean(default=False)
microsoft_id = fields.Char('Microsoft Calendar Recurrence Id')
def _compute_rrule(self):
# Note: 'need_sync_m' is set to False to avoid syncing the updated recurrence with
# Outlook, as this update may already come from Outlook. If not, this modification will
# be already synced through the calendar.event.write()
for recurrence in self:
if recurrence.rrule != recurrence._rrule_serialize():
recurrence.write({'rrule': recurrence._rrule_serialize()})
def _inverse_rrule(self):
# Note: 'need_sync_m' is set to False to avoid syncing the updated recurrence with
# Outlook, as this update mainly comes from Outlook (the 'rrule' field is not directly
# modified in Odoo but computed from other fields).
for recurrence in self.filtered('rrule'):
values = self._rrule_parse(recurrence.rrule, recurrence.dtstart)
recurrence.write(dict(values, need_sync_m=False))
def _apply_recurrence(self, specific_values_creation=None, no_send_edit=False, generic_values_creation=None):
events = self.filtered('need_sync_m').calendar_event_ids
detached_events = super()._apply_recurrence(specific_values_creation, no_send_edit, generic_values_creation)
# If a synced event becomes a recurrence, the event needs to be deleted from
# Microsoft since it's now the recurrence which is synced.
vals = []
for event in events._get_synced_events():
if event.active and event.ms_universal_event_id and not event.recurrence_id.ms_universal_event_id:
vals += [{
'name': event.name,
'microsoft_id': event.microsoft_id,
'start': event.start,
'stop': event.stop,
'active': False,
'need_sync_m': True,
}]
event._microsoft_delete(event.user_id, event.ms_organizer_event_id)
event.ms_universal_event_id = False
self.env['calendar.event'].create(vals)
self.calendar_event_ids.need_sync_m = False
return detached_events
def _write_events(self, values, dtstart=None):
# If only some events are updated, sync those events.
# If all events are updated, sync the recurrence instead.
values['need_sync_m'] = bool(dtstart) or values.get("need_sync_m", True)
return super()._write_events(values, dtstart=dtstart)
def _get_organizer(self):
return self.base_event_id.user_id
def _get_rrule(self, dtstart=None):
if not dtstart and self.dtstart:
dtstart = self.dtstart
return super()._get_rrule(dtstart)
def _get_microsoft_synced_fields(self):
return {'rrule'} | self.env['calendar.event']._get_microsoft_synced_fields()
@api.model
def _restart_microsoft_sync(self):
self.env['calendar.recurrence'].search(self._get_microsoft_sync_domain()).write({
'need_sync_m': True,
})
def _has_base_event_time_fields_changed(self, new):
"""
Indicates if at least one time field of the base event has changed, based
on provided `new` values.
Note: for all day event comparison, hours/minutes are ignored.
"""
def _convert(value, to_convert):
return value.date() if to_convert else value
old = self.base_event_id and self.base_event_id.read(['start', 'stop', 'allday'])[0]
return old and (
old['allday'] != new['allday']
or any(
_convert(new[f], new['allday']) != _convert(old[f], old['allday'])
for f in ('start', 'stop')
)
)
def _write_from_microsoft(self, microsoft_event, vals):
current_rrule = self.rrule
# event_tz is written on event in Microsoft but on recurrence in Odoo
vals['event_tz'] = microsoft_event.start.get('timeZone')
super()._write_from_microsoft(microsoft_event, vals)
new_event_values = self.env["calendar.event"]._microsoft_to_odoo_values(microsoft_event)
if self._has_base_event_time_fields_changed(new_event_values):
# we need to recreate the recurrence, time_fields were modified.
base_event_id = self.base_event_id
# We archive the old events to recompute the recurrence. These events are already deleted on Microsoft side.
# We can't call _cancel because events without user_id would not be deleted
(self.calendar_event_ids - base_event_id).microsoft_id = False
(self.calendar_event_ids - base_event_id).unlink()
base_event_id.with_context(dont_notify=True).write(dict(
new_event_values, microsoft_id=False, need_sync_m=False
))
if self.rrule == current_rrule:
# if the rrule has changed, it will be recalculated below
# There is no detached event now
self.with_context(dont_notify=True)._apply_recurrence()
else:
time_fields = (
self.env["calendar.event"]._get_time_fields()
| self.env["calendar.event"]._get_recurrent_fields()
)
# We avoid to write time_fields because they are not shared between events.
self._write_events(dict({
field: value
for field, value in new_event_values.items()
if field not in time_fields
}, need_sync_m=False)
)
# We apply the rrule check after the time_field check because the microsoft ids are generated according
# to base_event start datetime.
if self.rrule != current_rrule:
detached_events = self._apply_recurrence()
detached_events.microsoft_id = False
detached_events.unlink()
def _get_microsoft_sync_domain(self):
# Empty rrule may exists in historical data. It is not a desired behavior but it could have been created with
# older versions of the module. When synced, these recurrence may come back from Microsoft after database cleaning
# and trigger errors as the records are not properly populated.
# We also prevent sync of other user recurrent events.
return [('calendar_event_ids.user_id', '=', self.env.user.id), ('rrule', '!=', False)]
def _cancel_microsoft(self):
self.calendar_event_ids._cancel_microsoft()
super()._cancel_microsoft()
@api.model
def _microsoft_to_odoo_values(self, microsoft_recurrence, default_reminders=(), default_values=None, with_ids=False):
recurrence = microsoft_recurrence.get_recurrence()
if with_ids:
recurrence = {
**recurrence,
'ms_organizer_event_id': microsoft_recurrence.id,
'ms_universal_event_id': microsoft_recurrence.iCalUId,
}
return recurrence
def _microsoft_values(self, fields_to_sync):
"""
Get values to update the whole Outlook event recurrence.
(done through the first event of the Outlook recurrence).
"""
return self.base_event_id._microsoft_values(fields_to_sync, initial_values={'type': 'seriesMaster'})
def _ensure_attendees_have_email(self):
self.calendar_event_ids.filtered(lambda e: e.active)._ensure_attendees_have_email()
def _split_from(self, event, recurrence_values=None):
"""
When a recurrence is splitted, the base event of the new recurrence already
exist and may be already synced with Outlook.
In this case, we need to be removed this event on Outlook side to avoid duplicates while posting
the new recurrence.
"""
new_recurrence = super()._split_from(event, recurrence_values)
if new_recurrence and new_recurrence.base_event_id.microsoft_id:
new_recurrence.base_event_id._microsoft_delete(
new_recurrence.base_event_id._get_organizer(),
new_recurrence.base_event_id.ms_organizer_event_id
)
return new_recurrence