diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py index 5ba62bf7744..d0bab630603 100644 --- a/addons/calendar/models/calendar_event.py +++ b/addons/calendar/models/calendar_event.py @@ -3,7 +3,7 @@ import logging import math -from datetime import timedelta +from datetime import datetime, timedelta from itertools import repeat from werkzeug.urls import url_parse @@ -40,6 +40,13 @@ SORT_ALIASES = { 'start_date': 'sort_start', } +RRULE_TYPE_SELECTION_UI = [ + ('daily', 'Daily'), + ('weekly', 'Weekly'), + ('monthly', 'Monthly'), + ('yearly', 'Yearly'), + ('custom', 'Custom') +] def get_weekday_occurence(date): """ @@ -99,6 +106,17 @@ class Meeting(models.Model): partners |= self.env['res.partner'].browse(active_id) return partners + @api.model + def _default_start(self): + now = fields.Datetime.now() + return now + (datetime.min - now) % timedelta(minutes=30) + + @api.model + def _default_stop(self): + now = fields.Datetime.now() + start = now + (datetime.min - now) % timedelta(minutes=30) + return start + timedelta(hours=1) + # description name = fields.Char('Meeting Subject', required=True) description = fields.Html('Description') @@ -138,10 +156,10 @@ class Meeting(models.Model): 'calendar.event.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags') # timing start = fields.Datetime( - 'Start', required=True, tracking=True, default=fields.Date.today, + 'Start', required=True, tracking=True, default=_default_start, help="Start date of an event, without time for full days events") stop = fields.Datetime( - 'Stop', required=True, tracking=True, default=lambda self: fields.Datetime.today() + timedelta(hours=1), + 'Stop', required=True, tracking=True, default=_default_stop, compute='_compute_stop', readonly=False, store=True, help="Stop date of an event, without time for full days events") display_time = fields.Char('Event Time', compute='_compute_display_time') @@ -189,6 +207,10 @@ class Meeting(models.Model): # If some of these fields are set and recurrence_id does not exists, # a `calendar.recurrence.rule` will be dynamically created. rrule = fields.Char('Recurrent Rule', compute='_compute_recurrence', readonly=False) + rrule_type_ui = fields.Selection(RRULE_TYPE_SELECTION_UI, string='Repeat', + compute="_compute_rrule_type_ui", + readonly=False, + help="Let the event automatically repeat at that interval") rrule_type = fields.Selection(RRULE_TYPE_SELECTION, string='Recurrence', help="Let the event automatically repeat at that interval", compute='_compute_recurrence', readonly=False) @@ -198,10 +220,10 @@ class Meeting(models.Model): END_TYPE_SELECTION, string='Recurrence Termination', compute='_compute_recurrence', readonly=False) interval = fields.Integer( - string='Repeat Every', compute='_compute_recurrence', readonly=False, + string='Repeat On', compute='_compute_recurrence', readonly=False, help="Repeat every (Days/Week/Month/Year)") count = fields.Integer( - string='Repeat', help="Repeat x times", compute='_compute_recurrence', readonly=False) + string='Number of Repetitions', help="Repeat x times", compute='_compute_recurrence', readonly=False) mon = fields.Boolean(compute='_compute_recurrence', readonly=False) tue = fields.Boolean(compute='_compute_recurrence', readonly=False) wed = fields.Boolean(compute='_compute_recurrence', readonly=False) @@ -373,6 +395,16 @@ class Meeting(models.Model): ) @api.depends('recurrence_id', 'recurrency') + def _compute_rrule_type_ui(self): + defaults = self.env["calendar.recurrence"].default_get(["interval", "rrule_type"]) + for event in self: + if event.recurrency: + if event.recurrence_id: + event.rrule_type_ui = 'custom' if event.recurrence_id.interval != 1 else (event.recurrence_id.rrule_type) + else: + event.rrule_type_ui = defaults["rrule_type"] + + @api.depends('recurrence_id', 'recurrency', 'rrule_type_ui') def _compute_recurrence(self): recurrence_fields = self._get_recurrent_fields() false_values = {field: False for field in recurrence_fields} # computes need to set a value @@ -388,6 +420,7 @@ class Meeting(models.Model): if event.recurrence_id[field] } rrule_values = rrule_values or default_rrule_values + rrule_values['rrule_type'] = (event.rrule_type if event.rrule_type_ui == "custom" else event.rrule_type_ui) or defaults.pop('rrule_type') event.update({**false_values, **defaults, **event_values, **rrule_values}) else: event.update(false_values) diff --git a/addons/calendar/static/src/views/widgets/calendar_week_days.js b/addons/calendar/static/src/views/widgets/calendar_week_days.js new file mode 100644 index 00000000000..84a4d66eb5a --- /dev/null +++ b/addons/calendar/static/src/views/widgets/calendar_week_days.js @@ -0,0 +1,18 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { WeekDays, weekDays } from "@web/views/widgets/week_days/week_days"; + +export class CalendarWeekDays extends WeekDays { + onChange(day) { + this.props.record.update({ [day]: !this.data[day] }); + } +}; +CalendarWeekDays.template = 'calendar.WeekDays'; + +export const calendarWeekDays = { + component: CalendarWeekDays, + fieldDependencies: weekDays.fieldDependencies, +}; + +registry.category("view_widgets").add("calendar_week_days", calendarWeekDays); diff --git a/addons/calendar/static/src/views/widgets/calendar_week_days.scss b/addons/calendar/static/src/views/widgets/calendar_week_days.scss new file mode 100644 index 00000000000..1b57e10aff8 --- /dev/null +++ b/addons/calendar/static/src/views/widgets/calendar_week_days.scss @@ -0,0 +1,10 @@ +.o_calendar_week_days_rounded { + border-radius: 50%; + margin-right: 8px; + width: 24px; + height: 24px; + justify-content: center; + display: flex; + cursor: pointer; + align-items: center; +} diff --git a/addons/calendar/static/src/views/widgets/calendar_week_days.xml b/addons/calendar/static/src/views/widgets/calendar_week_days.xml new file mode 100644 index 00000000000..964cdd1d74a --- /dev/null +++ b/addons/calendar/static/src/views/widgets/calendar_week_days.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/addons/calendar/tests/test_calendar_recurrent_event_case2.py b/addons/calendar/tests/test_calendar_recurrent_event_case2.py index 917548baf54..2701f28881d 100644 --- a/addons/calendar/tests/test_calendar_recurrent_event_case2.py +++ b/addons/calendar/tests/test_calendar_recurrent_event_case2.py @@ -21,7 +21,7 @@ class TestRecurrentEvent(common.TransactionCase): 'duration': 1.0, 'name': 'Test Meeting', 'recurrency': True, - 'rrule_type': 'daily' + 'rrule_type': 'daily', }) # I search for all the recurrent meetings meetings_count = self.CalendarEvent.with_context({'virtual_id': True}).search_count([ @@ -45,7 +45,7 @@ class TestRecurrentEvent(common.TransactionCase): 'wed': True, 'name': 'Review code with programmer', 'recurrency': True, - 'rrule_type': 'weekly' + 'rrule_type': 'weekly', }) # I search for all the recurrent weekly meetings. diff --git a/addons/calendar/tests/test_event_recurrence.py b/addons/calendar/tests/test_event_recurrence.py index 48e36af6fb3..5d13e03cc7d 100644 --- a/addons/calendar/tests/test_event_recurrence.py +++ b/addons/calendar/tests/test_event_recurrence.py @@ -497,7 +497,7 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): 'start': event.start + relativedelta(days=4), 'stop': event.stop + relativedelta(days=5), }) - recurrence = self.env['calendar.recurrence'].search([]) + recurrence = self.env['calendar.recurrence'].search([], limit=1) self.assertEventDates(recurrence.calendar_event_ids, [ (datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 29, 18, 0)), (datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)), @@ -587,7 +587,7 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents): 'recurrence_update': 'all_events', 'mon': True, # recurrence is now Tuesday AND Monday }) - recurrence = self.env['calendar.recurrence'].search([]) + recurrence = self.env['calendar.recurrence'].search([], limit=1) self.assertEventDates(recurrence.calendar_event_ids, [ (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), (datetime(2019, 10, 28, 1, 0), datetime(2019, 10, 30, 18, 0)), @@ -748,7 +748,7 @@ class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents): 'start': event.start + relativedelta(days=2), 'stop': event.stop + relativedelta(days=2), }) - recurrence = self.env['calendar.recurrence'].search([]) + recurrence = self.env['calendar.recurrence'].search([], limit=1) # We don't try to do magic tricks. First event is moved, other remain self.assertEventDates(recurrence.calendar_event_ids, [ (datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 26, 18, 0)), @@ -766,7 +766,7 @@ class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents): 'start': event.start + relativedelta(days=2), 'stop': event.stop + relativedelta(days=3), }) - recurrence = self.env['calendar.recurrence'].search([]) + recurrence = self.env['calendar.recurrence'].search([], limit=1) self.assertEventDates(recurrence.calendar_event_ids, [ (datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 27, 18, 0)), (datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 3, 18, 0)), @@ -819,7 +819,7 @@ class TestUpdateMonthlyByDay(TestRecurrentEvents): 'start': event.start + relativedelta(hours=5), 'stop': event.stop + relativedelta(hours=5), }) - recurrence = self.env['calendar.recurrence'].search([]) + recurrence = self.env['calendar.recurrence'].search([], limit=1) self.assertEventDates(recurrence.calendar_event_ids, [ (datetime(2019, 10, 15, 6, 0), datetime(2019, 10, 16, 23, 0)), (datetime(2019, 11, 19, 6, 0), datetime(2019, 11, 20, 23, 0)), @@ -877,3 +877,46 @@ class TestUpdateMonthlyByDate(TestRecurrentEvents): (datetime(2019, 11, 25, 1, 0), datetime(2019, 11, 27, 18, 0)), (datetime(2019, 12, 25, 1, 0), datetime(2019, 12, 27, 18, 0)), ]) + + def test_recurring_ui_options_daily(self): + with Form(self.env['calendar.event']) as calendar_form: + calendar_form.name = 'test recurrence daily' + calendar_form.recurrency = True + calendar_form.rrule_type_ui = 'daily' + calendar_form.count = 2 + calendar_form.start = datetime(2019, 6, 23, 16) + calendar_form.stop = datetime(2019, 6, 23, 17) + event = calendar_form.save() + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 6, 23, 16), datetime(2019, 6, 23, 17)), + (datetime(2019, 6, 24, 16, 0), datetime(2019, 6, 24, 17)), + ]) + + def test_recurring_ui_options_monthly(self): + with Form(self.env['calendar.event']) as calendar_form: + calendar_form.name = 'test recurrence monthly' + calendar_form.recurrency = True + calendar_form.rrule_type_ui = 'monthly' + calendar_form.count = 2 + calendar_form.start = datetime(2019, 6, 11, 16) + calendar_form.stop = datetime(2019, 6, 11, 17) + calendar_form.day = 11 + event = calendar_form.save() + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 6, 11, 16), datetime(2019, 6, 11, 17)), + (datetime(2019, 7, 11, 16), datetime(2019, 7, 11, 17)), + ]) + + def test_recurring_ui_options_yearly(self): + with Form(self.env['calendar.event']) as calendar_form: + calendar_form.name = 'test recurrence yearly' + calendar_form.recurrency = True + calendar_form.rrule_type_ui = 'yearly' + calendar_form.count = 2 + calendar_form.start = datetime(2019, 6, 11, 16) + calendar_form.stop = datetime(2019, 6, 11, 17) + event = calendar_form.save() + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 6, 11, 16), datetime(2019, 6, 11, 17)), + (datetime(2020, 6, 11, 16), datetime(2020, 6, 11, 17)), + ]) diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index 18dc1756eb3..718c39eb703 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -171,14 +171,14 @@ - + Attendees - + @@ -192,8 +192,10 @@ + readonly="not user_can_edit" + /> The following attendees have invalid email addresses and won't receive any email notifications: @@ -202,53 +204,55 @@ + - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Repeat on + + + + + + + + + + + + + + + @@ -320,7 +324,7 @@ diff --git a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.scss b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.scss index 47fec579845..ce51c4dcc87 100644 --- a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.scss +++ b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.scss @@ -4,6 +4,7 @@ $o-cw-popup-avatar-size: 16px; .o_cw_popover { min-width: 256px; max-width: 328px; + z-index: $zindex-modal - 1; font-size: $font-size-base; .card-header, diff --git a/addons/web/static/src/views/calendar/calendar_controller.js b/addons/web/static/src/views/calendar/calendar_controller.js index 6be9de28262..f1ccfb56a41 100644 --- a/addons/web/static/src/views/calendar/calendar_controller.js +++ b/addons/web/static/src/views/calendar/calendar_controller.js @@ -160,6 +160,7 @@ export class CalendarController extends Component { return { resModel: this.model.resModel, viewId: this.model.quickCreateFormViewId, + title: _t("New Event"), context, }; }
The following attendees have invalid email addresses and won't receive any email notifications: