diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py
index 8ba70e498f8..cd4b33c864e 100644
--- a/addons/event/models/event_event.py
+++ b/addons/event/models/event_event.py
@@ -208,7 +208,7 @@ class EventEvent(models.Model):
else:
event.kanban_state_label = event.stage_id.legend_done
- @api.depends('seats_max', 'registration_ids.state')
+ @api.depends('seats_max', 'registration_ids.state', 'registration_ids.active')
def _compute_seats(self):
""" Determine reserved, available, reserved but unconfirmed and used seats. """
# initialize fields to 0
@@ -225,10 +225,10 @@ class EventEvent(models.Model):
if self.ids:
query = """ SELECT event_id, state, count(event_id)
FROM event_registration
- WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
+ WHERE event_id IN %s AND state IN ('draft', 'open', 'done') AND active = true
GROUP BY event_id, state
"""
- self.env['event.registration'].flush(['event_id', 'state'])
+ self.env['event.registration'].flush(['event_id', 'state', 'active'])
self._cr.execute(query, (tuple(self.ids),))
res = self._cr.fetchall()
for event_id, state, num in res:
@@ -496,8 +496,11 @@ class EventEvent(models.Model):
@api.constrains('seats_max', 'seats_available', 'seats_limited')
def _check_seats_limit(self):
- if any(event.seats_limited and event.seats_max and event.seats_available < 0 for event in self):
- raise ValidationError(_('No more available seats.'))
+ for event in self:
+ if event.seats_limited and event.seats_max and event.seats_available < 0:
+ raise ValidationError(_('No more available seats for %s. '
+ 'Raise the limit or remove some other confirmed registrations first.',
+ event.name))
@api.constrains('date_begin', 'date_end')
def _check_closing_date(self):
diff --git a/addons/event/models/event_registration.py b/addons/event/models/event_registration.py
index e104434459f..18fea786c42 100644
--- a/addons/event/models/event_registration.py
+++ b/addons/event/models/event_registration.py
@@ -96,8 +96,9 @@ class EventRegistration(models.Model):
@api.constrains('event_id', 'state')
def _check_seats_limit(self):
for registration in self:
- if registration.event_id.seats_limited and registration.event_id.seats_max and registration.event_id.seats_available < (1 if registration.state == 'draft' else 0):
- raise ValidationError(_('No more seats available for this event.'))
+ event_id = registration.event_id
+ if event_id.seats_limited and event_id.seats_max and event_id.seats_available < (1 if registration.state == 'draft' and event_id.auto_confirm else 0):
+ raise ValidationError(_('There are no more available seats for %s. Raise the limit or remove some other confirmed registrations first.', event_id.name))
@api.constrains('event_ticket_id', 'state')
def _check_ticket_seats_limit(self):
diff --git a/addons/event/models/event_ticket.py b/addons/event/models/event_ticket.py
index 61ef734e96c..d5207f61f08 100644
--- a/addons/event/models/event_ticket.py
+++ b/addons/event/models/event_ticket.py
@@ -91,7 +91,7 @@ class EventTicket(models.Model):
else:
ticket.sale_available = True
- @api.depends('seats_max', 'registration_ids.state')
+ @api.depends('seats_max', 'registration_ids.state', 'registration_ids.active')
def _compute_seats(self):
""" Determine reserved, available, reserved but unconfirmed and used seats. """
# initialize fields to 0 + compute seats availability
@@ -107,10 +107,10 @@ class EventTicket(models.Model):
}
query = """ SELECT event_ticket_id, state, count(event_id)
FROM event_registration
- WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
+ WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done') AND active = true
GROUP BY event_ticket_id, state
"""
- self.env['event.registration'].flush(['event_id', 'event_ticket_id', 'state'])
+ self.env['event.registration'].flush(['event_id', 'event_ticket_id', 'state', 'active'])
self.env.cr.execute(query, (tuple(self.ids),))
for event_ticket_id, state, num in self.env.cr.fetchall():
results.setdefault(event_ticket_id, {})[state_field[state]] = num
@@ -129,8 +129,12 @@ class EventTicket(models.Model):
@api.constrains('seats_available', 'seats_max')
def _constrains_seats_available(self):
- if any(record.seats_max and record.seats_available < 0 for record in self):
- raise ValidationError(_('No more available seats for this ticket.'))
+ for record in self:
+ if record.seats_max and record.seats_available < 0:
+ raise ValidationError(
+ _('No more available seats for the ticket %s (%s). '
+ 'Raise the limit or remove some other confirmed registrations first.',
+ record.name, record.event_id.name))
def _get_ticket_multiline_description(self):
""" Compute a multiline description of this ticket. It is used when ticket
diff --git a/addons/event/static/src/scss/event.scss b/addons/event/static/src/scss/event.scss
index 703f90377e8..439b7a125da 100644
--- a/addons/event/static/src/scss/event.scss
+++ b/addons/event/static/src/scss/event.scss
@@ -23,6 +23,32 @@
min-height: 80px;
}
}
+ .oe_kanban_card_ribbon {
+ min-height: 95px;
+ .ribbon {
+ &::before, &::after {
+ display: none;
+ }
+ span {
+ padding: 5px;
+ font-size: small;
+ z-index: 0;
+ }
+ }
+ .ribbon-top-right {
+ margin-top: -1px;
+ span {
+ left: 7px;
+ right: 30px;
+ height: 25px;
+ top: 18px;
+ }
+ }
+ // Used for "Group By"
+ div.row {
+ min-height: 95px;
+ }
+ }
}
.o_event_registration_view_tree {
diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py
index e02a42aedf5..2b1b3b9b546 100644
--- a/addons/event/tests/test_event_internals.py
+++ b/addons/event/tests/test_event_internals.py
@@ -486,12 +486,13 @@ class TestEventData(TestEventInternalsCommon):
# create registration in order to check the seats computation
self.assertTrue(event.auto_confirm)
- for x in range(5):
- reg = self.env['event.registration'].create({
- 'event_id': event.id,
- 'name': 'reg_open',
- })
- self.assertEqual(reg.state, 'open')
+ reg_open_multiple = self.env['event.registration'].create([{
+ 'event_id': event.id,
+ 'name': 'reg_open',
+ } for _ in range(5)])
+ self.assertEqual(set(reg_open_multiple.mapped('state')), {'open'})
+ reg_open = reg_open_multiple[0]
+
reg_draft = self.env['event.registration'].create({
'event_id': event.id,
'name': 'reg_draft',
@@ -508,6 +509,38 @@ class TestEventData(TestEventInternalsCommon):
self.assertEqual(event.seats_used, 1)
self.assertEqual(event.seats_expected, 7)
+ # ------------------------------------------------------------
+ # (UN-)ARCHIVING REGISTRATIONS
+ # ------------------------------------------------------------
+
+ # Archiving and seats availability
+ reg_open.action_archive()
+ self.assertEqual(event.seats_reserved, 4)
+ self.assertEqual(event.seats_available, event.event_type_id.seats_max - 5)
+ self.assertEqual(event.seats_expected, 6)
+
+ reg_draft.action_archive()
+ self.assertEqual(event.seats_unconfirmed, 0)
+ self.assertEqual(event.seats_available, event.event_type_id.seats_max - 5)
+ self.assertEqual(event.seats_expected, 5)
+
+ # Un-archiving confirmed seats requires available seat(s)
+ reg_open.action_unarchive()
+ self.assertEqual(event.seats_reserved, 5)
+ self.assertEqual(event.seats_available, event.event_type_id.seats_max - 6)
+ self.assertEqual(event.seats_expected, 6)
+
+ reg_draft.action_unarchive()
+ self.assertEqual(event.seats_unconfirmed, 1)
+ self.assertEqual(event.seats_available, event.event_type_id.seats_max - 6)
+ self.assertEqual(event.seats_expected, 7)
+
+ reg_open.action_archive()
+ event.write({'seats_max': 5})
+ # It is not possible to unarchive confirmed seat if event is full
+ with self.assertRaises(exceptions.ValidationError):
+ reg_open.action_unarchive()
+
class TestEventRegistrationData(TestEventInternalsCommon):
@@ -609,19 +642,21 @@ class TestEventTicketData(TestEventInternalsCommon):
@users('user_eventmanager')
def test_event_ticket_fields(self):
""" Test event ticket fields synchronization """
+ INITIAL_TICKET_SEATS_MAX = 30
event = self.event_0.with_user(self.env.user)
event.write({
'event_ticket_ids': [
(5, 0),
(0, 0, {
'name': 'First Ticket',
- 'seats_max': 30,
+ 'seats_max': INITIAL_TICKET_SEATS_MAX,
}), (0, 0, { # limited in time, available (01/10 (start) < 01/31 (today) < 02/10 (end))
'name': 'Second Ticket',
'start_sale_datetime': datetime(2020, 1, 10, 0, 0, 0),
'end_sale_datetime': datetime(2020, 2, 10, 23, 59, 59),
})
],
+ 'auto_confirm': False # to interact with registrations states
})
first_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'First Ticket')
second_ticket = event.event_ticket_ids.filtered(lambda t: t.name == 'Second Ticket')
@@ -677,6 +712,67 @@ class TestEventTicketData(TestEventInternalsCommon):
self.assertFalse(second_ticket.sale_available)
self.assertTrue(second_ticket.is_expired)
+ # ------------------------------------------------------------
+ # (UN-)ARCHIVING TICKET REGISTRATIONS
+ # ------------------------------------------------------------
+
+ # Archiving and seats availability
+ reg_draft_multiple = self.env['event.registration'].create([{
+ 'event_id': event.id,
+ 'name': f'reg_draft #{idx}',
+ 'event_ticket_id': first_ticket.id,
+ } for idx in range(3)])
+ reg_draft = reg_draft_multiple[0]
+
+ reg_open = self.env['event.registration'].create({
+ 'event_id': event.id,
+ 'name': 'reg_open',
+ 'event_ticket_id': first_ticket.id,
+ 'state': 'open'
+ })
+
+ reg_done = self.env['event.registration'].create({
+ 'event_id': event.id,
+ 'name': 'reg_done',
+ 'event_ticket_id': first_ticket.id,
+ 'state': 'done'
+ })
+
+ self.assertEqual(first_ticket.seats_unconfirmed, 3)
+ self.assertEqual(first_ticket.seats_reserved, 1)
+ self.assertEqual(first_ticket.seats_used, 1)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX - 2)
+
+ reg_done.action_archive()
+ self.assertEqual(first_ticket.seats_used, 0)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX - 1)
+
+ reg_open.action_archive()
+ self.assertEqual(first_ticket.seats_reserved, 0)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX)
+
+ reg_draft.action_archive()
+ self.assertEqual(first_ticket.seats_unconfirmed, 2)
+
+ # Un-archiving confirmed/done seats requires available seat(s)
+ reg_open.action_unarchive()
+ self.assertEqual(first_ticket.seats_reserved, 1)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX - 1)
+
+ reg_done.action_unarchive()
+ self.assertEqual(first_ticket.seats_used, 1)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX - 2)
+
+ reg_draft.action_unarchive()
+ self.assertEqual(first_ticket.seats_unconfirmed, 3)
+ self.assertEqual(first_ticket.seats_available, INITIAL_TICKET_SEATS_MAX - 2)
+
+ reg_open.action_archive()
+ first_ticket.write({'seats_max': 1})
+ # It is not possible to unarchive confirmed seat if ticket is fully booked
+ with self.assertRaises(exceptions.ValidationError):
+ reg_open.action_unarchive()
+
class TestEventTypeData(TestEventInternalsCommon):
diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml
index 2dfb2c4c691..83fe16fc936 100644
--- a/addons/event/views/event_views.xml
+++ b/addons/event/views/event_views.xml
@@ -30,7 +30,7 @@