forked from Mapan/odoo17e
1210 lines
59 KiB
Python
1210 lines
59 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime, timedelta
|
|
from freezegun import freeze_time
|
|
|
|
from odoo import Command
|
|
from odoo.addons.appointment.tests.common import AppointmentCommon
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tests import Form, tagged, users, warmup
|
|
|
|
|
|
@tagged('appointment_resources', 'post_install', '-at_install')
|
|
class AppointmentResource(AppointmentCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.appointment_manage_capacity, cls.appointment_regular = cls.env['appointment.type'].create([{
|
|
'appointment_tz': 'UTC',
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 8,
|
|
'name': 'Managed Test',
|
|
'resource_manage_capacity': True,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(cls.reference_monday.isoweekday()),
|
|
'start_hour': 6,
|
|
'end_hour': 18,
|
|
})],
|
|
}, {
|
|
'appointment_tz': 'UTC',
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 8,
|
|
'name': 'Unmanaged Test',
|
|
'resource_manage_capacity': False,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(cls.reference_monday.isoweekday()),
|
|
'start_hour': 6,
|
|
'end_hour': 18,
|
|
})],
|
|
}])
|
|
|
|
cls.resource_1, cls.resource_2, cls.resource_3 = cls.env['appointment.resource'].create([{
|
|
'appointment_type_ids': cls.appointment_manage_capacity.ids,
|
|
'capacity': 3,
|
|
'name': 'Resource 1',
|
|
}, {
|
|
'appointment_type_ids': cls.appointment_manage_capacity.ids,
|
|
'capacity': 2,
|
|
'name': 'Resource 2',
|
|
'shareable': True,
|
|
}, {
|
|
'appointment_type_ids': (cls.appointment_manage_capacity | cls.appointment_regular).ids,
|
|
'capacity': 1,
|
|
'name': 'Resource 3',
|
|
}])
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resource_default_appointment_type(self):
|
|
"""Check that the default appointment type is properly deduced from the default appointment resource."""
|
|
resource_3_type_ids = self.resource_3.appointment_type_ids
|
|
Event = self.env['calendar.event']
|
|
|
|
states = [(self.resource_3, None, resource_3_type_ids[0]),
|
|
(self.resource_3, resource_3_type_ids[1], resource_3_type_ids[1])]
|
|
for resource, default_type, expected_type_id in states:
|
|
context = {
|
|
'booking_gantt_create_record': True,
|
|
'default_resource_ids': resource.ids
|
|
}
|
|
if default_type:
|
|
context.update(default_appointment_type_id=default_type.id)
|
|
|
|
event = Form(Event.with_context(context))
|
|
self.assertEqual(event.appointment_type_id, expected_type_id)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resource_link(self):
|
|
""" Test link between resources when they are combinable. """
|
|
resource_1, resource_2 = self.env['appointment.resource'].create([
|
|
{
|
|
'capacity': 4,
|
|
'name': 'Table of 4',
|
|
},
|
|
{
|
|
'capacity': 2,
|
|
'name': 'Table of 2',
|
|
}
|
|
])
|
|
self.assertFalse(resource_1.linked_resource_ids)
|
|
self.assertFalse(resource_2.linked_resource_ids)
|
|
|
|
# Test: link resource 2 to resource 1, and add a new resource as an
|
|
# embedded 2many creation
|
|
resource_1.write({
|
|
'linked_resource_ids': [
|
|
(4, resource_2.id), # new link
|
|
(0, 0, { # embedded creation
|
|
'capacity': 1,
|
|
'name': 'OnTheFly Table of 1'
|
|
}),
|
|
],
|
|
})
|
|
new_resource_1 = self.env['appointment.resource'].search([('name', '=', 'OnTheFly Table of 1')])
|
|
self.assertEqual(len(new_resource_1), 1, 'Should have created a new resource')
|
|
self.assertEqual(new_resource_1.linked_resource_ids, resource_1,
|
|
'Link works both ways')
|
|
self.assertFalse(new_resource_1.source_resource_ids)
|
|
self.assertEqual(new_resource_1.destination_resource_ids, resource_1,
|
|
'Resource 2 is destination of link between 1 and 2')
|
|
self.assertEqual(
|
|
resource_1.linked_resource_ids,
|
|
resource_2 + new_resource_1,
|
|
'Resource 1 should be linked to linked resource 2 and newly created new'
|
|
)
|
|
self.assertEqual(resource_1.source_resource_ids, resource_2 + new_resource_1)
|
|
self.assertFalse(resource_1.destination_resource_ids)
|
|
self.assertEqual(resource_2.linked_resource_ids, resource_1,
|
|
'Link works both ways')
|
|
self.assertFalse(resource_2.source_resource_ids)
|
|
self.assertEqual(resource_2.destination_resource_ids, resource_1,
|
|
'Resource 2 is destination of link between 1 and 2')
|
|
|
|
# Test: break link to resource 2, add an existing one (check duplication)
|
|
# and create yet another one
|
|
resource_1.write({
|
|
'linked_resource_ids': [
|
|
(3, resource_2.id), # break link
|
|
(4, new_resource_1.id), # already existing
|
|
(0, 0, {
|
|
'capacity': 1,
|
|
'name': 'OnTheFly Table of 1 (bis)'
|
|
}),
|
|
],
|
|
})
|
|
new_resource_2 = self.env['appointment.resource'].search([('name', '=', 'OnTheFly Table of 1 (bis)')])
|
|
self.assertEqual(len(new_resource_2), 1, 'Should have created a new resource')
|
|
self.assertEqual(new_resource_1.linked_resource_ids, resource_1,
|
|
'Link works both ways')
|
|
self.assertFalse(new_resource_1.source_resource_ids)
|
|
self.assertEqual(new_resource_1.destination_resource_ids, resource_1)
|
|
self.assertEqual(new_resource_2.linked_resource_ids, resource_1,
|
|
'Link works both ways')
|
|
self.assertFalse(new_resource_2.source_resource_ids)
|
|
self.assertEqual(new_resource_2.destination_resource_ids, resource_1)
|
|
self.assertEqual(
|
|
resource_1.linked_resource_ids,
|
|
new_resource_1 + new_resource_2,
|
|
'Resource 1 should be linked to linked resource 2 and newly created new'
|
|
)
|
|
self.assertEqual(resource_1.source_resource_ids, new_resource_1 + new_resource_2)
|
|
self.assertFalse(resource_1.destination_resource_ids)
|
|
self.assertFalse(resource_2.linked_resource_ids)
|
|
self.assertFalse(resource_2.source_resource_ids)
|
|
self.assertFalse(resource_2.destination_resource_ids)
|
|
|
|
# Test: update link based on destination, not source as previous tests
|
|
resource_2.write({
|
|
'linked_resource_ids': [
|
|
(4, new_resource_1.id), # add a new entry in destination
|
|
]
|
|
})
|
|
new_resource_2.write({
|
|
'linked_resource_ids': [
|
|
(3, resource_1.id), # break link
|
|
(4, resource_2.id), # new link, will have both sources and dest
|
|
(4, new_resource_1.id), # new link
|
|
]
|
|
})
|
|
self.assertEqual(len(new_resource_2), 1, 'Should have created a new resource')
|
|
self.assertEqual(new_resource_1.linked_resource_ids, resource_1 + resource_2 + new_resource_2)
|
|
self.assertFalse(new_resource_1.source_resource_ids)
|
|
self.assertEqual(new_resource_1.destination_resource_ids, resource_1 + resource_2 + new_resource_2)
|
|
self.assertEqual(new_resource_2.linked_resource_ids, resource_2 + new_resource_1)
|
|
self.assertEqual(new_resource_2.source_resource_ids, resource_2 + new_resource_1)
|
|
self.assertFalse(new_resource_2.destination_resource_ids)
|
|
self.assertEqual(resource_1.linked_resource_ids, new_resource_1)
|
|
self.assertEqual(resource_1.source_resource_ids, new_resource_1)
|
|
self.assertFalse(resource_1.destination_resource_ids)
|
|
self.assertEqual(resource_2.linked_resource_ids, new_resource_1 + new_resource_2)
|
|
self.assertEqual(resource_2.source_resource_ids, new_resource_1)
|
|
self.assertEqual(resource_2.destination_resource_ids, new_resource_2)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resource_field(self):
|
|
"""Check that the appointment_resource_id field works as expected"""
|
|
booking = self.env['calendar.event'].with_context(self._test_context).create([{
|
|
'appointment_type_id': self.appointment_manage_capacity.id,
|
|
'name': 'Booking',
|
|
'start': datetime(2022, 2, 15, 14, 0, 0),
|
|
'stop': datetime(2022, 2, 15, 15, 0, 0),
|
|
}])
|
|
|
|
self.assertFalse(booking.appointment_resource_id)
|
|
|
|
booking.appointment_resource_id = self.resource_1
|
|
self.assertEqual(booking.appointment_resource_id, self.resource_1)
|
|
self.assertEqual(len(booking.booking_line_ids), 1)
|
|
self.assertEqual(booking.booking_line_ids.appointment_resource_id, self.resource_1)
|
|
|
|
booking_line_1 = booking.booking_line_ids[0]
|
|
|
|
booking.write({'booking_line_ids': [Command.create({
|
|
'appointment_resource_id': self.resource_2.id,
|
|
'calendar_event_id': booking.id,
|
|
'capacity_reserved': 1})]
|
|
})
|
|
self.assertFalse(booking.appointment_resource_id, 'More than one booking lines should mean no singular resource id.')
|
|
self.assertEqual(booking.booking_line_ids.appointment_resource_id, self.resource_1 | self.resource_2)
|
|
|
|
booking_lines_before = booking.booking_line_ids
|
|
resources_before = booking.appointment_resource_ids
|
|
booking.appointment_resource_id = self.resource_3
|
|
self.assertEqual(len(booking.booking_line_ids), 2, 'Setting the singular resource when there already are multiple booking lines should do nothing.')
|
|
self.assertEqual(booking_lines_before, booking.booking_line_ids)
|
|
self.assertEqual(resources_before, booking.appointment_resource_ids)
|
|
|
|
booking.booking_line_ids = booking_line_1
|
|
self.assertEqual(len(booking.booking_line_ids), 1)
|
|
self.assertEqual(booking.appointment_resource_id, self.resource_1)
|
|
|
|
booking.appointment_resource_id = False
|
|
self.assertEqual(len(booking.booking_line_ids), 0)
|
|
self.assertFalse(booking.appointment_resource_id)
|
|
self.assertFalse(booking.booking_line_ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_remaining_capacity(self):
|
|
""" Test that the remaining capacity of resources are correctly computed """
|
|
appointment = self.appointment_manage_capacity
|
|
resource_1 = self.resource_1
|
|
resource_2 = self.resource_2
|
|
|
|
start = datetime(2022, 2, 15, 14, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
|
|
self.assertTrue(appointment._get_resources_remaining_capacity(resource_1, start, end)['total_remaining_capacity'] == 3)
|
|
self.assertTrue(appointment._get_resources_remaining_capacity(resource_2, start, end)['total_remaining_capacity'] == 2)
|
|
|
|
# Create bookings for resource
|
|
booking_1, booking_2 = self.env['calendar.event'].with_context(self._test_context).create([{
|
|
'appointment_type_id': appointment.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_1.id, 'capacity_reserved': 1, 'capacity_used': resource_1.capacity})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
}, {
|
|
'appointment_type_id': appointment.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_2.id, 'capacity_reserved': 1})],
|
|
'name': 'Booking 2',
|
|
'start': start,
|
|
'stop': end,
|
|
}])
|
|
bookings = booking_1 + booking_2
|
|
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_1, start, end)['total_remaining_capacity'] == 0,
|
|
'The resource should have no availabilities left')
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_2, start, end)['total_remaining_capacity'] == 1,
|
|
'The resource should have 1 availability left because it is shareable')
|
|
|
|
bookings.unlink()
|
|
resource_1.linked_resource_ids = resource_2
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_1, start, end)['total_remaining_capacity'] == 5,
|
|
"The resource should have 5 availabilities (3 from the resource and 2 from the other linked to it)")
|
|
self.assertTrue(appointment._get_resources_remaining_capacity(resource_2, start, end)['total_remaining_capacity'] == 5)
|
|
self.assertTrue(appointment._get_resources_remaining_capacity(resource_1, start, end)[resource_1] == 3)
|
|
self.assertTrue(appointment._get_resources_remaining_capacity(resource_2, start, end)[resource_2] == 2)
|
|
|
|
booking = self.env['calendar.event'].with_context(self._test_context).create([{
|
|
'appointment_type_id': appointment.id,
|
|
'booking_line_ids': [
|
|
(0, 0, {'appointment_resource_id': resource_1.id, 'capacity_reserved': 3, 'capacity_used': 3}),
|
|
(0, 0, {'appointment_resource_id': resource_2.id, 'capacity_reserved': 1}),
|
|
],
|
|
'name': 'Booking',
|
|
'start': start,
|
|
'stop': end,
|
|
}])
|
|
|
|
self.assertTrue(len(booking.appointment_resource_ids) == 2)
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_1, start, end)['total_remaining_capacity'] == 1,
|
|
'The resource should have 1 availability left (the one from resource_2)')
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_1, start, end)[resource_1] == 0,
|
|
'The resource should have no availability left if alone')
|
|
self.assertTrue(
|
|
appointment._get_resources_remaining_capacity(resource_2, start, end)['total_remaining_capacity'] == 1,
|
|
'The resource should have 1 availability left')
|
|
|
|
self.assertDictEqual(
|
|
appointment._get_resources_remaining_capacity(self.env['appointment.resource'], start, end),
|
|
{'total_remaining_capacity': 0},
|
|
'No result should give dict with correct accumulated values.')
|
|
|
|
|
|
@tagged('appointment_resources', 'post_install', '-at_install')
|
|
class AppointmentResourceBookingTest(AppointmentCommon):
|
|
|
|
@users('apt_manager')
|
|
@warmup
|
|
def test_appointment_resources(self):
|
|
""" Slots generation and availability of appointment type with resources """
|
|
appointment = self.env['appointment.type'].create({
|
|
'appointment_tz': 'UTC',
|
|
'assign_method': 'time_auto_assign',
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 8,
|
|
'name': 'Test',
|
|
'resource_manage_capacity': True,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday()),
|
|
'start_hour': 6,
|
|
'end_hour': 18,
|
|
})],
|
|
})
|
|
|
|
self.env['appointment.resource'].create([
|
|
{
|
|
'appointment_type_ids': appointment.ids,
|
|
'capacity': 3,
|
|
'name': 'Resource %s' % i,
|
|
} for i in range(20)
|
|
])
|
|
# Flush everything, notably tracking values, as it may impact performances
|
|
self.flush_tracking()
|
|
|
|
with freeze_time(self.reference_now):
|
|
with self.assertQueryCount(default=10):
|
|
appointment._get_appointment_slots('UTC')
|
|
|
|
def test_appointment_resources_availability(self):
|
|
""" Check that resource slots are all available for an appointment """
|
|
appointment = self.env['appointment.type'].create({
|
|
'appointment_tz': 'UTC',
|
|
'name': 'Resource appointment',
|
|
'resource_manage_capacity': False,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [
|
|
(0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday()),
|
|
'start_hour': 0,
|
|
'end_hour': 0,
|
|
}),
|
|
],
|
|
})
|
|
periods = [
|
|
{'name': 'Morning', 'hour_from': 0, 'hour_to': 11.99, 'day_period': 'morning'},
|
|
{'name': 'Afternoon', 'hour_from': 12, 'hour_to': 24, 'day_period': 'afternoon'}
|
|
]
|
|
week_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
|
resource_calendar = self.env['resource.calendar'].create({
|
|
'name': 'Default Calendar',
|
|
'company_id': False,
|
|
'hours_per_day': 24,
|
|
'attendance_ids': [
|
|
(0, 0, {
|
|
'name': f'{day} {period["name"]}',
|
|
'dayofweek': str(week_days.index(day)),
|
|
'hour_from': period['hour_from'],
|
|
'hour_to': period['hour_to'],
|
|
'day_period': period['day_period']
|
|
})
|
|
for day in week_days
|
|
for period in periods
|
|
],
|
|
})
|
|
# Default resource_calendar_id, Mon-Sun 12am-11:59am 12pm-11:59pm
|
|
resource = self.env['appointment.resource'].create({
|
|
'name': 'Resource',
|
|
'appointment_type_ids': appointment,
|
|
'resource_calendar_id': resource_calendar.id,
|
|
})
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = appointment._get_appointment_slots(timezone='UTC', filter_resources=resource)
|
|
|
|
self.assertSlots(
|
|
slots,
|
|
[
|
|
{
|
|
'name_formated': 'February 2022',
|
|
'month_date': datetime(2022, 2, 1),
|
|
'weeks_count': 5,
|
|
},
|
|
],
|
|
{
|
|
'enddate': self.global_slots_enddate,
|
|
'startdate': self.reference_now_monthweekstart,
|
|
'slots_start_hours': list(range(24)),
|
|
'slots_startdate': self.reference_monday.date(),
|
|
'slots_weekdays_nowork': range(1, 7), # Resource only available on monday (0)
|
|
},
|
|
)
|
|
|
|
def test_appointment_resources_check_organizer_validation_conditions(self):
|
|
appointment_vals_list = [{
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'name': 'Booking',
|
|
'start': datetime(2022, 2, 15, 14, 0, 0),
|
|
'stop': datetime(2022, 2, 15, 15, 0, 0),
|
|
}, {
|
|
'appointment_type_id': self.apt_type_bxls_2days.id,
|
|
'name': 'Booking',
|
|
'start': datetime(2022, 2, 15, 10, 0, 0),
|
|
'stop': datetime(2022, 2, 15, 11, 0, 0),
|
|
}, {
|
|
'appointment_type_id': False,
|
|
'name': 'Booking',
|
|
'start': datetime(2022, 2, 15, 10, 0, 0),
|
|
'stop': datetime(2022, 2, 15, 11, 0, 0),
|
|
}]
|
|
|
|
self.assertEqual(self.env['calendar.event']._check_organizer_validation_conditions(appointment_vals_list), [False, True, True])
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable(self):
|
|
""" Check that combinable resources are correctly process. """
|
|
table_c2, table_c4, table_c6 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': i,
|
|
'name': 'Table for %s' % i,
|
|
'sequence': i,
|
|
} for i in range(2, 7, 2)])
|
|
|
|
table_c2.linked_resource_ids = (table_c4 + table_c6)
|
|
table_c4.linked_resource_ids = (table_c2 + table_c6)
|
|
table_c6.linked_resource_ids = (table_c2 + table_c4)
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots_c1 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=3)
|
|
resource_slots_c3 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=9)
|
|
resource_slots_c9 = self._filter_appointment_slots(slots)
|
|
available_resources_c1 = [resource['id'] for resource in resource_slots_c1[0]['available_resources']]
|
|
available_resources_c3 = [resource['id'] for resource in resource_slots_c3[0]['available_resources']]
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c6 = [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
available_resources_c9 = [resource['id'] for resource in resource_slots_c9[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c1, table_c2.ids)
|
|
self.assertListEqual(available_resources_c3, table_c4.ids,
|
|
"The table for 4 should be selected here as it's linked to the table for 2 and we don't want to lose capacity with a combination")
|
|
self.assertListEqual(available_resources_c4, table_c4.ids)
|
|
self.assertListEqual(available_resources_c6, table_c6.ids)
|
|
self.assertListEqual(available_resources_c9, (table_c4 + table_c6).ids)
|
|
|
|
table_c4.sequence = 1
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots_c1 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=3)
|
|
resource_slots_c3 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=9)
|
|
resource_slots_c9 = self._filter_appointment_slots(slots)
|
|
available_resources_c1 = [resource['id'] for resource in resource_slots_c1[0]['available_resources']]
|
|
available_resources_c3 = [resource['id'] for resource in resource_slots_c3[0]['available_resources']]
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c6 = [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
available_resources_c9 = [resource['id'] for resource in resource_slots_c9[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c1, table_c4.ids)
|
|
self.assertListEqual(available_resources_c3, table_c4.ids)
|
|
self.assertListEqual(available_resources_c4, table_c4.ids)
|
|
self.assertListEqual(available_resources_c6, table_c6.ids)
|
|
self.assertListEqual(available_resources_c9, (table_c4 + table_c6).ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable_avoid_losing_extra_capacity(self):
|
|
""" Check that we don't lose capacity with the resource selected.
|
|
If a capacity needed is greater than the capacity of the resource initially selected
|
|
we should check if the linked resources of the one selected are not a better fit to avoid losing capacity.
|
|
"""
|
|
|
|
nordic, scandinavian, snow = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 4,
|
|
'name': 'Nordic',
|
|
'sequence': 2,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 8,
|
|
'name': 'Scandinavian',
|
|
'sequence': 3,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Snow',
|
|
'sequence': 4,
|
|
}])
|
|
|
|
nordic.linked_resource_ids = scandinavian
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c5, scandinavian.ids)
|
|
|
|
snow.sequence = 1
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c5, snow.ids)
|
|
|
|
snow.sequence = 4
|
|
scandinavian.write({
|
|
'linked_resource_ids': [(4, nordic.id)],
|
|
'sequence': 1,
|
|
})
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=2)
|
|
resource_slots_c2 = self._filter_appointment_slots(slots)
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
available_resources_c2 = [resource['id'] for resource in resource_slots_c2[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c5, scandinavian.ids)
|
|
self.assertListEqual(available_resources_c2, scandinavian.ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable_complex(self):
|
|
""" Check resources assignation when the linked resources are not mirrored for all resources """
|
|
|
|
table1_c2, table2_c2, table3_c2 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table 2A',
|
|
'sequence': 1,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table 2B',
|
|
'sequence': 3,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table 2C',
|
|
'sequence': 2,
|
|
}])
|
|
table1_c2.linked_resource_ids = table2_c2 + table3_c2
|
|
table2_c2.linked_resource_ids = table1_c2 + table3_c2
|
|
table3_c2.linked_resource_ids = table1_c2 + table2_c2
|
|
|
|
table_c3 = self.env['appointment.resource'].create({
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 3,
|
|
'name': 'Table 3A',
|
|
'sequence': 4,
|
|
})
|
|
|
|
table1_c6, table2_c6 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Table 6A',
|
|
'sequence': 5,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Table 6B',
|
|
'sequence': 6,
|
|
}])
|
|
|
|
table1_c6.linked_resource_ids = table_c3
|
|
table2_c6.linked_resource_ids = table1_c2 + table2_c2 + table3_c2
|
|
|
|
table1_c8, table2_c8, bar = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 8,
|
|
'name': 'Table 8A',
|
|
'sequence': 8,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 8,
|
|
'name': 'Table 8B',
|
|
'sequence': 9,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 10,
|
|
'name': 'Bar',
|
|
'sequence': 15,
|
|
'shareable': True,
|
|
}])
|
|
table1_c8.sequence = 10
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=8)
|
|
resource_slots_c8 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=10)
|
|
resource_slots_c10 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=12)
|
|
resource_slots_c12 = self._filter_appointment_slots(slots)
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c8 = [resource['id'] for resource in resource_slots_c8[0]['available_resources']]
|
|
available_resources_c10 = [resource['id'] for resource in resource_slots_c10[0]['available_resources']]
|
|
available_resources_c12 = [resource['id'] for resource in resource_slots_c12[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c4, (table1_c2 + table3_c2).ids)
|
|
self.assertListEqual(available_resources_c8, table2_c8.ids)
|
|
self.assertListEqual(available_resources_c10, bar.ids)
|
|
self.assertListEqual(available_resources_c12, (table2_c6 + table2_c6.linked_resource_ids).sorted('sequence').ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable_last_availability(self):
|
|
""" Check that the last resource available is correctly computed with linked resources """
|
|
table_c2, table_c3 = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table for 2',
|
|
'sequence': 1,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 3,
|
|
'name': 'Table for 3',
|
|
'sequence': 2,
|
|
}])
|
|
table_c2.linked_resource_ids = table_c3
|
|
|
|
# Create a booking for the first resource for all its capacity
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': table_c2.id, 'capacity_reserved': 2, 'capacity_used': 2})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
})
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=2)
|
|
resource_slots_c2 = self._filter_appointment_slots(slots)
|
|
available_resources_c2 = [resource['id'] for resource in resource_slots_c2[0]['available_resources']]
|
|
self.assertEqual(set(available_resources_c2), set(table_c3.ids),
|
|
"Only the table for 3 should be available as the other is booked")
|
|
self._test_slot_generate_available_resources(self.apt_type_resource, 2, 'UTC', start, end, table_c3, available_resources_c2, reference_date=self.reference_now)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable_performance(self):
|
|
""" Simple use case of appointment type with combinable resources """
|
|
appointment = self.env['appointment.type'].create({
|
|
'appointment_tz': 'UTC',
|
|
'assign_method': 'time_auto_assign',
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 8,
|
|
'name': 'Test',
|
|
'resource_manage_capacity': True,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday()),
|
|
'start_hour': 6,
|
|
'end_hour': 18,
|
|
})],
|
|
})
|
|
|
|
table1_c2, table2_c2, table3_c2 = self.env['appointment.resource'].create([
|
|
{
|
|
'appointment_type_ids': appointment.ids,
|
|
'capacity': 2,
|
|
'name': 'Table %s - 2' % (i + 1),
|
|
} for i in range(3)
|
|
])
|
|
|
|
table1_c4, table2_c4, table3_c4 = self.env['appointment.resource'].create([
|
|
{
|
|
'appointment_type_ids': appointment.ids,
|
|
'capacity': 4,
|
|
'name': 'Table %s - 4' % (i + 1),
|
|
} for i in range(3)
|
|
])
|
|
|
|
table1_c6 = self.env['appointment.resource'].create({
|
|
'appointment_type_ids': appointment.ids,
|
|
'capacity': 6,
|
|
'name': 'Table - 6',
|
|
})
|
|
|
|
(table1_c4 + table2_c4 + table3_c4).linked_resource_ids = table1_c2 + table2_c2 + table3_c2 + table1_c6
|
|
(table1_c2 + table2_c2 + table3_c2).linked_resource_ids = table1_c4 + table2_c4 + table3_c4
|
|
table1_c6.linked_resource_ids = table1_c4 + table2_c4 + table3_c4
|
|
|
|
# Flush everything, notably tracking values, as it may impact performances
|
|
self.flush_tracking()
|
|
|
|
with freeze_time(self.reference_now):
|
|
with self.assertQueryCount(default=12):
|
|
slots = appointment._get_appointment_slots('UTC')
|
|
resource_slots = self._filter_appointment_slots(
|
|
slots,
|
|
filter_weekdays=[0],
|
|
)
|
|
table1_c2_slots = self._filter_appointment_slots(
|
|
slots,
|
|
filter_weekdays=[0],
|
|
filter_resources=table1_c2,
|
|
)
|
|
self.assertTrue(len(resource_slots) > 0)
|
|
self.assertEqual(len(resource_slots), len(table1_c2_slots))
|
|
with self.assertQueryCount(default=4):
|
|
slots = appointment._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots = self._filter_appointment_slots(
|
|
slots,
|
|
filter_weekdays=[0],
|
|
)
|
|
table1_c2_slots = self._filter_appointment_slots(
|
|
slots,
|
|
filter_weekdays=[0],
|
|
filter_resources=table1_c2,
|
|
)
|
|
table1_c2_c4_slots = self._filter_appointment_slots(
|
|
slots,
|
|
filter_weekdays=[0],
|
|
filter_resources=(table1_c2 + table1_c4),
|
|
)
|
|
self.assertTrue(len(resource_slots) > 0)
|
|
self.assertEqual(len(table1_c2_slots), 0)
|
|
self.assertEqual(len(resource_slots), len(table1_c2_c4_slots))
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_combinable_with_time_resource(self):
|
|
""" Check that the last resource available is correctly computed with linked resources """
|
|
table_c2, table_c3 = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table for 2',
|
|
'sequence': 1,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 3,
|
|
'name': 'Table for 3',
|
|
'sequence': 2,
|
|
}])
|
|
table_c2.linked_resource_ids = table_c3
|
|
self.apt_type_resource.assign_method = 'time_resource'
|
|
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=2)
|
|
resource_slots_c2 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c2 = [resource['id'] for resource in resource_slots_c2[0]['available_resources']]
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
self.assertEqual(set(available_resources_c2), set((table_c2 + table_c3).ids),
|
|
"Both resources should be available as the asked capacity is available in both")
|
|
self._test_slot_generate_available_resources(self.apt_type_resource, 2, 'UTC', start, end, table_c2 + table_c3, available_resources_c2, reference_date=self.reference_now)
|
|
self.assertEqual(set(available_resources_c5), set((table_c2 + table_c3).ids),
|
|
"Both resources should be available as the asked capacity correspond to the remaining total")
|
|
self._test_slot_generate_available_resources(self.apt_type_resource, 5, 'UTC', start, end, table_c2 + table_c3, available_resources_c5, reference_date=self.reference_now)
|
|
|
|
# Create a booking for the first resource
|
|
self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': table_c2.id, 'capacity_reserved': 1, 'capacity_used': 1})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
})
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=2)
|
|
resource_slots_c2 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c2 = [resource['id'] for resource in resource_slots_c2[0]['available_resources']]
|
|
self.assertEqual(set(available_resources_c2), set(table_c3.ids),
|
|
"Only the table for 3 should be remaining as the other is booked")
|
|
self._test_slot_generate_available_resources(self.apt_type_resource, 2, 'UTC', start, end, table_c3, available_resources_c2, reference_date=self.reference_now)
|
|
self.assertEqual(len(resource_slots_c5), 0, "There should not be enough availability for the asked capacity")
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_sequence(self):
|
|
""" Check that the sequence is correctly taken into account when selecting resources for slots """
|
|
|
|
resource_1, resource_2 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 4,
|
|
'name': 'Resource 1',
|
|
'sequence': 5,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 10,
|
|
'name': 'Resource 2',
|
|
'sequence': 10,
|
|
}])
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots_c1 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
available_resources_c1 = [resource['id'] for resource in resource_slots_c1[0]['available_resources']]
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c6 = [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
self.assertTrue(len(resource_slots_c1) > 0)
|
|
self.assertListEqual(available_resources_c1, resource_1.ids)
|
|
self.assertTrue(len(resource_slots_c4) > 0)
|
|
self.assertListEqual(available_resources_c4, resource_1.ids)
|
|
self.assertTrue(len(resource_slots_c6) > 0)
|
|
self.assertListEqual(available_resources_c6, resource_2.ids)
|
|
|
|
resource_2.sequence = 1
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots_c1 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
available_resources_c1 = [resource['id'] for resource in resource_slots_c1[0]['available_resources']]
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c6 = [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
self.assertTrue(len(resource_slots_c1) > 0)
|
|
self.assertListEqual(available_resources_c1, resource_2.ids)
|
|
self.assertTrue(len(resource_slots_c4) > 0)
|
|
self.assertListEqual(available_resources_c4, resource_1.ids)
|
|
self.assertTrue(len(resource_slots_c6) > 0)
|
|
self.assertListEqual(available_resources_c6, resource_2.ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_shareable(self):
|
|
""" Check shareable resources are correctly used """
|
|
|
|
resource, resource_shareable = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 5,
|
|
'name': 'Resource',
|
|
'sequence': 5,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 10,
|
|
'name': 'Resource Shareable',
|
|
'sequence': 10,
|
|
'shareable': True,
|
|
}])
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c6 = [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c4, resource.ids)
|
|
self.assertListEqual(available_resources_c6, resource_shareable.ids)
|
|
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
self.env['calendar.event'].with_context(self._test_context).create([{
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_shareable.id, 'capacity_reserved': 4, 'capacity_used': 4})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
}, {
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_shareable.id, 'capacity_reserved': 2, 'capacity_used': 2})],
|
|
'name': 'Booking 2',
|
|
'start': start,
|
|
'stop': end,
|
|
}])
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=6)
|
|
resource_slots_c6 = self._filter_appointment_slots(slots)
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
available_resources_c6 = resource_slots_c6 and [resource['id'] for resource in resource_slots_c6[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c4, resource.ids)
|
|
self.assertListEqual(available_resources_c5, resource.ids)
|
|
self.assertListEqual(available_resources_c6, [])
|
|
|
|
resource_shareable.sequence = 1
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
resource_slots_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c4 = [resource['id'] for resource in resource_slots_c4[0]['available_resources']]
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c4, resource_shareable.ids)
|
|
self.assertListEqual(available_resources_c5, resource.ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_shareable_linked_with_capacity(self):
|
|
""" Check that resources shareable linked together with the capacity management
|
|
correctly compute the remaining capacity """
|
|
|
|
resource_1, resource_2 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 5,
|
|
'name': 'Resource 1',
|
|
'sequence': 5,
|
|
'shareable': True,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Resource 2',
|
|
'sequence': 10,
|
|
'shareable': True,
|
|
}])
|
|
|
|
resource_1.linked_resource_ids = resource_2
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=7)
|
|
resource_slots_c7 = self._filter_appointment_slots(slots)
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
available_resources_c7 = [resource['id'] for resource in resource_slots_c7[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c5, resource_1.ids,
|
|
"Should pick the first resource as it's a best match")
|
|
self.assertListEqual(available_resources_c7, (resource_1 + resource_2).ids,
|
|
"Should pick both resources as asked capacity exceeds each resource capacity")
|
|
|
|
# Create a booking for the first resource for all its capacity
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_1.id, 'capacity_reserved': 5, 'capacity_used': 5})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
})
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
resource_slots_c5 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=7)
|
|
resource_slots_c7 = self._filter_appointment_slots(slots)
|
|
available_resources_c5 = [resource['id'] for resource in resource_slots_c5[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c5, resource_2.ids,
|
|
"Should pick the second resource as the first one is already taken")
|
|
self.assertEqual(len(resource_slots_c7), 0,
|
|
"There should not be enough capacity remaining for the asked capacity")
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_shareable_performance(self):
|
|
"""" Simple use case with shareable resources """
|
|
appointment = self.env['appointment.type'].create({
|
|
'appointment_tz': 'UTC',
|
|
'assign_method': 'time_auto_assign',
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 8,
|
|
'name': 'Test',
|
|
'resource_manage_capacity': True,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday()),
|
|
'start_hour': 6,
|
|
'end_hour': 18,
|
|
})],
|
|
})
|
|
|
|
self.env['appointment.resource'].create([
|
|
{
|
|
'appointment_type_ids': appointment.ids,
|
|
'capacity': 5,
|
|
'name': 'Resource %s' % i,
|
|
'shareable': True,
|
|
} for i in range(20)
|
|
])
|
|
# Flush everything, notably tracking values, as it may impact performances
|
|
self.flush_tracking()
|
|
|
|
with freeze_time(self.reference_now):
|
|
with self.assertQueryCount(default=12):
|
|
appointment._get_appointment_slots('UTC')
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_restrict_resources(self):
|
|
"""" Check that resources restricted on slots are taken into account """
|
|
appointment = self.env['appointment.type'].create({
|
|
'appointment_tz': 'UTC',
|
|
'assign_method': 'time_resource', # easier to check all resources available for each slot
|
|
'min_schedule_hours': 1.0,
|
|
'max_schedule_days': 5,
|
|
'name': 'Test',
|
|
'resource_manage_capacity': True,
|
|
'schedule_based_on': 'resources',
|
|
'slot_ids': [(0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday()),
|
|
'start_hour': 15,
|
|
'end_hour': 16,
|
|
}), (0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday() + 1),
|
|
'start_hour': 15,
|
|
'end_hour': 16,
|
|
}), (0, 0, {
|
|
'weekday': str(self.reference_monday.isoweekday() + 2),
|
|
'start_hour': 15,
|
|
'end_hour': 16,
|
|
})],
|
|
})
|
|
|
|
resource1, resource2, resource3 = self.env['appointment.resource'].create([
|
|
{
|
|
'appointment_type_ids': appointment.ids,
|
|
'name': 'Resource %s' % i,
|
|
} for i in range(3)
|
|
])
|
|
|
|
appointment.slot_ids[0].restrict_to_resource_ids = resource1.ids
|
|
appointment.slot_ids[1].restrict_to_resource_ids = (resource2 + resource3).ids
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = appointment._get_appointment_slots('UTC')
|
|
monday_slots = self._filter_appointment_slots(slots, filter_weekdays=[0])
|
|
tuesday_slots = self._filter_appointment_slots(slots, filter_weekdays=[1])
|
|
wednesday_slots = self._filter_appointment_slots(slots, filter_weekdays=[2])
|
|
available_resources_monday = [resource['id'] for resource in monday_slots[0]['available_resources']]
|
|
available_resources_tuesday = [resource['id'] for resource in tuesday_slots[0]['available_resources']]
|
|
available_resources_wednesday = [resource['id'] for resource in wednesday_slots[0]['available_resources']]
|
|
self.assertListEqual(available_resources_monday, resource1.ids)
|
|
self.assertListEqual(available_resources_tuesday, (resource2 + resource3).ids)
|
|
self.assertListEqual(available_resources_wednesday, (resource1 + resource2 + resource3).ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_assign_time_resource(self):
|
|
""" Check that all resources are available with time_resource assign method. """
|
|
self.apt_type_resource.assign_method = 'time_resource'
|
|
|
|
nordic, scandinavian, snow = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 4,
|
|
'name': 'Nordic',
|
|
'sequence': 2,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 8,
|
|
'name': 'Scandinavian',
|
|
'sequence': 3,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Snow',
|
|
'sequence': 4,
|
|
}])
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
available_resources_c1 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=4)
|
|
available_resources_c4 = self._filter_appointment_slots(slots)
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC', asked_capacity=5)
|
|
available_resources_c5 = self._filter_appointment_slots(slots)
|
|
available_resources_c1 = [resource['id'] for resource in available_resources_c1[0]['available_resources']]
|
|
available_resources_c4 = [resource['id'] for resource in available_resources_c4[0]['available_resources']]
|
|
available_resources_c5 = [resource['id'] for resource in available_resources_c5[0]['available_resources']]
|
|
self.assertListEqual(available_resources_c1, (nordic + scandinavian + snow).ids,
|
|
"All resources should be available with asked_capacity=1")
|
|
self.assertListEqual(available_resources_c4, (nordic + scandinavian + snow).ids,
|
|
"All resources should be available, the perfect matches are ignored with time_resource assign method")
|
|
self.assertListEqual(available_resources_c5, (scandinavian + snow).ids)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_booked_for_all_appointments(self):
|
|
""" Check that a resource can only be booked once, even if shared among appointment_types """
|
|
paddle_court = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 1,
|
|
'name': 'Paddle Court',
|
|
}])
|
|
self.apt_type_resource_2 = self.apt_type_resource.copy()
|
|
|
|
# Assert initial data
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource_2._get_appointment_slots('UTC')
|
|
resource_slots = self._filter_appointment_slots(slots)
|
|
self.assertEqual(len(resource_slots), 1)
|
|
|
|
# Resource is booked on its slot on appointment_type_resource
|
|
self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {
|
|
'appointment_resource_id': paddle_court.id,
|
|
'capacity_reserved': 1,
|
|
'capacity_used': paddle_court.capacity
|
|
})],
|
|
'name': 'Booking 1',
|
|
'start': datetime(2022, 2, 14, 15, 0, 0),
|
|
'stop': datetime(2022, 2, 14, 15, 0, 0) + timedelta(hours=1),
|
|
})
|
|
|
|
# Check other appointment_type availabilities
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource_2._get_appointment_slots('UTC')
|
|
resource_slots = self._filter_appointment_slots(slots)
|
|
|
|
self.assertEqual(len(resource_slots), 0, "Once a resource is booked on a slot, it should not be available anymore to other appointment types.")
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_duplicate(self):
|
|
""" Check that we can correctly duplicate resource bookings """
|
|
table, other_table = self.env["appointment.resource"].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 2,
|
|
'name': 'Table for 2',
|
|
'sequence': 1,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 4,
|
|
'name': 'Table for 4',
|
|
'sequence': 2,
|
|
}])
|
|
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
booking = self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [
|
|
(0, 0, {'appointment_resource_id': table.id, 'capacity_reserved': 2, 'capacity_used': 2}),
|
|
(0, 0, {'appointment_resource_id': other_table.id, 'capacity_reserved': 3}),
|
|
],
|
|
'name': 'Resource booking',
|
|
'start': start,
|
|
'stop': end,
|
|
})
|
|
|
|
duplicate = booking.copy()
|
|
self.assertEqual(booking.name, duplicate.name, "Resource booking should have duplicated correctly")
|
|
self.assertListEqual(booking.resource_ids.ids, duplicate.resource_ids.ids, "Resources should be the same")
|
|
self.assertEqual(booking.resource_total_capacity_used, duplicate.resource_total_capacity_used, "Capacity used should be the same")
|
|
self.assertEqual(booking.resource_total_capacity_reserved, duplicate.resource_total_capacity_reserved, "Capacity reserved should be the same")
|
|
self.assertListEqual(
|
|
[bl.capacity_reserved for bl in booking.booking_line_ids],
|
|
[bl.capacity_reserved for bl in duplicate.booking_line_ids],
|
|
"Capacity reserved should be the same for each booking line",
|
|
)
|
|
|
|
@users('apt_manager')
|
|
def test_appointment_resources_without_capacity_management(self):
|
|
""" Check use case where capacity management is not activated """
|
|
|
|
self.apt_type_resource.resource_manage_capacity = False
|
|
|
|
resource_1, resource_2, resource_3 = self.env['appointment.resource'].create([{
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 6,
|
|
'name': 'Resource 1',
|
|
'sequence': 1,
|
|
'shareable': True,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 4,
|
|
'name': 'Resource 2',
|
|
'sequence': 2,
|
|
}, {
|
|
'appointment_type_ids': self.apt_type_resource.ids,
|
|
'capacity': 1,
|
|
'name': 'Resource 3',
|
|
'sequence': 3
|
|
}])
|
|
|
|
resource_2.linked_resource_ids = resource_3
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots = self._filter_appointment_slots(slots)
|
|
available_resources = [resource['id'] for resource in resource_slots[0]['available_resources']]
|
|
self.assertListEqual(available_resources, resource_1.ids)
|
|
|
|
start = datetime(2022, 2, 14, 15, 0, 0)
|
|
end = start + timedelta(hours=1)
|
|
booking = self.env['calendar.event'].with_context(self._test_context).create({
|
|
'appointment_type_id': self.apt_type_resource.id,
|
|
'booking_line_ids': [(0, 0, {'appointment_resource_id': resource_1.id, 'capacity_reserved': 1, 'capacity_used': resource_1.capacity})],
|
|
'name': 'Booking 1',
|
|
'start': start,
|
|
'stop': end,
|
|
})
|
|
|
|
self.assertEqual(booking.booking_line_ids.capacity_reserved, 1)
|
|
self.assertEqual(booking.booking_line_ids.capacity_used, 6,
|
|
"When we don't manage capacity, the shareable option should be ignored")
|
|
|
|
with freeze_time(self.reference_now):
|
|
slots = self.apt_type_resource._get_appointment_slots('UTC')
|
|
resource_slots = self._filter_appointment_slots(slots)
|
|
available_resources = [resource['id'] for resource in resource_slots[0]['available_resources']]
|
|
self.assertListEqual(available_resources, resource_2.ids,
|
|
"The first resource should now be unavailable and the second one is chosen")
|