Before this commit, when the first synchronization with Outlook Calendar was done or when the synchronization was restarted, all events previously created in Odoo (before synchronizing) were being synced with Outlook, triggering lots of emails and notifications for attendees. Additionaly, all microsoft credentials information were concentrated within the res_users file, difficulting updates in this file. After this commit, only events created or updated after the synchronization starts (or restarts) are synced with Outlook Calendar, with 5 minutes of time acceptance for avoiding network delay issues. Moreover, a Microsoft Credentials module was added for gathering microsoft credentials in one place. closes odoo/odoo#128150 Task-id: 3410651 Related: odoo/upgrade#5047 Signed-off-by: Arnaud Joset (arj) <arj@odoo.com>
181 lines
8.5 KiB
Python
181 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import requests
|
|
from odoo.addons.microsoft_calendar.models.microsoft_sync import microsoft_calendar_token
|
|
from datetime import timedelta
|
|
|
|
from odoo import api, fields, models, _, Command
|
|
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__)
|
|
|
|
|
|
class User(models.Model):
|
|
_inherit = 'res.users'
|
|
|
|
microsoft_calendar_account_id = fields.Many2one('microsoft.calendar.credentials')
|
|
microsoft_calendar_sync_token = fields.Char(related='microsoft_calendar_account_id.calendar_sync_token')
|
|
microsoft_synchronization_stopped = fields.Boolean(related='microsoft_calendar_account_id.synchronization_stopped', readonly=False)
|
|
microsoft_last_sync_date = fields.Datetime(related='microsoft_calendar_account_id.last_sync_date', readonly=False)
|
|
|
|
_sql_constraints = [
|
|
('microsoft_token_uniq', 'unique (microsoft_calendar_account_id)', "The user has already a microsoft account"),
|
|
]
|
|
|
|
@property
|
|
def SELF_READABLE_FIELDS(self):
|
|
return super().SELF_READABLE_FIELDS + ['microsoft_synchronization_stopped', 'microsoft_calendar_account_id', 'microsoft_last_sync_date']
|
|
|
|
@property
|
|
def SELF_WRITEABLE_FIELDS(self):
|
|
return super().SELF_WRITEABLE_FIELDS + ['microsoft_synchronization_stopped', 'microsoft_calendar_account_id', 'microsoft_last_sync_date']
|
|
|
|
def _microsoft_calendar_authenticated(self):
|
|
return bool(self.sudo().microsoft_calendar_rtoken)
|
|
|
|
def _get_microsoft_calendar_token(self):
|
|
if not self:
|
|
return None
|
|
|
|
self.ensure_one()
|
|
if self.microsoft_calendar_rtoken and not self._is_microsoft_calendar_valid():
|
|
self._refresh_microsoft_calendar_token()
|
|
return self.microsoft_calendar_token
|
|
|
|
def _is_microsoft_calendar_valid(self):
|
|
return self.microsoft_calendar_token_validity and self.microsoft_calendar_token_validity >= (fields.Datetime.now() + timedelta(minutes=1))
|
|
|
|
def _refresh_microsoft_calendar_token(self):
|
|
self.ensure_one()
|
|
get_param = self.env['ir.config_parameter'].sudo().get_param
|
|
client_id = get_param('microsoft_calendar_client_id')
|
|
client_secret = get_param('microsoft_calendar_client_secret')
|
|
|
|
if not client_id or not client_secret:
|
|
raise UserError(_("The account for the Outlook Calendar service is not configured."))
|
|
|
|
headers = {"content-type": "application/x-www-form-urlencoded"}
|
|
data = {
|
|
'refresh_token': self.microsoft_calendar_rtoken,
|
|
'client_id': client_id,
|
|
'client_secret': client_secret,
|
|
'grant_type': 'refresh_token',
|
|
}
|
|
|
|
try:
|
|
dummy, response, dummy = self.env['microsoft.service']._do_request(
|
|
DEFAULT_MICROSOFT_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri=''
|
|
)
|
|
ttl = response.get('expires_in')
|
|
self.write({
|
|
'microsoft_calendar_token': response.get('access_token'),
|
|
'microsoft_calendar_token_validity': fields.Datetime.now() + timedelta(seconds=ttl),
|
|
})
|
|
except requests.HTTPError as error:
|
|
if error.response.status_code in (400, 401): # invalid grant or invalid client
|
|
# Delete refresh token and make sure it's commited
|
|
self.env.cr.rollback()
|
|
self.write({
|
|
'microsoft_calendar_rtoken': False,
|
|
'microsoft_calendar_token': False,
|
|
'microsoft_calendar_token_validity': False,
|
|
})
|
|
self.microsoft_calendar_account_id.write({
|
|
'calendar_sync_token': False
|
|
})
|
|
self.env.cr.commit()
|
|
error_key = error.response.json().get("error", "nc")
|
|
error_msg = _(
|
|
"An error occurred while generating the token. Your authorization code may be invalid or has already expired [%s]. "
|
|
"You should check your Client ID and secret on the Microsoft Azure portal or try to stop and restart your calendar synchronisation.",
|
|
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()
|
|
# Create the Calendar Credentials if it is not defined yet and define the last sync date after creation.
|
|
if not self.microsoft_calendar_account_id and self._check_microsoft_calendar_credentials():
|
|
self.microsoft_last_sync_date = fields.datetime.now()
|
|
|
|
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)
|
|
with microsoft_calendar_token(self) as token:
|
|
try:
|
|
events, next_sync_token = calendar_service.get_events(self.microsoft_calendar_sync_token, token=token)
|
|
except InvalidSyncToken:
|
|
events, next_sync_token = calendar_service.get_events(token=token)
|
|
full_sync = True
|
|
self.microsoft_calendar_account_id.calendar_sync_token = next_sync_token
|
|
|
|
# Microsoft -> Odoo
|
|
synced_events, synced_recurrences = self.env['calendar.event']._sync_microsoft2odoo(events) if events else (self.env['calendar.event'], self.env['calendar.recurrence'])
|
|
|
|
# Odoo -> Microsoft
|
|
recurrences = self.env['calendar.recurrence']._get_microsoft_records_to_sync(full_sync=full_sync)
|
|
recurrences -= synced_recurrences
|
|
recurrences._sync_odoo2microsoft()
|
|
synced_events |= recurrences.calendar_event_ids
|
|
|
|
events = self.env['calendar.event']._get_microsoft_records_to_sync(full_sync=full_sync)
|
|
(events - synced_events)._sync_odoo2microsoft()
|
|
self.microsoft_last_sync_date = fields.datetime.now()
|
|
|
|
return bool(events | synced_events) or bool(recurrences | synced_recurrences)
|
|
|
|
@api.model
|
|
def _sync_all_microsoft_calendar(self):
|
|
""" Cron job """
|
|
users = self.env['res.users'].search([('microsoft_calendar_rtoken', '!=', False), ('microsoft_synchronization_stopped', '=', False)])
|
|
for user in users:
|
|
_logger.info("Calendar Synchro - Starting synchronization for %s", user)
|
|
try:
|
|
user.with_user(user).sudo()._sync_microsoft_calendar()
|
|
self.env.cr.commit()
|
|
except Exception as e:
|
|
_logger.exception("[%s] Calendar Synchro - Exception : %s!", user, exception_to_unicode(e))
|
|
self.env.cr.rollback()
|
|
|
|
def _check_microsoft_calendar_credentials(self):
|
|
# Create the Calendar Credentials for the current user if not already created.
|
|
self.ensure_one()
|
|
if not self.microsoft_calendar_account_id:
|
|
self.microsoft_calendar_account_id = self.env['microsoft.calendar.credentials'].sudo().create([{'user_ids': [Command.set(self.ids)]}])
|
|
return True
|
|
|
|
def stop_microsoft_synchronization(self):
|
|
self.ensure_one()
|
|
self._check_microsoft_calendar_credentials()
|
|
self.microsoft_synchronization_stopped = True
|
|
self.microsoft_last_sync_date = None
|
|
|
|
def restart_microsoft_synchronization(self):
|
|
self.ensure_one()
|
|
self._check_microsoft_calendar_credentials()
|
|
self.microsoft_last_sync_date = fields.datetime.now()
|
|
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)
|