# Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime, timedelta import pytz from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.resource.models.utils import Intervals from odoo.tests import users from .common import AppointmentCommon class AppointmentGanttTestCommon(AppointmentCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.partners = cls.env['res.partner'].create([{ 'name': 'gantt attendee 1' }, { 'name': 'gantt attendee 2' }]) # create some appointments and users to ensure they are not linked to anything else [cls.user_bob, cls.user_john] = [mail_new_test_user( cls.env, company_id=cls.company_admin.id, email='bob@aptgantt.lan', groups='base.group_user', name='bob', login='bob@aptgantt.lan', ), mail_new_test_user( cls.env, company_id=cls.company_admin.id, email='john@aptgantt.lan', groups='base.group_user', name='john', login='john@aptgantt.lan', )] cls.apt_users = cls.user_bob + cls.user_john cls.apt_resource_calendar = cls.env['resource.calendar'].create([{ 'name': 'Gantt Test Apt Resource Calendar', 'company_id': False, 'hours_per_day': 24, 'attendance_ids': [ (0, 0, { 'name': f'{nday} Morning', 'dayofweek': str(nday), 'hour_from': 0, 'hour_to': 12, 'day_period': 'morning', }) for nday in range(6)] + [ (0, 0, { 'name': f'{nday} Afternoon', 'dayofweek': str(nday), 'hour_from': 12, 'hour_to': 24, 'day_period': 'afternoon', }) for nday in range(6)] }]) cls.apt_resource_1, cls.apt_resource_2 = cls.env['appointment.resource'].create([{ 'capacity': 1, 'name': 'Resource 1', 'resource_calendar_id': cls.apt_resource_calendar.id, }, { 'capacity': 2, 'name': 'Resource 2', 'resource_calendar_id': cls.apt_resource_calendar.id, }]) cls.apt_types = cls.env['appointment.type'].create([{ 'appointment_tz': 'UTC', 'name': 'bob apt type', 'staff_user_ids': [(4, cls.user_bob.id)], }, { 'appointment_tz': 'UTC', 'name': 'nouser apt type', 'staff_user_ids': [], }, { 'appointment_tz': 'UTC', 'name': 'resource apt type', 'resource_ids': [(4, cls.apt_resource_1.id), (4, cls.apt_resource_2.id)], 'schedule_based_on': 'resources', 'resource_manage_capacity': True, }]) cls.resource_apt_types = cls.apt_types[2] cls.gantt_context = {'appointment_booking_gantt_show_all_resources': True} cls.gantt_domain = [('appointment_type_id', 'in', cls.apt_types.ids)] class AppointmentGanttTest(AppointmentGanttTestCommon): @users('apt_manager') def test_default_assign_user_attendees(self): """ 1> To check, Single attendee should be set as an organizer by default. (This is typically applied when selecting a specific slot in the appointment kanban.) 2> (special gantt case) The current user should be an attendee if he is set as the organizer. """ # context while clicking the 'New' btn no_attendees_context = { 'booking_gantt_create_record': True, 'appointment_default_assign_user_attendees': True, 'default_partner_ids': [], } # context while clicking the time slot of the staff user single_attendee_context = { **no_attendees_context, 'default_partner_ids': [self.user_bob.partner_id.id], } # Case 1: Specify a single attendee, simulating clicking a slot on the gantt view event_with_partner = self.env['calendar.event'].with_context(single_attendee_context).create({ 'name': 'event with partner', 'appointment_type_id': self.apt_types[0].id, }) # Case 2: Create an appointment without specifying attendees event_without_partner = self.env['calendar.event'].with_context(no_attendees_context).create({ 'name': 'event without partner', 'appointment_type_id': self.apt_types[0].id, }) # Case 1 check self.assertEqual( event_with_partner.user_id, self.user_bob, "Single attendee should be set as an organizer by default", ) # Case 2 check self.assertEqual( event_without_partner.user_id, self.apt_manager, "The current user should be an organizer", ) self.assertEqual( event_without_partner.attendee_ids.partner_id, self.apt_manager.partner_id, "If we don't specify an attendee, the current user should be set as an attendee", ) def test_gantt_empty_groups(self): """Check that the data sent to gantt includes the right groups in the context of appointments.""" gantt_data = self.env['calendar.event'].with_context(self.gantt_context).get_gantt_data( self.gantt_domain, ['partner_ids'], {} ) group_partner_ids = [group['partner_ids'][0] for group in gantt_data['groups']] self.assertIn(self.user_bob.partner_id.id, group_partner_ids, 'Staff assigned to a user-scheduled appointment type should be shown in the default groups') self.assertNotIn(self.user_john.partner_id.id, group_partner_ids, 'Staff not assigned to any appointment type should be hidden') # add john as a staff user of an appointment type -> in the default groups self.apt_types[1].staff_user_ids = self.user_john gantt_data = self.env['calendar.event'].with_context(self.gantt_context).get_gantt_data( self.gantt_domain, ['partner_ids'], {} ) group_partner_ids = [group['partner_ids'][0] for group in gantt_data['groups']] self.assertIn(self.user_bob.partner_id.id, group_partner_ids) self.assertIn(self.user_john.partner_id.id, group_partner_ids) # have default appointment in context -> only show staff assigned to that type context = self.gantt_context | {'default_appointment_type_id': self.apt_types[0].id} gantt_data = self.env['calendar.event'].with_context(context).get_gantt_data( self.gantt_domain, ['partner_ids'], {} ) group_partner_ids = [group['partner_ids'][0] for group in gantt_data['groups']] self.assertIn(self.user_bob.partner_id.id, group_partner_ids) self.assertNotIn(self.user_john.partner_id.id, group_partner_ids, 'Should only display staff assigned to the default apt type.') def test_gantt_hide_non_staff(self): """Check that only the attendees that are part of the staff are used to compute the gantt data. The other attendees, such as the website visitors that created the meeting, are excluded and should not be displayed as gantt rows. """ meeting = self._create_meetings( self.apt_users[0], [(self.reference_monday, self.reference_monday + timedelta(hours=1), False)], self.apt_types[0].id ) meeting.partner_ids += self.partners[0] gantt_data = self.env['calendar.event'].with_context(self.gantt_context).get_gantt_data( self.gantt_domain, ['partner_ids'], {} ) group_partner_ids = [group['partner_ids'][0] for group in gantt_data['groups']] self.assertNotIn(self.partners[0].id, group_partner_ids, 'Attendees with no users should be hidden from the grouping.') self.assertIn(self.user_bob.partner_id.id, group_partner_ids) self.assertNotIn(self.user_john.partner_id.id, group_partner_ids) self.assertEqual(gantt_data['records'], [{'id': meeting.id}]) @users('staff_user_bxls') def test_gantt_resource_unavailabilities_multi_company(self): """Check that resources outside of allowed companies don't get calendar unavailabilities.""" company_a = self.env.company company_b = self.env['res.company'].sudo().create({ 'name': 'Gantt Test Company B', }) self.env.user.sudo().company_ids += company_a + company_b resource_calendar_unavailabilities = Intervals([ (datetime(2022, 2, 14, 11, 00, tzinfo=pytz.UTC), datetime(2022, 2, 14, 11, 0, tzinfo=pytz.UTC), set()), ]) for allowed_company_ids, res_1_company, res_2_company, res_1_unavailabilities, res_2_unavailabilities in [ ([], False, False, resource_calendar_unavailabilities, resource_calendar_unavailabilities), ([], False, company_a, resource_calendar_unavailabilities, resource_calendar_unavailabilities), (company_a.ids, False, company_a, resource_calendar_unavailabilities, resource_calendar_unavailabilities), (company_b.ids, company_a, company_a, [], []), (company_b.ids, company_a, company_b, [], resource_calendar_unavailabilities), ([company_a.id, company_b.id], company_a, company_b, resource_calendar_unavailabilities, resource_calendar_unavailabilities), ]: with self.subTest( allowed_company_ids=allowed_company_ids, resource_1_company=res_1_company, resource_2_company=res_2_company, ): self.apt_resource_1.sudo().company_id = res_1_company self.apt_resource_2.sudo().company_id = res_2_company unavailabilities = self.env['calendar.event'].with_context(self.gantt_context | { 'allowed_company_ids': allowed_company_ids }).gantt_unavailability( self.reference_monday.replace(hour=0), self.reference_monday.replace(hour=23), 'day', group_bys=['resource_ids'], rows=[{ 'groupedBy': ['resource_ids'], 'resId': self.apt_resource_1.id, 'rows': [], }, { 'groupedBy': ['resource_ids'], 'resId': self.apt_resource_2.id, 'rows': [], }] ) self.assertListEqual( list(Intervals([(unavailability['start'], unavailability['stop'], set()) for unavailability in unavailabilities[0]['unavailabilities']])), list(res_1_unavailabilities) ) self.assertListEqual( list(Intervals([(unavailability['start'], unavailability['stop'], set()) for unavailability in unavailabilities[1]['unavailabilities']])), list(res_2_unavailabilities) ) def test_gantt_without_attendees(self): meeting = self._create_meetings( self.user_john[0], [(self.reference_monday, self.reference_monday + timedelta(hours=1), False)], self.apt_types[0].id ) meeting.partner_ids = False gantt_data = self.env['calendar.event'].with_context(self.gantt_context).get_gantt_data( self.gantt_domain, ['partner_ids'], {} ) group_partner_ids = [group['partner_ids'][0] for group in gantt_data['groups']] self.assertIn(self.user_bob.partner_id.id, group_partner_ids) self.assertNotIn(self.user_john.partner_id.id, group_partner_ids) self.assertEqual(gantt_data['records'], [{'id': meeting.id}])