[IMP] microsoft_calendar: add synchronization pause button and tests
Configuration and tests added for 'pause synchronization' button (Outlook module). closes odoo/odoo#126560 Task-id: 3290898 Signed-off-by: Arnaud Joset (arj) <arj@odoo.com>
This commit is contained in:
@@ -42,10 +42,11 @@ class MicrosoftCalendarController(http.Controller):
|
||||
# If App authorized, and user access accepted, We launch the synchronization
|
||||
need_refresh = request.env.user.sudo()._sync_microsoft_calendar()
|
||||
|
||||
# If synchronization has been stopped
|
||||
if not need_refresh and request.env.user.microsoft_synchronization_stopped:
|
||||
# If synchronization has been stopped or paused
|
||||
sync_status = request.env.user._get_microsoft_sync_status()
|
||||
if not need_refresh and sync_status != "sync_active":
|
||||
return {
|
||||
"status": "sync_stopped",
|
||||
"status": sync_status,
|
||||
"url": ''
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -150,11 +150,12 @@ class Meeting(models.Model):
|
||||
self._check_recurrence_overlapping(values['start'])
|
||||
|
||||
# if a single event becomes the base event of a recurrency, it should be first
|
||||
# removed from the Outlook calendar.
|
||||
if 'recurrency' in values and values['recurrency']:
|
||||
for e in self.filtered(lambda e: not e.recurrency and not e.recurrence_id):
|
||||
e._microsoft_delete(e._get_organizer(), e.ms_organizer_event_id, timeout=3)
|
||||
e.microsoft_id = False
|
||||
# removed from the Outlook calendar. Additionaly, checks if synchronization is not paused.
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused" and values.get('recurrency'):
|
||||
for event in self:
|
||||
if not event.recurrency and not event.recurrence_id:
|
||||
event._microsoft_delete(event._get_organizer(), event.ms_organizer_event_id, timeout=3)
|
||||
event.microsoft_id = False
|
||||
|
||||
res = super(Meeting, self.with_context(dont_notify=notify_context)).write(values)
|
||||
|
||||
|
||||
@@ -76,20 +76,22 @@ class MicrosoftSync(models.AbstractModel):
|
||||
|
||||
def write(self, vals):
|
||||
fields_to_sync = [x for x in vals if x in self._get_microsoft_synced_fields()]
|
||||
if fields_to_sync and 'need_sync_m' not in vals and not self.env.user.microsoft_synchronization_stopped:
|
||||
if fields_to_sync and 'need_sync_m' not in vals and self.env.user._get_microsoft_sync_status() == "sync_active":
|
||||
vals['need_sync_m'] = True
|
||||
|
||||
result = super().write(vals)
|
||||
|
||||
for record in self.filtered(lambda e: e.need_sync_m and e.ms_organizer_event_id):
|
||||
if not vals.get('active', True):
|
||||
# We need to delete the event. Cancel is not sufficant. Errors may occurs
|
||||
record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id, timeout=3)
|
||||
elif fields_to_sync:
|
||||
values = record._microsoft_values(fields_to_sync)
|
||||
if not values:
|
||||
continue
|
||||
record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values, timeout=3)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
for record in self:
|
||||
if record.need_sync_m and record.ms_organizer_event_id:
|
||||
if not vals.get('active', True):
|
||||
# We need to delete the event. Cancel is not sufficient. Errors may occur.
|
||||
record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id, timeout=3)
|
||||
elif fields_to_sync:
|
||||
values = record._microsoft_values(fields_to_sync)
|
||||
if not values:
|
||||
continue
|
||||
record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values, timeout=3)
|
||||
|
||||
return result
|
||||
|
||||
@@ -100,9 +102,10 @@ class MicrosoftSync(models.AbstractModel):
|
||||
vals.update({'need_sync_m': False})
|
||||
records = super().create(vals_list)
|
||||
|
||||
records_to_sync = records.filtered(lambda r: r.need_sync_m and r.active)
|
||||
for record in records_to_sync:
|
||||
record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=3)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
for record in records:
|
||||
if record.need_sync_m and record.active:
|
||||
record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=3)
|
||||
return records
|
||||
|
||||
@api.depends('microsoft_id')
|
||||
@@ -159,8 +162,9 @@ class MicrosoftSync(models.AbstractModel):
|
||||
|
||||
def unlink(self):
|
||||
synced = self._get_synced_events()
|
||||
for ev in synced:
|
||||
ev._microsoft_delete(ev._get_organizer(), ev.ms_organizer_event_id)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
for ev in synced:
|
||||
ev._microsoft_delete(ev._get_organizer(), ev.ms_organizer_event_id)
|
||||
return super().unlink()
|
||||
|
||||
def _write_from_microsoft(self, microsoft_event, vals):
|
||||
|
||||
@@ -9,3 +9,5 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
cal_microsoft_client_id = fields.Char("Microsoft Client_id", config_parameter='microsoft_calendar_client_id', default='')
|
||||
cal_microsoft_client_secret = fields.Char("Microsoft Client_key", config_parameter='microsoft_calendar_client_secret', default='')
|
||||
cal_microsoft_sync_paused = fields.Boolean("Microsoft Synchronization Paused", config_parameter='microsoft_calendar_sync_paused',
|
||||
help="Indicates if synchronization with Outlook Calendar is paused or not.")
|
||||
|
||||
@@ -11,6 +11,7 @@ from odoo.exceptions import UserError
|
||||
from odoo.loglevels import exception_to_unicode
|
||||
from odoo.addons.microsoft_account.models.microsoft_service import DEFAULT_MICROSOFT_TOKEN_ENDPOINT
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import InvalidSyncToken
|
||||
from odoo.tools import str2bool
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -88,9 +89,18 @@ class User(models.Model):
|
||||
error_key)
|
||||
raise UserError(error_msg)
|
||||
|
||||
def _get_microsoft_sync_status(self):
|
||||
""" Returns the calendar synchronization status (active, paused or stopped). """
|
||||
status = "sync_active"
|
||||
if str2bool(self.env['ir.config_parameter'].sudo().get_param("microsoft_calendar_sync_paused"), default=False):
|
||||
status = "sync_paused"
|
||||
elif self.microsoft_synchronization_stopped:
|
||||
status = "sync_stopped"
|
||||
return status
|
||||
|
||||
def _sync_microsoft_calendar(self):
|
||||
self.ensure_one()
|
||||
if self.microsoft_synchronization_stopped:
|
||||
if self._get_microsoft_sync_status() != "sync_active":
|
||||
return False
|
||||
calendar_service = self.env["calendar.event"]._get_microsoft_service()
|
||||
full_sync = not bool(self.microsoft_calendar_sync_token)
|
||||
@@ -138,3 +148,9 @@ class User(models.Model):
|
||||
self.microsoft_synchronization_stopped = False
|
||||
self.env['calendar.recurrence']._restart_microsoft_sync()
|
||||
self.env['calendar.event']._restart_microsoft_sync()
|
||||
|
||||
def unpause_microsoft_synchronization(self):
|
||||
self.env['ir.config_parameter'].sudo().set_param("microsoft_calendar_sync_paused", False)
|
||||
|
||||
def pause_microsoft_synchronization(self):
|
||||
self.env['ir.config_parameter'].sudo().set_param("microsoft_calendar_sync_paused", True)
|
||||
|
||||
+11
@@ -59,5 +59,16 @@ patch(AttendeeCalendarController.prototype, {
|
||||
await this.model.load();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
onMicrosoftSyncUnpause() {
|
||||
if (this.isSystemUser) {
|
||||
this.env.services.action.doAction("base_setup.action_general_configuration");
|
||||
} else {
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: this.env._t("Configuration"),
|
||||
body: this.env._t("Your administrator paused the synchronization with Outlook Calendar."),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+7
-1
@@ -6,7 +6,13 @@
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='microsoft_calendar_sync']" position="replace">
|
||||
<div id="microsoft_calendar_sync" class="o_calendar_sync">
|
||||
<button t-if="!model.microsoftIsSync" type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary text-nowrap" t-on-click="onMicrosoftSyncCalendar">
|
||||
<button t-if="!model.microsoftIsSync and model.microsoftIsPaused" type="button" id="microsoft_sync_paused" class="o_google_sync_button o_microsoft_sync_paused btn btn-warning" t-on-click="onMicrosoftSyncUnpause">
|
||||
<b>
|
||||
<i id="microsoft_paused" class='fa fa-pause'/>
|
||||
<span class="mx-1">Outlook</span>
|
||||
</b>
|
||||
</button>
|
||||
<button t-elif="!model.microsoftIsSync" type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary text-nowrap" t-on-click="onMicrosoftSyncCalendar">
|
||||
<b><i class='fa fa-refresh'/> Outlook</b>
|
||||
</button>
|
||||
<!-- class change on hover -->
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ patch(AttendeeCalendarModel.prototype, {
|
||||
this.rpc = rpc;
|
||||
this.microsoftIsSync = true;
|
||||
this.microsoftPendingSync = false;
|
||||
this.microsoftIsPaused = false;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -49,11 +50,12 @@ patch(AttendeeCalendarModel.prototype, {
|
||||
silent,
|
||||
},
|
||||
);
|
||||
if (["need_config_from_admin", "need_auth", "sync_stopped"].includes(result.status)) {
|
||||
if (["need_config_from_admin", "need_auth", "sync_stopped", "sync_paused"].includes(result.status)) {
|
||||
this.microsoftIsSync = false;
|
||||
} else if (result.status === "no_new_event_from_microsoft" || result.status === "need_refresh") {
|
||||
this.microsoftIsSync = true;
|
||||
}
|
||||
this.microsoftIsPaused = result.status == "sync_paused";
|
||||
this.microsoftPendingSync = false;
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ class TestCommon(HttpCase):
|
||||
@patch_api
|
||||
def setUp(self):
|
||||
super(TestCommon, self).setUp()
|
||||
self.env.user.unpause_microsoft_synchronization()
|
||||
|
||||
# prepare users
|
||||
self.organizer_user = self.env["res.users"].search([("name", "=", "Mike Organizer")])
|
||||
|
||||
@@ -278,3 +278,24 @@ class TestCreateEvents(TestCommon):
|
||||
)
|
||||
# Assert that no insert call was made.
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_create_event_with_sync_config_paused(self, mock_insert):
|
||||
"""
|
||||
Creates an event with the synchronization paused, the event must have its field 'need_sync_m' as True
|
||||
for later synchronizing it with Outlook Calendar.
|
||||
"""
|
||||
# Set user sync configuration as active and then pause the synchronization.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to create a simple event in Odoo Calendar.
|
||||
record = self.env["calendar.event"].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
self.call_post_commit_hooks()
|
||||
record.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, insert wasn't called and record is waiting to be synced.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertTrue(record.need_sync_m, "Sync variable must be true for updating event when sync re-activates")
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
@@ -297,3 +297,23 @@ class TestDeleteEvents(TestCommon):
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_delete_synced_event_with_sync_config_paused(self, mock_delete):
|
||||
"""
|
||||
Deletes an event with the Outlook Calendar synchronization paused, the event must be archived completely.
|
||||
"""
|
||||
# Set user synchronization configuration as active and pause it.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to delete a simple event in Odoo Calendar.
|
||||
self.simple_event.with_user(self.organizer_user).unlink()
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, delete wasn't called and record doesn't exist anymore.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertFalse(self.simple_event.exists(), "Event must be deleted from Odoo even though sync configuration is off")
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@@ -1314,3 +1314,24 @@ class TestUpdateEvents(TestCommon):
|
||||
|
||||
# Assert that no patch call was made due to the recurrence update forbiddance.
|
||||
mock_patch.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'patch')
|
||||
def test_update_synced_event_with_sync_config_paused(self, mock_patch):
|
||||
"""
|
||||
Updates an event with the synchronization paused, the event must have its field 'need_sync_m' as True
|
||||
for later synchronizing it with Outlook Calendar.
|
||||
"""
|
||||
# Set user synchronization configuration as active and pause it.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to update a simple event in Odoo Calendar.
|
||||
self.simple_event.with_user(self.organizer_user).write({"name": "updated simple event"})
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, delete wasn't called and record is waiting to be synced again.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertTrue(self.simple_event.need_sync_m, "Sync variable must be true for updating event when sync re-activates")
|
||||
mock_patch.assert_not_called()
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<label for="cal_microsoft_client_secret" string="Client Secret" class="col-3 col-lg-3 o_light_label"/>
|
||||
<field name="cal_microsoft_client_secret" password="True" nolabel="1"/>
|
||||
</div>
|
||||
<div class="mt16 d-flex">
|
||||
<label for="cal_microsoft_sync_paused" string="Pause Synchronization" class="o_light_label"/>
|
||||
<field name="cal_microsoft_sync_paused" class="ml16"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user