167 lines
7.6 KiB
Python
167 lines
7.6 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, _
|
||
|
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
|
||
|
|
||
|
_logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class User(models.Model):
|
||
|
_inherit = 'res.users'
|
||
|
|
||
|
microsoft_calendar_sync_token = fields.Char('Microsoft Next Sync Token', copy=False)
|
||
|
microsoft_synchronization_stopped = fields.Boolean('Outlook Synchronization stopped', copy=False)
|
||
|
|
||
|
@property
|
||
|
def SELF_READABLE_FIELDS(self):
|
||
|
return super().SELF_READABLE_FIELDS + ['microsoft_synchronization_stopped']
|
||
|
|
||
|
@property
|
||
|
def SELF_WRITEABLE_FIELDS(self):
|
||
|
return super().SELF_WRITEABLE_FIELDS + ['microsoft_synchronization_stopped']
|
||
|
|
||
|
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,
|
||
|
'microsoft_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 _sync_microsoft_calendar(self):
|
||
|
self.ensure_one()
|
||
|
if self.microsoft_synchronization_stopped:
|
||
|
return False
|
||
|
|
||
|
# Set the first synchronization date as an ICP parameter before writing the variable
|
||
|
# 'microsoft_calendar_sync_token' below, so we identify the first synchronization.
|
||
|
self._set_ICP_first_synchronization_date(fields.Datetime.now())
|
||
|
|
||
|
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_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()
|
||
|
|
||
|
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 stop_microsoft_synchronization(self):
|
||
|
self.ensure_one()
|
||
|
self.microsoft_synchronization_stopped = True
|
||
|
|
||
|
def restart_microsoft_synchronization(self):
|
||
|
self.ensure_one()
|
||
|
self.microsoft_synchronization_stopped = False
|
||
|
self.env['calendar.recurrence']._restart_microsoft_sync()
|
||
|
self.env['calendar.event']._restart_microsoft_sync()
|
||
|
|
||
|
def _set_ICP_first_synchronization_date(self, now):
|
||
|
"""
|
||
|
Set the first synchronization date as an ICP parameter when applicable (param not defined yet
|
||
|
and calendar never synchronized before). This parameter is used for not synchronizing previously
|
||
|
created Odoo events and thus avoid spamming invitations for those events.
|
||
|
"""
|
||
|
ICP = self.env['ir.config_parameter'].sudo()
|
||
|
first_synchronization_date = ICP.get_param('microsoft_calendar.sync.first_synchronization_date')
|
||
|
|
||
|
if not first_synchronization_date:
|
||
|
# Check if any calendar has synchronized before by checking the user's tokens.
|
||
|
any_calendar_synchronized = self.env['res.users'].sudo().search_count(
|
||
|
domain=[('microsoft_calendar_sync_token', '!=', False)],
|
||
|
limit=1
|
||
|
)
|
||
|
|
||
|
# Check if any user synchronized its calendar before by saving the date token.
|
||
|
# Add one minute of time diff for avoiding write time delay conflicts with the next sync methods.
|
||
|
if not any_calendar_synchronized:
|
||
|
ICP.set_param('microsoft_calendar.sync.first_synchronization_date', now - timedelta(minutes=1))
|