- % if not recurrent:
|
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
@@ -46,24 +66,35 @@
|
|
- % endif
Details of the event
+ % if is_online:
+ - Appointment Type: ${object.event_id.appointment_type_id.name}
+ % endif
% if object.event_id.location:
- Location: ${object.event_id.location}
(View Map)
% endif
- % if object.event_id.description :
- - Description: ${object.event_id.description}
- % endif
% if recurrent:
- When: ${object.recurrence_id.name}
% endif
% if not object.event_id.allday and object.event_id.duration
- Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}
% endif
+ % if not is_online and object.event_id.description :
+ - Description: ${object.event_id.description}
+ % elif is_online and object.event_id.description:
+ % set splitted_description = object.event_id.description_to_html_lines()
+ - Description:
+
+ % for description_line in splitted_description:
+ - ${description_line | safe}
+ % endfor
+
+
+ % endif
- Attendees
% for attendee in object.event_id.attendee_ids:
@@ -105,18 +136,38 @@
${object.partner_id.lang}
- % set colors = ctx.get('colors', {})
- % set recurrent = object.recurrence_id and not ctx['ignore_recurrence']
+ % set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
+ % set is_online = 'appointment_type_id' in object.event_id and object.event_id.appointment_type_id
+ % set customer = object.event_id.find_partner_customer()
+ % set target_responsible = object.partner_id == object.event_id.partner_id
+ % set target_customer = object.partner_id == customer
Hello ${object.common_name},
- The date of the meeting has been updated. The meeting ${object.event_id.name} created by ${object.event_id.user_id.partner_id.name} is now scheduled for ${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
+ % if is_online and target_responsible:
+ % if customer:
+ The date of your appointment with ${customer.name} has been updated.
+ % else
+ Your appointment has been updated.
+ % endif
+ The appointment ${object.event_id.appointment_type_id.name} is now scheduled for
+ ${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
+
+ % elif is_online and target_customer:
+ The date of your appointment with ${object.event_id.user_id.partner_id.name} has been updated.
+ The appointment ${object.event_id.appointment_type_id.name} is now scheduled for
+ ${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
+
+ % else:
+ The date of the meeting has been updated.
+ The meeting ${object.event_id.name} created by ${object.event_id.user_id.partner_id.name} is now scheduled for
+ ${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
+ % endif
- % set target = 'recurrence' if recurrent else 'meeting'
-
Accept
-
Decline
- % if not recurrent:
|
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
@@ -140,7 +190,6 @@
|
|
- % endif
Details of the event
@@ -149,15 +198,24 @@
(View Map)
% endif
- % if object.event_id.description :
- - Description: ${object.event_id.description}
- % endif
% if recurrent:
- When: ${object.recurrence_id.name}
% endif
% if not object.event_id.allday and object.event_id.duration
- Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}
% endif
+ % if not is_online and object.event_id.description :
+ - Description: ${object.event_id.description}
+ % elif is_online and object.event_id.description:
+ % set splitted_description = object.event_id.description_to_html_lines()
+ - Description:
+
+ % for description_line in splitted_description:
+ - ${description_line | safe}
+ % endfor
+
+
+ % endif
- Attendees
% for attendee in object.event_id.attendee_ids:
@@ -199,21 +257,18 @@
${object.partner_id.lang}
- % set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
-
- % set event_id = ctx.get('force_event_id') or object.event_id
+ % set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
+ % set is_online = 'appointment_type_id' in object.event_id and object.event_id.appointment_type_id
+ % set recurrent = object.recurrence_id and not ctx.get('calendar_template_ignore_recurrence')
Hello ${object.common_name},
This is a reminder for the below event :
-
Accept
-
Decline
|
- ${event_id.get_interval('dayname', tz=object.partner_id.tz if not event_id.allday else None)}
+ ${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
- ${event_id.get_interval('day', tz=object.partner_id.tz if not event_id.allday else None)}
+ ${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
- ${event_id.get_interval('month', tz=object.partner_id.tz if not event_id.allday else None)}
+ ${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
- ${not event_id.allday and event_id.get_interval('time', tz=object.partner_id.tz) or ''}
+ ${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
|
|
@@ -244,11 +299,23 @@
(View Map)
% endif
- % if object.event_id.description :
- - Description: ${object.event_id.description}
+ % if recurrent:
+ - When: ${object.recurrence_id.name}
% endif
% if not object.event_id.allday and object.event_id.duration
- - Duration: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
+ - Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}
+ % endif
+ % if not is_online and object.event_id.description :
+ - Description: ${object.event_id.description}
+ % elif is_online and object.event_id.description:
+ % set splitted_description = object.event_id.description_to_html_lines()
+ - Description:
+
+ % for description_line in splitted_description:
+ - ${description_line | safe}
+ % endfor
+
+
% endif
- Attendees
diff --git a/addons/calendar/models/calendar_alarm_manager.py b/addons/calendar/models/calendar_alarm_manager.py
index 9855ec0af51..6450690f1d2 100644
--- a/addons/calendar/models/calendar_alarm_manager.py
+++ b/addons/calendar/models/calendar_alarm_manager.py
@@ -167,10 +167,9 @@ class AlarmManager(models.AbstractModel):
# Executed via cron
events = self._get_events_to_notify('email')
attendees = events.attendee_ids.filtered(lambda a: a.state != 'declined')
- attendees._send_mail_to_attendees(
+ attendees.with_context(calendar_template_ignore_recurrence=True)._send_mail_to_attendees(
'calendar.calendar_template_meeting_reminder',
force_send=True,
- ignore_recurrence=True,
)
@api.model
diff --git a/addons/calendar/models/calendar_attendee.py b/addons/calendar/models/calendar_attendee.py
index a3636cd634b..50232d0a6b7 100644
--- a/addons/calendar/models/calendar_attendee.py
+++ b/addons/calendar/models/calendar_attendee.py
@@ -15,6 +15,7 @@ class Attendee(models.Model):
_name = 'calendar.attendee'
_rec_name = 'common_name'
_description = 'Calendar Attendee Information'
+ _order = 'create_date ASC'
def _default_access_token(self):
return uuid.uuid4().hex
@@ -80,18 +81,16 @@ class Attendee(models.Model):
partners = (event.attendee_ids & self).partner_id & event.message_partner_ids
event.message_unsubscribe(partner_ids=partners.ids)
- def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
+ def _send_mail_to_attendees(self, template_xmlid, force_send=False):
""" Send mail for event invitation to event attendees.
:param template_xmlid: xml id of the email template to use to send the invitation
:param force_send: if set to True, the mail(s) will be sent immediately (instead of the next queue processing)
- :param ignore_recurrence: ignore event recurrence
"""
res = False
if self.env['ir.config_parameter'].sudo().get_param('calendar.block_mail') or self._context.get("no_mail_to_attendees"):
return res
- calendar_view = self.env.ref('calendar.view_calendar_event_calendar')
invitation_template = self.env.ref(template_xmlid, raise_if_not_found=False)
if not invitation_template:
_logger.warning("Template %s could not be found. %s not notified." % (template_xmlid, self))
@@ -99,25 +98,8 @@ class Attendee(models.Model):
# get ics file for all meetings
ics_files = self.mapped('event_id')._get_ics_file()
- # prepare rendering context for mail template
- colors = {
- 'needsAction': 'grey',
- 'accepted': 'green',
- 'tentative': '#FFFF00',
- 'declined': 'red'
- }
- rendering_context = dict(self._context)
- rendering_context.update({
- 'colors': colors,
- 'ignore_recurrence': ignore_recurrence,
- 'action_id': self.env['ir.actions.act_window'].sudo().search([('view_id', '=', calendar_view.id)], limit=1).id,
- 'dbname': self._cr.dbname,
- 'base_url': self.env['ir.config_parameter'].sudo().get_param('web.base.url', default='http://localhost:8069'),
- })
-
for attendee in self:
if attendee.email and attendee.partner_id != self.env.user.partner_id:
- # FIXME: is ics_file text or bytes?
event_id = attendee.event_id.id
ics_file = ics_files.get(event_id)
@@ -128,7 +110,7 @@ class Attendee(models.Model):
'mimetype': 'text/calendar',
'datas': base64.b64encode(ics_file)})
]
- body = invitation_template.with_context(rendering_context)._render_field(
+ body = invitation_template._render_field(
'body_html',
attendee.ids,
compute_lang=True,
diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py
index ba4e988560a..4570465ffce 100644
--- a/addons/calendar/models/calendar_event.py
+++ b/addons/calendar/models/calendar_event.py
@@ -16,7 +16,7 @@ from odoo.addons.calendar.models.calendar_attendee import Attendee
from odoo.addons.calendar.models.calendar_recurrence import weekday_to_field, RRULE_TYPE_SELECTION, END_TYPE_SELECTION, MONTH_BY_SELECTION, WEEKDAY_SELECTION, BYDAY_SELECTION
from odoo.tools.translate import _
from odoo.tools.misc import get_lang
-from odoo.tools import pycompat
+from odoo.tools import pycompat, html_escape
from odoo.exceptions import UserError, ValidationError, AccessError
_logger = logging.getLogger(__name__)
@@ -287,7 +287,11 @@ class Meeting(models.Model):
def _compute_attendee(self):
for meeting in self:
- attendee = meeting._find_my_attendee()
+ attendee = next(
+ (attendee for attendee in self.attendee_ids
+ if attendee.partner_id == self.env.user.partner_id),
+ self.env['calendar.attendee']
+ )
meeting.attendee_status = attendee.state if attendee else 'needsAction'
@api.constrains('start', 'stop', 'start_date', 'stop_date')
@@ -477,7 +481,11 @@ class Meeting(models.Model):
start_date = fields.Datetime.to_datetime(values.get('start'))
# Only notify on future events
if start_date and start_date >= fields.Datetime.now():
- (current_attendees & previous_attendees)._send_mail_to_attendees('calendar.calendar_template_meeting_changedate', ignore_recurrence=not update_recurrence)
+ (current_attendees & previous_attendees).with_context(
+ calendar_template_ignore_recurrence=not update_recurrence
+ )._send_mail_to_attendees(
+ 'calendar.calendar_template_meeting_changedate'
+ )
return True
@@ -713,15 +721,13 @@ class Meeting(models.Model):
return attendee.do_decline()
return attendee.do_tentative()
- def _find_my_attendee(self):
- """ Return the first attendee where the user connected has been invited
- from all the meeting_ids in parameters.
- """
+ def find_partner_customer(self):
self.ensure_one()
- for attendee in self.attendee_ids:
- if self.env.user.partner_id == attendee.partner_id:
- return attendee
- return False
+ return next(
+ (attendee.partner_id for attendee in self.attendee_ids.sorted('create_date')
+ if attendee.partner_id != self.user_id.partner_id),
+ self.env['calendar.attendee']
+ )
# ------------------------------------------------------------
# TOOLS
@@ -920,3 +926,56 @@ class Meeting(models.Model):
'id', 'active', 'allday',
'duration', 'user_id', 'interval',
'count', 'rrule', 'recurrence_id', 'show_as', 'privacy'}
+
+ def description_to_html_lines(self):
+ """ Description could contain some structure content, depending on
+ its use. Notably appointment could add some structured data in it
+ in addition to free text. Purpose of this method is to generate a
+ simple html.
+
+ Input (self.description) example:
+Some free text salespeople added
+as multi line
+ * Mobile: +320475000000
+ * Email: my.email@test.example.com
+ * SingleLine: answer
+ * MultiLine:
+Answer1
+Answer2
+Answer3
+ * Dropdown: answer
+ * Radio: answer
+ * Checkboxes: answer1, answer2
+Some free text salespeople added
+as multi line
+
+ Output: a list of items[
+'Some free text salespeople added as multi line',
+'Mobile: +320475000000',
+'Email: my.email@test.example.com',
+'SingleLine: answer',
+'MultiLine: Answer1 Answer2 Answer3',
+'Dropdown: answer',
+'Radio: answer',
+'Checkboxes: answer1, answer2',
+'Some free text salespeople added as multi line']
+
+ Each item of returned list is escaped so that only our intended
+ are html tags. It should therefore be safe.
+ """
+
+ final_lines = []
+ parsed_lines = []
+ for line in self.description.split('\n'):
+ if not line.strip():
+ continue
+ # new line
+ if line.startswith(' *'):
+ if parsed_lines:
+ final_lines.append(' '.join(html_escape(line) for line in parsed_lines))
+ parsed_lines = [line.lstrip(' *')]
+ else:
+ parsed_lines.append(line)
+ if parsed_lines:
+ final_lines.append(' '.join(html_escape(line) for line in parsed_lines))
+ return final_lines
diff --git a/addons/google_calendar/models/calendar_attendee.py b/addons/google_calendar/models/calendar_attendee.py
index d62702f8972..52327cfc2aa 100644
--- a/addons/google_calendar/models/calendar_attendee.py
+++ b/addons/google_calendar/models/calendar_attendee.py
@@ -9,11 +9,11 @@ class Attendee(models.Model):
_name = 'calendar.attendee'
_inherit = 'calendar.attendee'
- def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
+ def _send_mail_to_attendees(self, template_xmlid, force_send=False):
""" Override
If not synced with Google, let Odoo in charge of sending emails
Otherwise, nothing to do: Google will send them
"""
with google_calendar_token(self.env.user.sudo()) as token:
if not token:
- super()._send_mail_to_attendees(template_xmlid, force_send, ignore_recurrence)
+ super()._send_mail_to_attendees(template_xmlid, force_send)
diff --git a/addons/microsoft_calendar/models/calendar_attendee.py b/addons/microsoft_calendar/models/calendar_attendee.py
index 9a462c38f70..5da93635099 100644
--- a/addons/microsoft_calendar/models/calendar_attendee.py
+++ b/addons/microsoft_calendar/models/calendar_attendee.py
@@ -10,11 +10,11 @@ class Attendee(models.Model):
_name = 'calendar.attendee'
_inherit = 'calendar.attendee'
- def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
+ def _send_mail_to_attendees(self, template_xmlid, force_send=False):
""" Override the super method
If not synced with Microsoft Outlook, let Odoo in charge of sending emails
Otherwise, Microsoft Outlook will send them
"""
with microsoft_calendar_token(self.env.user.sudo()) as token:
if not token:
- super()._send_mail_to_attendees(template_xmlid, force_send, ignore_recurrence)
+ super()._send_mail_to_attendees(template_xmlid, force_send)
|
|