193 lines
8.7 KiB
Python
193 lines
8.7 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
from odoo import api, fields, models
|
||
|
from odoo.osv import expression
|
||
|
|
||
|
|
||
|
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.with_context(dont_notify=True).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)
|
||
|
# Edge case: if the base event was deleted manually in 'self_only' update, skip applying recurrence.
|
||
|
if self._has_base_event_time_fields_changed(new_event_values) and (new_event_values['start'] >= self.base_event_id.start):
|
||
|
# 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.with_context(dont_notify=True)._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):
|
||
|
# Do not sync Odoo recurrences with Outlook Calendar anymore.
|
||
|
domain = expression.FALSE_DOMAIN
|
||
|
return self._extend_microsoft_domain(domain)
|
||
|
|
||
|
def _cancel_microsoft(self):
|
||
|
self.calendar_event_ids.with_context(dont_notify=True)._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
|
||
|
|
||
|
def _get_event_user_m(self, user_id=None):
|
||
|
""" Get the user who will send the request to Microsoft (organizer if synchronized and current user otherwise). """
|
||
|
self.ensure_one()
|
||
|
event = self._get_first_event()
|
||
|
if event:
|
||
|
return event._get_event_user_m(user_id)
|
||
|
return self.env.user
|