mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 14:32:04 +02:00
299 lines
15 KiB
Python
299 lines
15 KiB
Python
from datetime import date, datetime, timedelta
|
|
|
|
from odoo.addons.event.tests.common import EventCase
|
|
from odoo import exceptions
|
|
from odoo.tests import tagged
|
|
|
|
|
|
class TestEventSlotsCommon(EventCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
# Mock dates to have reproducible computed fields based on time
|
|
cls.reference_now = datetime(2025, 4, 15, 10, 0, 0)
|
|
cls.reference_beg = datetime(2025, 4, 21, 6, 30, 0)
|
|
cls.reference_end = datetime(2025, 8, 21, 17, 45, 0)
|
|
|
|
with cls.mock_datetime_and_now(cls, cls.reference_now):
|
|
cls.test_event = cls.env['event.event'].create({
|
|
'date_begin': cls.reference_beg,
|
|
'date_end': cls.reference_end,
|
|
'date_tz': 'Europe/Brussels',
|
|
'event_ticket_ids': [
|
|
(0, 0, {
|
|
'name': 'Classic',
|
|
'seats_limited': False,
|
|
'seats_max': 0,
|
|
}), (0, 0, {
|
|
'name': 'Better',
|
|
'seats_limited': True,
|
|
'seats_max': 3,
|
|
}), (0, 0, {
|
|
'name': 'VIP',
|
|
'seats_limited': True,
|
|
'seats_max': 1,
|
|
}),
|
|
],
|
|
'name': 'Test Event',
|
|
'seats_limited': True,
|
|
'seats_max': 5,
|
|
'event_slot_ids': [
|
|
(0, 0, {
|
|
'date': date(2025, 4, 21),
|
|
'end_hour': 12,
|
|
'start_hour': 9,
|
|
}),
|
|
(0, 0, {
|
|
'date': date(2025, 4, 21),
|
|
'end_hour': 16,
|
|
'start_hour': 13,
|
|
}),
|
|
],
|
|
'user_id': cls.user_eventuser.id,
|
|
})
|
|
|
|
first_slot = cls.test_event.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = cls.test_event.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
first_ticket = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Classic')
|
|
second_ticket = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Better')
|
|
|
|
# already existing registrations
|
|
cls.test_reg_slot_1 = cls._create_registrations_for_slot_and_ticket(cls.test_event, first_slot, first_ticket, 3)
|
|
cls.test_reg_slot_2 = cls._create_registrations_for_slot_and_ticket(cls.test_event, second_slot, second_ticket, 1)
|
|
|
|
|
|
@tagged('event_slot', 'event_registration')
|
|
class TestEventSlotRegistration(TestEventSlotsCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.test_event_no_slot = cls.env['event.event'].create({
|
|
'date_begin': cls.reference_beg,
|
|
'date_end': cls.reference_end,
|
|
'date_tz': 'Europe/Brussels',
|
|
'name': 'Test Event No Slot',
|
|
})
|
|
cls.test_reg_no_slot = cls.env["event.registration"].create({
|
|
"event_id": cls.test_event_no_slot.id,
|
|
"name": "Test Registration No Slot",
|
|
})
|
|
|
|
def test_search_event_begin_date(self):
|
|
""" Searching on the registration 'event_begin_date' field should correctly search
|
|
on the slot start datetime if the registration is linked to a slot
|
|
else on the event start date.
|
|
"""
|
|
for search_from_date, expected in [
|
|
(self.reference_beg, self.test_reg_no_slot + self.test_reg_slot_1 + self.test_reg_slot_2),
|
|
(self.reference_beg + timedelta(minutes=30), self.test_reg_slot_1 + self.test_reg_slot_2),
|
|
(self.reference_beg + timedelta(hours=1), self.test_reg_slot_2),
|
|
]:
|
|
self.assertEqual(
|
|
self.env["event.registration"].search([
|
|
('event_id', 'in', [self.test_event_no_slot.id, self.test_event.id]),
|
|
('event_begin_date', '>=', search_from_date),
|
|
]),
|
|
expected,
|
|
)
|
|
|
|
def test_search_event_end_date(self):
|
|
""" Searching on the registration 'event_end_date' field should correctly search
|
|
on the slot end datetime if the registration is linked to a slot,
|
|
else on the event end date.
|
|
"""
|
|
for search_to_date, expected in [
|
|
(self.reference_end, self.test_reg_no_slot + self.test_reg_slot_1 + self.test_reg_slot_2),
|
|
(self.reference_beg + timedelta(hours=12), self.test_reg_slot_1 + self.test_reg_slot_2),
|
|
(self.reference_beg + timedelta(hours=6), self.test_reg_slot_1),
|
|
]:
|
|
self.assertEqual(
|
|
self.env["event.registration"].search([
|
|
('event_id', 'in', [self.test_event_no_slot.id, self.test_event.id]),
|
|
('event_end_date', '<=', search_to_date),
|
|
]),
|
|
expected,
|
|
)
|
|
|
|
|
|
@tagged('event_slot', 'event_seats')
|
|
class TestEventSlotSeats(TestEventSlotsCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.test_event_slot_noticket = cls.test_event.copy({'event_ticket_ids': False})
|
|
|
|
first_slot = cls.test_event_slot_noticket.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = cls.test_event_slot_noticket.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
|
|
# already existing registrations: 3 on first slot (1 archived), 1 on second slot (2 archived)
|
|
with cls.mock_datetime_and_now(cls, cls.reference_now):
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, first_slot, False, 1, state='open')
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, first_slot, False, 2, state='done')
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, first_slot, False, 1, active=False)
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, second_slot, False, 1)
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, second_slot, False, 2, active=False)
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, second_slot, False, 1, state='cancel')
|
|
cls._create_registrations_for_slot_and_ticket(cls.test_event_slot_noticket, second_slot, False, 1, state='draft')
|
|
|
|
def test_assert_initial_values(self):
|
|
""" Check initial values, ensure test conditions """
|
|
test_event = self.test_event.with_user(self.user_eventregistrationdesk)
|
|
first_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
self.assertTrue(first_slot)
|
|
self.assertTrue(second_slot)
|
|
self.assertEqual(first_slot.seats_available, 2)
|
|
self.assertEqual(first_slot.seats_reserved, 3)
|
|
self.assertEqual(second_slot.seats_available, 4)
|
|
self.assertEqual(second_slot.seats_reserved, 1)
|
|
|
|
test_event_nt = self.test_event_slot_noticket.with_user(self.user_eventregistrationdesk)
|
|
first_slot = test_event_nt.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = test_event_nt.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
self.assertTrue(first_slot)
|
|
self.assertTrue(second_slot)
|
|
self.assertEqual(first_slot.seats_available, 2)
|
|
self.assertEqual(first_slot.seats_reserved, 1)
|
|
self.assertEqual(second_slot.seats_available, 4)
|
|
self.assertEqual(second_slot.seats_reserved, 1)
|
|
|
|
def test_seats_slots_notickets(self):
|
|
""" Test: slots, no tickets -> limits come from event itself """
|
|
# self.test_event_slot_noticket.with_user(self.user_eventuser).write({'event_ticket_ids': [(5, 0)]})
|
|
test_event = self.test_event_slot_noticket.with_user(self.user_eventregistrationdesk)
|
|
first_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
|
|
Registration = self.env['event.registration'].with_user(self.user_eventregistrationdesk)
|
|
|
|
# check ``_get_seats_availability`` tool, giving availabilities for slot / ticket combinations
|
|
res = test_event._get_seats_availability([(first_slot, False), (second_slot, False)])
|
|
self.assertEqual(res, [first_slot.seats_available, second_slot.seats_available])
|
|
|
|
# check constraints at registration creation
|
|
for create_input, should_crash in [
|
|
# ok for event max seats for both slots
|
|
(((first_slot, 2), (second_slot, 2)), False),
|
|
# not enough seats on first slot
|
|
(((first_slot, 3),), True),
|
|
# not enough seats on second slot
|
|
(((second_slot, 5),), True),
|
|
]:
|
|
with self.subTest(create_input=create_input, should_crash=should_crash):
|
|
create_values = []
|
|
for slot, count in create_input:
|
|
create_values += [
|
|
{
|
|
'email': f'{slot.display_name}.{idx}@test.example.com',
|
|
'event_id': test_event.id,
|
|
'event_slot_id': slot.id,
|
|
'name': f'{slot.display_name} {idx}',
|
|
} for idx in range(count)
|
|
]
|
|
if should_crash:
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
new = Registration.create(create_values)
|
|
else:
|
|
new = Registration.create(create_values)
|
|
self.assertEqual(len(new), sum(count for _slot, count in create_input))
|
|
new.with_user(self.user_eventmanager).unlink()
|
|
|
|
# check ``_verify_seats_availability`` itself
|
|
for check_input, should_crash in [
|
|
# ok for event max seats for both slots
|
|
(((first_slot, False, 2), (second_slot, False, 4)), False),
|
|
# not enough seats on first slot
|
|
(((first_slot, False, 3),), True),
|
|
# not enough seats on second slot
|
|
(((second_slot, False, 5),), True),
|
|
]:
|
|
with self.subTest(check_input=check_input, should_crash=should_crash):
|
|
if should_crash:
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
test_event._verify_seats_availability(check_input)
|
|
else:
|
|
test_event._verify_seats_availability(check_input)
|
|
|
|
# check constraint at write (active change) -> ok, check count
|
|
all_slot2 = test_event.with_context(active_test=False).registration_ids.filtered(lambda r: r.event_slot_id == second_slot)
|
|
self.assertEqual(len(all_slot2), 5, 'Test setup data: 3 active, 2 inactive')
|
|
all_slot2.active = True
|
|
self.assertEqual(second_slot.seats_available, 2)
|
|
self.assertEqual(second_slot.seats_reserved, 3)
|
|
|
|
# move them on first slot -> crash as it would be out of limits
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
all_slot2.event_slot_id = first_slot.id
|
|
|
|
def test_seats_slots_tickets(self):
|
|
""" Test: slots and tickets -> limits come from event (global) and tickets """
|
|
test_event = self.test_event.with_user(self.user_eventregistrationdesk)
|
|
first_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 9)
|
|
second_slot = test_event.event_slot_ids.filtered(lambda s: s.start_hour == 13)
|
|
first_ticket = test_event.event_ticket_ids.filtered(lambda t: t.name == 'Classic')
|
|
second_ticket = test_event.event_ticket_ids.filtered(lambda t: t.name == 'Better')
|
|
third_ticket = test_event.event_ticket_ids.filtered(lambda t: t.name == 'VIP')
|
|
|
|
Registration = self.env['event.registration'].with_user(self.user_eventregistrationdesk)
|
|
|
|
# check ``_get_seats_availability`` tool, giving availabilities for slot / ticket combinations
|
|
res = test_event._get_seats_availability([
|
|
(first_slot, first_ticket), (first_slot, second_ticket), (first_slot, third_ticket),
|
|
(second_slot, first_ticket), (second_slot, second_ticket), (second_slot, third_ticket),
|
|
])
|
|
# first slot: 2 seats available, and VIP ticket has 1 seat anyway
|
|
# second slot: 4 seats available, Better has 3 max and 1 taken and VIP 1 max
|
|
self.assertEqual(res, [2, 2, 1, 4, 2, 1])
|
|
|
|
# check constraints at registration creation
|
|
for create_input, should_crash in [
|
|
(((first_slot, second_ticket, 2),), False),
|
|
# not enough seats for first slot
|
|
(((first_slot, first_ticket, 5),), True),
|
|
# not enough seats on VIP ticket
|
|
(((second_slot, third_ticket, 2),), True),
|
|
]:
|
|
with self.subTest(create_input=create_input, should_crash=should_crash):
|
|
create_values = []
|
|
for slot, ticket, count in create_input:
|
|
create_values += [
|
|
{
|
|
'email': f'{slot.display_name}.{ticket.name}.{idx}@test.example.com',
|
|
'event_id': test_event.id,
|
|
'event_slot_id': slot.id,
|
|
'event_ticket_id': ticket.id,
|
|
'name': f'{slot.display_name} {ticket.name} {idx}',
|
|
} for idx in range(count)
|
|
]
|
|
if should_crash:
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
new = Registration.create(create_values)
|
|
else:
|
|
new = Registration.create(create_values)
|
|
self.assertEqual(len(new), sum(count for _slot, _ticket, count in create_input))
|
|
new.with_user(self.user_eventmanager).unlink()
|
|
|
|
# check create constraint through embed 2many: 2 VIPs is not possible
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
test_event.with_user(self.user_eventmanager).write({
|
|
'registration_ids': [
|
|
(0, 0, {'event_slot_id': second_slot.id, 'event_ticket_id': third_ticket.id}),
|
|
(0, 0, {'event_slot_id': second_slot.id, 'event_ticket_id': third_ticket.id}),
|
|
],
|
|
})
|
|
# one of them is archived, ok for limit
|
|
test_event.with_user(self.user_eventmanager).write({
|
|
'registration_ids': [
|
|
(0, 0, {'event_slot_id': second_slot.id, 'event_ticket_id': third_ticket.id, 'active': False}),
|
|
(0, 0, {'event_slot_id': second_slot.id, 'event_ticket_id': third_ticket.id}),
|
|
],
|
|
})
|
|
archived_vip = test_event.with_context(active_test=False).registration_ids.filtered(lambda r: r.event_slot_id == second_slot and r.event_ticket_id == third_ticket and not r.active)
|
|
self.assertTrue(archived_vip)
|
|
# writing on active triggers constraint on VIP
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
archived_vip.active = True
|