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 $('', {
+ type: 'button',
+ html: _t("Sync with Google"),
+ class: 'o_google_sync_button w-100 m-auto btn btn-secondary'
+ });
+ },
+
+ _getGoogleStopButton: function () {
+ return $('', {
+ html: _t("Synched with Google"),
+ class: 'w-100 badge badge-pill badge-success border-0 o_stop_google_sync_button'
+ })
+ .prepend($('', {class: "fa mr-2 fa-check"}));
+ },
+
/**
* Adds the Sync with Google button in the sidebar
*
@@ -140,21 +227,13 @@ const GoogleCalendarRenderer = CalendarRenderer.include({
var self = this;
this._super.apply(this, arguments);
this.$googleButton = $();
+ this.$googleStopButton = $();
if (this.model === "calendar.event") {
if (this.state.google_is_sync) {
- this.$googleButton = $('', {
- html: _t("Synched with Google"),
- class: 'w-100 badge badge-pill badge-success border-0'
- })
- .prepend($('', {class: "fa mr-2 fa-check"}))
- .appendTo(self.$sidebar);
+ this.$googleStopButton = this._getGoogleStopButton().appendTo(self.$sidebar);
+ this._initGooglePillButton();
} else {
- this.$googleButton = $('', {
- type: 'button',
- html: _t("Sync with Google"),
- class: 'o_google_sync_button w-100 m-auto btn btn-secondary'
- })
- .appendTo(self.$sidebar);
+ this.$googleButton = this._getGoogleButton().appendTo(self.$sidebar);
}
}
},
@@ -176,8 +255,36 @@ const GoogleCalendarRenderer = CalendarRenderer.include({
on_always: function () {
self.$googleButton.prop('disabled', false);
},
+ on_refresh: function () {
+ if (_.isEmpty(self.$googleStopButton)) {
+ self.$googleStopButton = self._getGoogleStopButton();
+ }
+ self.$googleButton.replaceWith(self.$googleStopButton);
+ self._initGooglePillButton();
+ }
});
},
+
+ _onStopGoogleSynchronization: function() {
+ var self = this;
+ this.$googleStopButton.prop('disabled', true);
+ this.trigger_up('stopGoogleSynchronization' , {
+ on_confirm: function () {
+ if (_.isEmpty(self.$googleButton)) {
+ self.$googleButton = self._getGoogleButton();
+ }
+ self.$googleStopButton.replaceWith(self.$googleButton);
+ },
+ on_always: function() {
+ self.$googleStopButton.prop('disabled', false);
+ }
+ });
+ },
+
+ _onArchiveEvent: function (event) {
+ this._unselectEvent();
+ this.trigger_up('archiveRecord', {id: parseInt(event.data.id, 10)});
+ },
});
return {
diff --git a/addons/google_calendar/static/src/js/google_calendar_popover.js b/addons/google_calendar/static/src/js/google_calendar_popover.js
new file mode 100644
index 00000000000..aae4a1897eb
--- /dev/null
+++ b/addons/google_calendar/static/src/js/google_calendar_popover.js
@@ -0,0 +1,26 @@
+odoo.define('google_calendar.GoogleCalendarPopover', function(require) {
+ "use strict";
+
+ const CalendarPopover = require('web.CalendarPopover');
+
+ const GoogleCalendarPopover = CalendarPopover.include({
+ events: _.extend({}, CalendarPopover.prototype.events, {
+ 'click .o_cw_popover_archive_g': '_onClickPopoverGArchive',
+ }),
+
+ isGEventSyncedAndArchivable() {
+ return this.event.extendedProps.record.google_id;
+ },
+
+ isEventDeletable() {
+ return !this.isGEventSyncedAndArchivable() && this._super();
+ },
+
+ _onClickPopoverGArchive: function (ev) {
+ ev.preventDefault();
+ this.trigger_up('archive_event', {id: this.event.id});
+ },
+ });
+
+ return GoogleCalendarPopover;
+});
diff --git a/addons/google_calendar/static/src/xml/google_calendar_popover.xml b/addons/google_calendar/static/src/xml/google_calendar_popover.xml
new file mode 100644
index 00000000000..53dc371b306
--- /dev/null
+++ b/addons/google_calendar/static/src/xml/google_calendar_popover.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ Archive
+
+
+
diff --git a/addons/google_calendar/tests/test_sync_odoo2google.py b/addons/google_calendar/tests/test_sync_odoo2google.py
index a78d19f60a7..7028d797470 100644
--- a/addons/google_calendar/tests/test_sync_odoo2google.py
+++ b/addons/google_calendar/tests/test_sync_odoo2google.py
@@ -234,3 +234,53 @@ class TestSyncOdoo2Google(TransactionCase):
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
}, timeout=3)
+
+ @patch_api
+ def test_stop_synchronization(self):
+ self.env.user.stop_google_synchronization()
+ self.assertTrue(self.env.user.google_synchronization_stopped, "The google synchronization flag should be switched on")
+ self.assertFalse(self.env.user._sync_google_calendar(self.google_service), "The google synchronization should be stopped")
+
+ # If synchronization stopped, creating a new event should not call _google_insert.
+ self.env['calendar.event'].create({
+ 'name': "Event",
+ 'start': datetime(2020, 1, 15, 8, 0),
+ 'stop': datetime(2020, 1, 15, 18, 0),
+ 'privacy': 'private',
+ })
+ self.assertGoogleEventNotInserted()
+
+ @patch_api
+ def test_restart_synchronization(self):
+ # Test new event created after stopping synchronization are correctly patched when restarting sync.
+ google_id = 'aaaaaaaaa'
+ partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'})
+ user = self.env['res.users'].create({
+ 'name': 'Test user Calendar',
+ 'login': 'jean-luc@opoo.com',
+ 'partner_id': partner.id,
+ })
+ user.stop_google_synchronization()
+ event = self.env['calendar.event'].with_user(user).create({
+ 'google_id': google_id,
+ 'name': "Event",
+ 'start': datetime(2020, 1, 15, 8, 0),
+ 'stop': datetime(2020, 1, 15, 18, 0),
+ 'partner_ids': [(4, partner.id)],
+ })
+
+ user.with_user(user).restart_google_synchronization()
+ self.assertGoogleEventPatched(event.google_id, {
+ 'id': event.google_id,
+ 'start': {'dateTime': '2020-01-15T08:00:00+00:00'},
+ 'end': {'dateTime': '2020-01-15T18:00:00+00:00'},
+ 'summary': 'Event',
+ 'description': '',
+ 'location': '',
+ 'visibility': 'public',
+ 'guestsCanModify': True,
+ 'reminders': {'overrides': [], 'useDefault': False},
+ 'organizer': {'email': 'jean-luc@opoo.com', 'self': True},
+ 'attendees': [{'email': 'jean-luc@opoo.com', 'responseStatus': 'accepted'}],
+ 'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}}
+ }, timeout=3)
diff --git a/addons/google_calendar/views/google_calendar_templates.xml b/addons/google_calendar/views/google_calendar_templates.xml
index a5f3816b938..dbf8fab6f6b 100644
--- a/addons/google_calendar/views/google_calendar_templates.xml
+++ b/addons/google_calendar/views/google_calendar_templates.xml
@@ -2,6 +2,7 @@
+
diff --git a/addons/google_calendar/views/google_calendar_views.xml b/addons/google_calendar/views/google_calendar_views.xml
new file mode 100644
index 00000000000..1d22a10e548
--- /dev/null
+++ b/addons/google_calendar/views/google_calendar_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ google_calendar.event.calendar
+ calendar.event
+
+
+
+
+
+
+
+