diff --git a/addons/google_calendar/__manifest__.py b/addons/google_calendar/__manifest__.py index a743f7528a2..8e90d83ac8c 100644 --- a/addons/google_calendar/__manifest__.py +++ b/addons/google_calendar/__manifest__.py @@ -14,6 +14,7 @@ 'wizard/reset_account_views.xml', 'views/res_config_settings_views.xml', 'views/res_users_views.xml', + 'views/google_calendar_views.xml', 'views/google_calendar_templates.xml', ], 'demo': [], diff --git a/addons/google_calendar/controllers/main.py b/addons/google_calendar/controllers/main.py index 99ed0b0b2a1..bd76d4dd9c1 100644 --- a/addons/google_calendar/controllers/main.py +++ b/addons/google_calendar/controllers/main.py @@ -13,7 +13,7 @@ class GoogleCalendarController(http.Controller): """ This route/function is called when we want to synchronize Odoo calendar with Google Calendar. Function return a dictionary with the status : need_config_from_admin, need_auth, - need_refresh, success if not calendar_event + need_refresh, sync_stopped, success if not calendar_event The dictionary may contains an url, to allow Odoo Client to redirect user on this URL for authorization for example """ @@ -42,6 +42,13 @@ class GoogleCalendarController(http.Controller): } # If App authorized, and user access accepted, We launch the synchronization need_refresh = request.env.user.sudo()._sync_google_calendar(GoogleCal) + + # If synchronization has been stopped + if not need_refresh and request.env.user.google_synchronization_stopped: + return { + "status": "sync_stopped", + "url": '' + } return { "status": "need_refresh" if need_refresh else "no_new_event_from_google", "url": '' diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py index 765473509ba..b2aeae22984 100644 --- a/addons/google_calendar/models/calendar.py +++ b/addons/google_calendar/models/calendar.py @@ -33,6 +33,12 @@ class Meeting(models.Model): return {'name', 'description', 'allday', 'start', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'privacy', 'active'} + @api.model + def _restart_google_sync(self): + self.env['calendar.event'].search(self._get_sync_domain()).write({ + 'need_sync': True, + }) + @api.model_create_multi def create(self, vals_list): return super().create([ diff --git a/addons/google_calendar/models/calendar_recurrence_rule.py b/addons/google_calendar/models/calendar_recurrence_rule.py index b3cd06db329..1bc94084faa 100644 --- a/addons/google_calendar/models/calendar_recurrence_rule.py +++ b/addons/google_calendar/models/calendar_recurrence_rule.py @@ -79,6 +79,12 @@ class RecurrenceRule(models.Model): def _get_google_synced_fields(self): return {'rrule'} + @api.model + def _restart_google_sync(self): + self.env['calendar.recurrence'].search(self._get_sync_domain()).write({ + 'need_sync': True, + }) + @api.model def _sync_google2odoo(self, *args, **kwargs): synced_recurrences = super()._sync_google2odoo(*args, **kwargs) diff --git a/addons/google_calendar/models/google_sync.py b/addons/google_calendar/models/google_sync.py index a16cce527f4..06c09bc48b5 100644 --- a/addons/google_calendar/models/google_sync.py +++ b/addons/google_calendar/models/google_sync.py @@ -72,7 +72,7 @@ class GoogleSync(models.AbstractModel): if 'google_id' in vals: self._from_google_ids.clear_cache(self) synced_fields = self._get_google_synced_fields() - if 'need_sync' not in vals and vals.keys() & synced_fields: + if 'need_sync' not in vals and vals.keys() & synced_fields and not self.env.user.google_synchronization_stopped: vals['need_sync'] = True result = super().write(vals) @@ -86,6 +86,9 @@ class GoogleSync(models.AbstractModel): def create(self, vals_list): if any(vals.get('google_id') for vals in vals_list): self._from_google_ids.clear_cache(self) + if self.env.user.google_synchronization_stopped: + for vals in vals_list: + vals.update({'need_sync': False}) records = super().create(vals_list) google_service = GoogleCalendarService(self.env['google.service']) @@ -244,3 +247,10 @@ class GoogleSync(models.AbstractModel): marks the record to be re-synchronized. """ raise NotImplementedError() + + @api.model + def _restart_google_sync(self): + """ Turns on the google synchronization for all the events of + a given user. + """ + raise NotImplementedError() diff --git a/addons/google_calendar/models/res_users.py b/addons/google_calendar/models/res_users.py index 416cc9f8667..1530c19f20e 100644 --- a/addons/google_calendar/models/res_users.py +++ b/addons/google_calendar/models/res_users.py @@ -23,7 +23,8 @@ class User(models.Model): google_calendar_token_validity = fields.Datetime('Token Validity', copy=False) google_calendar_sync_token = fields.Char('Next Sync Token', copy=False) google_calendar_cal_id = fields.Char('Calendar ID', copy=False, help='Last Calendar ID who has been synchronized. If it is changed, we remove all links between GoogleID and Odoo Google Internal ID') - + google_synchronization_stopped = fields.Boolean('Google Synchronization stopped', copy=False) + def _set_auth_tokens(self, access_token, refresh_token, ttl): self.write({ 'google_calendar_rtoken': refresh_token, @@ -79,6 +80,8 @@ class User(models.Model): def _sync_google_calendar(self, calendar_service: GoogleCalendarService): self.ensure_one() + if self.google_synchronization_stopped: + return False full_sync = not bool(self.google_calendar_sync_token) with google_calendar_token(self) as token: try: @@ -106,7 +109,7 @@ class User(models.Model): @api.model def _sync_all_google_calendar(self): """ Cron job """ - users = self.env['res.users'].search([('google_calendar_rtoken', '!=', False)]) + users = self.env['res.users'].search([('google_calendar_rtoken', '!=', False), ('google_synchronization_stopped', '=', False)]) google = GoogleCalendarService(self.env['google.service']) for user in users: _logger.info("Calendar Synchro - Starting synchronization for %s", user) @@ -114,3 +117,13 @@ class User(models.Model): user.with_user(user).sudo()._sync_google_calendar(google) except Exception as e: _logger.exception("[%s] Calendar Synchro - Exception : %s !", user, exception_to_unicode(e)) + + def stop_google_synchronization(self): + self.ensure_one() + self.sudo().google_synchronization_stopped = True + + def restart_google_synchronization(self): + self.ensure_one() + self.sudo().google_synchronization_stopped = False + self.env['calendar.recurrence']._restart_google_sync() + self.env['calendar.event']._restart_google_sync() diff --git a/addons/google_calendar/static/src/js/google_calendar.js b/addons/google_calendar/static/src/js/google_calendar.js index cfa2437bb51..00f75118822 100644 --- a/addons/google_calendar/static/src/js/google_calendar.js +++ b/addons/google_calendar/static/src/js/google_calendar.js @@ -9,6 +9,7 @@ const CalendarRenderer = require('calendar.CalendarRenderer'); const CalendarController = require('calendar.CalendarController'); const CalendarModel = require('calendar.CalendarModel'); const viewRegistry = require('web.view_registry'); +const session = require('web.session'); var _t = core._t; @@ -63,7 +64,7 @@ const GoogleCalendarModel = CalendarModel.include({ local_context: context, // LUL TODO remove this local_context } }, {shadow}).then(function (result) { - if (result.status === "need_config_from_admin" || result.status === "need_auth") { + if (["need_config_from_admin", "need_auth", "sync_stopped"].includes(result.status)) { self.google_is_sync = false; } else if (result.status === "no_new_event_from_google" || result.status === "need_refresh") { self.google_is_sync = true; @@ -71,11 +72,22 @@ const GoogleCalendarModel = CalendarModel.include({ return result }); }, + + archiveRecords: function (ids, model) { + return this._rpc({ + model: model, + method: 'action_archive', + args: [ids], + context: session.user_context, + }); + }, }) const GoogleCalendarController = CalendarController.include({ custom_events: _.extend({}, CalendarController.prototype.custom_events, { syncGoogleCalendar: '_onGoogleSyncCalendar', + stopGoogleSynchronization: '_onStopGoogleSynchronization', + archiveRecord: '_onArchiveRecord', }), @@ -94,7 +106,7 @@ const GoogleCalendarController = CalendarController.include({ _onGoogleSyncCalendar: function (event) { var self = this; - return this.model._syncGoogleCalendar().then(function (o) { + return this._restartGoogleSynchronization().then(() => {return this.model._syncGoogleCalendar();}).then(function (o) { if (o.status === "need_auth") { Dialog.alert(self, _t("You will be redirected to Google to authorize access to your calendar!"), { confirm_callback: function() { @@ -117,20 +129,95 @@ const GoogleCalendarController = CalendarController.include({ } } else if (o.status === "need_refresh") { self.reload(); + return event.data.on_refresh(); } }).then(event.data.on_always, event.data.on_always); - } + }, + + _onStopGoogleSynchronization: function (event) { + var self = this; + Dialog.confirm(this, _t("You are about to stop the synchronization of your calendar with Google. Are you sure you want to continue?"), { + confirm_callback: function() { + return self._rpc({ + model: 'res.users', + method: 'stop_google_synchronization', + args: [[self.context.uid]], + }).then(() => { + self.displayNotification({ + title: _t("Success"), + message: _t("The synchronization with Google calendar was successfully stopped."), + type: 'success', + }); + }).then(event.data.on_confirm); + }, + title: _t('Confirmation'), + }); + + return event.data.on_always(); + }, + + _restartGoogleSynchronization: function () { + return this._rpc({ + model: 'res.users', + method: 'restart_google_synchronization', + args: [[this.context.uid]], + }); + }, + + _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 () { + self.reload(); + }); + } + }); + }, }); const GoogleCalendarRenderer = CalendarRenderer.include({ + custom_events: _.extend({}, CalendarRenderer.prototype.custom_events, { + archive_event: '_onArchiveEvent', + }), + events: _.extend({}, CalendarRenderer.prototype.events, { 'click .o_google_sync_button': '_onGoogleSyncCalendar', + 'click .o_stop_google_sync_button': '_onStopGoogleSynchronization', }), //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + _initGooglePillButton: function() { + this.$googleStopButton.css({"cursor":"pointer", "font-size":"0.9em"}); + var switchBadgeClass = (elem) => {elem.toggleClass('badge-success'); elem.toggleClass('badge-danger');}; + this.$('.o_stop_google_sync_button').hover(() => { + switchBadgeClass(this.$googleStopButton); + this.$googleStopButton.html("".concat(_t("Stop the Synchronization"))); + }, () => { + switchBadgeClass(this.$googleStopButton); + this.$googleStopButton.html("".concat(_t("Synched with Google"))); + }); + }, + + _getGoogleButton: function () { + return $('