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

1310 lines
59 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
from datetime import date, datetime, timedelta, timezone
from freezegun import freeze_time
from werkzeug.urls import url_encode, url_join
import odoo
from odoo.addons.appointment.tests.common import AppointmentCommon
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
from odoo.exceptions import ValidationError
from odoo.tests import Form, tagged, users
from odoo.tools import mute_logger
from odoo.fields import Command
@tagged('appointment_slots')
class AppointmentTest(AppointmentCommon, HttpCaseWithUserDemo):
@freeze_time('2023-12-12')
@users('apt_manager')
def test_appointment_availability_after_utc_conversion(self):
""" Check that when an event starts the day before,
it doesn't show the date as available for the user.
ie: In the brussels TZ, when placing an event on the 15 dec at 00:15 to 18:00,
the event is stored in the UTC TZ on the 14 dec at 23:15 to 17:00
Because the event start on another day, the 15th was displayed as available.
"""
staff_user = self.staff_users[0]
week_days = [0, 1, 2]
# The user works on mondays, tuesdays, and wednesdays
# Only one hour slot per weekday
self.apt_type_bxls_2days.slot_ids = [(5, 0)] + [(0, 0, {
'weekday': str(week_day + 1),
'start_hour': 8,
'end_hour': 9,
}) for week_day in week_days]
# Available on the 12, 13, 18, 19, 20, 25, 26, 27 dec
max_available_slots = 8
test_data = [
# Test 1, after UTC
# Brussels TZ: 2023-12-19 00:15 to 2023-12-19 18:00 => same day
# UTC TZ: 2023-12-18 23:15 to 2023-12-19 17:00 => different day
(
datetime(2023, 12, 18, 23, 15),
datetime(2023, 12, 19, 17, 0),
max_available_slots - 1,
{date(2023, 12, 19): []},
),
# Test 2, before UTC
# New York TZ: 2023-12-18 10:00 to 2023-12-18 22:00 => same day
# UTC TZ: 2023-12-18 15:00 to 2023-12-19 03:00 => different day
(
datetime(2023, 12, 18, 15, 0),
datetime(2023, 12, 19, 3, 0),
max_available_slots - 0,
{},
),
]
global_slots_startdate = date(2023, 11, 26)
global_slots_enddate = date(2024, 1, 6)
slots_startdate = date(2023, 12, 12)
slots_enddate = slots_startdate + timedelta(days=15)
for start, stop, nb_available_slots, slots_day_specific in test_data:
with self.subTest(start=start, stop=stop, nb_available_slots=nb_available_slots):
event = self.env["calendar.event"].create([
{
"name": "event-1",
"start": start,
"stop": stop,
"show_as": 'busy',
"partner_ids": staff_user.partner_id.ids,
"attendee_ids": [(0, 0, {
"state": "accepted",
"availability": "busy",
"partner_id": staff_user.partner_id.id,
})],
},
])
slots = self.apt_type_bxls_2days._get_appointment_slots(timezone='Europe/Brussels', filter_users=staff_user)
self.assertSlots(
slots,
[{'name_formated': 'December 2023',
'month_date': datetime(2023, 12, 1),
'weeks_count': 6,
}
],
{'startdate': global_slots_startdate,
'enddate': global_slots_enddate,
'slots_startdate': slots_startdate,
'slots_enddate': slots_enddate,
'slots_start_hours': [8],
'slots_weekdays_nowork': range(3, 7),
'slots_day_specific': slots_day_specific,
}
)
available_slots = self._filter_appointment_slots(slots, filter_weekdays=week_days)
self.assertEqual(nb_available_slots, len(available_slots))
event.unlink()
@freeze_time('2023-01-6')
@users('apt_manager')
def test_appointment_availability_with_show_as(self):
""" Checks that if a normal event and custom event both set at the same time but
the normal event is set as free then the custom meeting should be available and
available_unique_slots will contains only available slots """
employee = self.staff_users[0]
self.env["calendar.event"].create([
{
"name": "event-1",
"start": datetime(2023, 6, 5, 10, 10),
"stop": datetime(2023, 6, 5, 11, 11),
"show_as": 'free',
"partner_ids": [(Command.set(employee.partner_id.ids))],
"attendee_ids": [(0, 0, {
"state": "accepted",
"availability": "free",
"partner_id": employee.partner_id.id,
})],
}, {
"name": "event-2",
"start": datetime(2023, 6, 5, 12, 0),
"stop": datetime(2023, 6, 5, 13, 0),
"show_as": 'busy',
"partner_ids": [(Command.set(employee.partner_id.ids))],
"attendee_ids": [(0, 0, {
"state": "accepted",
"availability": "busy",
"partner_id": employee.partner_id.id,
})],
},
])
unique_slots = [{
'allday': False,
'start_datetime': datetime(2023, 6, 5, 10, 10),
'end_datetime': datetime(2023, 6, 5, 11, 11),
}, {
'allday': False,
'start_datetime': datetime(2023, 6, 5, 12, 0),
'end_datetime': datetime(2023, 6, 5, 13, 0),
}]
hour_fifty_float_repr_A = 1.8333333333333335
hour_fifty_float_repr_B = 1.8333333333333333
apt_types = self.env['appointment.type'].create([
{
'category': 'custom',
'name': 'Custom Meeting 1',
'staff_user_ids': [(4, employee.id)],
'slot_ids': [(0, 0, {
'allday': slot['allday'],
'end_datetime': slot['end_datetime'],
'slot_type': 'unique',
'start_datetime': slot['start_datetime'],
}) for slot in unique_slots
],
}, {
'category': 'custom',
'name': 'Custom Meeting 2',
'staff_user_ids': [(4, employee.id)],
'slot_ids': [(0, 0, {
'allday': unique_slots[1]['allday'],
'end_datetime': unique_slots[1]['end_datetime'],
'slot_type': 'unique',
'start_datetime': unique_slots[1]['start_datetime'],
})
],
}, {
'category': 'recurring',
'name': 'Recurring Meeting 3',
'staff_user_ids': [(4, employee.id)],
'appointment_duration': hour_fifty_float_repr_A, # float presenting 1h 50min
'appointment_tz': 'UTC',
'slot_ids': [
(0, False, {
'weekday': '1', # Monday
'start_hour': 8,
'end_hour': 17,
}
)
]
},
])
self.assertTrue(
apt_types[-1]._check_appointment_is_valid_slot(
employee,
0,
0,
'UTC',
datetime(2023, 1, 9, 8, 0, tzinfo=timezone.utc), # First monday in the future
duration=hour_fifty_float_repr_B
),
"Small imprecision on float value for duration should not impact slot validity"
)
slots = apt_types[0]._get_appointment_slots('UTC')
available_unique_slots = self._filter_appointment_slots(
slots,
filter_months=[(6, 2023)],
filter_users=employee)
self.assertEqual(len(available_unique_slots), 1)
for unique_slot, apt_type, is_available in zip(unique_slots, apt_types, [True, False]):
duration = (unique_slot['end_datetime'] - unique_slot['start_datetime']).total_seconds() / 3600
self.assertEqual(
apt_type._check_appointment_is_valid_slot(
employee,
0,
0,
'UTC',
unique_slot['start_datetime'],
duration
),
is_available
)
self.assertEqual(
employee.partner_id.calendar_verify_availability(
unique_slot['start_datetime'],
unique_slot['end_datetime'],
),
is_available
)
@users('apt_manager')
def test_appointment_type_create_anytime(self):
# Any Time: only 1 / employee
apt_type = self.env['appointment.type'].create({
'category': 'anytime',
'name': 'Any time on me',
})
self.assertEqual(apt_type.staff_user_ids, self.apt_manager)
# should be able to create 2 'anytime' appointment types at once on different users
self.env['appointment.type'].create([{
'category': 'anytime',
'name': 'Any on staff user',
'staff_user_ids': [(4, staff_user.id)],
} for staff_user in self.staff_users])
with self.assertRaises(ValidationError):
self.env['appointment.type'].create({
'category': 'anytime',
'name': 'Any time on me, duplicate',
})
with self.assertRaises(ValidationError):
self.env['appointment.type'].create({
'name': 'Any time without employees',
'category': 'anytime',
'staff_user_ids': False
})
with self.assertRaises(ValidationError):
self.env['appointment.type'].create({
'name': 'Any time with multiple employees',
'category': 'anytime',
'staff_user_ids': [(6, 0, self.staff_users.ids)]
})
@users('apt_manager')
def test_appointment_type_create_custom(self):
# Custom: current user set as default
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'name': 'Custom without user',
})
self.assertEqual(apt_type.staff_user_ids, self.apt_manager)
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'staff_user_ids': [(4, self.staff_users[0].id)],
'name': 'Custom with user',
})
self.assertEqual(apt_type.staff_user_ids, self.staff_users[0])
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'staff_user_ids': self.staff_users.ids,
'name': 'Custom with users',
})
self.assertEqual(apt_type.staff_user_ids, self.staff_users)
@mute_logger('odoo.sql_db')
@users('apt_manager')
def test_appointment_slot_start_end_hour_auto_correction(self):
""" Test the autocorrection of invalid intervals [start_hour, end_hour]. """
appt_type = self.env['appointment.type'].create({
'category': 'recurring',
'name': 'Schedule a demo',
'appointment_duration': 1,
'slot_ids': [(0, 0, {
'weekday': '1', # Monday
'start_hour': 9,
'end_hour': 17,
})],
})
appt_form = Form(appt_type)
# invalid interval, no adaptation because start_hour is not changed
with self.assertRaises(ValidationError):
with appt_form.slot_ids.edit(0) as slot_form:
slot_form.end_hour = 8
appt_form.save()
# invalid interval, adapted because start_hour is changed
with appt_form.slot_ids.edit(0) as slot_form:
slot_form.start_hour = 18
self.assertEqual(slot_form.start_hour, 18)
self.assertEqual(slot_form.end_hour, 19)
appt_form.save()
# empty interval, adapted because start_hour is changed
with appt_form.slot_ids.edit(0) as slot_form:
slot_form.start_hour = 19
self.assertEqual(slot_form.start_hour, 19)
self.assertEqual(slot_form.end_hour, 20)
appt_form.save()
# invalid interval, end_hour not adapted [23.5, 19] because it will exceed 24
with self.assertRaises(ValidationError):
with appt_form.slot_ids.edit(0) as slot_form:
slot_form.start_hour = 23.5
appt_form.save()
def test_generate_slots_until_midnight(self):
""" Generate recurring slots until midnight. """
appt_type = self.env['appointment.type'].create({
'category': 'recurring',
'name': 'Schedule a demo',
'max_schedule_days': 1,
'appointment_duration': 1,
'appointment_tz': 'Europe/Brussels',
'slot_ids': [(0, 0, {
'weekday': '1', # Monday
'start_hour': 18,
'end_hour': 0,
})],
'staff_user_ids': [(4, self.staff_user_bxls.id)],
}).with_user(self.env.user)
with freeze_time(self.reference_now):
slots = appt_type._get_appointment_slots('Europe/Brussels')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_start_hours': [18, 19, 20, 21, 22, 23],
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_enddate': self.reference_monday.date(), # only test that day
}
)
@users('apt_manager')
def test_appointment_type_custom_badge(self):
""" Check that the number of previous and next slots in the badge are correctly based on availability """
reference_start = self.reference_monday.replace(microsecond=0)
unique_slots = [{
'allday': True,
'end_datetime': reference_start + timedelta(days=delta_day + 1),
'slot_type': 'unique',
'start_datetime': reference_start + timedelta(days=delta_day),
} for delta_day in (0, 1, 31, 62, 63)]
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'name': 'Custom Appointment Type',
'slot_ids': [(5, 0)] + [(0, 0, slot) for slot in unique_slots],
})
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('UTC')
nb_february_slots = len(self._filter_appointment_slots(
slots,
filter_months=[(2, 2022)],
filter_users=self.apt_manager))
nb_march_slots = len(self._filter_appointment_slots(
slots,
filter_months=[(3, 2022)],
filter_users=self.apt_manager))
nb_april_slots = len(self._filter_appointment_slots(
slots, filter_months=[(4, 2022)],
filter_users=self.apt_manager))
# February month
self.assertEqual(slots[0]['nb_slots_previous_months'], 0)
self.assertEqual(slots[0]['nb_slots_next_months'], nb_march_slots + nb_april_slots)
# March month
self.assertEqual(slots[1]['nb_slots_previous_months'], nb_february_slots)
self.assertEqual(slots[1]['nb_slots_next_months'], nb_april_slots)
# April month
self.assertEqual(slots[2]['nb_slots_previous_months'], nb_february_slots + nb_march_slots)
self.assertEqual(slots[2]['nb_slots_next_months'], 0)
# Create a meeting during the duration of the first slot
self._create_meetings(self.apt_manager, [(
reference_start + timedelta(hours=2),
reference_start + timedelta(hours=3),
False,
)])
previous_nb_feb_slots = nb_february_slots
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('UTC')
nb_february_slots = len(self._filter_appointment_slots(
slots,
filter_months=[(2, 2022)],
filter_users=self.apt_manager))
nb_march_slots = len(self._filter_appointment_slots(
slots,
filter_months=[(3, 2022)],
filter_users=self.apt_manager))
nb_april_slots = len(self._filter_appointment_slots(
slots, filter_months=[(4, 2022)],
filter_users=self.apt_manager))
# February month
self.assertEqual(slots[0]['nb_slots_previous_months'], 0)
self.assertEqual(slots[0]['nb_slots_next_months'], nb_march_slots + nb_april_slots)
self.assertEqual(nb_february_slots, previous_nb_feb_slots - 1)
# March month
self.assertEqual(slots[1]['nb_slots_previous_months'], nb_february_slots)
self.assertEqual(slots[1]['nb_slots_next_months'], nb_april_slots)
# April month
self.assertEqual(slots[2]['nb_slots_previous_months'], nb_february_slots + nb_march_slots)
self.assertEqual(slots[2]['nb_slots_next_months'], 0)
@freeze_time('2023-01-9')
def test_booking_validity(self):
"""
When confirming an appointment, we must recheck that it is indeed a valid slot,
because the user can modify the date URL parameter used to book the appointment.
We make sure the date is a valid slot, not outside of those specified by the employee,
and that it's not an old valid slot (a slot that is valid, but it's in the past,
so we shouldn't be able to book for a date that has already passed)
"""
# add the timezone of the visitor on the session (same as appointment to simplify)
session = self.authenticate(None, None)
session['timezone'] = self.apt_type_bxls_2days.appointment_tz
odoo.http.root.session_store.save(session)
appointment = self.apt_type_bxls_2days
appointment_invite = self.env['appointment.invite'].create({'appointment_type_ids': appointment.ids})
appointment_url = url_join(appointment.get_base_url(), '/appointment/%s' % appointment.id)
appointment_info_url = "%s/info?" % appointment_url
url_inside_of_slot = appointment_info_url + url_encode({
'staff_user_id': self.staff_user_bxls.id,
'date_time': datetime(2023, 1, 9, 9, 0), # 9/01/2023 is a Monday, there is a slot at 9:00
'duration': 1,
**appointment_invite._get_redirect_url_parameters(),
})
response = self.url_open(url_inside_of_slot)
self.assertEqual(response.status_code, 200, "Response should be Ok (200)")
url_outside_of_slot = appointment_info_url + url_encode({
'staff_user_id': self.staff_user_bxls.id,
'date_time': datetime(2023, 1, 9, 22, 0), # 9/01/2023 is a Monday, there is no slot at 22:00
'duration': 1,
**appointment_invite._get_redirect_url_parameters(),
})
response = self.url_open(url_outside_of_slot)
self.assertEqual(response.status_code, 404, "Response should be Page Not Found (404)")
url_inactive_past_slot = appointment_info_url + url_encode({
'staff_user_id': self.staff_user_bxls.id,
'date_time': datetime(2023, 1, 2, 22, 0),
# 2/01/2023 is a Monday, there is a slot at 9:00, but that Monday has already passed
'duration': 1,
**appointment_invite._get_redirect_url_parameters(),
})
response = self.url_open(url_inactive_past_slot)
self.assertEqual(response.status_code, 404, "Response should be Page Not Found (404)")
@freeze_time('2023-04-23')
def test_booking_validity_timezone(self):
"""
When the utc offset of the timezone is large, it is possible that the day of the week no longer corresponds.
It is necessary to take this into account when checking the slots.
"""
appointment = self.env['appointment.type'].create({
'appointment_tz': 'Pacific/Auckland',
'appointment_duration': 1,
'assign_method': 'time_auto_assign',
'category': 'recurring',
'location_id': self.staff_user_nz.partner_id.id,
'name': 'New Zealand Appointment',
'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']
for hour in range(9, 12)
],
'staff_user_ids': [(4, self.staff_user_nz.id)],
})
session = self.authenticate(None, None)
session['timezone'] = appointment.appointment_tz
odoo.http.root.session_store.save(session)
appointment_invite = self.env['appointment.invite'].create({'appointment_type_ids': appointment.ids})
appointment_url = url_join(appointment.get_base_url(), '/appointment/%s' % appointment.id)
appointment_info_url = "%s/info?" % appointment_url
url = appointment_info_url + url_encode({
'staff_user_id': self.staff_user_nz.id,
'date_time': datetime(2023, 4, 24, 9, 0),
'duration': 1,
**appointment_invite._get_redirect_url_parameters(),
})
response = self.url_open(url)
self.assertEqual(response.status_code, 200, "Response should be Ok (200)")
def test_exclude_all_day_events(self):
""" Ensure appointment slots don't overlap with "busy" allday events. """
staff_user = self.staff_users[0]
valentime = datetime(2022, 2, 14, 0, 0) # 2022-02-14 is a Monday
slots = self.apt_type_bxls_2days._get_appointment_slots(
self.apt_type_bxls_2days.appointment_tz,
reference_date=valentime,
)
slot = slots[0]['weeks'][2][1]
self.assertEqual(slot['day'], valentime.date())
self.assertTrue(slot['slots'], "Should be available on 2022-02-14")
self.env['calendar.event'].with_user(staff_user).create({
'name': "Valentine's day",
'start': valentime,
'stop': valentime,
'allday': True,
'show_as': 'busy',
'attendee_ids': [(0, 0, {
'state': 'accepted',
'availability': 'busy',
'partner_id': staff_user.partner_id.id,
})],
})
slots = self.apt_type_bxls_2days._get_appointment_slots(
self.apt_type_bxls_2days.appointment_tz,
reference_date=valentime,
)
slot = slots[0]['weeks'][2][1]
self.assertEqual(slot['day'], valentime.date())
self.assertFalse(slot['slots'], "Shouldn't be available on 2022-02-14")
@users('apt_manager')
def test_customer_event_description(self):
"""Check calendar file description and summary generation."""
appointment_type = self.apt_type_bxls_2days
host_user = self.apt_manager
host_partner = host_user.partner_id
message_confirmation = '<p>Please try to be there <strong>5 minutes</strong> before the time.<p><br>Thank you.'
host_name = 'Appointment Manager'
host_mail = 'manager@appointments.lan'
host_phone = '2519475531'
def _set_values(message=message_confirmation, name=host_name, mail=host_mail, phone=host_phone):
appointment_type.message_confirmation = message
host_partner.write({
'name': name,
'email': mail,
'phone': phone,
})
attendee = self.env['res.partner'].sudo().create({
'name': 'John Doe',
})
appointment = self.env['calendar.event'].create({
'name': '%s with %s' % (appointment_type.name, attendee.name),
'start': datetime.now(),
'start_date': datetime.now(),
'stop': datetime.now() + timedelta(hours=1),
'allday': False,
'duration': appointment_type.appointment_duration,
'description': "<p>Test</p>",
'location': appointment_type.location,
'partner_ids': [odoo.Command.link(partner.id) for partner in [attendee, host_partner]],
'appointment_type_id': appointment_type.id,
'user_id': host_user.id,
})
# sanity check with a simple test
_set_values()
self.assertEqual(appointment._get_customer_description(),
'Please try to be there *5 minutes* before the time.\nThank you.\n\n'
'Contact Details:\n'
'Appointment Manager\nEmail: manager@appointments.lan\nPhone: 2519475531')
for changes in [{},
{'message': False},
{'name': ''}, {'mail': False}, {'phone': False},
{'name': '', 'mail': False, 'phone': False},
{'message': False, 'name': '', 'mail': False, 'phone': False}]:
_set_values(**changes)
message = ''
details = ''
if appointment_type.message_confirmation:
message = 'Please try to be there *5 minutes* before the time.\nThank you.\n\n'
if host_partner.name or host_partner.email or host_partner.phone:
details = '\n'.join(line for line in (
'Contact Details:',
host_partner.name,
f'Email: {host_partner.email}' if host_partner.email else False,
f'Phone: {host_partner.phone}' if host_partner.phone else False)
if line)
self.assertEqual(appointment._get_customer_description(), (message + details).strip())
# Test summary for all appointment types
resource_appointment = self.apt_type_resource
resource_event = self.env['calendar.event'].create({
'name': '%s - %s' % (resource_appointment.name, attendee.name),
'start': datetime.now(),
'stop': datetime.now() + timedelta(hours=1),
'appointment_type_id': resource_appointment.id,
'user_id': host_user.id,
})
user_summary = f'{appointment_type.name} with {host_partner.name or "somebody"}'
for event, summary in ((appointment, user_summary), (resource_event, resource_event.name)):
with self.subTest(summary=summary):
self.assertEqual(event._get_customer_summary(), summary)
@users('apt_manager')
@freeze_time('2022-02-13T20:00:00')
def test_generate_slots_punctual_appointment_type(self):
""" Generates recurring slots, check begin and end slot boundaries depending on the start and end datetimes. """
apt_type = self.env['appointment.type'].create({
'appointment_tz': 'Europe/Brussels',
'appointment_duration': 1,
'assign_method': 'time_auto_assign',
'category': 'punctual',
'location_id': self.staff_user_bxls.partner_id.id,
'name': 'Punctual Appt Type',
'max_schedule_days': False,
'min_cancellation_hours': 1,
'min_schedule_hours': 1,
'start_datetime': datetime(2022, 2, 14, 8, 0, 0),
'end_datetime': datetime(2022, 2, 20, 20, 0, 0),
'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, self.staff_user_bxls.id)],
}).with_user(self.env.user)
slots_weekdays = {slot.weekday for slot in apt_type.slot_ids}
timezone = 'Europe/Brussels'
requested_tz = pytz.timezone(timezone)
# reference_now: datetime(2022, 2, 13, 20, 0, 0) (sunday evening)
# apt slot_ids: Monday 8AM -> 2PM, Tuesday 8AM -> 2PM
cases = [
# start datetime / end_datetime (UTC)
(datetime(2022, 2, 14, 9, 0, 0), datetime(2022, 2, 25, 9, 0, 0)), # Slots fully in the future
(datetime(2022, 2, 1, 9, 0, 0), datetime(2022, 2, 25, 9, 0, 0)), # start_datetime < now < end_datetimes
(datetime(2022, 2, 1, 9, 0, 0), datetime(2022, 2, 12, 9, 0, 0)), # Slots fully in the past
]
expected = [
# first slot start datetime / last slot end_datetime (UTC)
(datetime(2022, 2, 14, 9, 0, 0), datetime(2022, 2, 22, 13, 0, 0)), # start = specified start_datetime
(datetime(2022, 2, 14, 7, 0, 0), datetime(2022, 2, 22, 13, 0, 0)), # start = 8AM from apt slots_ids converted to UTC
(False, False)
]
for (start_datetime, end_datetime), (first_slot_expected_start, last_slot_expected_end) in zip(cases, expected):
with self.subTest(start_datetime=start_datetime, end_datetime=end_datetime):
apt_type.write({'start_datetime': start_datetime, 'end_datetime': end_datetime})
reference_date = start_datetime if start_datetime > self.reference_now else self.reference_now
first_day = requested_tz.fromutc(reference_date)
last_day = requested_tz.fromutc(end_datetime)
slots = apt_type._slots_generate(first_day, last_day, timezone, reference_date=reference_date)
if not slots:
self.assertFalse(first_slot_expected_start)
self.assertFalse(last_slot_expected_end)
continue
self.assertTrue({slot['slot'].weekday for slot in slots}.issubset(slots_weekdays), 'Slots: wrong weekday')
self.assertEqual(slots[0]['UTC'][0], first_slot_expected_start, 'Slots: wrong first slot start datetime')
self.assertEqual(slots[-1]['UTC'][1], last_slot_expected_end, 'Slots: wrong last slot end datetime')
@users('apt_manager')
def test_generate_slots_recurring(self):
""" Generates recurring slots, check begin and end slot boundaries. """
apt_type = self.apt_type_bxls_2days.with_user(self.env.user)
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('Europe/Brussels')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_start_hours': [8, 9, 10, 11, 12, 13], # based on appointment type start hours of slots, no work hours / no meetings / no leaves
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
@users('apt_manager')
def test_generate_slots_recurring_start_hour_day_overflow(self):
""" Generates recurring slots, make sure we don't overshoot the current day and generate meaningless slots """
slots = [{
'weekday': '1',
'start_hour': 9.0,
'end_hour': 10.0,
}, {
'weekday': '1',
'start_hour': 10.0,
'end_hour': 11.0,
}, {
'weekday': '1',
'start_hour': 15.0,
'end_hour': 16.0,
}, {
'weekday': '2',
'start_hour': 9.0,
'end_hour': 17.0,
},]
apt_type = self.env['appointment.type'].create({
'appointment_duration': 1.0,
'appointment_tz': 'Europe/Brussels',
'category': 'recurring',
'name': 'Overflow Appointment',
'max_schedule_days': 8,
'min_schedule_hours': 12.0,
'slot_ids': [(0, 0, slot) for slot in slots],
'staff_user_ids': [self.env.user.id],
})
# Check around the 11AM(Brussels) mark, or 15:30PM(Kolkata)
# If we add 12 for the minimum schedule hour it's past 11PM
# Past 11 the appointment duration will put us past the current day
brussels_tz = pytz.timezone('Europe/Brussels')
for hour, minute in [[h, m] for h in [2, 9, 10, 11, 12] for m in [0, 1, 59]]:
time = brussels_tz.localize(self.reference_monday.replace(hour=hour, minute=minute))
with freeze_time(time):
slots = apt_type._get_appointment_slots('Asia/Kolkata')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': date(2022, 3, 5),
'startdate': self.reference_now_monthweekstart,
'slots_day_specific': { # +4 instead of +4.5 because the test method only accounts for the absolute hour
time.date(): [{'start': 15 + 4, 'end': 16 + 4}] if hour == 2 else [], # min_schedule_hours is too large
(time + timedelta(days=1)).date(): [{'start': start + 4, 'end': start + 5} for start in range(9, 17)],
(time + timedelta(days=7)).date(): [{'start': start + 4, 'end': start + 5} for start in range(9, 11)] + [{'start': 15 + 4, 'end': 16 + 4}],
(time + timedelta(days=8)).date(): [{'start': start + 4, 'end': start + 5} for start in range(9, 17)],
},
'slots_start_hours': [],
'slots_startdate': time.date(),
'slots_weekdays_nowork': range(2, 7)
},
)
@users('apt_manager')
def test_generate_slots_recurring_UTC(self):
""" Generates recurring slots, check begin and end slot boundaries. Force
UTC results event if everything is Europe/Brussels based. """
apt_type = self.apt_type_bxls_2days.with_user(self.env.user)
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('UTC')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_start_hours': [7, 8, 9, 10, 11, 12], # based on appointment type start hours of slots, no work hours / no meetings / no leaves
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
@users('admin')
def test_generate_slots_recurring_westrict(self):
""" Generates recurring slots, check user restrictions """
apt_type = self.apt_type_bxls_2days.with_user(self.env.user)
# add second staff user and split days based on the two people
apt_type.write({'staff_user_ids': [(4, self.staff_user_aust.id)]})
apt_type.slot_ids.filtered(lambda slot: slot.weekday == '1').write({
'restrict_to_user_ids': [(4, self.staff_user_bxls.id)],
})
apt_type.slot_ids.filtered(lambda slot: slot.weekday != '1').write({
'restrict_to_user_ids': [(4, self.staff_user_aust.id)],
})
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('Europe/Brussels')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_start_hours': [8, 9, 10, 11, 12, 13], # based on appointment type start hours of slots, no work hours / no meetings / no leaves
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
# check staff_user_id
monday_slots = [
slot
for month in slots for week in month['weeks'] for day in week
for slot in day['slots']
if day['day'].weekday() == 0
]
tuesday_slots = [
slot
for month in slots for week in month['weeks'] for day in week
for slot in day['slots']
if day['day'].weekday() == 1
]
self.assertEqual(len(monday_slots), 18, 'Slots: 3 mondays of 6 slots')
self.assertTrue(all(slot['staff_user_id'] == self.staff_user_bxls.id for slot in monday_slots))
self.assertEqual(len(tuesday_slots), 12, 'Slots: 2 tuesdays of 6 slots (3rd tuesday is out of range')
self.assertTrue(all(slot['staff_user_id'] == self.staff_user_aust.id for slot in tuesday_slots))
@users('apt_manager')
def test_generate_slots_recurring_wmeetings(self):
""" Generates recurring slots, check begin and end slot boundaries
with leaves involved. """
apt_type = self.apt_type_bxls_2days.with_user(self.env.user)
# create meetings
_meetings = self._create_meetings(
self.staff_user_bxls,
[(self.reference_monday + timedelta(days=1), # 3 hours first Tuesday
self.reference_monday + timedelta(days=1, hours=3),
False
),
(self.reference_monday + timedelta(days=7), # next Monday: one full day
self.reference_monday + timedelta(days=7, hours=1),
True,
),
(self.reference_monday + timedelta(days=8, hours=2), # 1 hour next Tuesday (9 UTC)
self.reference_monday + timedelta(days=8, hours=3),
False,
),
(self.reference_monday + timedelta(days=8, hours=3), # 1 hour next Tuesday (10 UTC, declined)
self.reference_monday + timedelta(days=8, hours=4),
False,
),
(self.reference_monday + timedelta(days=8, hours=5), # 2 hours next Tuesday (12 UTC)
self.reference_monday + timedelta(days=8, hours=7),
False,
),
]
)
attendee = _meetings[-2].attendee_ids.filtered(lambda att: att.partner_id == self.staff_user_bxls.partner_id)
attendee.do_decline()
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('Europe/Brussels')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_day_specific': {
(self.reference_monday + timedelta(days=1)).date(): [
{'end': 12, 'start': 11},
{'end': 13, 'start': 12},
{'end': 14, 'start': 13},
], # meetings on 7-10 UTC
(self.reference_monday + timedelta(days=7)).date(): [], # on meeting "allday"
(self.reference_monday + timedelta(days=8)).date(): [
{'end': 9, 'start': 8},
{'end': 10, 'start': 9},
{'end': 12, 'start': 11},
{'end': 13, 'start': 12},
], # meetings 9-10 and 12-14
},
'slots_start_hours': [8, 9, 10, 11, 12, 13], # based on appointment type start hours of slots, no work hours / no meetings / no leaves
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
@users('apt_manager')
def test_generate_slots_unique(self):
""" Check unique slots (note: custom appointment type does not check working
hours). """
unique_slots = [{
'start_datetime': self.reference_monday.replace(microsecond=0),
'end_datetime': (self.reference_monday + timedelta(hours=1)).replace(microsecond=0),
'allday': False,
}, {
'start_datetime': (self.reference_monday + timedelta(days=1)).replace(microsecond=0),
'end_datetime': (self.reference_monday + timedelta(days=2)).replace(microsecond=0),
'allday': True,
}]
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'name': 'Custom with unique slots',
'slot_ids': [(5, 0)] + [
(0, 0, {'allday': slot['allday'],
'end_datetime': slot['end_datetime'],
'slot_type': 'unique',
'start_datetime': slot['start_datetime'],
}
) for slot in unique_slots
],
})
self.assertEqual(apt_type.category, 'custom', "It should be a custom appointment type")
self.assertEqual(apt_type.staff_user_ids, self.apt_manager)
self.assertEqual(len(apt_type.slot_ids), 2, "Two slots should have been assigned to the appointment type")
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('Europe/Brussels')
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_day_specific': {
self.reference_monday.date(): [{'end': 9, 'start': 8}], # first unique 1 hour long
(self.reference_monday + timedelta(days=1)).date(): [{'allday': True, 'end': False, 'start': 8}], # second unique all day-based
},
'slots_start_hours': [], # all slots in this tests are unique, other dates have no slots
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
@users('apt_manager')
def test_multi_user_slot_availabilities(self):
""" Check that when called with no user / one user / several users, the methods computing the slots work as expected:
if no user is set, all users of the appointment_type will be used. If one or more users are set, they will be used to
compute availabilities. If users given as argument is not among the staff of the appointment type, return empty list.
This test only concern random appointments: if it were 'chosen' assignment, then the dropdown of user selection would
be in the view. Hence, in practice, only one user would be used to generate / update the slots : the one selected. For
random ones, the users can be multiple if a filter is set, assigning randomly among several users. This tests asserts
that _get_appointment_slots returns slots properly when called with several users too. If no filter, then the update
method would be called with staff_users = False (since select not in view, getting the input value returns false) """
reference_monday = self.reference_monday.replace(microsecond=0)
reccuring_slots_utc = [{
'weekday': '1',
'start_hour': 6.0, # 1 slot : Monday 06:00 -> 07:00
'end_hour': 7.0,
}, {
'weekday': '2',
'start_hour': 9.0, # 2 slots : Tuesday 09:00 -> 11:00
'end_hour': 11.0,
}]
staff_user_no_tz = mail_new_test_user(
self.env(su=True),
company_id=self.company_admin.id,
email='no_tz@test.example.com',
groups='base.group_user',
name='Employee Without Tz',
notification_type='email',
login='staff_user_no_tz',
tz=False,
)
apt_type_UTC = self.env['appointment.type'].create({
'appointment_tz': 'UTC',
'assign_method': 'time_auto_assign',
'category': 'recurring',
'max_schedule_days': 5, # Only consider the first three slots
'name': 'Private Guitar Lesson',
'slot_ids': [(0, False, {
'weekday': slot['weekday'],
'start_hour': slot['start_hour'],
'end_hour': slot['end_hour'],
}) for slot in reccuring_slots_utc],
'staff_user_ids': [self.staff_user_aust.id, self.staff_user_bxls.id, staff_user_no_tz.id],
})
exterior_staff_user = self.apt_manager
# staff_user_bxls is only available on Wed and staff_user_aust only on Mon and Tue
self._create_meetings(
self.staff_user_bxls,
[(reference_monday - timedelta(hours=1), # Monday 06:00 -> 07:00
reference_monday,
False
)]
)
self._create_meetings(
self.staff_user_aust,
[(reference_monday + timedelta(days=1, hours=2), # Tuesday 09:00 -> 11:00
reference_monday + timedelta(days=1, hours=4),
False
)]
)
# staff_user_no_tz is only available on Tue between 10 and 11 AM
self._create_meetings(
staff_user_no_tz,
[(
self.reference_monday,
self.reference_monday.replace(hour=9),
True
), (
self.reference_monday + timedelta(days=1, hours=2),
self.reference_monday + timedelta(days=1, hours=3),
False
)])
with freeze_time(self.reference_now):
slots_no_user = apt_type_UTC._get_appointment_slots('UTC')
slots_exterior_user = apt_type_UTC._get_appointment_slots('UTC', exterior_staff_user)
slots_user_aust = apt_type_UTC._get_appointment_slots('UTC', self.staff_user_aust)
slots_user_all = apt_type_UTC._get_appointment_slots('UTC', self.staff_user_bxls | self.staff_user_aust)
slots_user_bxls_exterior_user = apt_type_UTC._get_appointment_slots('UTC', self.staff_user_bxls | exterior_staff_user)
slots_user_no_tz = apt_type_UTC._get_appointment_slots('UTC', staff_user_no_tz)
self.assertTrue(len(self._filter_appointment_slots(slots_no_user)) == 3)
self.assertFalse(slots_exterior_user)
self.assertTrue(len(self._filter_appointment_slots(slots_user_aust)) == 1)
self.assertTrue(len(self._filter_appointment_slots(slots_user_all)) == 3)
self.assertTrue(len(self._filter_appointment_slots(slots_user_bxls_exterior_user)) == 2)
self.assertTrue(len(self._filter_appointment_slots(slots_user_no_tz)) == 1)
@users('apt_manager')
def test_slots_for_today(self):
test_reference_now = datetime(2022, 2, 14, 11, 0, 0) # is a Monday
appointment = self.env['appointment.type'].create({
'appointment_tz': 'UTC',
'min_schedule_hours': 1.0,
'max_schedule_days': 8,
'name': 'Test',
'slot_ids': [(0, 0, {
'weekday': str(test_reference_now.isoweekday()),
'start_hour': 6,
'end_hour': 18,
})],
'staff_user_ids': [self.staff_user_bxls.id],
})
first_day = (test_reference_now + timedelta(hours=appointment.min_schedule_hours)).astimezone(pytz.UTC)
last_day = (test_reference_now + timedelta(days=appointment.max_schedule_days)).astimezone(pytz.UTC)
with freeze_time(test_reference_now):
slots = appointment._slots_generate(first_day, last_day, 'UTC')
self.assertEqual(len(slots), 18, '2 mondays of 12 slots but 6 would be before reference date')
for slot in slots:
self.assertTrue(
test_reference_now.astimezone(pytz.UTC) < slot['UTC'][0].astimezone(pytz.UTC),
"A slot shouldn't be generated before the first_day datetime")
@users('apt_manager')
def test_slots_days_min_schedule(self):
""" Test that slots are generated correctly when min_schedule_hours is 47.0.
This means that the first returned slots should be on wednesday at 11:36.
"""
test_reference_now = datetime(2022, 2, 14, 11, 45, 0) # is a Monday
appointment = self.env['appointment.type'].create({
'appointment_tz': 'UTC',
'appointment_duration': 1.2, # 1h12
'min_schedule_hours': 47.0,
'max_schedule_days': 8,
'name': 'Test',
'slot_ids': [
(0, False, {'weekday': weekday,
'start_hour': 8,
'end_hour': 14,
})
for weekday in map(str, range(1, 4))
],
'staff_user_ids': [self.staff_user_bxls.id],
})
first_day = (test_reference_now + timedelta(hours=appointment.min_schedule_hours)).astimezone(pytz.UTC)
last_day = (test_reference_now + timedelta(days=appointment.max_schedule_days)).astimezone(pytz.UTC)
with freeze_time(test_reference_now):
slots = appointment._slots_generate(first_day, last_day, 'UTC')
for slot in slots:
self.assertTrue(
first_day < slot['UTC'][0].astimezone(pytz.UTC),
"A slot shouldn't be generated before the first_day datetime")
self.assertEqual(len(slots), 12) # 2 days of 5 slots and 2 slots on wednesday
@users('apt_manager')
def test_slots_days_min_schedule_punctual(self):
""" Test that slots are generated correctly when min_schedule_hours is 47.0 for punctual appointment.
This means that the first returned slots should be on wednesday at 11:36.
"""
test_reference_now = datetime(2022, 2, 14, 11, 45, 0) # is a Monday
appointment = self.env['appointment.type'].create({
'appointment_tz': 'UTC',
'appointment_duration': 1.2, # 1h12
'category': 'punctual',
'min_schedule_hours': 47.0,
'max_schedule_days': False,
'name': 'Test',
'slot_ids': [
(0, False, {'weekday': weekday,
'start_hour': 8,
'end_hour': 14,
})
for weekday in ['1', '2', '3', '4', '5']
],
'start_datetime': datetime(2022, 2, 15, 9, 0, 0),
'end_datetime': datetime(2022, 2, 25, 9, 0, 0),
'staff_user_ids': [self.staff_user_bxls.id],
})
with freeze_time(test_reference_now):
slots = appointment.sudo()._get_appointment_slots('UTC')
slots = self._filter_appointment_slots(slots)
self.assertEqual(slots[0]['datetime'], "2022-02-16 11:36:00",
"The first slot should take into account the min schedule hours")
self.assertEqual(slots[-1]['datetime'], "2022-02-24 12:48:00")
@users('staff_user_aust')
def test_timezone_delta(self):
""" Test timezone delta. Not sure what original test was really doing. """
# As if the second user called the function
apt_type = self.apt_type_bxls_2days.with_user(self.env.user).with_context(
lang='en_US',
tz=self.staff_user_aust.tz,
uid=self.staff_user_aust.id,
)
# Do what the controller actually does, aka sudo
with freeze_time(self.reference_now):
slots = apt_type.sudo()._get_appointment_slots('Australia/Perth', filter_users=None)
global_slots_enddate = date(2022, 4, 2) # last day of last week of March
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
},
{'name_formated': 'March 2022',
'month_date': datetime(2022, 3, 1),
'weeks_count': 5, # 28/02 -> 28/03 (03/04)
}
],
{'enddate': global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_enddate': self.reference_now.date() + timedelta(days=15), # maximum 2 weeks of slots
'slots_start_hours': [15, 16, 17, 18, 19, 20], # based on appointment type start hours of slots, no work hours / no meetings / no leaves, set in UTC+8
'slots_startdate': self.reference_monday.date(), # first Monday after reference_now
'slots_weekdays_nowork': range(2, 7) # working hours only on Monday/Tuesday (0, 1)
}
)
@users('apt_manager')
def test_unique_slots_availabilities(self):
""" Check that the availability of each unique slot is correct.
First we test that the 2 unique slots of the custom appointment type
are available. Then we check that there is now only 1 availability left
after the creation of a meeting which encompasses a slot. """
reference_monday = self.reference_monday.replace(microsecond=0)
unique_slots = [{
'allday': False,
'end_datetime': reference_monday + timedelta(hours=1),
'start_datetime': reference_monday,
}, {
'allday': False,
'end_datetime': reference_monday + timedelta(hours=3),
'start_datetime': reference_monday + timedelta(hours=2),
}]
apt_type = self.env['appointment.type'].create({
'category': 'custom',
'name': 'Custom with unique slots',
'slot_ids': [(0, 0, {
'allday': slot['allday'],
'end_datetime': slot['end_datetime'],
'slot_type': 'unique',
'start_datetime': slot['start_datetime'],
}) for slot in unique_slots
],
})
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('UTC')
# get all monday slots where apt_manager is available
available_unique_slots = self._filter_appointment_slots(
slots,
filter_months=[(2, 2022)],
filter_weekdays=[0],
filter_users=self.apt_manager)
self.assertEqual(len(available_unique_slots), 2)
# Create a meeting encompassing the first unique slot
self._create_meetings(self.apt_manager, [(
unique_slots[0]['start_datetime'],
unique_slots[0]['end_datetime'],
False,
)])
with freeze_time(self.reference_now):
slots = apt_type._get_appointment_slots('UTC')
available_unique_slots = self._filter_appointment_slots(
slots,
filter_months=[(2, 2022)],
filter_weekdays=[0],
filter_users=self.apt_manager)
self.assertEqual(len(available_unique_slots), 1)
self.assertEqual(
available_unique_slots[0]['datetime'],
unique_slots[1]['start_datetime'].strftime('%Y-%m-%d %H:%M:%S'),
)
def test_check_appointment_timezone(self):
session = self.authenticate(None, None)
odoo.http.root.session_store.save(session)
appointment = self.apt_type_bxls_2days
appointment_invite = self.env['appointment.invite'].create({'appointment_type_ids': appointment.ids})
appointment_url = url_join(appointment.get_base_url(), '/appointment/%s' % appointment.id)
appointment_info_url = "%s/info?" % appointment_url
url_inside_of_slot = appointment_info_url + url_encode({
'staff_user_id': self.staff_user_bxls.id,
'date_time': datetime(2023, 1, 9, 9, 0), # 9/01/2023 is a Monday, there is a slot at 9:00
'duration': 1,
**appointment_invite._get_redirect_url_parameters(),
})
# User should be able open url without timezone session
self.url_open(url_inside_of_slot)
@freeze_time('2022-02-14')
@users('apt_manager')
def test_different_timezones_with_allday_events_availabilities(self):
"""
When the utc offset of the timezone is large, it is possible that the day of the week no longer corresponds.
Testing that allday event slots are all not available.
"""
appointment = self.env['appointment.type'].create({
'appointment_tz': 'Pacific/Auckland',
'appointment_duration': 21,
'assign_method': 'time_auto_assign',
'category': 'recurring',
'location_id': self.staff_user_nz.partner_id.id,
'name': 'New Zealand Appointment',
'max_schedule_days': 14,
'min_cancellation_hours': 1,
'min_schedule_hours': 1,
'slot_ids': [(0, 0, {
'weekday': '1',
'start_hour': 1,
'end_hour': 23,
})],
'staff_user_ids': [(4, self.staff_user_nz.id)],
})
self._create_meetings(
self.staff_user_nz,
[(self.reference_monday + timedelta(days=7),
self.reference_monday + timedelta(days=7, hours=1),
True
)])
slots = appointment._get_appointment_slots(
appointment.appointment_tz)
self.assertSlots(
slots,
[{'name_formated': 'February 2022',
'month_date': datetime(2022, 2, 1),
'weeks_count': 5, # 31/01 -> 28/02 (06/03)
}
],
{'enddate': self.global_slots_enddate,
'startdate': self.reference_now_monthweekstart,
'slots_start_hours': [],
# first Monday after reference_now
'slots_startdate': self.reference_monday + timedelta(days=7),
# only test that day
'slots_enddate': self.reference_monday + timedelta(days=14),
'slots_day_specific': {date(2022, 2, 28): [{'start':1}]}
}
)