diff --git a/caldav_sync/__manifest__.py b/caldav_sync/__manifest__.py index 0ad599a..fbed16b 100644 --- a/caldav_sync/__manifest__.py +++ b/caldav_sync/__manifest__.py @@ -8,7 +8,7 @@ { 'name': 'CalDAV Synchronization', - 'version': '17.0.0.5.2', + 'version': '17.0.0.5.3', 'license': 'LGPL-3', 'category': 'Productivity', 'summary': 'Synchronize Odoo Calendar Events with CalDAV Servers', @@ -20,6 +20,9 @@ 'author': 'Bemade Inc.', 'website': 'https://www.bemade.org', 'depends': ['base', 'calendar'], + 'external_dependencies': { + 'python': ['caldav', 'icalendar', 'bs4'], + }, 'images': ['static/description/images/main_screenshot.png'], 'data': [ 'views/res_users_views.xml', diff --git a/caldav_sync/models/calendar_event.py b/caldav_sync/models/calendar_event.py index 44de0f9..f228952 100644 --- a/caldav_sync/models/calendar_event.py +++ b/caldav_sync/models/calendar_event.py @@ -2,12 +2,13 @@ import uuid from odoo import models, api, fields import caldav import logging -from datetime import datetime -from icalendar import Calendar, Event, vCalAddress, Alarm, vText, vWeekday +from datetime import datetime, timezone +from icalendar import Calendar, Event, vCalAddress, vText, vWeekday from bs4 import BeautifulSoup from datetime import timedelta import dateutil import re +from pytz import timezone, utc _logger = logging.getLogger(__name__) @@ -143,16 +144,32 @@ class CalendarEvent(models.Model): calendar.add('version', '2.0') for event in self: + user_tz = timezone('UTC') + if event.user_id.tz: + user_tz = timezone(event.user_id.tz) ical_event = Event() ical_event.add('uid', event.caldav_uid) - ical_event.add('dtstamp', event.write_date.replace(tzinfo=None)) + ical_event.add('dtstamp', utc.localize(event.write_date).astimezone(user_tz)) if event.name: ical_event.add('summary', event.name) - if event.description: - ical_event.add('description', event.description) + if event.description and self._html_to_text(event.description): + ical_event.add('description', self._html_to_text(event.description)) if event.location: ical_event.add('location', event.location) - + if event.videocall_location: + ical_event.add('CONFERENCE', event.videocall_location) + for partner in event.partner_ids: + if partner == event.user_id.partner_id: + continue + attendee = vCalAddress(f'MAILTO:{partner.email}') + attendee.params['cn'] = vText(partner.name) + attendee_record = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', partner.id)], limit=1) + if attendee_record: + attendee.params['partstat'] = vText(self._map_attendee_status(attendee_record.state)) + ical_event.add('attendee', attendee, encode=0) + organizer = vCalAddress(f"MAILTO:{event.user_id.email}") + organizer.params['cn'] = event.user_id.name + ical_event.add('organizer', organizer) # Add RRULE if the event is recurrent if event.recurrency and event.recurrence_id: rrule = event.recurrence_id._get_rrule() @@ -160,8 +177,8 @@ class CalendarEvent(models.Model): ical_event.add('rrule', rrule_dict) # Add DTSTART and DTEND - ical_event.add('dtstart', event.start.replace(tzinfo=None)) - ical_event.add('dtend', event.stop.replace(tzinfo=None)) + ical_event.add('dtstart', utc.localize(event.start).astimezone(user_tz)) + ical_event.add('dtend', utc.localize(event.stop).astimezone(user_tz)) calendar.add_component(ical_event) @@ -269,7 +286,6 @@ class CalendarEvent(models.Model): def sync_event_from_ical(self, ical_event): email_regex = re.compile(r'[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+') - now = datetime.now() current_user_email = self.env.user.email.lower() for component in ical_event.subcomponents: @@ -300,16 +316,18 @@ class CalendarEvent(models.Model): existing_instance = self._get_existing_instance(uid, recurrence_id) start = component.decoded('dtstart') if isinstance(start, datetime): - start = start.replace(tzinfo=None) + start = start.astimezone(utc).replace(tzinfo=None) end = component.decoded('dtend') if isinstance(end, datetime): - end = end.replace(tzinfo=None) + end = end.astimezone(utc).replace(tzinfo=None) + values = { 'name': str(component.get('summary')), 'start': start, 'stop': end, - 'description': str(component.get('description')), - 'location': str(component.get('location')), + 'description': self._extract_component_text(component, 'description'), + 'location': self._extract_component_text(component, 'location'), + 'videocall_location': self._extract_component_text(component, 'conference'), 'caldav_uid': uid, 'partner_ids': [(6, 0, attendee_ids.ids)], } @@ -328,6 +346,10 @@ class CalendarEvent(models.Model): }) existing_instance.with_context({'caldav_no_sync': True}).write(values) + @staticmethod + def _extract_component_text(component, subcomponent_name): + text = str(component.get(subcomponent_name)) + text = text if text != 'None' else '' @staticmethod def _html_to_text(html):