From bb9f21d945793eed2d61f2b82ceef05d5b5aad4a Mon Sep 17 00:00:00 2001 From: Arnaud Joset Date: Wed, 30 Jun 2021 08:48:47 +0000 Subject: [PATCH] [IMP] calendar,google_calendar,google_calendar: allow modifying all event in recurrence Before this commit, it was not possible to reapply all events in recurrence, delete all events of a recurrence or archive them. This commit remove that limitation by reapplying the recurrence and remove all the existing events. When the events are synched, the action_mass_archive is used and it must ensure that only a request for the recurrency should be sent. Individual request for each event are not sent. Taskid: 2484335 Part-of: odoo/odoo#68700 --- addons/calendar/models/calendar_event.py | 139 ++++++++++++-- .../calendar/static/src/xml/base_calendar.xml | 6 + .../calendar/tests/test_event_recurrence.py | 180 ++++++++++++------ .../static/src/js/google_calendar.js | 36 +++- .../static/src/js/microsoft_calendar.js | 36 +++- .../js/views/calendar/calendar_controller.js | 54 +++++- 6 files changed, 359 insertions(+), 92 deletions(-) diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py index 448a51dd833..11ac5d772fa 100644 --- a/addons/calendar/models/calendar_event.py +++ b/addons/calendar/models/calendar_event.py @@ -473,16 +473,18 @@ class Meeting(models.Model): break_recurrence = values.get('recurrency') is False update_alarms = False + update_time = False if 'partner_ids' in values: values['attendee_ids'] = self._attendees_values(values['partner_ids']) update_alarms = True - # master arj todo: factorize use of _get_time_fields() - if any([values.get(key) for key in self.env['calendar.event']._get_time_fields()]) or 'alarm_ids' in values: + time_fields = self.env['calendar.event']._get_time_fields() + if any([values.get(key) for key in time_fields]) or 'alarm_ids' in values: update_alarms = True + update_time = True if (not recurrence_update_setting or recurrence_update_setting == 'self_only' and len(self) == 1) and 'follow_recurrence' not in values: - if any({field: values.get(field) for field in self.env['calendar.event']._get_time_fields() if field in values}): + if any({field: values.get(field) for field in time_fields if field in values}): values['follow_recurrence'] = False previous_attendees = self.attendee_ids @@ -490,25 +492,36 @@ class Meeting(models.Model): recurrence_values = {field: values.pop(field) for field in self._get_recurrent_fields() if field in values} if update_recurrence: if break_recurrence: + # Update this event detached_events |= self._break_recurrence(future=recurrence_update_setting == 'future_events') else: - update_start = self.start if recurrence_update_setting == 'future_events' else None - time_values = {field: values.pop(field) for field in self.env['calendar.event']._get_time_fields() if field in values} - if not update_start and (time_values or recurrence_values): - raise UserError(_("Updating All Events is not allowed when dates or time is modified. You can only update one particular event and following events.")) - detached_events |= self._split_recurrence(time_values) - self.recurrence_id._write_events(values, dtstart=update_start) + future_update_start = self.start if recurrence_update_setting == 'future_events' else None + time_values = {field: values.pop(field) for field in time_fields if field in values} + if recurrence_update_setting == 'all_events': + # Update all events: we create a new reccurrence and dismiss the existing events + self._rewrite_recurrence(values, time_values, recurrence_values) + else: + # Update future events + detached_events |= self._split_recurrence(time_values) + self.recurrence_id._write_events(values, dtstart=future_update_start) else: super().write(values) self._sync_activities(fields=values.keys()) - if recurrence_update_setting != 'self_only' and not break_recurrence: + # We reapply recurrence for future events and when we add a rrule and 'recurrency' == True on the event + if recurrence_update_setting not in ['self_only', 'all_events'] and not break_recurrence: detached_events |= self._apply_recurrence_values(recurrence_values, future=recurrence_update_setting == 'future_events') (detached_events & self).active = False (detached_events - self).with_context(archive_on_error=True).unlink() if not self.env.context.get('dont_notify') and update_alarms: self._setup_alarms() + attendee_update_events = self.filtered(lambda ev: ev.user_id != self.env.user) + if update_time and attendee_update_events: + # Another user update the event time fields. It should not be auto accepted for the organizer. + # This prevent weird behavior when a user modified future events time fields and + # the base event of a recurrence is accepted by the organizer but not the following events + attendee_update_events.attendee_ids.filtered(lambda att: self.user_id.partner_id == att.partner_id).write({'state': 'needsAction'}) current_attendees = self.filtered('active').attendee_ids if 'partner_ids' in values: @@ -635,6 +648,27 @@ class Meeting(models.Model): if partner not in self.partner_ids: self.write({'partner_ids': [(4, partner.id)]}) + def action_mass_deletion(self, recurrence_update_setting): + self.ensure_one() + if recurrence_update_setting == 'all_events': + events = self.recurrence_id.calendar_event_ids + self.recurrence_id.unlink() + events.unlink() + elif recurrence_update_setting == 'future_events': + future_events = self.recurrence_id.calendar_event_ids.filtered(lambda ev: ev.start >= self.start) + future_events.unlink() + + def action_mass_archive(self, recurrence_update_setting): + """ + The aim of this action purpose is to be called from sync calendar module when mass deletion is not possible. + """ + self.ensure_one() + if recurrence_update_setting == 'all_events': + self.recurrence_id.calendar_event_ids.write({'active': False}) + elif recurrence_update_setting == 'future_events': + detached_events = self.recurrence_id._stop_at(self) + detached_events.write({'active': False}) + # ------------------------------------------------------------ # MAILING # ------------------------------------------------------------ @@ -734,11 +768,19 @@ class Meeting(models.Model): :return: detached events """ + self.ensure_one() if not time_values: return self.browse() - - previous_week_day_field = weekday_to_field(self._get_start_date().weekday()) - self.write(time_values) + before = previous_week_day_field = weekday_to_field(self._get_start_date().weekday()) + if self.follow_recurrence and self.recurrency: + # arj fixme understand what is done here !!!! it can explain the issues... + previous_week_day_field = weekday_to_field(self._get_start_date().weekday()) + else: + # arj fixme understand what is done here !!!! it can explain the issues... + # When we try to change recurrence values of an event not following the recurrence, we get the parameters from + # the base_event + previous_week_day_field = weekday_to_field(self.recurrence_id.base_event_id._get_start_date().weekday()) + self.write({**time_values}) return self._apply_recurrence_values({ previous_week_day_field: False, **self._get_recurrence_params(), @@ -763,12 +805,79 @@ class Meeting(models.Model): recurrences_to_unlink.with_context(archive_on_error=True).unlink() return detached_events - self + def _rewrite_recurrence(self, values, time_values, recurrence_values): + """ Recreate the whole recurrence when all recurrent events must be moved + time_values corresponds to date times for one specific event. We need to update the base_event of the recurrence + and reapply the recurrence later. All exceptions are lost. + """ + self.ensure_one() + base_event = self.recurrence_id.base_event_id + if not base_event: + raise UserError(_("You can't update a recurrence without base event.")) + [base_time_values] = self.recurrence_id.base_event_id.read(['start', 'stop', 'allday']) + update_dict = {} + start_update = fields.Datetime.to_datetime(time_values.get('start')) + stop_update = fields.Datetime.to_datetime(time_values.get('stop')) + # Convert the base_event_id hours according to new values: time shift + if start_update or stop_update: + if start_update: + start = base_time_values['start'] + (start_update - self.start) + stop = base_time_values['stop'] + (start_update - self.start) + start_date = base_time_values['start'].date() + (start_update.date() - self.start.date()) + stop_date = base_time_values['stop'].date() + (start_update.date() - self.start.date()) + update_dict.update({'start': start, 'start_date': start_date, 'stop': stop, 'stop_date': stop_date}) + if stop_update: + if not start_update: + # Apply the same shift for start + start = base_time_values['start'] + (stop_update - self.stop) + start_date = base_time_values['start_date'] + (stop_update.date() - self.stop.date()) + update_dict.update({'start': start, 'start_date': start_date}) + stop = base_time_values['stop'] + (stop_update - self.stop) + stop_date = base_time_values['stop'].date() + (stop_update.date() - self.stop.date()) + update_dict.update({'stop': stop, 'stop_date': stop_date}) + + time_values.update(update_dict) + if time_values or recurrence_values: + rec_fields = list(self._get_recurrent_fields()) + [rec_vals] = base_event.read(rec_fields) + old_recurrence_values = {field: rec_vals.pop(field) for field in rec_fields if + field in rec_vals} + base_event.write({**values, **time_values}) + # Delete all events except the base event and the currently modified + expandable_events = self.recurrence_id.calendar_event_ids - (self.recurrence_id.base_event_id + self) + self.recurrence_id.with_context(archive_on_error=True).unlink() + expandable_events.with_context(archive_on_error=True).unlink() + # Make sure to recreate a new recurrence. Needed to prevent sync issues + base_event.recurrence_id = False + # Recreate all events and the recurrence: override updated values + new_values = { + **old_recurrence_values, + **base_event._get_recurrence_params(), + **recurrence_values, + } + new_values.pop('rrule') + detached_events = base_event._apply_recurrence_values(new_values) + detached_events.write({'active': False}) + # archive the current event if all the events were recreated + if self != self.recurrence_id.base_event_id and time_values: + self.active = False + else: + # Write on all events. Carefull, it could trigger a lot of noise to Google/Microsoft... + self.recurrence_id._write_events(values) + # ------------------------------------------------------------ # MANAGEMENT # ------------------------------------------------------------ - def change_attendee_status(self, status): - attendee = self.attendee_ids.filtered(lambda x: x.partner_id == self.env.user.partner_id) + def change_attendee_status(self, status, recurrence_update_setting): + self.ensure_one() + if recurrence_update_setting == 'all_events': + events = self.recurrence_id.calendar_event_ids + elif recurrence_update_setting == 'future_events': + events = self.recurrence_id.calendar_event_ids.filtered(lambda ev: ev.start >= self.start) + else: + events = self + attendee = events.attendee_ids.filtered(lambda x: x.partner_id == self.env.user.partner_id) if status == 'accepted': return attendee.do_accept() if status == 'declined': diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml index ea6276d2924..6c8a3c0a332 100644 --- a/addons/calendar/static/src/xml/base_calendar.xml +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -17,6 +17,12 @@ + +
+ + +
+ diff --git a/addons/calendar/tests/test_event_recurrence.py b/addons/calendar/tests/test_event_recurrence.py index ea857fdfb85..83c2a6d05c3 100644 --- a/addons/calendar/tests/test_event_recurrence.py +++ b/addons/calendar/tests/test_event_recurrence.py @@ -364,12 +364,25 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): def test_shift_all(self): event = self.events[1] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=4), - 'stop': event.stop + relativedelta(days=5), - }) + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)), + (datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)), + ]) + event.write({ + 'recurrence_update': 'all_events', + 'tue': False, + 'fri': False, + 'sat': True, + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + recurrence = self.env['calendar.recurrence'].search([]) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 29, 18, 0)), + (datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)), + (datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)), + ]) def test_change_week_day_rrule(self): recurrence = self.events.recurrence_id @@ -380,27 +393,35 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): def test_shift_all_base_inactive(self): self.recurrence.base_event_id.active = False event = self.events[1] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=4), - 'stop': event.stop + relativedelta(days=5), - }) + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + self.assertFalse(self.recurrence.calendar_event_ids, "Inactive event should not create recurrent events") def test_shift_all_with_outlier(self): outlier = self.events[1] outlier.write({ 'recurrence_update': 'self_only', - 'start': datetime(2019, 9, 26, 1, 0), # Thursday - 'stop': datetime(2019, 9, 26, 18, 0), + 'start': datetime(2019, 10, 31, 1, 0), # Thursday + 'stop': datetime(2019, 10, 31, 18, 0), }) event = self.events[0] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=4), - 'stop': event.stop + relativedelta(days=5), - }) + event.write({ + 'recurrence_update': 'all_events', + 'tue': False, + 'fri': False, + 'sat': True, + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=4), + }) + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 28, 18, 0)), + (datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 4, 18, 0)), + (datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 11, 18, 0)) + ]) + self.assertFalse(outlier.exists(), 'The outlier should have been deleted') def test_update_recurrence_future(self): event = self.events[1] @@ -429,11 +450,16 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): self.assertTrue(event.recurrence_id.fri) def test_update_recurrence_all(self): - with self.assertRaises(UserError): - self.events[1].write({ - 'recurrence_update': 'all_events', - 'mon': True, # recurrence is now Tuesday AND Monday - }) + self.events[1].write({ + 'recurrence_update': 'all_events', + 'mon': True, # recurrence is now Tuesday AND Monday + }) + recurrence = self.env['calendar.recurrence'].search([]) + # Monday and Tuesday but not in the past + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 10, 28, 1, 0), datetime(2019, 10, 30, 18, 0)), + ]) def test_shift_single(self): event = self.events[1] @@ -513,7 +539,26 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): (datetime(2019, 11, 9, 8, 0), datetime(2019, 11, 12, 18, 0)), ]) - # TODO test followers, and alarms are copied + def test_archive_recurrence_all(self): + self.events[1].action_mass_archive('all_events') + self.assertEqual([False, False, False], self.events.mapped('active')) + + def test_archive_recurrence_future(self): + event = self.events[1] + event.action_mass_archive('future_events') + self.assertEqual([True, False, False], self.events.mapped('active')) + + def test_unlink_recurrence_all(self): + event = self.events[1] + event.action_mass_deletion('all_events') + self.assertFalse(self.recurrence.exists()) + self.assertFalse(self.events.exists()) + + def test_unlink_recurrence_future(self): + event = self.events[1] + event.action_mass_deletion('future_events') + self.assertTrue(self.recurrence) + self.assertEqual(self.events.exists(), self.events[0]) class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents): @@ -541,21 +586,39 @@ class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents): def test_shift_all_multiple_weekdays(self): event = self.events[0] # Tuesday - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=2), - 'stop': event.stop + relativedelta(days=2), - }) + # We go from 2 days a week Thuesday and Friday to one day a week, Thursday + event.write({ + 'recurrence_update': 'all_events', + 'tue': False, + 'thu': True, + 'fri': False, + 'start': event.start + relativedelta(days=2), + 'stop': event.stop + relativedelta(days=2), + }) + recurrence = self.env['calendar.recurrence'].search([]) + # We don't try to do magic tricks. First event is moved, other remain + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 26, 18, 0)), + (datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 2, 18, 0)), + (datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 9, 18, 0)), + ]) def test_shift_all_multiple_weekdays_duration(self): event = self.events[0] # Tuesday - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=2), - 'stop': event.stop + relativedelta(days=3), - }) + event.write({ + 'recurrence_update': 'all_events', + 'tue': False, + 'thu': True, + 'fri': False, + 'start': event.start + relativedelta(days=2), + 'stop': event.stop + relativedelta(days=3), + }) + recurrence = self.env['calendar.recurrence'].search([]) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 27, 18, 0)), + (datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 3, 18, 0)), + (datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 10, 18, 0)), + ]) def test_shift_future_multiple_weekdays(self): event = self.events[1] # Friday @@ -598,12 +661,17 @@ class TestUpdateMonthlyByDay(TestRecurrentEvents): def test_shift_all(self): event = self.events[1] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start - relativedelta(days=5), - 'stop': event.stop - relativedelta(days=4), - }) + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(hours=5), + 'stop': event.stop + relativedelta(hours=5), + }) + recurrence = self.env['calendar.recurrence'].search([]) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 15, 6, 0), datetime(2019, 10, 16, 23, 0)), + (datetime(2019, 11, 19, 6, 0), datetime(2019, 11, 20, 23, 0)), + (datetime(2019, 12, 17, 6, 0), datetime(2019, 12, 18, 23, 0)), + ]) class TestUpdateMonthlyByDate(TestRecurrentEvents): @@ -644,19 +712,15 @@ class TestUpdateMonthlyByDate(TestRecurrentEvents): (datetime(2019, 12, 26, 1, 0), datetime(2019, 12, 29, 18, 0)), ]) - def test_shift_all(self): - event = self.events[1] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'start': event.start + relativedelta(days=4), - 'stop': event.stop + relativedelta(days=5), - }) - def test_update_all(self): event = self.events[1] - with self.assertRaises(UserError): - event.write({ - 'recurrence_update': 'all_events', - 'day': 25, - }) + event.write({ + 'recurrence_update': 'all_events', + 'day': 25, + }) + recurrence = self.env['calendar.recurrence'].search([('day', '=', 25)]) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 25, 1, 0), datetime(2019, 10, 27, 18, 0)), + (datetime(2019, 11, 25, 1, 0), datetime(2019, 11, 27, 18, 0)), + (datetime(2019, 12, 25, 1, 0), datetime(2019, 12, 27, 18, 0)), + ]) diff --git a/addons/google_calendar/static/src/js/google_calendar.js b/addons/google_calendar/static/src/js/google_calendar.js index d1af0fa145d..939b656673d 100644 --- a/addons/google_calendar/static/src/js/google_calendar.js +++ b/addons/google_calendar/static/src/js/google_calendar.js @@ -172,15 +172,35 @@ const GoogleCalendarController = CalendarController.include({ }); }, - _onArchiveRecord: function (event) { - var self = this; - Dialog.confirm(this, _t("Are you sure you want to archive this record ?"), { - confirm_callback: function () { - self.model.archiveRecords([event.data.id], self.modelName).then(function () { + _onArchiveRecord: async function (event) { + const self = this; + if (event.data.event.record.recurrency) { + const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); + event.data = Object.assign({}, event.data, { + 'recurrenceUpdate': recurrenceUpdate, + }); + if (recurrenceUpdate === 'self_only') { + self.model.archiveRecords([event.data.id], self.modelName).then(function () { self.reload(); }); - } - }); + } else { + return this._rpc({ + model: self.modelName, + method: 'action_mass_archive', + args: [[event.data.id], recurrenceUpdate], + }).then( function () { + self.reload(); + }); + } + } else { + Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { + confirm_callback: function () { + self.model.archiveRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } + }); + } }, }); @@ -291,7 +311,7 @@ const GoogleCalendarRenderer = CalendarRenderer.include({ _onArchiveEvent: function (event) { this._unselectEvent(); - this.trigger_up('archiveRecord', {id: parseInt(event.data.id, 10)}); + this.trigger_up('archiveRecord', {id: parseInt(event.data.id, 10), event: event.target.event.extendedProps}); }, }); diff --git a/addons/microsoft_calendar/static/src/js/microsoft_calendar.js b/addons/microsoft_calendar/static/src/js/microsoft_calendar.js index 6623bc712a5..58b7826d965 100644 --- a/addons/microsoft_calendar/static/src/js/microsoft_calendar.js +++ b/addons/microsoft_calendar/static/src/js/microsoft_calendar.js @@ -161,15 +161,35 @@ const MicrosoftCalendarController = CalendarController.include({ }); }, - _onArchiveRecord: function (event) { - var self = this; - Dialog.confirm(this, _t("Are you sure you want to archive this record ?"), { - confirm_callback: function () { - self.model.archiveRecords([event.data.id], self.modelName).then(function () { + _onArchiveRecord: async function (event) { + const self = this; + if (event.data.event.record.recurrency) { + const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); + event.data = Object.assign({}, event.data, { + 'recurrenceUpdate': recurrenceUpdate, + }); + if (recurrenceUpdate === 'self_only') { + self.model.archiveRecords([event.data.id], self.modelName).then(function () { self.reload(); }); - } - }); + } else { + return this._rpc({ + model: self.modelName, + method: 'action_mass_archive', + args: [[event.data.id], recurrenceUpdate], + }).then( function () { + self.reload(); + }); + } + } else { + Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { + confirm_callback: function () { + self.model.archiveRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } + }); + } }, }); @@ -280,7 +300,7 @@ const MicrosoftCalendarRenderer = CalendarRenderer.include({ _onArchiveEvent: function (event) { this._unselectEvent(); - this.trigger_up('archiveRecord', {id: parseInt(event.data.id, 10)}); + this.trigger_up('archiveRecord', {id: parseInt(event.data.id, 10), event: event.target.event.extendedProps}); }, }); diff --git a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js b/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js index 2dc2f3ba9ae..9f9f348f263 100644 --- a/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/legacy/js/views/calendar/calendar_controller.js @@ -39,6 +39,7 @@ var CalendarController = AbstractController.extend({ quickCreate: '_onQuickCreate', updateRecord: '_onUpdateRecord', viewUpdated: '_onViewUpdated', + AttendeeStatus: '_onAttendeeStatus', }), events: _.extend({}, AbstractController.prototype.events, { 'click button.o_calendar_button_new': '_onButtonNew', @@ -271,15 +272,37 @@ var CalendarController = AbstractController.extend({ * @private * @param {OdooEvent} event */ - _onDeleteRecord: function (event) { + _onDeleteRecord: async function (event) { var self = this; - Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { + if (event.data.event.record.recurrency) { + const recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); + event.data = _.extend({}, event.data, { + 'recurrenceUpdate': recurrenceUpdate, + }); + if (recurrenceUpdate === 'self_only') { + self.model.deleteRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } else { + return this._rpc({ + model: self.modelName, + method: 'action_mass_deletion', + args: [[event.data.id], recurrenceUpdate], + }).then( function () { + self.reload(); + }); + } + } else { + Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { confirm_callback: function () { self.model.deleteRecords([event.data.id], self.modelName).then(function () { self.reload(); }); } }); + } + + }, /** * @private @@ -351,7 +374,7 @@ var CalendarController = AbstractController.extend({ return; } - const title = _t('New Event') + const title = _t('New Event'); if (this.eventOpenPopup) { if (this.previousOpen) { this.previousOpen.close(); } this.previousOpen = new dialogs.FormViewDialog(self, { @@ -486,6 +509,31 @@ var CalendarController = AbstractController.extend({ const title = `${this.displayName} (${event.data.title})`; return this.updateControlPanel({ title }); }, + + /** + * Update Attendee status in batch for recurrent events + * @private + * @param {OdooEvent} event + */ + _onAttendeeStatus: async function(event) { + const self = this; + let recurrenceUpdate; + if (event.data.record.recurrency) { + recurrenceUpdate = await this._askRecurrenceUpdatePolicy(); + event.data = _.extend({}, event.data, { + 'recurrenceUpdate': recurrenceUpdate, + }); + } else { + recurrenceUpdate = false; + } + return this._rpc({ + model: self.modelName, + method: 'change_attendee_status', + args: [[event.data.id], event.data.selectedStatus, recurrenceUpdate], + }).then( function () { + self.reload(); + }); + } }); return CalendarController;