1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/appointment/tests/common.py
2024-12-10 09:04:09 +07:00

281 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
from contextlib import contextmanager
from datetime import date, datetime
from unittest.mock import patch
from odoo.addons.appointment.models.res_partner import Partner
from odoo.addons.calendar.models.calendar_event import Meeting
from odoo.addons.resource.models.resource_calendar import ResourceCalendar
from odoo.addons.mail.tests.common import mail_new_test_user, MailCommon
from odoo.tests import common
class AppointmentCommon(MailCommon, common.HttpCase):
@classmethod
def setUpClass(cls):
super(AppointmentCommon, cls).setUpClass()
# ensure admin configuration
cls.admin_user = cls.env.ref('base.user_admin')
cls.admin_user.write({
'country_id': cls.env.ref('base.be').id,
'login': 'admin',
'notification_type': 'inbox',
'tz': "Europe/Brussels",
})
cls.company_admin = cls.admin_user.company_id
# set country in order to format Belgian numbers
cls.company_admin.write({
'country_id': cls.env.ref('base.be').id,
})
# reference dates to have reproducible tests (sunday evening, allowing full week)
cls.reference_now = datetime(2022, 2, 13, 20, 0, 0)
cls.reference_monday = datetime(2022, 2, 14, 7, 0, 0)
cls.reference_now_monthweekstart = date(2022, 1, 30) # starts on a Sunday, first week containing Feb day
cls.global_slots_enddate = date(2022, 3, 5) # last day of last week of February
cls.apt_manager = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
email='apt_manager@test.example.com',
groups='base.group_user,appointment.group_appointment_manager',
name='Appointment Manager',
notification_type='email',
login='apt_manager',
tz='Europe/Brussels'
)
cls.staff_user_bxls = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
email='brussels@test.example.com',
groups='base.group_user',
name='Employee Brussels',
notification_type='email',
login='staff_user_bxls',
tz='Europe/Brussels' # UTC + 1 (at least in February)
)
cls.staff_user_aust = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
email='australia@test.example.com',
groups='base.group_user',
name='Employee Australian',
notification_type='email',
login='staff_user_aust',
tz='Australia/Perth' # UTC + 8 (at least in February)
)
cls.staff_user_nz = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
email='new_zealand@test.example.com',
groups='base.group_user',
name='Employee New Zealand',
notification_type='email',
login='staff_user_nz',
tz='Pacific/Auckland' # UTC + 12
)
cls.staff_users = cls.staff_user_bxls + cls.staff_user_aust + cls.staff_user_nz
# Default (test) appointment type
# Slots are each hours from 8 to 13 (UTC + 1)
# -> working hours: 7, 8, 9, 10 and 12 UTC as 11 is lunch time in working hours
cls.apt_type_bxls_2days = cls.env['appointment.type'].create({
'appointment_tz': 'Europe/Brussels',
'appointment_duration': 1,
'assign_method': 'time_auto_assign',
'category': 'recurring',
'location_id': cls.staff_user_bxls.partner_id.id,
'name': 'Bxls Appt Type',
'max_schedule_days': 15,
'min_cancellation_hours': 1,
'min_schedule_hours': 1,
'slot_ids': [
(0, False, {'weekday': weekday,
'start_hour': hour,
'end_hour': hour + 1,
})
for weekday in ['1', '2']
for hour in range(8, 14)
],
'staff_user_ids': [(4, cls.staff_user_bxls.id)],
})
cls.apt_type_resource = cls.env['appointment.type'].create({
'appointment_tz': 'UTC',
'assign_method': 'time_auto_assign',
'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(cls.reference_monday.isoweekday()),
'start_hour': 15,
'end_hour': 16,
})],
})
def _test_url_open(self, url):
""" Call url_open with nocache parameter """
url += ('?' not in url and '?' or '&') + 'nocache'
return self.url_open(url)
def _create_meetings(self, user, time_info, appointment_type_id=None):
return self.env['calendar.event'].with_context(self._test_context).create([
{'allday': allday,
'attendee_ids': [(0, 0, {'partner_id': user.partner_id.id})],
'name': 'Event for %s (%s / %s - %s)' % (user.name, allday, start, stop),
'partner_ids': [(4, user.partner_id.id)],
'start': start,
'stop': stop,
'user_id': user.id,
'appointment_type_id': appointment_type_id
}
for start, stop, allday in time_info
])
def _create_invite_test_data(self):
apt_type_test = self.env['appointment.type'].create({
'name': 'Appointment Test',
})
self.all_apts = self.apt_type_bxls_2days + apt_type_test
self.invite_apt_type_bxls_2days = self.env['appointment.invite'].create({
'appointment_type_ids': self.apt_type_bxls_2days.ids,
})
self.invite_all_apts = self.env['appointment.invite'].create({
'appointment_type_ids': self.all_apts.ids,
})
def _filter_appointment_slots(self, slots, filter_months=False, filter_weekdays=False, filter_users=False, filter_resources=False):
""" Get all the slots info computed.
Can target a part of slots by referencing the expected months or days we want.
:param list slots: slots content computed from _get_appointment_slots() method.
:param list filter_months: list of tuples representing months we want to check
[(2, 2022), ...] where (2, 2022) represents February 2022
:param list filter_weekdays: list of integers of the weekdays we want to check 0 = monday and 6 = sunday
[0, 1, 3] to filter only monday, tuesday and thursday slots
:param recordset filter_users: recordset of users for which we want to get slots when they are available
:return list: [{
'datetime': '2022-02-14 08:00:00',
'duration': '1.0',
'staff_user_id': 21,
'hours': '08:00 - 09:00',
}, ...] """
slots_info = []
for month in slots:
# We use the last day of the first week to be sure that we use the correct month
last_day_first_week = month['weeks'][0][-1]['day']
month_tuple = (last_day_first_week.month, last_day_first_week.year)
if filter_months and month_tuple not in filter_months:
continue
for week in month['weeks']:
for day in week:
if not day['slots'] or (filter_weekdays and day['day'].weekday() not in filter_weekdays):
continue
for slot in day['slots']:
if filter_users and slot.get('staff_user_id') not in filter_users.ids:
continue
if filter_resources:
if any(slot_resource['id'] not in filter_resources.ids for slot_resource in slot.get('available_resources')):
continue
slots_info.append(slot)
return slots_info
def assertSlots(self, slots, exp_months, slots_data):
""" Check slots content. Method to be improved soon, currently doing
only basic checks. """
self.assertEqual(len(slots), len(exp_months), 'Slots: wrong number of covered months')
self.assertEqual(slots[0]['weeks'][0][0]['day'], slots_data['startdate'], 'Slots: wrong starting date')
self.assertEqual(slots[-1]['weeks'][-1][-1]['day'], slots_data['enddate'], 'Slots: wrong ending date')
for month, expected_month in zip(slots, exp_months):
self.assertEqual(month['month'], expected_month['name_formated'])
self.assertEqual(len(month['weeks']), expected_month['weeks_count'])
if not slots_data.get('slots_startdate'): # not all tests are detailed
continue
# global slots configuration
slots_days_leave = slots_data.get('slots_days_leave', [])
slots_enddate = slots_data.get('slots_enddate')
slots_startdate = slots_data.get('slots_startdate')
slots_weekdays_nowork = slots_data.get('slots_weekdays_nowork', [])
# slots specific
slots_start_hours = slots_data.get('slots_start_hours', [])
for week in month['weeks']:
for day in week:
day_date = day['day']
# days linked to "next month" or "previous month" are there for filling but have no slots
is_void = day_date.month != expected_month['month_date'].month
# before reference date: no slots generated (just there to fill up calendar)
is_working = day_date >= slots_startdate
if is_working and slots_enddate:
is_working = day_date <= slots_enddate
if is_working:
is_working = day_date not in slots_days_leave
if is_working:
is_working = day_date.weekday() not in slots_weekdays_nowork
# after end date: no slots generated (just there to fill up calendar)
# standard day: should have slots according to apt type slots hours
if not is_void and is_working:
if day_date in slots_data.get('slots_day_specific', {}):
slot_count = len(slots_data['slots_day_specific'][day_date])
slot_start_hours = [slot['start'] for slot in slots_data['slots_day_specific'][day_date]]
else:
slot_count = len(slots_start_hours)
slot_start_hours = slots_start_hours
self.assertEqual(
len(day['slots']), slot_count,
'Slot: wrong number of slots for %s' % day
)
self.assertEqual(
[datetime.strptime(slot['datetime'], '%Y-%m-%d %H:%M:%S').hour for slot in day['slots']],
slot_start_hours,
'Slot: wrong starting hours'
)
elif is_void:
self.assertFalse(len(day['slots']), 'Slot: out of range should have no slot for %s' % day)
else:
self.assertFalse(len(day['slots']), 'Slot: not worked should have no slot for %s' % day)
def _test_slot_generate_available_resources(self, appointment_type, asked_capacity, timezone, start_dt, end_dt, filter_resources, expected_available_resource_ids, reference_date=None):
""" Simulate the check done after selecting a particular time slot
:param appointment_type: appointment type tested
:param asked_capacity<int>: asked capacity for the appointment
:param timezone<str>: timezone selected
:param start_dt<datetime>: start datetime of the slot (naive UTC)
:param end_dt<datetime>: end datetime of the slot (naive UTC)
:param filter_resources<recordset>: the resources for which the appointment was booked for
:param expected_available_resource_ids<list>: list of resource ids available for the slot we want to check
"""
slots = appointment_type._slots_generate(start_dt.astimezone(pytz.utc), end_dt.astimezone(pytz.utc), timezone, reference_date=reference_date)
slots = [slot for slot in slots if slot['UTC'] == (start_dt.replace(tzinfo=None), end_dt.replace(tzinfo=None))]
appointment_type._slots_fill_resources_availability(slots, start_dt, end_dt, filter_resources=filter_resources, asked_capacity=asked_capacity)
self.assertEqual(set(expected_available_resource_ids), set(slots[0]['available_resource_ids'].ids))
@contextmanager
def mockAppointmentCalls(self):
_original_search = Meeting.search
_original_search_count = Meeting.search_count
_original_calendar_verify_availability = Partner.calendar_verify_availability
_original_work_intervals_batch = ResourceCalendar._work_intervals_batch
with patch.object(Meeting, 'search',
autospec=True, side_effect=_original_search) as mock_ce_search, \
patch.object(Meeting, 'search_count',
autospec=True, side_effect=_original_search_count) as mock_ce_sc, \
patch.object(Partner, 'calendar_verify_availability',
autospec=True, side_effect=_original_calendar_verify_availability) as mock_partner_cal, \
patch.object(ResourceCalendar, '_work_intervals_batch',
autospec=True, side_effect=_original_work_intervals_batch) as mock_cal_wit:
self._mock_calevent_search = mock_ce_search
self._mock_calevent_search_count = mock_ce_sc
self._mock_partner_calendar_check = mock_partner_cal
self._mock_cal_work_intervals = mock_cal_wit
yield