19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:34 +01:00
parent 5faf7397c5
commit 2696f14ed7
721 changed files with 220375 additions and 91221 deletions

View file

@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import UserError
import pytz
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from odoo.tests import Form, TransactionCase
from freezegun import freeze_time
@ -22,8 +21,7 @@ class TestRecurrentEvents(TransactionCase):
events = events.sorted('start')
self.assertEqual(len(events), len(dates), "Wrong number of events in the recurrence")
self.assertTrue(all(events.mapped('active')), "All events should be active")
for event, dates in zip(events, dates):
start, stop = dates
for event, (start, stop) in zip(events, dates):
self.assertEqual(event.start, start)
self.assertEqual(event.stop, stop)
@ -353,6 +351,60 @@ class TestCreateRecurrentEvents(TestRecurrentEvents):
(datetime(2023, 4, 27, 7, 00), datetime(2023, 4, 27, 8, 00)),
])
def test_all_day_date(self):
recurrence = self.env['calendar.event'].with_context(
default_start=datetime(2019, 10, 22),
default_stop=datetime(2019, 10, 22),
default_start_date=date(2019, 10, 22),
default_stop_date=date(2019, 10, 22),
).create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 8, 0),
'stop': datetime(2019, 10, 22, 18, 0),
'start_date': date(2019, 10, 22),
'stop_date': date(2019, 10, 22),
'recurrency': True,
'rrule_type': 'weekly',
'tue': True,
'interval': 1,
'count': 2,
'event_tz': 'UTC',
'allday': True,
}).recurrence_id
events = recurrence.calendar_event_ids
self.assertEqual(events[0].start_date, date(2019, 10, 22), "The first event has the initial start date")
self.assertEqual(events[1].start_date, date(2019, 10, 29), "The start date of the second event is one week later")
def test_recurrency_with_this_event(self):
"""
1) Create an event with a recurrence set on it
2) Try updating the event with a different recurrence without specifying 'recurrence_update'
3) Update the recurrence of one of the events, this time using the 'recurrence_update' as future_events
4) Finally, check that the updated event correctly reflects the recurrence
"""
event = self.env['calendar.event'].create({
'name': "Test Event",
'allday': False,
'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=10',
'recurrency': True,
'start': datetime(2023, 7, 28, 1, 0),
'stop': datetime(2023, 7, 29, 18, 0),
})
events = self.env['calendar.recurrence'].search([('base_event_id', '=', event.id)]).calendar_event_ids
self.assertEqual(len(events), 10, "It should have 10 events in the recurrence")
# Update the recurrence without without specifying 'recurrence_update'
with self.assertRaises(UserError):
event.write({'rrule': u'FREQ=DAILY;INTERVAL=2;COUNT=5'})
# Update the recurrence of the earlier event
events[5].write({
'recurrence_update': 'future_events',
'count': 2,
})
updated_events = self.env['calendar.recurrence'].search([('base_event_id', '=', events[5].id)]).calendar_event_ids
self.assertEqual(len(updated_events), 2, "It should have 2 events in the recurrence")
self.assertTrue(updated_events[1].recurrency, "It should have recurrency in the updated events")
class TestUpdateRecurrentEvents(TestRecurrentEvents):
@classmethod
@ -443,7 +495,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)),
@ -489,7 +541,7 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents):
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertFalse(self.recurrence.calendar_event_ids, "Inactive event should not create recurrent events")
self.assertFalse(self.recurrence.exists(), "Inactive event should not create recurrent events")
def test_shift_all_with_outlier(self):
outlier = self.events[1]
@ -571,7 +623,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)),
@ -692,6 +744,60 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents):
self.assertTrue(self.recurrence)
self.assertEqual(self.events.exists(), self.events[0])
def test_unlink_recurrence_wizard_next(self):
""" Test unlinking the next recurrent event using the delete wizard. """
# Retrieve the recurring event to delete the next event occurrence.
event = self.events[1]
# Step 1: Use the popover delete wizard to delete the next occurrence of the event.
wizard = self.env['calendar.popover.delete.wizard'].with_context(
form_view_ref='calendar.calendar_popover_delete_view').create({'calendar_event_id': event.id})
form = Form(wizard)
form.delete = 'next'
form.save()
wizard.close()
# Step 2: Use another delete wizard to handle the deletion of the next occurrence.
wizard_delete = self.env['calendar.popover.delete.wizard'].with_context(
form_view_ref='calendar.view_event_delete_wizard_form',
default_recurrence='next'
).create({'calendar_event_id': event.id})
form_delete = Form(wizard_delete)
form_delete.save()
# Step 3: Send cancellation notifications and delete the next occurrence.
# Ensure that the recurrence still exists but the next event occurrence is deleted.
wizard_delete.action_send_mail_and_delete()
self.assertTrue(self.recurrence)
self.assertEqual(self.events.exists(), self.events[0])
def test_unlink_recurrence_wizard_all(self):
""" Test unlinking all recurrences using the delete wizard. """
# Step 0: Retrieve the recurring event to be deleted.
event = self.events[1]
# Step 1: Use the popover delete wizard to delete all occurrences of the event.
wizard = self.env['calendar.popover.delete.wizard'].with_context(
form_view_ref='calendar.calendar_popover_delete_view').create({'calendar_event_id': event.id})
form = Form(wizard)
form.delete = 'all'
form.save()
wizard.close()
# Step 2: Use another delete wizard to handle the deletion of the event recurrence.
wizard_delete = self.env['calendar.popover.delete.wizard'].with_context(
form_view_ref='calendar.view_event_delete_wizard_form',
default_recurrence='all'
).create({'calendar_event_id': event.id})
form_delete = Form(wizard_delete)
form_delete.save()
# Step 3: Send cancellation notifications and delete all recurrences.
# Ensure that the recurrence and all related events have been deleted.
wizard_delete.action_send_mail_and_delete()
self.assertFalse(self.recurrence.exists())
self.assertFalse(self.events.exists())
def test_recurrence_update_all_first_archived(self):
"""Test to check the flow when a calendar event is
created from a day that does not belong to the recurrence.
@ -734,7 +840,6 @@ class TestUpdateRecurrentEvents(TestRecurrentEvents):
(datetime(2019, 11, 6, 1, 0), datetime(2019, 11, 6, 2, 0)),
])
class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents):
@classmethod
@ -769,7 +874,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)),
@ -787,7 +892,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)),
@ -840,7 +945,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)),
@ -898,3 +1003,116 @@ 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)),
])
self.assertEqual(event.rrule_type_ui, 'daily')
self.assertEqual(event.count, 2)
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)),
])
self.assertEqual(event.rrule_type_ui, 'monthly')
self.assertEqual(event.count, 2)
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)),
])
# set to custom because a yearly recurrence, becomes a monthly recurrence every 12 months
self.assertEqual(event.rrule_type_ui, 'yearly')
self.assertEqual(event.count, 2)
self.assertEqual(event.interval, 1)
self.assertEqual(event.rrule_type, 'yearly')
def test_attendees_state_after_update(self):
""" Ensure that after the organizer updates a recurrence, the attendees state will be pending and current user accepted. """
# Create events with organizer and attendee state set as accepted.
organizer = self.env.ref('base.user_admin')
attendee_partner = self.env['res.partner'].create({'name': "attendee", "email": 'attendee@email.com'})
first_event = self.env['calendar.event'].with_user(organizer).create({
'name': "Recurrence",
'start': datetime(2023, 10, 18, 8, 0),
'stop': datetime(2023, 10, 18, 10, 0),
'rrule': u'FREQ=WEEKLY;COUNT=5;BYDAY=WE',
'recurrency': True,
'partner_ids': [(4, organizer.partner_id.id), (4, attendee_partner.id)],
})
recurrence_id = first_event.recurrence_id.id
# Accept all events for all attendees and ensure their acceptance.
for event in first_event.recurrence_id.calendar_event_ids:
for attendee in event.attendee_ids:
attendee.state = 'accepted'
# Change time fields of the recurrence by organizer in "all_events" mode. Events must reset attendee status to 'needsAction'.
first_event.with_user(organizer).write({
'start': first_event.start + relativedelta(hours=2),
'stop': first_event.stop + relativedelta(hours=2),
'recurrence_update': 'all_events',
})
first_event = self.env['calendar.recurrence'].search([('id', '>', recurrence_id)]).base_event_id
recurrence_id = first_event.recurrence_id.id
# Ensure that attendee status is pending after organizer (current user) update time values.
for event in first_event.recurrence_id.calendar_event_ids:
for attendee in event.attendee_ids:
if attendee.partner_id == organizer.partner_id:
self.assertEqual(attendee.state, "accepted", "Organizer must remain accepted after time values update.")
else:
self.assertEqual(attendee.state, "needsAction", "Attendees state except organizer must be pending after update.")
# Accept all events again for all attendes.
for event in first_event.recurrence_id.calendar_event_ids:
for attendee in event.attendee_ids:
attendee.state = 'accepted'
# Change time fields of the recurrence by organizer in "future_events" mode. Events must reset attendee status to 'needsAction'.
second_event = first_event.recurrence_id.calendar_event_ids.sorted('start')[1]
second_event.with_user(organizer).write({
'start': second_event.start + relativedelta(hours=2),
'stop': second_event.stop + relativedelta(hours=2),
'recurrence_update': 'future_events',
})
second_event = self.env['calendar.recurrence'].search([('id', '>', recurrence_id)]).base_event_id
# Ensure that first event is accepted for everyone and also from the second event on, the state in pending for attendees except organizer.
self.assertTrue(first_event.active, "Event from previous recurrence must remain active after the second event got updated.")
self.assertTrue(all(attendee.state == 'accepted' for attendee in first_event.attendee_ids), "Attendees state from previous event must remain accepted.")
for event in second_event.recurrence_id.calendar_event_ids:
for attendee in event.attendee_ids:
if attendee.partner_id == organizer.partner_id:
self.assertEqual(attendee.state, "accepted", "Current user must remain accepted after time values update.")
else:
self.assertEqual(attendee.state, "needsAction", "Attendees state except current user must be pending after update.")