mirror of
https://github.com/bringout/oca-ocb-technical.git
synced 2026-04-20 11:12:04 +02:00
Initial commit: Technical packages
This commit is contained in:
commit
3473fa71a0
873 changed files with 297766 additions and 0 deletions
13
odoo-bringout-oca-ocb-calendar/calendar/tests/__init__.py
Normal file
13
odoo-bringout-oca-ocb-calendar/calendar/tests/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_access_rights
|
||||
from . import test_attendees
|
||||
from . import test_calendar
|
||||
from . import test_calendar_controller
|
||||
from . import test_calendar_recurrent_event_case2
|
||||
from . import test_event_recurrence
|
||||
from . import test_event_notifications
|
||||
from . import test_mail_activity_mixin
|
||||
from . import test_res_partner
|
||||
from . import test_recurrence_rule
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase, new_test_user
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestAccessRights(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
@mute_logger('odoo.tests', 'odoo.addons.auth_signup.models.res_users')
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.john = new_test_user(cls.env, login='john', groups='base.group_user')
|
||||
cls.raoul = new_test_user(cls.env, login='raoul', groups='base.group_user')
|
||||
cls.george = new_test_user(cls.env, login='george', groups='base.group_user')
|
||||
cls.portal = new_test_user(cls.env, login='pot', groups='base.group_portal')
|
||||
cls.admin_user = new_test_user(cls.env, login='admin_user', groups='base.group_partner_manager,base.group_user')
|
||||
|
||||
def create_event(self, user, **values):
|
||||
return self.env['calendar.event'].with_user(user).create({
|
||||
'name': 'Event',
|
||||
'start': datetime(2020, 2, 2, 8, 0),
|
||||
'stop': datetime(2020, 2, 2, 18, 0),
|
||||
'user_id': user.id,
|
||||
'partner_ids': [(4, self.george.partner_id.id, 0)],
|
||||
**values
|
||||
})
|
||||
|
||||
def read_event(self, user, events, field):
|
||||
data = events.with_user(user).read([field])
|
||||
if len(events) == 1:
|
||||
return data[0][field]
|
||||
return [r[field] for r in data]
|
||||
|
||||
# don't spam logs with ACL failures from portal
|
||||
@mute_logger('odoo.addons.base.models.ir_rule')
|
||||
def test_privacy(self):
|
||||
event = self.create_event(
|
||||
self.john,
|
||||
privacy='private',
|
||||
name='my private event',
|
||||
location='in the Sky'
|
||||
)
|
||||
for user, field, expect, error in [
|
||||
# public field, any employee can read
|
||||
(self.john, 'privacy', 'private', None),
|
||||
(self.george, 'privacy', 'private', None),
|
||||
(self.raoul, 'privacy', 'private', None),
|
||||
(self.portal, 'privacy', None, AccessError),
|
||||
# substituted private field, only owner and invitees can read, other
|
||||
# employees get substitution
|
||||
(self.john, 'name', 'my private event', None),
|
||||
(self.george, 'name', 'my private event', None),
|
||||
(self.raoul, 'name', 'Busy', None),
|
||||
(self.portal, 'name', None, AccessError),
|
||||
# computed from private field
|
||||
(self.john, 'display_name', 'my private event', None),
|
||||
(self.george, 'display_name', 'my private event', None),
|
||||
(self.raoul, 'display_name', 'Busy', None),
|
||||
(self.portal, 'display_name', None, AccessError),
|
||||
# non-substituted private field, only owner and invitees can read,
|
||||
# other employees get an empty field
|
||||
(self.john, 'location', 'in the Sky', None),
|
||||
(self.george, 'location', 'in the Sky', None),
|
||||
(self.raoul, 'location', False, None),
|
||||
(self.portal, 'location', None, AccessError),
|
||||
# non-substituted sequence field
|
||||
(self.john, 'partner_ids', self.george.partner_id, None),
|
||||
(self.george, 'partner_ids', self.george.partner_id, None),
|
||||
(self.raoul, 'partner_ids', self.env['res.partner'], None),
|
||||
(self.portal, 'partner_ids', None, AccessError),
|
||||
]:
|
||||
self.env.invalidate_all()
|
||||
with self.subTest("private read", user=user.display_name, field=field, error=error):
|
||||
e = event.with_user(user)
|
||||
if error:
|
||||
with self.assertRaises(error):
|
||||
_ = e[field]
|
||||
else:
|
||||
self.assertEqual(e[field], expect)
|
||||
|
||||
def test_private_and_public(self):
|
||||
private = self.create_event(
|
||||
self.john,
|
||||
privacy='private',
|
||||
location='in the Sky',
|
||||
)
|
||||
public = self.create_event(
|
||||
self.john,
|
||||
privacy='public',
|
||||
location='In Hell',
|
||||
)
|
||||
[private_location, public_location] = self.read_event(self.raoul, private + public, 'location')
|
||||
self.assertFalse(private_location, "Private value should be obfuscated")
|
||||
self.assertEqual(public_location, 'In Hell', "Public value should not be obfuscated")
|
||||
|
||||
def test_read_group_public(self):
|
||||
event = self.create_event(self.john)
|
||||
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start')
|
||||
self.assertTrue(data, "It should be able to read group")
|
||||
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'],
|
||||
groupby='name')
|
||||
self.assertTrue(data, "It should be able to read group")
|
||||
|
||||
def test_read_group_private(self):
|
||||
event = self.create_event(self.john, privacy='private')
|
||||
result = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'], groupby='name')
|
||||
self.assertFalse(result, "Private events should not be fetched")
|
||||
|
||||
|
||||
def test_read_group_agg(self):
|
||||
event = self.create_event(self.john)
|
||||
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start:week')
|
||||
self.assertTrue(data, "It should be able to read group")
|
||||
|
||||
def test_read_group_list(self):
|
||||
event = self.create_event(self.john)
|
||||
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby=['start'])
|
||||
self.assertTrue(data, "It should be able to read group")
|
||||
|
||||
def test_private_attendee(self):
|
||||
event = self.create_event(
|
||||
self.john,
|
||||
privacy='private',
|
||||
location='in the Sky',
|
||||
)
|
||||
partners = (self.john|self.raoul).mapped('partner_id')
|
||||
event.write({'partner_ids': [(6, 0, partners.ids)]})
|
||||
self.assertEqual(self.read_event(self.raoul, event, 'location'), 'in the Sky',
|
||||
"Owner should be able to read the event")
|
||||
with self.assertRaises(AccessError):
|
||||
self.read_event(self.portal, event, 'location')
|
||||
|
||||
def test_meeting_edit_access_notification_handle_in_odoo(self):
|
||||
# set notifications to "handle in Odoo" in Preferences for john, raoul, and george
|
||||
(self.john | self.raoul | self.george).write({'notification_type': 'inbox'})
|
||||
|
||||
# raoul creates a meeting for john, excluding themselves
|
||||
meeting = self.env['calendar.event'].with_user(self.raoul).create({
|
||||
'name': 'Test Meeting',
|
||||
'start': datetime.now(),
|
||||
'stop': datetime.now() + timedelta(hours=2),
|
||||
'user_id': self.john.id,
|
||||
'partner_ids': [(4, self.raoul.partner_id.id)],
|
||||
})
|
||||
|
||||
# george tries to modify the start date of the meeting to a future date
|
||||
# this verifies that users with "handle in Odoo" notification setting can
|
||||
# successfully edit meetings created by other users. If this write fails,
|
||||
# it indicates that there might be an issue with access rights for meeting attendees.
|
||||
meeting = meeting.with_user(self.george)
|
||||
meeting.write({
|
||||
'start': datetime.now() + timedelta(days=2),
|
||||
'stop': datetime.now() + timedelta(days=2, hours=2),
|
||||
})
|
||||
|
||||
def test_hide_sensitive_fields_private_events_from_uninvited_admins(self):
|
||||
"""
|
||||
Ensure that it is not possible fetching sensitive fields for uninvited administrators,
|
||||
i.e. admins who are not attendees of private events. Sensitive fields are fields that
|
||||
could contain sensitive information, such as 'name', 'description', 'location', etc.
|
||||
"""
|
||||
sensitive_fields = [
|
||||
'location', 'attendee_ids', 'partner_ids', 'description',
|
||||
'videocall_location', 'categ_ids', 'message_ids',
|
||||
]
|
||||
|
||||
# Create event with all sensitive fields defined on it.
|
||||
event_type = self.env['calendar.event.type'].create({'name': 'type'})
|
||||
john_private_evt = self.create_event(
|
||||
self.john,
|
||||
name='private-event',
|
||||
privacy='private',
|
||||
location='private-location',
|
||||
description='private-description',
|
||||
attendee_status='accepted',
|
||||
partner_ids=[self.john.partner_id.id, self.raoul.partner_id.id],
|
||||
categ_ids=[event_type.id],
|
||||
videocall_location='private-url.com'
|
||||
)
|
||||
john_private_evt.message_post(body="Message to be hidden.")
|
||||
|
||||
# Read the event as an uninvited administrator and ensure that the sensitive fields were hidden.
|
||||
# Do the same for the search_read method: the information of sensitive fields must be hidden.
|
||||
private_event_domain = ('id', '=', john_private_evt.id)
|
||||
readed_event = john_private_evt.with_user(self.admin_user).read(sensitive_fields + ['name'])
|
||||
search_readed_event = self.env['calendar.event'].with_user(self.admin_user).search_read([private_event_domain])
|
||||
for event in [readed_event, search_readed_event]:
|
||||
self.assertEqual(len(event), 1, "The event itself must be fetched since the record is not hidden from uninvited admins.")
|
||||
self.assertEqual(event[0]['name'], "Busy", "Event name must be 'Busy', hiding the information from uninvited administrators.")
|
||||
for field in sensitive_fields:
|
||||
self.assertFalse(event[0][field], "Field %s contains private information, it must be hidden from uninvited administrators." % field)
|
||||
|
||||
# Ensure that methods like 'mapped', 'filtered', 'filtered_domain', '_search' and 'read_group' do not
|
||||
# bypass the override of read, which will hide the private information of the events from uninvited administrators.
|
||||
sensitive_stored_fields = ['name', 'location', 'description', 'videocall_location']
|
||||
searched_event = self.env['calendar.event'].with_user(self.admin_user).search([private_event_domain])
|
||||
|
||||
for field in sensitive_stored_fields:
|
||||
# For each method, fetch the information of the private event as an uninvited administrator.
|
||||
check_mapped_event = searched_event.with_user(self.admin_user).mapped(field)
|
||||
check_filtered_event = searched_event.with_user(self.admin_user).filtered(lambda ev: ev.id == john_private_evt.id)
|
||||
check_filtered_domain = searched_event.with_user(self.admin_user).filtered_domain([private_event_domain])
|
||||
check_search_query = self.env['calendar.event'].with_user(self.admin_user)._search([private_event_domain])
|
||||
check_search_object = self.env['calendar.event'].with_user(self.admin_user).browse(check_search_query)
|
||||
check_read_group = self.env['calendar.event'].with_user(self.admin_user).read_group([private_event_domain], [field], [field])
|
||||
|
||||
if field == 'name':
|
||||
# The 'name' field is manually changed to 'Busy' by default. We need to ensure it is shown as 'Busy' in all following methods.
|
||||
self.assertEqual(check_mapped_event, ['Busy'], 'Private event name should be shown as Busy using the mapped function.')
|
||||
self.assertEqual(check_filtered_event.name, 'Busy', 'Private event name should be shown as Busy using the filtered function.')
|
||||
self.assertEqual(check_filtered_domain.name, 'Busy', 'Private event name should be shown as Busy using the filtered_domain function.')
|
||||
self.assertEqual(check_search_object.name, 'Busy', 'Private event name should be shown as Busy using the _search function.')
|
||||
else:
|
||||
# The remaining private fields should be falsy for uninvited administrators.
|
||||
self.assertFalse(check_mapped_event[0], 'Private event field "%s" should be hidden when using the mapped function.' % field)
|
||||
self.assertFalse(check_filtered_event[field], 'Private event field "%s" should be hidden when using the filtered function.' % field)
|
||||
self.assertFalse(check_filtered_domain[field], 'Private event field "%s" should be hidden when using the filtered_domain function.' % field)
|
||||
self.assertFalse(check_search_object[field], 'Private event field "%s" should be hidden when using the _search function.' % field)
|
||||
|
||||
# Private events are excluded from read_group by default, ensure that we do not fetch it.
|
||||
self.assertFalse(len(check_read_group), 'Private event should be hidden using the function _read_group.')
|
||||
186
odoo-bringout-oca-ocb-calendar/calendar/tests/test_attendees.py
Normal file
186
odoo-bringout-oca-ocb-calendar/calendar/tests/test_attendees.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase, new_test_user, Form
|
||||
from odoo import fields, Command
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
class TestEventNotifications(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user = new_test_user(cls.env, 'xav', email='em@il.com', notification_type='inbox')
|
||||
cls.event = cls.env['calendar.event'].with_user(cls.user).create({
|
||||
'name': "Doom's day",
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
}).with_context(mail_notrack=True)
|
||||
cls.partner = cls.user.partner_id
|
||||
|
||||
def test_attendee_added(self):
|
||||
self.event.partner_ids = self.partner
|
||||
self.assertTrue(self.event.attendee_ids, "It should have created an attendee")
|
||||
self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner")
|
||||
self.assertIn(self.partner, self.event.message_follower_ids.partner_id, "He should be follower of the event")
|
||||
|
||||
def test_attendee_added_create(self):
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Doom's day",
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
})
|
||||
self.assertTrue(event.attendee_ids, "It should have created an attendee")
|
||||
self.assertEqual(event.attendee_ids.partner_id, self.partner, "It should be linked to the partner")
|
||||
self.assertIn(self.partner, event.message_follower_ids.partner_id, "He should be follower of the event")
|
||||
|
||||
def test_attendee_added_create_with_specific_states(self):
|
||||
"""
|
||||
When an event is created from an external calendar account (such as Google) which is not linked to an
|
||||
Odoo account, attendee info such as email and state are given at sync.
|
||||
In this case, attendee_ids should be created accordingly.
|
||||
"""
|
||||
organizer_partner = self.env['res.partner'].create({'name': "orga", "email": "orga@google.com"})
|
||||
event = self.env['calendar.event'].with_user(self.user).create({
|
||||
'name': "Doom's day",
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
'attendee_ids': [
|
||||
(0, 0, {'partner_id': self.partner.id, 'state': 'needsAction'}),
|
||||
(0, 0, {'partner_id': organizer_partner.id, 'state': 'accepted'})
|
||||
],
|
||||
'partner_ids': [(4, self.partner.id), (4, organizer_partner.id)],
|
||||
})
|
||||
attendees_info = [(a.email, a.state) for a in event.attendee_ids]
|
||||
self.assertEqual(len(event.attendee_ids), 2)
|
||||
self.assertIn((self.partner.email, "needsAction"), attendees_info)
|
||||
self.assertIn((organizer_partner.email, "accepted"), attendees_info)
|
||||
|
||||
def test_attendee_added_multi(self):
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Doom's day",
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
})
|
||||
events = self.event | event
|
||||
events.partner_ids = self.partner
|
||||
self.assertEqual(len(events.attendee_ids), 2, "It should have created one attendee per event")
|
||||
|
||||
def test_attendee_added_write(self):
|
||||
"""Test that writing ids directly on partner_ids instead of commands is handled."""
|
||||
self.event.write({'partner_ids': [self.partner.id]})
|
||||
self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner")
|
||||
|
||||
def test_existing_attendee_added(self):
|
||||
self.event.partner_ids = self.partner
|
||||
attendee = self.event.attendee_ids
|
||||
self.event.write({'partner_ids': [(4, self.partner.id)]}) # Add existing partner
|
||||
self.assertEqual(self.event.attendee_ids, attendee, "It should not have created an new attendee record")
|
||||
|
||||
def test_attendee_add_self(self):
|
||||
self.event.with_user(self.user).partner_ids = self.partner
|
||||
self.assertTrue(self.event.attendee_ids, "It should have created an attendee")
|
||||
self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner")
|
||||
self.assertEqual(self.event.attendee_ids.state, 'accepted', "It should be accepted for the current user")
|
||||
|
||||
def test_attendee_removed(self):
|
||||
partner_bis = self.env['res.partner'].create({'name': "Xavier"})
|
||||
self.event.partner_ids = partner_bis
|
||||
attendee = self.event.attendee_ids
|
||||
self.event.partner_ids |= self.partner
|
||||
self.event.partner_ids -= self.partner
|
||||
self.assertEqual(attendee, self.event.attendee_ids, "It should not have re-created an attendee record")
|
||||
self.assertNotIn(self.partner, self.event.attendee_ids.partner_id, "It should have removed the attendee")
|
||||
self.assertNotIn(self.partner, self.event.message_follower_ids.partner_id, "It should have unsubscribed the partner")
|
||||
self.assertIn(partner_bis, self.event.attendee_ids.partner_id, "It should have left the attendee")
|
||||
|
||||
def test_attendee_without_email(self):
|
||||
self.partner.email = False
|
||||
self.event.partner_ids = self.partner
|
||||
|
||||
self.assertTrue(self.event.attendee_ids)
|
||||
self.assertEqual(self.event.attendee_ids.partner_id, self.partner)
|
||||
self.assertTrue(self.event.invalid_email_partner_ids)
|
||||
self.assertEqual(self.event.invalid_email_partner_ids, self.partner)
|
||||
|
||||
def test_attendee_with_invalid_email(self):
|
||||
self.partner.email = "I'm an invalid email"
|
||||
self.event.partner_ids = self.partner
|
||||
|
||||
self.assertTrue(self.event.attendee_ids)
|
||||
self.assertEqual(self.event.attendee_ids.partner_id, self.partner)
|
||||
self.assertTrue(self.event.invalid_email_partner_ids)
|
||||
self.assertEqual(self.event.invalid_email_partner_ids, self.partner)
|
||||
|
||||
def test_default_attendee(self):
|
||||
"""
|
||||
Check if priority list id correctly followed
|
||||
1) vals_list[0]['attendee_ids']
|
||||
2) vals_list[0]['partner_ids']
|
||||
3) context.get('default_attendee_ids')
|
||||
"""
|
||||
partner_bis = self.env['res.partner'].create({'name': "Xavier"})
|
||||
event = self.env['calendar.event'].with_user(
|
||||
self.user
|
||||
).with_context(
|
||||
default_attendee_ids=[(0, 0, {'partner_id': partner_bis.id})]
|
||||
).create({
|
||||
'name': "Doom's day",
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
})
|
||||
self.assertIn(self.partner, event.attendee_ids.partner_id, "Partner should be in attendee")
|
||||
self.assertNotIn(partner_bis, event.attendee_ids.partner_id, "Partner bis should not be in attendee")
|
||||
|
||||
def test_push_meeting_start(self):
|
||||
"""
|
||||
Checks that you can push the start date of an all day meeting.
|
||||
"""
|
||||
attendee = self.env['res.partner'].create({
|
||||
'name': "Xavier",
|
||||
'email': "xavier@example.com",
|
||||
})
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Doom's day",
|
||||
'attendee_ids': [Command.create({'partner_id': attendee.id})],
|
||||
'allday': True,
|
||||
'start_date': fields.Date.today(),
|
||||
'stop_date': fields.Date.today(),
|
||||
})
|
||||
initial_start = event.start
|
||||
with Form(event) as event_form:
|
||||
event_form.stop_date = datetime.today() + relativedelta(days=1)
|
||||
event_form.start_date = datetime.today() + relativedelta(days=1)
|
||||
self.assertFalse(initial_start == event.start)
|
||||
|
||||
@freeze_time("2019-10-24 09:00:00", tick=True)
|
||||
def test_multi_attendee_mt_note_default(self):
|
||||
mt_note = self.env.ref("mail.mt_note")
|
||||
mt_note.default = True
|
||||
user_exta = new_test_user(self.env, "extra", email="extra@il.com")
|
||||
partner_extra = user_exta.partner_id
|
||||
event = self.env["calendar.event"].create({
|
||||
"name": "Team meeting",
|
||||
"attendee_ids": [
|
||||
(0, 0, {"partner_id": self.partner.id}),
|
||||
(0, 0, {"partner_id": partner_extra.id})
|
||||
],
|
||||
"start": datetime(2019, 10, 25, 8, 0),
|
||||
"stop": datetime(2019, 10, 25, 10, 0),
|
||||
})
|
||||
messages = self.env["mail.message"].search([
|
||||
("model", "=", event._name),
|
||||
("res_id", "=", event.id),
|
||||
("message_type", "=", "user_notification")
|
||||
])
|
||||
self.assertEqual(len(messages), 2)
|
||||
mesage_user = messages.filtered(lambda x: self.partner in x.partner_ids)
|
||||
self.assertNotIn(partner_extra, mesage_user.notified_partner_ids)
|
||||
mesage_user_extra = messages.filtered(lambda x: partner_extra in x.partner_ids)
|
||||
self.assertNotIn(self.partner, mesage_user_extra.notified_partner_ids)
|
||||
554
odoo-bringout-oca-ocb-calendar/calendar/tests/test_calendar.py
Normal file
554
odoo-bringout-oca-ocb-calendar/calendar/tests/test_calendar.py
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import datetime
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from odoo import fields, Command
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.tests import Form, tagged, new_test_user
|
||||
from odoo.addons.base.tests.common import SavepointCaseWithUserDemo
|
||||
import pytz
|
||||
import re
|
||||
import base64
|
||||
|
||||
|
||||
class TestCalendar(SavepointCaseWithUserDemo):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCalendar, self).setUp()
|
||||
|
||||
self.CalendarEvent = self.env['calendar.event']
|
||||
# In Order to test calendar, I will first create One Simple Event with real data
|
||||
self.event_tech_presentation = self.CalendarEvent.create({
|
||||
'privacy': 'private',
|
||||
'start': '2011-04-30 16:00:00',
|
||||
'stop': '2011-04-30 18:30:00',
|
||||
'description': 'The Technical Presentation will cover following topics:\n* Creating Odoo class\n* Views\n* Wizards\n* Workflows',
|
||||
'duration': 2.5,
|
||||
'location': 'Odoo S.A.',
|
||||
'name': 'Technical Presentation'
|
||||
})
|
||||
|
||||
def test_event_order(self):
|
||||
""" check the ordering of events when searching """
|
||||
def create_event(name, date):
|
||||
return self.CalendarEvent.create({
|
||||
'name': name,
|
||||
'start': date + ' 12:00:00',
|
||||
'stop': date + ' 14:00:00',
|
||||
})
|
||||
foo1 = create_event('foo', '2011-04-01')
|
||||
foo2 = create_event('foo', '2011-06-01')
|
||||
bar1 = create_event('bar', '2011-05-01')
|
||||
bar2 = create_event('bar', '2011-06-01')
|
||||
domain = [('id', 'in', (foo1 + foo2 + bar1 + bar2).ids)]
|
||||
|
||||
# sort them by name only
|
||||
events = self.CalendarEvent.search(domain, order='name')
|
||||
self.assertEqual(events.mapped('name'), ['bar', 'bar', 'foo', 'foo'])
|
||||
events = self.CalendarEvent.search(domain, order='name desc')
|
||||
self.assertEqual(events.mapped('name'), ['foo', 'foo', 'bar', 'bar'])
|
||||
|
||||
# sort them by start date only
|
||||
events = self.CalendarEvent.search(domain, order='start')
|
||||
self.assertEqual(events.mapped('start'), (foo1 + bar1 + foo2 + bar2).mapped('start'))
|
||||
events = self.CalendarEvent.search(domain, order='start desc')
|
||||
self.assertEqual(events.mapped('start'), (foo2 + bar2 + bar1 + foo1).mapped('start'))
|
||||
|
||||
# sort them by name then start date
|
||||
events = self.CalendarEvent.search(domain, order='name asc, start asc')
|
||||
self.assertEqual(list(events), [bar1, bar2, foo1, foo2])
|
||||
events = self.CalendarEvent.search(domain, order='name asc, start desc')
|
||||
self.assertEqual(list(events), [bar2, bar1, foo2, foo1])
|
||||
events = self.CalendarEvent.search(domain, order='name desc, start asc')
|
||||
self.assertEqual(list(events), [foo1, foo2, bar1, bar2])
|
||||
events = self.CalendarEvent.search(domain, order='name desc, start desc')
|
||||
self.assertEqual(list(events), [foo2, foo1, bar2, bar1])
|
||||
|
||||
# sort them by start date then name
|
||||
events = self.CalendarEvent.search(domain, order='start asc, name asc')
|
||||
self.assertEqual(list(events), [foo1, bar1, bar2, foo2])
|
||||
events = self.CalendarEvent.search(domain, order='start asc, name desc')
|
||||
self.assertEqual(list(events), [foo1, bar1, foo2, bar2])
|
||||
events = self.CalendarEvent.search(domain, order='start desc, name asc')
|
||||
self.assertEqual(list(events), [bar2, foo2, bar1, foo1])
|
||||
events = self.CalendarEvent.search(domain, order='start desc, name desc')
|
||||
self.assertEqual(list(events), [foo2, bar2, bar1, foo1])
|
||||
|
||||
def test_event_activity(self):
|
||||
# ensure meeting activity type exists
|
||||
meeting_act_type = self.env['mail.activity.type'].search([('category', '=', 'meeting')], limit=1)
|
||||
if not meeting_act_type:
|
||||
meeting_act_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting Test',
|
||||
'category': 'meeting',
|
||||
})
|
||||
|
||||
# have a test model inheriting from activities
|
||||
test_record = self.env['res.partner'].create({
|
||||
'name': 'Test',
|
||||
})
|
||||
now = datetime.now()
|
||||
test_user = self.user_demo
|
||||
test_name, test_description, test_description2 = 'Test-Meeting', 'Test-Description', 'NotTest'
|
||||
test_note, test_note2 = '<p>Test-Description</p>', '<p>NotTest</p>'
|
||||
|
||||
# create using default_* keys
|
||||
test_event = self.env['calendar.event'].with_user(test_user).with_context(
|
||||
default_res_model=test_record._name,
|
||||
default_res_id=test_record.id,
|
||||
).create({
|
||||
'name': test_name,
|
||||
'description': test_description,
|
||||
'start': fields.Datetime.to_string(now + timedelta(days=-1)),
|
||||
'stop': fields.Datetime.to_string(now + timedelta(hours=2)),
|
||||
'user_id': self.env.user.id,
|
||||
})
|
||||
self.assertEqual(test_event.res_model, test_record._name)
|
||||
self.assertEqual(test_event.res_id, test_record.id)
|
||||
self.assertEqual(len(test_record.activity_ids), 1)
|
||||
self.assertEqual(test_record.activity_ids.summary, test_name)
|
||||
self.assertEqual(test_record.activity_ids.note, test_note)
|
||||
self.assertEqual(test_record.activity_ids.user_id, self.env.user)
|
||||
self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-1)).date())
|
||||
|
||||
# updating event should update activity
|
||||
test_event.write({
|
||||
'name': '%s2' % test_name,
|
||||
'description': test_description2,
|
||||
'start': fields.Datetime.to_string(now + timedelta(days=-2)),
|
||||
'user_id': test_user.id,
|
||||
})
|
||||
self.assertEqual(test_record.activity_ids.summary, '%s2' % test_name)
|
||||
self.assertEqual(test_record.activity_ids.note, test_note2)
|
||||
self.assertEqual(test_record.activity_ids.user_id, test_user)
|
||||
self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-2)).date())
|
||||
|
||||
# update event with a description that have a special character and a new line
|
||||
test_description3 = 'Test & <br> Description'
|
||||
test_note3 = '<p>Test & <br> Description</p>'
|
||||
test_event.write({
|
||||
'description': test_description3,
|
||||
})
|
||||
|
||||
self.assertEqual(test_record.activity_ids.note, test_note3)
|
||||
|
||||
# deleting meeting should delete its activity
|
||||
test_record.activity_ids.unlink()
|
||||
self.assertEqual(self.env['calendar.event'], self.env['calendar.event'].search([('name', '=', test_name)]))
|
||||
|
||||
# create using active_model keys
|
||||
test_event = self.env['calendar.event'].with_user(self.user_demo).with_context(
|
||||
active_model=test_record._name,
|
||||
active_id=test_record.id,
|
||||
).create({
|
||||
'name': test_name,
|
||||
'description': test_description,
|
||||
'start': now + timedelta(days=-1),
|
||||
'stop': now + timedelta(hours=2),
|
||||
'user_id': self.env.user.id,
|
||||
})
|
||||
self.assertEqual(test_event.res_model, test_record._name)
|
||||
self.assertEqual(test_event.res_id, test_record.id)
|
||||
self.assertEqual(len(test_record.activity_ids), 1)
|
||||
|
||||
def test_activity_event_multiple_meetings(self):
|
||||
# Creating multiple meetings from an activity creates additional activities
|
||||
# ensure meeting activity type exists
|
||||
meeting_act_type = self.env['mail.activity.type'].search([('category', '=', 'meeting')], limit=1)
|
||||
if not meeting_act_type:
|
||||
meeting_act_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting Test',
|
||||
'category': 'meeting',
|
||||
})
|
||||
|
||||
# have a test model inheriting from activities
|
||||
test_record = self.env['res.partner'].create({
|
||||
'name': 'Test',
|
||||
})
|
||||
|
||||
activity_id = self.env['mail.activity'].create({
|
||||
'summary': 'Meeting with partner',
|
||||
'activity_type_id': meeting_act_type.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('res.partner'),
|
||||
'res_id': test_record.id,
|
||||
})
|
||||
|
||||
calendar_action = activity_id.with_context(default_res_model='res.partner', default_res_id=test_record.id).action_create_calendar_event()
|
||||
event_1 = self.env['calendar.event'].with_context(calendar_action['context']).create({
|
||||
'name': 'Meeting 1',
|
||||
'start': datetime(2025, 3, 10, 17),
|
||||
'stop': datetime(2025, 3, 10, 22),
|
||||
})
|
||||
|
||||
self.assertEqual(event_1.activity_ids, activity_id)
|
||||
|
||||
total_activities = self.env['mail.activity'].search_count(domain=[])
|
||||
|
||||
event_2 = self.env['calendar.event'].with_context(calendar_action['context']).create({
|
||||
'name': 'Meeting 2',
|
||||
'start': datetime(2025, 3, 11, 17),
|
||||
'stop': datetime(2025, 3, 11, 22),
|
||||
})
|
||||
self.assertEqual(event_1.activity_ids, activity_id, "Event 1's activity should still be the first activity")
|
||||
self.assertEqual(activity_id.calendar_event_id, event_1, "The first activity's event should still be event 1")
|
||||
self.assertEqual(total_activities + 1, self.env['mail.activity'].search_count(domain=[]), "1 more activity record should have been created (by event 2)")
|
||||
self.assertNotEqual(event_2.activity_ids, activity_id, "Event 2's activity should not be the first activity")
|
||||
self.assertEqual(event_2.activity_ids.activity_type_id, activity_id.activity_type_id, "Event 2's activity should be the same activity type as the first activity")
|
||||
self.assertEqual(test_record.activity_ids, activity_id | event_2.activity_ids, "Resource record should now have both activities")
|
||||
|
||||
def test_event_allday(self):
|
||||
self.env.user.tz = 'Pacific/Honolulu'
|
||||
|
||||
event = self.CalendarEvent.create({
|
||||
'name': 'All Day',
|
||||
'start': "2018-10-16 00:00:00",
|
||||
'start_date': "2018-10-16",
|
||||
'stop': "2018-10-18 00:00:00",
|
||||
'stop_date': "2018-10-18",
|
||||
'allday': True,
|
||||
})
|
||||
self.env.invalidate_all()
|
||||
self.assertEqual(str(event.start), '2018-10-16 08:00:00')
|
||||
self.assertEqual(str(event.stop), '2018-10-18 18:00:00')
|
||||
|
||||
def test_recurring_around_dst(self):
|
||||
m = self.CalendarEvent.create({
|
||||
'name': "wheee",
|
||||
'start': '2018-10-27 14:30:00',
|
||||
'allday': False,
|
||||
'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=4',
|
||||
'recurrency': True,
|
||||
'stop': '2018-10-27 16:30:00',
|
||||
'event_tz': 'Europe/Brussels',
|
||||
})
|
||||
|
||||
start_recurring_dates = m.recurrence_id.calendar_event_ids.sorted('start').mapped('start')
|
||||
self.assertEqual(len(start_recurring_dates), 4)
|
||||
|
||||
for d in start_recurring_dates:
|
||||
if d.day < 28: # DST switch happens between 2018-10-27 and 2018-10-28
|
||||
self.assertEqual(d.hour, 14)
|
||||
else:
|
||||
self.assertEqual(d.hour, 15)
|
||||
self.assertEqual(d.minute, 30)
|
||||
|
||||
def test_recurring_ny(self):
|
||||
self.env.user.tz = 'America/New_York'
|
||||
f = Form(self.CalendarEvent.with_context(tz='America/New_York'))
|
||||
f.name = 'test'
|
||||
f.start = '2022-07-07 01:00:00' # This is in UTC. In NY, it corresponds to the 6th of july at 9pm.
|
||||
f.recurrency = True
|
||||
self.assertEqual(f.weekday, 'WED')
|
||||
self.assertEqual(f.event_tz, 'America/New_York', "The value should correspond to the user tz")
|
||||
self.assertEqual(f.count, 1, "The default value should be displayed")
|
||||
self.assertEqual(f.interval, 1, "The default value should be displayed")
|
||||
self.assertEqual(f.month_by, "date", "The default value should be displayed")
|
||||
self.assertEqual(f.end_type, "count", "The default value should be displayed")
|
||||
self.assertEqual(f.rrule_type, "weekly", "The default value should be displayed")
|
||||
|
||||
def test_event_activity_timezone(self):
|
||||
activty_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting',
|
||||
'category': 'meeting'
|
||||
})
|
||||
|
||||
activity_id = self.env['mail.activity'].create({
|
||||
'summary': 'Meeting with partner',
|
||||
'activity_type_id': activty_type.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('res.partner'),
|
||||
'res_id': self.env['res.partner'].create({'name': 'A Partner'}).id,
|
||||
})
|
||||
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'Meeting with partner',
|
||||
'activity_ids': [(6, False, activity_id.ids)],
|
||||
'start': '2018-11-12 21:00:00',
|
||||
'stop': '2018-11-13 00:00:00',
|
||||
})
|
||||
|
||||
# Check output in UTC
|
||||
self.assertEqual(str(activity_id.date_deadline), '2018-11-12')
|
||||
|
||||
# Check output in the user's tz
|
||||
# write on the event to trigger sync of activities
|
||||
calendar_event.with_context({'tz': 'Australia/Brisbane'}).write({
|
||||
'start': '2018-11-12 21:00:00',
|
||||
})
|
||||
|
||||
self.assertEqual(str(activity_id.date_deadline), '2018-11-13')
|
||||
|
||||
def test_event_allday_activity_timezone(self):
|
||||
# Covers use case of commit eef4c3b48bcb4feac028bf640b545006dd0c9b91
|
||||
# Also, read the comment in the code at calendar.event._inverse_dates
|
||||
activty_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Meeting',
|
||||
'category': 'meeting'
|
||||
})
|
||||
|
||||
activity_id = self.env['mail.activity'].create({
|
||||
'summary': 'Meeting with partner',
|
||||
'activity_type_id': activty_type.id,
|
||||
'res_model_id': self.env['ir.model']._get_id('res.partner'),
|
||||
'res_id': self.env['res.partner'].create({'name': 'A Partner'}).id,
|
||||
})
|
||||
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'All Day',
|
||||
'start': "2018-10-16 00:00:00",
|
||||
'start_date': "2018-10-16",
|
||||
'stop': "2018-10-18 00:00:00",
|
||||
'stop_date': "2018-10-18",
|
||||
'allday': True,
|
||||
'activity_ids': [(6, False, activity_id.ids)],
|
||||
})
|
||||
|
||||
# Check output in UTC
|
||||
self.assertEqual(str(activity_id.date_deadline), '2018-10-16')
|
||||
|
||||
# Check output in the user's tz
|
||||
# write on the event to trigger sync of activities
|
||||
calendar_event.with_context({'tz': 'Pacific/Honolulu'}).write({
|
||||
'start': '2018-10-16 00:00:00',
|
||||
'start_date': '2018-10-16',
|
||||
})
|
||||
|
||||
self.assertEqual(str(activity_id.date_deadline), '2018-10-16')
|
||||
|
||||
def test_event_creation_mail(self):
|
||||
"""
|
||||
Check that mail are sent to the attendees on event creation
|
||||
Check that mail are sent to the added attendees on event edit
|
||||
Check that mail are NOT sent to the attendees when the event date is past
|
||||
Check that mail have extra attachement added by the user
|
||||
"""
|
||||
|
||||
def _test_one_mail_per_attendee(self, partners):
|
||||
# check that every attendee receive a (single) mail for the event
|
||||
for partner in partners:
|
||||
mail = self.env['mail.message'].sudo().search([
|
||||
('notified_partner_ids', 'in', partner.id),
|
||||
])
|
||||
self.assertEqual(len(mail), 1)
|
||||
|
||||
def _test_emails_has_attachment(self, partners):
|
||||
# check that every email has an attachment
|
||||
for partner in partners:
|
||||
mail = self.env['mail.message'].sudo().search([
|
||||
('notified_partner_ids', 'in', partner.id),
|
||||
])
|
||||
extra_attachment = mail.attachment_ids.filtered(lambda attachment: attachment.name == "fileText_attachment.txt")
|
||||
self.assertEqual(len(extra_attachment), 1)
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'datas': base64.b64encode(bytes("Event Attachment", 'utf-8')),
|
||||
'name': 'fileText_attachment.txt',
|
||||
'mimetype': 'text/plain'
|
||||
})
|
||||
self.env.ref('calendar.calendar_template_meeting_invitation').attachment_ids = attachment
|
||||
|
||||
partners = [
|
||||
self.env['res.partner'].create({'name': 'testuser0', 'email': u'bob@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser1', 'email': u'alice@example.com'}),
|
||||
]
|
||||
partner_ids = [(6, False, [p.id for p in partners]),]
|
||||
now = fields.Datetime.context_timestamp(partners[0], fields.Datetime.now())
|
||||
m = self.CalendarEvent.create({
|
||||
'name': "mailTest1",
|
||||
'allday': False,
|
||||
'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=5',
|
||||
'recurrency': True,
|
||||
'partner_ids': partner_ids,
|
||||
'start': fields.Datetime.to_string(now + timedelta(days=10)),
|
||||
'stop': fields.Datetime.to_string(now + timedelta(days=15)),
|
||||
})
|
||||
|
||||
# every partner should have 1 mail sent
|
||||
_test_one_mail_per_attendee(self, partners)
|
||||
_test_emails_has_attachment(self, partners)
|
||||
|
||||
# adding more partners to the event
|
||||
partners.extend([
|
||||
self.env['res.partner'].create({'name': 'testuser2', 'email': u'marc@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser3', 'email': u'carl@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser4', 'email': u'alain@example.com'}),
|
||||
])
|
||||
partner_ids = [(6, False, [p.id for p in partners]),]
|
||||
m.write({
|
||||
'partner_ids': partner_ids,
|
||||
'recurrence_update': 'all_events',
|
||||
})
|
||||
|
||||
# more email should be sent
|
||||
_test_one_mail_per_attendee(self, partners)
|
||||
|
||||
# create a new event in the past
|
||||
self.CalendarEvent.create({
|
||||
'name': "NOmailTest",
|
||||
'allday': False,
|
||||
'recurrency': False,
|
||||
'partner_ids': partner_ids,
|
||||
'start': fields.Datetime.to_string(now - timedelta(days=10)),
|
||||
'stop': fields.Datetime.to_string(now - timedelta(days=9)),
|
||||
})
|
||||
|
||||
# no more email should be sent
|
||||
_test_one_mail_per_attendee(self, partners)
|
||||
|
||||
def test_event_creation_internal_user_invitation_ics(self):
|
||||
""" Check that internal user can read invitation.ics attachment """
|
||||
internal_user = new_test_user(self.env, login='internal_user', groups='base.group_user')
|
||||
|
||||
partner = internal_user.partner_id
|
||||
self.event_tech_presentation.write({
|
||||
'partner_ids': [(4, partner.id)],
|
||||
})
|
||||
msg = self.env['mail.message'].search([
|
||||
('notified_partner_ids', 'in', partner.id),
|
||||
])
|
||||
msg.invalidate_recordset()
|
||||
|
||||
|
||||
# internal user can read the attachment without errors
|
||||
self.assertEqual(msg.with_user(internal_user).attachment_ids.name, 'invitation.ics')
|
||||
|
||||
def test_event_creation_sudo_other_company(self):
|
||||
""" Check Access right issue when create event with sudo
|
||||
|
||||
Create a company, a user in that company
|
||||
Create an event for someone else in another company as sudo
|
||||
Should not failed for acces right check
|
||||
"""
|
||||
now = fields.Datetime.context_timestamp(self.partner_demo, fields.Datetime.now())
|
||||
|
||||
web_company = self.env['res.company'].sudo().create({'name': "Website Company"})
|
||||
web_user = self.env['res.users'].with_company(web_company).sudo().create({
|
||||
'name': 'web user',
|
||||
'login': 'web',
|
||||
'company_id': web_company.id
|
||||
})
|
||||
self.CalendarEvent.with_user(web_user).with_company(web_company).sudo().create({
|
||||
'name': "Test",
|
||||
'allday': False,
|
||||
'recurrency': False,
|
||||
'partner_ids': [(6, 0, self.partner_demo.ids)],
|
||||
'alarm_ids': [(0, 0, {
|
||||
'name': 'Alarm',
|
||||
'alarm_type': 'notification',
|
||||
'interval': 'minutes',
|
||||
'duration': 30,
|
||||
})],
|
||||
'user_id': self.user_demo.id,
|
||||
'start': fields.Datetime.to_string(now + timedelta(hours=5)),
|
||||
'stop': fields.Datetime.to_string(now + timedelta(hours=6)),
|
||||
})
|
||||
|
||||
def test_meeting_creation_from_partner_form(self):
|
||||
""" When going from a partner to the Calendar and adding a meeting, both current user and partner
|
||||
should be attendees of the event """
|
||||
calendar_action = self.partner_demo.schedule_meeting()
|
||||
event = self.env['calendar.event'].with_context(calendar_action['context']).create({
|
||||
'name': 'Super Meeting',
|
||||
'start': datetime(2020, 12, 13, 17),
|
||||
'stop': datetime(2020, 12, 13, 22),
|
||||
})
|
||||
self.assertEqual(len(event.attendee_ids), 2)
|
||||
self.assertTrue(self.partner_demo in event.attendee_ids.mapped('partner_id'))
|
||||
self.assertTrue(self.env.user.partner_id in event.attendee_ids.mapped('partner_id'))
|
||||
|
||||
def test_discuss_videocall(self):
|
||||
self.event_tech_presentation._set_discuss_videocall_location()
|
||||
self.assertFalse(self.event_tech_presentation.videocall_channel_id.id, 'No channel should be set before the route is accessed')
|
||||
# create the first channel
|
||||
self.event_tech_presentation._create_videocall_channel()
|
||||
self.assertNotEqual(self.event_tech_presentation.videocall_channel_id.id, False)
|
||||
|
||||
partner1 = self.env['res.partner'].create({'name': 'Bob', 'email': u'bob@gm.co'})
|
||||
partner2 = self.env['res.partner'].create({'name': 'Jack', 'email': u'jack@gm.co'})
|
||||
new_partners = [partner1.id, partner2.id]
|
||||
# invite partners to meeting
|
||||
self.event_tech_presentation.write({
|
||||
'partner_ids': [Command.link(new_partner) for new_partner in new_partners]
|
||||
})
|
||||
self.assertTrue(set(new_partners) == set(self.event_tech_presentation.videocall_channel_id.channel_partner_ids.ids), 'new partners must be invited to the channel')
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestCalendarTours(HttpCaseWithUserDemo):
|
||||
def test_calendar_month_view_start_hour_displayed(self):
|
||||
""" Test that the time is displayed in the month view. """
|
||||
self.start_tour("/web", 'calendar_appointments_hour_tour', login="demo")
|
||||
|
||||
def test_calendar_delete_tour(self):
|
||||
"""
|
||||
Check that we can delete events with the "Everybody's calendars" filter.
|
||||
"""
|
||||
user_admin = self.env.ref('base.user_admin')
|
||||
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
|
||||
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
|
||||
event = self.env['calendar.event'].with_user(user_admin).create({
|
||||
'name': 'Test Event',
|
||||
'description': 'Test Description',
|
||||
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'duration': 3,
|
||||
'location': 'Odoo S.A.',
|
||||
'privacy': 'public',
|
||||
'show_as': 'busy',
|
||||
})
|
||||
action_id = self.env.ref('calendar.action_calendar_event')
|
||||
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
|
||||
self.start_tour(url, 'test_calendar_delete_tour', login='admin')
|
||||
event = self.env['calendar.event'].search([('name', '=', 'Test Event')])
|
||||
self.assertFalse(event) # Check if the event has been correctly deleted
|
||||
|
||||
def test_calendar_decline_tour(self):
|
||||
"""
|
||||
Check that we can decline events.
|
||||
"""
|
||||
user_admin = self.env.ref('base.user_admin')
|
||||
user_demo = self.user_demo
|
||||
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
|
||||
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
|
||||
event = self.env['calendar.event'].with_user(user_admin).create({
|
||||
'name': 'Test Event',
|
||||
'description': 'Test Description',
|
||||
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'duration': 3,
|
||||
'location': 'Odoo S.A.',
|
||||
'privacy': 'public',
|
||||
'show_as': 'busy',
|
||||
})
|
||||
event.partner_ids = [Command.link(user_demo.partner_id.id)]
|
||||
action_id = self.env.ref('calendar.action_calendar_event')
|
||||
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
|
||||
self.start_tour(url, 'test_calendar_decline_tour', login='demo')
|
||||
attendee = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', user_demo.partner_id.id)])
|
||||
self.assertEqual(attendee.state, 'declined') # Check if the event has been correctly declined
|
||||
|
||||
def test_calendar_decline_with_everybody_filter_tour(self):
|
||||
"""
|
||||
Check that we can decline events with the "Everybody's calendars" filter.
|
||||
"""
|
||||
user_admin = self.env.ref('base.user_admin')
|
||||
user_demo = self.user_demo
|
||||
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
|
||||
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
|
||||
event = self.env['calendar.event'].with_user(user_admin).create({
|
||||
'name': 'Test Event',
|
||||
'description': 'Test Description',
|
||||
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'duration': 3,
|
||||
'location': 'Odoo S.A.',
|
||||
'privacy': 'public',
|
||||
'show_as': 'busy',
|
||||
})
|
||||
event.partner_ids = [Command.link(user_demo.partner_id.id)]
|
||||
action_id = self.env.ref('calendar.action_calendar_event')
|
||||
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
|
||||
self.start_tour(url, 'test_calendar_decline_with_everybody_filter_tour', login='demo')
|
||||
attendee = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', user_demo.partner_id.id)])
|
||||
self.assertEqual(attendee.state, 'declined') # Check if the event has been correctly declined
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.tests.common import HttpCase, new_test_user, tagged
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestCalendarController(HttpCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = new_test_user(self.env, "test_user_1", email="test_user_1@nowhere.com", tz="UTC")
|
||||
self.other_user = new_test_user(self.env, "test_user_2", email="test_user_2@nowhere.com", password="P@ssw0rd!", tz="UTC")
|
||||
self.partner = self.user.partner_id
|
||||
self.event = (
|
||||
self.env["calendar.event"]
|
||||
.create(
|
||||
{
|
||||
"name": "Doom's day",
|
||||
"start": datetime(2019, 10, 25, 8, 0),
|
||||
"stop": datetime(2019, 10, 27, 18, 0),
|
||||
"partner_ids": [(4, self.partner.id)],
|
||||
}
|
||||
)
|
||||
.with_context(mail_notrack=True)
|
||||
)
|
||||
|
||||
def test_accept_meeting_unauthenticated(self):
|
||||
self.event.write({"partner_ids": [(4, self.other_user.partner_id.id)]})
|
||||
attendee = self.event.attendee_ids.filtered(lambda att: att.partner_id.id == self.other_user.partner_id.id)
|
||||
token = attendee.access_token
|
||||
url = "/calendar/meeting/accept?token=%s&id=%d" % (token, self.event.id)
|
||||
res = self.url_open(url)
|
||||
|
||||
self.assertEqual(res.status_code, 200, "Response should = OK")
|
||||
self.env.invalidate_all()
|
||||
self.assertEqual(attendee.state, "accepted", "Attendee should have accepted")
|
||||
|
||||
def test_accept_meeting_authenticated(self):
|
||||
self.event.write({"partner_ids": [(4, self.other_user.partner_id.id)]})
|
||||
attendee = self.event.attendee_ids.filtered(lambda att: att.partner_id.id == self.other_user.partner_id.id)
|
||||
token = attendee.access_token
|
||||
url = "/calendar/meeting/accept?token=%s&id=%d" % (token, self.event.id)
|
||||
self.authenticate("test_user_2", "P@ssw0rd!")
|
||||
res = self.url_open(url)
|
||||
|
||||
self.assertEqual(res.status_code, 200, "Response should = OK")
|
||||
self.env.invalidate_all()
|
||||
self.assertEqual(attendee.state, "accepted", "Attendee should have accepted")
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestRecurrentEvent(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRecurrentEvent, self).setUp()
|
||||
|
||||
self.CalendarEvent = self.env['calendar.event']
|
||||
|
||||
def test_recurrent_meeting1(self):
|
||||
# In order to test recurrent meetings in Odoo, I create meetings with different recurrence using different test cases.
|
||||
# I create a recurrent meeting with daily recurrence and fixed amount of time.
|
||||
self.CalendarEvent.create({
|
||||
'count': 5,
|
||||
'start': '2011-04-13 11:04:00',
|
||||
'stop': '2011-04-13 12:04:00',
|
||||
'duration': 1.0,
|
||||
'name': 'Test Meeting',
|
||||
'recurrency': True,
|
||||
'rrule_type': 'daily'
|
||||
})
|
||||
# I search for all the recurrent meetings
|
||||
meetings_count = self.CalendarEvent.with_context({'virtual_id': True}).search_count([
|
||||
('start', '>=', '2011-03-13'), ('stop', '<=', '2011-05-13')
|
||||
])
|
||||
self.assertEqual(meetings_count, 5, 'Recurrent daily meetings are not created !')
|
||||
|
||||
def test_recurrent_meeting2(self):
|
||||
# I create a weekly meeting till a particular end date.
|
||||
self.CalendarEvent.create({
|
||||
'start': '2011-04-18 11:47:00',
|
||||
'stop': '2011-04-18 12:47:00',
|
||||
'day': 1,
|
||||
'duration': 1.0,
|
||||
'until': '2011-04-30',
|
||||
'end_type': 'end_date',
|
||||
'fri': True,
|
||||
'mon': True,
|
||||
'thu': True,
|
||||
'tue': True,
|
||||
'wed': True,
|
||||
'name': 'Review code with programmer',
|
||||
'recurrency': True,
|
||||
'rrule_type': 'weekly'
|
||||
})
|
||||
|
||||
# I search for all the recurrent weekly meetings.
|
||||
meetings_count = self.CalendarEvent.search_count([
|
||||
('start', '>=', '2011-03-13'), ('stop', '<=', '2011-05-13')
|
||||
])
|
||||
self.assertEqual(meetings_count, 10, 'Recurrent weekly meetings are not created !')
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests.common import TransactionCase, new_test_user
|
||||
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
||||
from odoo.addons.mail.tests.common import MailCase
|
||||
|
||||
|
||||
class TestEventNotifications(TransactionCase, MailCase, CronMixinCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.event = cls.env['calendar.event'].create({
|
||||
'name': "Doom's day",
|
||||
'start': datetime(2019, 10, 25, 8, 0),
|
||||
'stop': datetime(2019, 10, 27, 18, 0),
|
||||
}).with_context(mail_notrack=True)
|
||||
cls.user = new_test_user(cls.env, 'xav', email='em@il.com', notification_type='inbox')
|
||||
cls.partner = cls.user.partner_id
|
||||
|
||||
def test_message_invite(self):
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.event.partner_ids = self.partner
|
||||
|
||||
def test_message_invite_allday(self):
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.env['calendar.event'].with_context(mail_create_nolog=True).create([{
|
||||
'name': 'Meeting',
|
||||
'allday': True,
|
||||
'start_date': fields.Date.today() + relativedelta(days=7),
|
||||
'stop_date': fields.Date.today() + relativedelta(days=8),
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
}])
|
||||
|
||||
|
||||
def test_message_invite_self(self):
|
||||
with self.assertNoNotifications():
|
||||
self.event.with_user(self.user).partner_ids = self.partner
|
||||
|
||||
def test_message_inactive_invite(self):
|
||||
self.event.active = False
|
||||
with self.assertNoNotifications():
|
||||
self.event.partner_ids = self.partner
|
||||
|
||||
def test_message_set_inactive_invite(self):
|
||||
self.event.active = False
|
||||
with self.assertNoNotifications():
|
||||
self.event.write({
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
'active': False,
|
||||
})
|
||||
|
||||
def test_message_datetime_changed(self):
|
||||
self.event.partner_ids = self.partner
|
||||
"Invitation to Presentation of the new Calendar"
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.event.start = fields.Datetime.now() + relativedelta(days=1)
|
||||
|
||||
def test_message_date_changed(self):
|
||||
self.event.write({
|
||||
'allday': True,
|
||||
'start_date': fields.Date.today() + relativedelta(days=7),
|
||||
'stop_date': fields.Date.today() + relativedelta(days=8),
|
||||
})
|
||||
self.event.partner_ids = self.partner
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.event.start_date += relativedelta(days=-1)
|
||||
|
||||
def test_message_date_changed_past(self):
|
||||
self.event.write({
|
||||
'allday': True,
|
||||
'start_date': fields.Date.today(),
|
||||
'stop_date': fields.Date.today() + relativedelta(days=1),
|
||||
})
|
||||
self.event.partner_ids = self.partner
|
||||
with self.assertNoNotifications():
|
||||
self.event.write({'start': date(2019, 1, 1)})
|
||||
|
||||
def test_message_set_inactive_date_changed(self):
|
||||
self.event.write({
|
||||
'allday': True,
|
||||
'start_date': date(2019, 10, 15),
|
||||
'stop_date': date(2019, 10, 15),
|
||||
})
|
||||
self.event.partner_ids = self.partner
|
||||
with self.assertNoNotifications():
|
||||
self.event.write({
|
||||
'start_date': self.event.start_date - relativedelta(days=1),
|
||||
'active': False,
|
||||
})
|
||||
|
||||
def test_message_inactive_date_changed(self):
|
||||
self.event.write({
|
||||
'allday': True,
|
||||
'start_date': date(2019, 10, 15),
|
||||
'stop_date': date(2019, 10, 15),
|
||||
'active': False,
|
||||
})
|
||||
self.event.partner_ids = self.partner
|
||||
with self.assertNoNotifications():
|
||||
self.event.start_date += relativedelta(days=-1)
|
||||
|
||||
def test_message_add_and_date_changed(self):
|
||||
self.event.partner_ids -= self.partner
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.event.write({
|
||||
'start': self.event.start - relativedelta(days=1),
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
})
|
||||
|
||||
def test_bus_notif(self):
|
||||
alarm = self.env['calendar.alarm'].create({
|
||||
'name': 'Alarm',
|
||||
'alarm_type': 'notification',
|
||||
'interval': 'minutes',
|
||||
'duration': 30,
|
||||
})
|
||||
now = fields.Datetime.now()
|
||||
with patch.object(fields.Datetime, 'now', lambda: now):
|
||||
with self.assertBus([(self.env.cr.dbname, 'res.partner', self.partner.id)], [
|
||||
{
|
||||
"type": "calendar.alarm",
|
||||
"payload": [{
|
||||
"alarm_id": alarm.id,
|
||||
"event_id": self.event.id,
|
||||
"title": "Doom's day",
|
||||
"message": self.event.display_time,
|
||||
"timer": 20 * 60,
|
||||
"notify_at": fields.Datetime.to_string(now + relativedelta(minutes=20)),
|
||||
}],
|
||||
},
|
||||
]):
|
||||
self.event.with_context(no_mail_to_attendees=True).write({
|
||||
'start': now + relativedelta(minutes=50),
|
||||
'stop': now + relativedelta(minutes=55),
|
||||
'partner_ids': [(4, self.partner.id)],
|
||||
'alarm_ids': [(4, alarm.id)]
|
||||
})
|
||||
|
||||
def test_email_alarm(self):
|
||||
now = fields.Datetime.now()
|
||||
with self.capture_triggers('calendar.ir_cron_scheduler_alarm') as capt:
|
||||
alarm = self.env['calendar.alarm'].create({
|
||||
'name': 'Alarm',
|
||||
'alarm_type': 'email',
|
||||
'interval': 'minutes',
|
||||
'duration': 20,
|
||||
})
|
||||
self.event.write({
|
||||
'name': 'test event',
|
||||
'start': now + relativedelta(minutes=15),
|
||||
'stop': now + relativedelta(minutes=18),
|
||||
'partner_ids': [fields.Command.link(self.partner.id)],
|
||||
'alarm_ids': [fields.Command.link(alarm.id)],
|
||||
})
|
||||
self.env.flush_all() # flush is required to make partner_ids be present in the event
|
||||
|
||||
capt.records.ensure_one()
|
||||
self.assertLessEqual(capt.records.call_at, now)
|
||||
|
||||
with patch.object(fields.Datetime, 'now', lambda: now):
|
||||
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
|
||||
'message_type': 'user_notification',
|
||||
'subtype': 'mail.mt_note',
|
||||
}):
|
||||
self.event.user_id = self.user
|
||||
old_messages = self.event.message_ids
|
||||
self.env['calendar.alarm_manager'].with_context(lastcall=now - relativedelta(minutes=15))._send_reminder()
|
||||
messages = self.env["mail.message"].search([
|
||||
("model", "=", self.event._name),
|
||||
("res_id", "=", self.event.id),
|
||||
("message_type", "=", "user_notification")
|
||||
])
|
||||
new_messages = messages - old_messages
|
||||
user_message = new_messages.filtered(lambda x: self.event.user_id.partner_id in x.partner_ids)
|
||||
self.assertTrue(user_message.notification_ids, "Organizer must receive a reminder")
|
||||
|
||||
def test_notification_event_timezone(self):
|
||||
"""
|
||||
Check the domain that decides when calendar events should be notified to the user.
|
||||
"""
|
||||
def search_event():
|
||||
return self.env['calendar.event'].search(self.env['res.users']._systray_get_calendar_event_domain())
|
||||
|
||||
self.env.user.tz = 'Europe/Brussels' # UTC +1 15th November 2023
|
||||
events = self.env['calendar.event'].create([{
|
||||
'name': "Meeting",
|
||||
'start': datetime(2023, 11, 15, 18, 0), # 19:00
|
||||
'stop': datetime(2023, 11, 15, 19, 0), # 20:00
|
||||
},
|
||||
{
|
||||
'name': "Tomorrow meeting",
|
||||
'start': datetime(2023, 11, 15, 23, 0), # 00:00 next day
|
||||
'stop': datetime(2023, 11, 16, 0, 0), # 01:00 next day
|
||||
}
|
||||
]).with_context(mail_notrack=True)
|
||||
with freeze_time('2023-11-15 17:30:00'): # 18:30 before event
|
||||
self.assertEqual(search_event(), events[0])
|
||||
with freeze_time('2023-11-15 18:00:00'): # 19:00 during event
|
||||
self.assertEqual(search_event(), events[0])
|
||||
with freeze_time('2023-11-15 18:30:00'): # 19:30 during event
|
||||
self.assertEqual(search_event(), events[0])
|
||||
with freeze_time('2023-11-15 19:00:00'): # 20:00 during event
|
||||
self.assertEqual(search_event(), events[0])
|
||||
with freeze_time('2023-11-15 19:30:00'): # 20:30 after event
|
||||
self.assertEqual(len(search_event()), 0)
|
||||
events.unlink()
|
||||
|
||||
self.env.user.tz = 'America/Lima' # UTC -5 15th November 2023
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Meeting",
|
||||
'start': datetime(2023, 11, 16, 0, 0), # 19:00 15th November
|
||||
'stop': datetime(2023, 11, 16, 1, 0), # 20:00 15th November
|
||||
}).with_context(mail_notrack=True)
|
||||
with freeze_time('2023-11-15 23:30:00'): # 18:30 before event
|
||||
self.assertEqual(search_event(), event)
|
||||
with freeze_time('2023-11-16 00:00:00'): # 19:00 during event
|
||||
self.assertEqual(search_event(), event)
|
||||
with freeze_time('2023-11-16 00:30:00'): # 19:30 during event
|
||||
self.assertEqual(search_event(), event)
|
||||
with freeze_time('2023-11-16 01:00:00'): # 20:00 during event
|
||||
self.assertEqual(search_event(), event)
|
||||
with freeze_time('2023-11-16 01:30:00'): # 20:30 after event
|
||||
self.assertEqual(len(search_event()), 0)
|
||||
event.unlink()
|
||||
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Meeting",
|
||||
'start': datetime(2023, 11, 16, 21, 0), # 16:00 16th November
|
||||
'stop': datetime(2023, 11, 16, 22, 0), # 27:00 16th November
|
||||
}).with_context(mail_notrack=True)
|
||||
with freeze_time('2023-11-15 19:00:00'): # 14:00 the day before event
|
||||
self.assertEqual(len(search_event()), 0)
|
||||
event.unlink()
|
||||
|
||||
self.env.user.tz = 'Asia/Manila' # UTC +8 15th November 2023
|
||||
events = self.env['calendar.event'].create([{
|
||||
'name': "Very early meeting",
|
||||
'start': datetime(2023, 11, 14, 16, 30), # 0:30
|
||||
'stop': datetime(2023, 11, 14, 17, 0), # 1:00
|
||||
},
|
||||
{
|
||||
'name': "Meeting on 2 days",
|
||||
'start': datetime(2023, 11, 15, 15, 30), # 23:30
|
||||
'stop': datetime(2023, 11, 15, 16, 30), # 0:30 next day
|
||||
},
|
||||
{
|
||||
'name': "Early meeting tomorrow",
|
||||
'start': datetime(2023, 11, 15, 23, 0), # 00:00 next day
|
||||
'stop': datetime(2023, 11, 16, 0, 0), # 01:00 next day
|
||||
},
|
||||
{
|
||||
'name': "All day meeting",
|
||||
'allday': True,
|
||||
'start': "2023-11-15",
|
||||
}
|
||||
]).with_context(mail_notrack=True)
|
||||
with freeze_time('2023-11-15 16:00:00'):
|
||||
self.assertEqual(len(search_event()), 3)
|
||||
events.unlink()
|
||||
|
|
@ -0,0 +1,900 @@
|
|||
# -*- 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.tests.common import TransactionCase
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
class TestRecurrentEvents(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestRecurrentEvents, cls).setUpClass()
|
||||
lang = cls.env['res.lang']._lang_get(cls.env.user.lang)
|
||||
lang.week_start = '1' # Monday
|
||||
|
||||
def assertEventDates(self, events, dates):
|
||||
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
|
||||
self.assertEqual(event.start, start)
|
||||
self.assertEqual(event.stop, stop)
|
||||
|
||||
|
||||
class TestCreateRecurrentEvents(TestRecurrentEvents):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.event = cls.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 21, 8, 0),
|
||||
'stop': datetime(2019, 10, 23, 18, 0),
|
||||
'recurrency': True,
|
||||
})
|
||||
|
||||
def test_weekly_count(self):
|
||||
""" Every week, on Tuesdays, for 3 occurences """
|
||||
detached_events = self.event._apply_recurrence_values({
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(detached_events, self.event, "It should be detached from the recurrence")
|
||||
self.assertFalse(self.event.recurrence_id, "It should be detached from the recurrence")
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 10, 29, 8, 0), datetime(2019, 10, 31, 18, 0)),
|
||||
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
|
||||
])
|
||||
|
||||
def test_weekly_interval_2(self):
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 2,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'count': 2,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
|
||||
])
|
||||
|
||||
def test_weekly_interval_2_week_start_sunday(self):
|
||||
lang = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
lang.week_start = '7' # Sunday
|
||||
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 2,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'count': 2,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
|
||||
])
|
||||
lang.week_start = '1' # Monday
|
||||
|
||||
def test_weekly_until(self):
|
||||
self.event._apply_recurrence_values({
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'interval': 2,
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2019, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 2, "It should have 2 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
|
||||
])
|
||||
|
||||
def test_monthly_count_by_date(self):
|
||||
self.event._apply_recurrence_values({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'date',
|
||||
'day': 27,
|
||||
'end_type': 'count',
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 27, 8, 0), datetime(2019, 10, 29, 18, 0)),
|
||||
(datetime(2019, 12, 27, 8, 0), datetime(2019, 12, 29, 18, 0)),
|
||||
(datetime(2020, 2, 27, 8, 0), datetime(2020, 2, 29, 18, 0)),
|
||||
])
|
||||
|
||||
def test_monthly_count_by_date_31(self):
|
||||
self.event._apply_recurrence_values({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 1,
|
||||
'month_by': 'date',
|
||||
'day': 31,
|
||||
'end_type': 'count',
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 31, 8, 0), datetime(2019, 11, 2, 18, 0)),
|
||||
# Missing 31th in November
|
||||
(datetime(2019, 12, 31, 8, 0), datetime(2020, 1, 2, 18, 0)),
|
||||
(datetime(2020, 1, 31, 8, 0), datetime(2020, 2, 2, 18, 0)),
|
||||
])
|
||||
|
||||
def test_monthly_until_by_day(self):
|
||||
""" Every 2 months, on the third Tuesday, until 27th March 2020 """
|
||||
self.event.start = datetime(2019, 10, 1, 8, 0)
|
||||
self.event.stop = datetime(2019, 10, 3, 18, 0)
|
||||
self.event._apply_recurrence_values({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'day',
|
||||
'byday': '3',
|
||||
'weekday': 'TUE',
|
||||
'end_type': 'end_date',
|
||||
'until': date(2020, 3, 27),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 15, 8, 0), datetime(2019, 10, 17, 18, 0)),
|
||||
(datetime(2019, 12, 17, 8, 0), datetime(2019, 12, 19, 18, 0)),
|
||||
(datetime(2020, 2, 18, 8, 0), datetime(2020, 2, 20, 18, 0)),
|
||||
])
|
||||
|
||||
def test_monthly_until_by_day_last(self):
|
||||
""" Every 2 months, on the last Wednesday, until 15th January 2020 """
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 2,
|
||||
'rrule_type': 'monthly',
|
||||
'month_by': 'day',
|
||||
'weekday': 'WED',
|
||||
'byday': '-1',
|
||||
'end_type': 'end_date',
|
||||
'until': date(2020, 1, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 2, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 30, 8, 0), datetime(2019, 11, 1, 18, 0)),
|
||||
(datetime(2019, 12, 25, 8, 0), datetime(2019, 12, 27, 18, 0)),
|
||||
])
|
||||
|
||||
def test_yearly_count(self):
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 2,
|
||||
'rrule_type': 'yearly',
|
||||
'count': 2,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
events = self.event.recurrence_id.calendar_event_ids
|
||||
self.assertEqual(len(events), 2, "It should have 3 events in the recurrence")
|
||||
self.assertEventDates(events, [
|
||||
(self.event.start, self.event.stop),
|
||||
(self.event.start + relativedelta(years=2), self.event.stop + relativedelta(years=2)),
|
||||
])
|
||||
|
||||
def test_dst_timezone(self):
|
||||
""" Test hours stays the same, regardless of DST changes """
|
||||
self.event.start = datetime(2002, 10, 28, 10, 0)
|
||||
self.event.stop = datetime(2002, 10, 28, 12, 0)
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 2,
|
||||
'rrule_type': 'weekly',
|
||||
'mon': True,
|
||||
'count': '2',
|
||||
'event_tz': 'America/New_York', # DST change on 2002/10/27
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
self.assertEventDates(recurrence.calendar_event_ids, [
|
||||
(datetime(2002, 10, 28, 10, 0), datetime(2002, 10, 28, 12, 0)),
|
||||
(datetime(2002, 11, 11, 10, 0), datetime(2002, 11, 11, 12, 0)),
|
||||
])
|
||||
|
||||
def test_ambiguous_dst_time_winter(self):
|
||||
""" Test hours stays the same, regardless of DST changes """
|
||||
eastern = pytz.timezone('America/New_York')
|
||||
dt = eastern.localize(datetime(2002, 10, 20, 1, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
|
||||
# Next occurence happens at 1:30am on 27th Oct 2002 which happened twice in the America/New_York
|
||||
# timezone when the clocks where put back at the end of Daylight Saving Time
|
||||
self.event.start = dt
|
||||
self.event.stop = dt + relativedelta(hours=1)
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 1,
|
||||
'rrule_type': 'weekly',
|
||||
'sun': True,
|
||||
'count': '2',
|
||||
'event_tz': 'America/New_York' # DST change on 2002/4/7
|
||||
})
|
||||
events = self.event.recurrence_id.calendar_event_ids
|
||||
self.assertEqual(events.mapped('duration'), [1, 1])
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2002, 10, 20, 5, 30), datetime(2002, 10, 20, 6, 30)),
|
||||
(datetime(2002, 10, 27, 6, 30), datetime(2002, 10, 27, 7, 30)),
|
||||
])
|
||||
|
||||
def test_ambiguous_dst_time_spring(self):
|
||||
""" Test hours stays the same, regardless of DST changes """
|
||||
eastern = pytz.timezone('America/New_York')
|
||||
dt = eastern.localize(datetime(2002, 3, 31, 2, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
|
||||
# Next occurence happens 2:30am on 7th April 2002 which never happened at all in the
|
||||
# America/New_York timezone, as the clocks where put forward at 2:00am skipping the entire hour
|
||||
self.event.start = dt
|
||||
self.event.stop = dt + relativedelta(hours=1)
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 1,
|
||||
'rrule_type': 'weekly',
|
||||
'sun': True,
|
||||
'count': '2',
|
||||
'event_tz': 'America/New_York' # DST change on 2002/4/7
|
||||
})
|
||||
events = self.event.recurrence_id.calendar_event_ids
|
||||
self.assertEqual(events.mapped('duration'), [1, 1])
|
||||
# The event begins at "the same time" (i.e. 2h30 after midnight), but that day, 2h30 after midnight happens to be at 3:30 am
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2002, 3, 31, 7, 30), datetime(2002, 3, 31, 8, 30)),
|
||||
(datetime(2002, 4, 7, 7, 30), datetime(2002, 4, 7, 8, 30)),
|
||||
])
|
||||
|
||||
def test_ambiguous_full_day(self):
|
||||
""" Test date stays the same, regardless of DST changes """
|
||||
self.event.write({
|
||||
'start': datetime(2020, 3, 23, 0, 0),
|
||||
'stop': datetime(2020, 3, 23, 23, 59),
|
||||
})
|
||||
self.event.allday = True
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 1,
|
||||
'rrule_type': 'weekly',
|
||||
'mon': True,
|
||||
'count': 2,
|
||||
'event_tz': 'Europe/Brussels' # DST change on 2020/3/23
|
||||
})
|
||||
events = self.event.recurrence_id.calendar_event_ids
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2020, 3, 23, 0, 0), datetime(2020, 3, 23, 23, 59)),
|
||||
(datetime(2020, 3, 30, 0, 0), datetime(2020, 3, 30, 23, 59)),
|
||||
])
|
||||
|
||||
def test_videocall_recurrency(self):
|
||||
self.event._set_discuss_videocall_location()
|
||||
self.event._apply_recurrence_values({
|
||||
'interval': 1,
|
||||
'rrule_type': 'weekly',
|
||||
'mon': True,
|
||||
'count': 2,
|
||||
})
|
||||
|
||||
recurrent_events = self.event.recurrence_id.calendar_event_ids
|
||||
detached_events = self.event.recurrence_id.calendar_event_ids - self.event
|
||||
rec_events_videocall_locations = recurrent_events.mapped('videocall_location')
|
||||
self.assertEqual(len(rec_events_videocall_locations), len(set(rec_events_videocall_locations)), 'Recurrent events should have different videocall locations')
|
||||
self.assertEqual(not any(recurrent_events.videocall_channel_id), True, 'No channel should be set before the route is accessed')
|
||||
# create the first channel
|
||||
detached_events[0]._create_videocall_channel()
|
||||
# after channel is created, all other events should have the same channel
|
||||
self.assertEqual(detached_events[0].videocall_channel_id.id, self.event.videocall_channel_id.id)
|
||||
|
||||
@freeze_time('2023-03-27')
|
||||
def test_backward_pass_dst(self):
|
||||
"""
|
||||
When we apply the rule to compute the period of the recurrence,
|
||||
we take an earlier date (in `_get_start_of_period` method).
|
||||
However, it is possible that this earlier date has a different DST.
|
||||
This causes time difference problems.
|
||||
"""
|
||||
# In Europe/Brussels: 26 March 2023 from winter to summer (from no DST to DST)
|
||||
# We are in the case where we create a recurring event after the time change (there is the DST).
|
||||
timezone = 'Europe/Brussels'
|
||||
tz = pytz.timezone(timezone)
|
||||
dt = tz.localize(datetime(2023, 3, 27, 9, 0, 00)).astimezone(pytz.utc).replace(tzinfo=None)
|
||||
self.event.start = dt
|
||||
self.event.stop = dt + relativedelta(hours=1)
|
||||
|
||||
# Check before apply the recurrence
|
||||
self.assertEqual(self.event.start, datetime(2023, 3, 27, 7, 0, 00)) # Because 2023-03-27 in Europe/Brussels is UTC+2
|
||||
|
||||
self.event._apply_recurrence_values({
|
||||
'rrule_type': 'monthly', # Because we will take the first day of the month (jump back)
|
||||
'interval': 1,
|
||||
'end_type': 'count',
|
||||
'count': 2, # To have the base event and the unique recurrence event
|
||||
'month_by': 'date',
|
||||
'day': 27,
|
||||
'event_tz': timezone,
|
||||
})
|
||||
|
||||
# What we expect:
|
||||
# - start date of base event: datetime(2023, 3, 27, 7, 0, 00)
|
||||
# - start date of the unique recurrence event: datetime(2023, 4, 27, 7, 0, 00)
|
||||
|
||||
# With the FIX, we replace the following lines with
|
||||
# `events = self.event.recurrence_id.calendar_event_ids`
|
||||
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertEqual(len(events), 2, "It should have 2 events in the recurrence")
|
||||
self.assertIn(self.event, events)
|
||||
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2023, 3, 27, 7, 00), datetime(2023, 3, 27, 8, 00)),
|
||||
(datetime(2023, 4, 27, 7, 00), datetime(2023, 4, 27, 8, 00)),
|
||||
])
|
||||
|
||||
class TestUpdateRecurrentEvents(TestRecurrentEvents):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
event = cls.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 22, 1, 0),
|
||||
'stop': datetime(2019, 10, 24, 18, 0),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
})
|
||||
cls.recurrence = event.recurrence_id
|
||||
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
|
||||
|
||||
def test_shift_future(self):
|
||||
event = self.events[1]
|
||||
self.events[1].write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
self.assertEqual(self.recurrence.end_type, 'end_date')
|
||||
self.assertEqual(self.recurrence.until, date(2019, 10, 27))
|
||||
self.assertEventDates(self.recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
])
|
||||
new_recurrence = event.recurrence_id
|
||||
self.assertNotEqual(self.recurrence, new_recurrence)
|
||||
self.assertEqual(new_recurrence.count, 2)
|
||||
self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 1, 0))
|
||||
self.assertFalse(new_recurrence.tue)
|
||||
self.assertTrue(new_recurrence.sat)
|
||||
self.assertEventDates(new_recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)),
|
||||
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_future_first(self):
|
||||
event = self.events[0]
|
||||
self.events[0].write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
new_recurrence = event.recurrence_id
|
||||
self.assertFalse(self.recurrence.exists())
|
||||
self.assertEqual(new_recurrence.count, 3)
|
||||
self.assertEqual(new_recurrence.dtstart, datetime(2019, 10, 26, 1, 0))
|
||||
self.assertFalse(new_recurrence.tue)
|
||||
self.assertTrue(new_recurrence.sat)
|
||||
self.assertEventDates(new_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)),
|
||||
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_reapply(self):
|
||||
event = self.events[2]
|
||||
self.events[2].write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
# re-Applying the first recurrence should be idempotent
|
||||
self.recurrence._apply_recurrence()
|
||||
self.assertEventDates(self.recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_all(self):
|
||||
event = self.events[1]
|
||||
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
|
||||
(datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)),
|
||||
])
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'tue': False,
|
||||
'fri': False,
|
||||
'sat': True,
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([])
|
||||
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)),
|
||||
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_stop_all(self):
|
||||
# testing the case where we only want to update the stop time
|
||||
event = self.events[0]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'stop': event.stop + relativedelta(hours=1),
|
||||
})
|
||||
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 2, 0), datetime(2019, 10, 24, 19, 0)),
|
||||
(datetime(2019, 10, 29, 2, 0), datetime(2019, 10, 31, 19, 0)),
|
||||
(datetime(2019, 11, 5, 2, 0), datetime(2019, 11, 7, 19, 0)),
|
||||
])
|
||||
|
||||
def test_change_week_day_rrule(self):
|
||||
recurrence = self.events.recurrence_id
|
||||
recurrence.rrule = 'FREQ=WEEKLY;COUNT=3;BYDAY=WE' # from TU to WE
|
||||
self.assertFalse(self.recurrence.tue)
|
||||
self.assertTrue(self.recurrence.wed)
|
||||
|
||||
def test_rrule_x_params(self):
|
||||
self.recurrence.rrule = 'RRULE;X-EVOLUTION-ENDDATE=20191112;X-OTHER-PARAM=0:X-AMAZING=1;FREQ=WEEKLY;COUNT=3;X-MAIL-special=1;BYDAY=WE;X-RELATIVE=True'
|
||||
self.assertFalse(self.recurrence.tue)
|
||||
self.assertTrue(self.recurrence.wed)
|
||||
|
||||
def test_rrule_x_params_no_rrule_prefix(self):
|
||||
self.recurrence.rrule = 'X-EVOLUTION-ENDDATE=20371102T114500Z:FREQ=WEEKLY;COUNT=720;BYDAY=MO'
|
||||
self.assertFalse(self.recurrence.tue)
|
||||
self.assertTrue(self.recurrence.mon)
|
||||
self.assertEqual(self.recurrence.count, 720)
|
||||
self.assertEqual(self.recurrence.rrule_type, 'weekly')
|
||||
|
||||
def test_shift_all_base_inactive(self):
|
||||
self.recurrence.base_event_id.active = False
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'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")
|
||||
|
||||
def test_shift_all_with_outlier(self):
|
||||
outlier = self.events[1]
|
||||
outlier.write({
|
||||
'recurrence_update': 'self_only',
|
||||
'start': datetime(2019, 10, 31, 1, 0), # Thursday
|
||||
'stop': datetime(2019, 10, 31, 18, 0),
|
||||
})
|
||||
event = self.events[0]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'tue': False,
|
||||
'fri': False,
|
||||
'sat': True,
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=4),
|
||||
})
|
||||
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
|
||||
(datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 28, 18, 0)),
|
||||
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 4, 18, 0)),
|
||||
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 11, 18, 0))
|
||||
])
|
||||
self.assertTrue(outlier.exists(), 'The outlier should have its date and time updated according to the change.')
|
||||
|
||||
def test_update_recurrence_future(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'future_events',
|
||||
'fri': True, # recurrence is now Tuesday AND Friday
|
||||
'count': 4,
|
||||
})
|
||||
self.assertEventDates(self.recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), # Tu
|
||||
])
|
||||
|
||||
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
|
||||
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)), # Tu
|
||||
(datetime(2019, 11, 1, 1, 0), datetime(2019, 11, 3, 18, 0)), # Fr
|
||||
(datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)), # Tu
|
||||
(datetime(2019, 11, 8, 1, 0), datetime(2019, 11, 10, 18, 0)), # Fr
|
||||
])
|
||||
|
||||
events = event.recurrence_id.calendar_event_ids.sorted('start')
|
||||
self.assertEqual(events[0], self.events[1], "Events on Tuesdays should not have changed")
|
||||
self.assertEqual(events[2].start, self.events[2].start, "Events on Tuesdays should not have changed")
|
||||
self.assertNotEqual(events.recurrence_id, self.recurrence, "Events should no longer be linked to the original recurrence")
|
||||
self.assertEqual(events.recurrence_id.count, 4, "The new recurrence should have 4")
|
||||
self.assertTrue(event.recurrence_id.tue)
|
||||
self.assertTrue(event.recurrence_id.fri)
|
||||
|
||||
def test_update_name_future(self):
|
||||
# update regular event (not the base event)
|
||||
old_events = self.events[1:]
|
||||
old_events[0].write({
|
||||
'name': 'New name',
|
||||
'recurrence_update': 'future_events',
|
||||
'rrule_type': 'daily',
|
||||
'count': 5,
|
||||
})
|
||||
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', self.events[0].recurrence_id.id)])
|
||||
self.assertTrue(self.events[0].recurrence_id.exists())
|
||||
self.assertEqual(new_recurrence.count, 5)
|
||||
self.assertFalse(any(old_event.active for old_event in old_events - old_events[0]))
|
||||
for event in new_recurrence.calendar_event_ids:
|
||||
self.assertEqual(event.name, 'New name')
|
||||
|
||||
# update the base event
|
||||
new_events = new_recurrence.calendar_event_ids.sorted('start')
|
||||
new_events[0].write({
|
||||
'name': 'Old name',
|
||||
'recurrence_update': 'future_events'
|
||||
})
|
||||
self.assertTrue(new_recurrence.exists())
|
||||
for event in new_recurrence.calendar_event_ids:
|
||||
self.assertEqual(event.name, 'Old name')
|
||||
|
||||
def test_update_recurrence_all(self):
|
||||
self.events[1].write({
|
||||
'recurrence_update': 'all_events',
|
||||
'mon': True, # recurrence is now Tuesday AND Monday
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([])
|
||||
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)),
|
||||
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_single(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'self_only',
|
||||
'name': "Updated event",
|
||||
'start': event.start - relativedelta(hours=2)
|
||||
})
|
||||
self.events[0].write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(hours=4),
|
||||
'stop': event.stop + relativedelta(hours=5),
|
||||
})
|
||||
|
||||
def test_break_recurrence_future(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'future_events',
|
||||
'recurrency': False,
|
||||
})
|
||||
self.assertFalse(event.recurrence_id)
|
||||
self.assertTrue(self.events[0].active)
|
||||
self.assertTrue(self.events[1].active)
|
||||
self.assertFalse(self.events[2].exists())
|
||||
self.assertEqual(self.recurrence.until, date(2019, 10, 27))
|
||||
self.assertEqual(self.recurrence.end_type, 'end_date')
|
||||
self.assertEventDates(self.recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
])
|
||||
|
||||
def test_break_recurrence_all(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'recurrency': False,
|
||||
'count': 0, # In practice, JS framework sends updated recurrency fields, since they have been recomputed, triggered by the `recurrency` change
|
||||
})
|
||||
self.assertFalse(self.events[0].exists())
|
||||
self.assertTrue(event.active)
|
||||
self.assertFalse(self.events[2].exists())
|
||||
self.assertFalse(event.recurrence_id)
|
||||
self.assertFalse(self.recurrence.exists())
|
||||
|
||||
def test_all_day_shift(self):
|
||||
recurrence = self.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start_date': datetime(2019, 10, 22),
|
||||
'stop_date': datetime(2019, 10, 24),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
'allday': True,
|
||||
}).recurrence_id
|
||||
events = recurrence.calendar_event_ids.sorted('start')
|
||||
event = events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
self.assertEqual(recurrence.end_type, 'end_date')
|
||||
self.assertEqual(recurrence.until, date(2019, 10, 27))
|
||||
self.assertEventDates(recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
])
|
||||
new_recurrence = event.recurrence_id
|
||||
self.assertNotEqual(recurrence, new_recurrence)
|
||||
self.assertEqual(new_recurrence.count, 2)
|
||||
self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 8, 0))
|
||||
self.assertFalse(new_recurrence.tue)
|
||||
self.assertTrue(new_recurrence.sat)
|
||||
self.assertEventDates(new_recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 11, 2, 8, 0), datetime(2019, 11, 5, 18, 0)),
|
||||
(datetime(2019, 11, 9, 8, 0), datetime(2019, 11, 12, 18, 0)),
|
||||
])
|
||||
|
||||
def test_update_name_all(self):
|
||||
old_recurrence = self.events[0].recurrence_id
|
||||
old_events = old_recurrence.calendar_event_ids - self.events[0]
|
||||
self.events[0].write({
|
||||
'name': 'New name',
|
||||
'recurrence_update': 'all_events',
|
||||
'count': '5'
|
||||
})
|
||||
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', old_recurrence.id)])
|
||||
self.assertFalse(old_recurrence.exists())
|
||||
self.assertEqual(new_recurrence.count, 5)
|
||||
self.assertFalse(any(old_event.active for old_event in old_events))
|
||||
for event in new_recurrence.calendar_event_ids:
|
||||
self.assertEqual(event.name, 'New name')
|
||||
|
||||
def test_archive_recurrence_all(self):
|
||||
self.events[1].action_mass_archive('all_events')
|
||||
self.assertEqual([False, False, False], self.events.mapped('active'))
|
||||
|
||||
def test_archive_recurrence_future(self):
|
||||
event = self.events[1]
|
||||
event.action_mass_archive('future_events')
|
||||
self.assertEqual([True, False, False], self.events.mapped('active'))
|
||||
|
||||
def test_unlink_recurrence_all(self):
|
||||
event = self.events[1]
|
||||
event.action_mass_deletion('all_events')
|
||||
self.assertFalse(self.recurrence.exists())
|
||||
self.assertFalse(self.events.exists())
|
||||
|
||||
def test_unlink_recurrence_future(self):
|
||||
event = self.events[1]
|
||||
event.action_mass_deletion('future_events')
|
||||
self.assertTrue(self.recurrence)
|
||||
self.assertEqual(self.events.exists(), self.events[0])
|
||||
|
||||
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.
|
||||
"""
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 22, 1, 0),
|
||||
'stop': datetime(2019, 10, 22, 2, 0),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': False,
|
||||
'wed': True,
|
||||
'fri': True,
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
})
|
||||
# Tuesday datetime(2019, 10, 22, 1, 0) - Archived
|
||||
# Wednesday datetime(2019, 10, 23, 1, 0)
|
||||
# Friday datetime(2019, 10, 25, 1, 0)
|
||||
# Wednesday datetime(2019, 10, 30, 1, 0)
|
||||
recurrence = self.env['calendar.recurrence'].search([('id', '!=', self.recurrence.id)])
|
||||
events = recurrence.calendar_event_ids.sorted('start')
|
||||
# Check first event is archived
|
||||
self.assertFalse(event.active)
|
||||
# Check base_event is different than archived and it is first active event
|
||||
self.assertNotEqual(recurrence.base_event_id, event)
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
# Update all events to check that error is not thrown
|
||||
events[0].write({
|
||||
'recurrence_update': 'all_events',
|
||||
'fri': False,
|
||||
})
|
||||
events = self.env['calendar.recurrence'].search(
|
||||
[('id', '!=', self.recurrence.id)]
|
||||
).calendar_event_ids.sorted('start')
|
||||
self.assertEventDates(events, [
|
||||
(datetime(2019, 10, 23, 1, 0), datetime(2019, 10, 23, 2, 0)),
|
||||
(datetime(2019, 10, 30, 1, 0), datetime(2019, 10, 30, 2, 0)),
|
||||
(datetime(2019, 11, 6, 1, 0), datetime(2019, 11, 6, 2, 0)),
|
||||
])
|
||||
|
||||
|
||||
class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
event = cls.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 22, 1, 0),
|
||||
'stop': datetime(2019, 10, 24, 18, 0),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'fri': True,
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
})
|
||||
cls.recurrence = event.recurrence_id
|
||||
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
|
||||
# Tuesday datetime(2019, 10, 22, 1, 0)
|
||||
# Friday datetime(2019, 10, 25, 1, 0)
|
||||
# Tuesday datetime(2019, 10, 29, 1, 0)
|
||||
|
||||
def test_shift_all_multiple_weekdays(self):
|
||||
event = self.events[0] # Tuesday
|
||||
# We go from 2 days a week Thuesday and Friday to one day a week, Thursday
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'tue': False,
|
||||
'thu': True,
|
||||
'fri': False,
|
||||
'start': event.start + relativedelta(days=2),
|
||||
'stop': event.stop + relativedelta(days=2),
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([])
|
||||
# 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)),
|
||||
(datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 2, 18, 0)),
|
||||
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 9, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_all_multiple_weekdays_duration(self):
|
||||
event = self.events[0] # Tuesday
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'tue': False,
|
||||
'thu': True,
|
||||
'fri': False,
|
||||
'start': event.start + relativedelta(days=2),
|
||||
'stop': event.stop + relativedelta(days=3),
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([])
|
||||
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)),
|
||||
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 10, 18, 0)),
|
||||
])
|
||||
|
||||
def test_shift_future_multiple_weekdays(self):
|
||||
event = self.events[1] # Friday
|
||||
event.write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=3),
|
||||
'stop': event.stop + relativedelta(days=3),
|
||||
})
|
||||
self.assertTrue(self.recurrence.fri)
|
||||
self.assertTrue(self.recurrence.tue)
|
||||
self.assertTrue(event.recurrence_id.tue)
|
||||
self.assertTrue(event.recurrence_id.mon)
|
||||
self.assertFalse(event.recurrence_id.fri)
|
||||
self.assertEqual(event.recurrence_id.count, 2)
|
||||
|
||||
|
||||
class TestUpdateMonthlyByDay(TestRecurrentEvents):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
event = cls.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 15, 1, 0),
|
||||
'stop': datetime(2019, 10, 16, 18, 0),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'month_by': 'day',
|
||||
'weekday': 'TUE',
|
||||
'byday': '3',
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
})
|
||||
cls.recurrence = event.recurrence_id
|
||||
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
|
||||
# datetime(2019, 10, 15, 1, 0)
|
||||
# datetime(2019, 11, 19, 1, 0)
|
||||
# datetime(2019, 12, 17, 1, 0)
|
||||
|
||||
def test_shift_all(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'start': event.start + relativedelta(hours=5),
|
||||
'stop': event.stop + relativedelta(hours=5),
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([])
|
||||
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)),
|
||||
(datetime(2019, 12, 17, 6, 0), datetime(2019, 12, 18, 23, 0)),
|
||||
])
|
||||
|
||||
|
||||
class TestUpdateMonthlyByDate(TestRecurrentEvents):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
event = cls.env['calendar.event'].create({
|
||||
'name': 'Recurrent Event',
|
||||
'start': datetime(2019, 10, 22, 1, 0),
|
||||
'stop': datetime(2019, 10, 24, 18, 0),
|
||||
'recurrency': True,
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 1,
|
||||
'count': 3,
|
||||
'month_by': 'date',
|
||||
'day': 22,
|
||||
'event_tz': 'Etc/GMT-4',
|
||||
})
|
||||
cls.recurrence = event.recurrence_id
|
||||
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
|
||||
# datetime(2019, 10, 22, 1, 0)
|
||||
# datetime(2019, 11, 22, 1, 0)
|
||||
# datetime(2019, 12, 22, 1, 0)
|
||||
|
||||
def test_shift_future(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'future_events',
|
||||
'start': event.start + relativedelta(days=4),
|
||||
'stop': event.stop + relativedelta(days=5),
|
||||
})
|
||||
self.assertEventDates(self.recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
|
||||
])
|
||||
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
|
||||
(datetime(2019, 11, 26, 1, 0), datetime(2019, 11, 29, 18, 0)),
|
||||
(datetime(2019, 12, 26, 1, 0), datetime(2019, 12, 29, 18, 0)),
|
||||
])
|
||||
|
||||
def test_update_all(self):
|
||||
event = self.events[1]
|
||||
event.write({
|
||||
'recurrence_update': 'all_events',
|
||||
'day': 25,
|
||||
})
|
||||
recurrence = self.env['calendar.recurrence'].search([('day', '=', 25)])
|
||||
self.assertEventDates(recurrence.calendar_event_ids, [
|
||||
(datetime(2019, 10, 25, 1, 0), datetime(2019, 10, 27, 18, 0)),
|
||||
(datetime(2019, 11, 25, 1, 0), datetime(2019, 11, 27, 18, 0)),
|
||||
(datetime(2019, 12, 25, 1, 0), datetime(2019, 12, 27, 18, 0)),
|
||||
])
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, time
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
import pytz
|
||||
|
||||
from odoo import Command
|
||||
from odoo import tests
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
|
||||
|
||||
@tests.tagged('mail_activity_mixin')
|
||||
class TestMailActivityMixin(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailActivityMixin, cls).setUpClass()
|
||||
# using res.partner as the model inheriting from mail.activity.mixin
|
||||
cls.test_record = cls.env['res.partner'].with_context(cls._test_context).create({'name': 'Test'})
|
||||
cls.activity_type_1 = cls.env['mail.activity.type'].create({
|
||||
'name': 'Calendar Activity Test Default',
|
||||
'summary': 'default activity',
|
||||
'res_model': 'res.partner',
|
||||
})
|
||||
cls.env['ir.model.data'].create({
|
||||
'name': cls.activity_type_1.name.lower().replace(' ', '_'),
|
||||
'module': 'calendar',
|
||||
'model': cls.activity_type_1._name,
|
||||
'res_id': cls.activity_type_1.id,
|
||||
})
|
||||
# reset ctx
|
||||
cls._reset_mail_context(cls.test_record)
|
||||
|
||||
def test_activity_calendar_event_id(self):
|
||||
"""Test the computed field "activity_calendar_event_id" which is the event of the
|
||||
next activity. It must evaluate to False if the next activity is not related to an event"""
|
||||
def create_event(name, event_date):
|
||||
return self.env['calendar.event'].create({
|
||||
'name': name,
|
||||
'start': datetime.combine(event_date, time(12, 0, 0)),
|
||||
'stop': datetime.combine(event_date, time(14, 0, 0)),
|
||||
})
|
||||
|
||||
def schedule_meeting_activity(record, date_deadline, calendar_event=False):
|
||||
meeting = record.activity_schedule('calendar.calendar_activity_test_default', date_deadline=date_deadline)
|
||||
meeting.calendar_event_id = calendar_event
|
||||
return meeting
|
||||
|
||||
group_partner_manager = self.env['ir.model.data']._xmlid_to_res_id('base.group_partner_manager')
|
||||
self.user_employee.write({
|
||||
'tz': self.user_admin.tz,
|
||||
'groups_id': [Command.link(group_partner_manager)]
|
||||
})
|
||||
with self.with_user('employee'):
|
||||
test_record = self.env['res.partner'].browse(self.test_record.id)
|
||||
self.assertEqual(test_record.activity_ids, self.env['mail.activity'])
|
||||
|
||||
now_utc = datetime.now(pytz.UTC)
|
||||
now_user = now_utc.astimezone(pytz.timezone(self.env.user.tz or 'UTC'))
|
||||
today_user = now_user.date()
|
||||
|
||||
date1 = today_user + relativedelta(days=1)
|
||||
date2 = today_user + relativedelta(days=2)
|
||||
|
||||
ev1 = create_event('ev1', date1)
|
||||
ev2 = create_event('ev2', date2)
|
||||
|
||||
act1 = schedule_meeting_activity(test_record, date1)
|
||||
schedule_meeting_activity(test_record, date2, ev2)
|
||||
|
||||
self.assertFalse(test_record.activity_calendar_event_id, "The next activity does not have a calendar event")
|
||||
|
||||
act1.calendar_event_id = ev1
|
||||
|
||||
self.assertEqual(test_record.activity_calendar_event_id.name, ev1.name, "This should be the calendar event of the next activity")
|
||||
|
||||
act1._action_done(feedback="Mark activity as done with text")
|
||||
|
||||
self.assertFalse(act1.exists(), "activity marked as done should be deleted")
|
||||
self.assertTrue(ev1.exists(), "event of done activity must not be deleted")
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
from datetime import datetime
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestRecurrenceRule(TransactionCase):
|
||||
|
||||
def test_daily_count(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'daily',
|
||||
'interval': 2,
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Days for 3 events')
|
||||
|
||||
def test_daily_until(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'daily',
|
||||
'interval': 2,
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2024, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Days until 2024-11-15')
|
||||
|
||||
def test_daily_none(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'daily',
|
||||
'interval': 2,
|
||||
'end_type': '',
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Days')
|
||||
|
||||
def test_weekly_count(self):
|
||||
""" Every week, on Tuesdays, for 3 occurences """
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'wed': True,
|
||||
'interval': 2,
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday for 3 events')
|
||||
|
||||
def test_weekly_until(self):
|
||||
""" Every week, on Tuesdays, for 3 occurences """
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'wed': True,
|
||||
'interval': 2,
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2024, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday until 2024-11-15')
|
||||
|
||||
def test_weekly_none(self):
|
||||
""" Every week, on Tuesdays, for 3 occurences """
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'weekly',
|
||||
'tue': True,
|
||||
'wed': True,
|
||||
'interval': 2,
|
||||
'end_type': '',
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday')
|
||||
|
||||
def test_monthly_count_by_day(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'day',
|
||||
'byday': '1',
|
||||
'weekday': 'MON',
|
||||
'end_type': 'count',
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday for 3 events')
|
||||
|
||||
def test_monthly_until_by_day(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'day',
|
||||
'byday': '1',
|
||||
'weekday': 'MON',
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2024, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday until 2024-11-15')
|
||||
|
||||
def test_monthly_none_by_day(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'day',
|
||||
'byday': '1',
|
||||
'weekday': 'MON',
|
||||
'end_type': '',
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday')
|
||||
|
||||
def test_monthly_count_by_date(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'date',
|
||||
'day': 27,
|
||||
'weekday': 'MON',
|
||||
'end_type': 'count',
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months day 27 for 3 events')
|
||||
|
||||
def test_monthly_until_by_date(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'date',
|
||||
'day': 27,
|
||||
'weekday': 'MON',
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2024, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months day 27 until 2024-11-15')
|
||||
|
||||
def test_monthly_none_by_date(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'monthly',
|
||||
'interval': 2,
|
||||
'month_by': 'date',
|
||||
'day': 27,
|
||||
'weekday': 'MON',
|
||||
'end_type': '',
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Months day 27')
|
||||
|
||||
def test_yearly_count(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'yearly',
|
||||
'interval': 2,
|
||||
'count': 3,
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Years for 3 events')
|
||||
|
||||
def test_yearly_until(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'yearly',
|
||||
'interval': 2,
|
||||
'end_type': 'end_date',
|
||||
'until': datetime(2024, 11, 15),
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Years until 2024-11-15')
|
||||
|
||||
def test_yearly_none(self):
|
||||
recurrence = self.env['calendar.recurrence'].create({
|
||||
'rrule_type': 'yearly',
|
||||
'interval': 2,
|
||||
'end_type': '',
|
||||
'event_tz': 'UTC',
|
||||
})
|
||||
self.assertEqual(recurrence.name, 'Every 2 Years')
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests.common import new_test_user
|
||||
|
||||
|
||||
class TestResPartner(TransactionCase):
|
||||
|
||||
def test_meeting_count(self):
|
||||
test_user = new_test_user(self.env, login='test_user', groups='base.group_user, base.group_partner_manager')
|
||||
Partner = self.env['res.partner'].with_user(test_user)
|
||||
Event = self.env['calendar.event'].with_user(test_user)
|
||||
|
||||
# Partner hierarchy
|
||||
# 1 5
|
||||
# /|
|
||||
# 2 3
|
||||
# |
|
||||
# 4
|
||||
|
||||
test_partner_1 = Partner.create({'name': 'test_partner_1'})
|
||||
test_partner_2 = Partner.create({'name': 'test_partner_2', 'parent_id': test_partner_1.id})
|
||||
test_partner_3 = Partner.create({'name': 'test_partner_3', 'parent_id': test_partner_1.id})
|
||||
test_partner_4 = Partner.create({'name': 'test_partner_4', 'parent_id': test_partner_3.id})
|
||||
test_partner_5 = Partner.create({'name': 'test_partner_5'})
|
||||
test_partner_6 = Partner.create({'name': 'test_partner_6'})
|
||||
test_partner_7 = Partner.create({'name': 'test_partner_7', 'parent_id': test_partner_6.id})
|
||||
|
||||
Event.create({'name': 'event_1',
|
||||
'partner_ids': [(6, 0, [test_partner_1.id,
|
||||
test_partner_2.id,
|
||||
test_partner_3.id,
|
||||
test_partner_4.id])]})
|
||||
Event.create({'name': 'event_2',
|
||||
'partner_ids': [(6, 0, [test_partner_1.id,
|
||||
test_partner_3.id])]})
|
||||
Event.create({'name': 'event_2',
|
||||
'partner_ids': [(6, 0, [test_partner_2.id,
|
||||
test_partner_3.id])]})
|
||||
Event.create({'name': 'event_3',
|
||||
'partner_ids': [(6, 0, [test_partner_3.id,
|
||||
test_partner_4.id])]})
|
||||
Event.create({'name': 'event_4',
|
||||
'partner_ids': [(6, 0, [test_partner_1.id])]})
|
||||
Event.create({'name': 'event_5',
|
||||
'partner_ids': [(6, 0, [test_partner_3.id])]})
|
||||
Event.create({'name': 'event_6',
|
||||
'partner_ids': [(6, 0, [test_partner_4.id])]})
|
||||
Event.create({'name': 'event_7',
|
||||
'partner_ids': [(6, 0, [test_partner_5.id])]})
|
||||
Event.create({'name': 'event_8',
|
||||
'partner_ids': [(6, 0, [test_partner_5.id,
|
||||
test_partner_7.id])]})
|
||||
|
||||
#Test rule to see if ir.rules are applied
|
||||
calendar_event_model_id = self.env['ir.model']._get('calendar.event').id
|
||||
self.env['ir.rule'].create({'name': 'test_rule',
|
||||
'model_id': calendar_event_model_id,
|
||||
'domain_force': [('name', 'not in', ['event_9', 'event_10'])],
|
||||
'perm_read': True,
|
||||
'perm_create': False,
|
||||
'perm_write': False})
|
||||
|
||||
Event.create({'name': 'event_9',
|
||||
'partner_ids': [(6, 0, [test_partner_2.id,
|
||||
test_partner_3.id])]})
|
||||
|
||||
Event.create({'name': 'event_10',
|
||||
'partner_ids': [(6, 0, [test_partner_5.id])]})
|
||||
|
||||
self.assertEqual(test_partner_1.meeting_count, 7)
|
||||
self.assertEqual(test_partner_2.meeting_count, 2)
|
||||
self.assertEqual(test_partner_3.meeting_count, 6)
|
||||
self.assertEqual(test_partner_4.meeting_count, 3)
|
||||
self.assertEqual(test_partner_5.meeting_count, 2)
|
||||
self.assertEqual(test_partner_6.meeting_count, 1)
|
||||
self.assertEqual(test_partner_7.meeting_count, 1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue