diff options
| author | Sébastien Mottet (oms) <oms@odoo.com> | 2020-04-27 14:37:02 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2020-06-16 09:14:31 +0000 |
| commit | be11161a42947437447c5aba699f558a12589c0d (patch) | |
| tree | 214a4c1a8f9707e4ee16f1dd54757d49270e78a1 | |
| parent | 7c37d457e7ff9261bf17deec25213bab8e56d1fc (diff) | |
[FIX] event(_questions): take better care of 2many fields when updating from event type
PURPOSE
Fix various issues spotted in 13.3 when testing event. Notably
computation of 2many fields coming from template.
SPECIFICATIONS
When updating template on an event, one2many fields should be better managed.
Previous heuristic was
* if event type uses o2m configuration (use_ticket / _schedule / _question)
and has lines
* erase existing lines;
* create new lines based on old one;
This has the drawback of loosing information of what is sent (mail) or
sold (tickets) or answered( questions). Another drawback is that only
types having line are synchronized. This means that if updating several
times the event type you could end up with an XMas configuration with
lines coming from different event types, depending on their o2m configuration.
We choose a better heuristic that should solve this issue
* every time we change type, independently of its use_* field that is used
mainly for UX on the type itself:
* erase existing lines that have not been used yet (no mail sent, no
ticket linked to registrations, no answer linked to registrations)
* create new lines based on old one; if type has no lines, event will
have its old empty line erased as well;
It means that we try to synchronize more the type to the event while keeping
configuration line already used in some registrations.
Also provide some other fixes like deletion restrictions or better domain
for UX purpose. See sub commits for more details.
LINKS
Task ID 2244487
PR odoo/odoo#52998
FwdPort of odoo/odoo#52923
X-original-commit: 645f70cad033083e82a3ef9102c694c331badef9
Co-authored-by: Sebastien Mottet <oms@odoo.com>
Co-authored-by: Thibault Delavallée <tde@odoo.com>
| -rw-r--r-- | addons/event/data/event_demo.xml | 4 | ||||
| -rw-r--r-- | addons/event/models/event_event.py | 93 | ||||
| -rw-r--r-- | addons/event/tests/test_event_internals.py | 18 | ||||
| -rw-r--r-- | addons/website_event_questions/models/event_event.py | 38 | ||||
| -rw-r--r-- | addons/website_event_questions/tests/test_event_internals.py | 2 |
5 files changed, 119 insertions, 36 deletions
diff --git a/addons/event/data/event_demo.xml b/addons/event/data/event_demo.xml index e29bfe99f37..2563053c959 100644 --- a/addons/event/data/event_demo.xml +++ b/addons/event/data/event_demo.xml @@ -82,6 +82,7 @@ <field name="event_type_id" ref="event_type_0"/> <field name="stage_id" ref="event_stage_booked"/> <field name="tag_ids" eval="[(6, 0, [event_tag_category_1_tag_1,event_tag_category_2_tag_1])]"/> + <field name="event_ticket_ids" eval="False"/> </record> <record id="event_0_ticket_0" model="event.event.ticket"> <field name="name">Free</field> @@ -172,6 +173,7 @@ <field name="seats_max">200</field> <field name="stage_id" ref="event_stage_booked"/> <field name="tag_ids" eval="[(6, 0, [event_tag_category_1_tag_4,event_tag_category_2_tag_1])]"/> + <field name="event_ticket_ids" eval="False"/> </record> <record id="event_2_ticket_1" model="event.event.ticket"> <field name="name">Standard</field> @@ -208,6 +210,7 @@ <field name="address_id" ref="event.res_partner_location_1"/> <field name="stage_id" ref="event_stage_announced"/> <field name="tag_ids" eval="[(6, 0, [event_tag_category_1_tag_3,event_tag_category_2_tag_2])]"/> + <field name="event_ticket_ids" eval="False"/> </record> <record id="event_3_ticket_0" model="event.event.ticket"> <field name="name">Standard</field> @@ -249,6 +252,7 @@ <field name="stage_id" ref="event_stage_done"/> <field name="kanban_state">done</field> <field name="tag_ids" eval="[(6, 0, [event_tag_category_1_tag_4,event_tag_category_2_tag_1])]"/> + <field name="event_ticket_ids" eval="False"/> </record> <record id="event_4_ticket_0" model="event.event.ticket"> <field name="name">General Admission</field> diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py index e5518122df8..30aaf428dbb 100644 --- a/addons/event/models/event_event.py +++ b/addons/event/models/event_event.py @@ -120,7 +120,7 @@ class EventEvent(models.Model): event_type_id = fields.Many2one('event.type', string='Template', ondelete='set null') event_mail_ids = fields.One2many( 'event.mail', 'event_id', string='Mail Schedule', copy=True, - compute='_compute_from_event_type', readonly=False, store=True) + compute='_compute_event_mail_ids', readonly=False, store=True) tag_ids = fields.Many2many( 'event.tag', string="Tags", readonly=False, store=True, compute="_compute_from_event_type") @@ -164,7 +164,7 @@ class EventEvent(models.Model): registration_ids = fields.One2many('event.registration', 'event_id', string='Attendees') event_ticket_ids = fields.One2many( 'event.event.ticket', 'event_id', string='Event Ticket', copy=True, - compute='_compute_from_event_type', readonly=False, store=True) + compute='_compute_event_ticket_ids', readonly=False, store=True) event_registrations_open = fields.Boolean( 'Registration open', compute='_compute_event_registrations_open', compute_sudo=True, help='Registrations are open if event is not ended, seats are available on event and if tickets are sellable if ticketing is used.') @@ -357,44 +357,97 @@ class EventEvent(models.Model): Updated by this method * seats_max -> triggers _compute_seats (all seats computation) + * seats_limited * auto_confirm - * event_mail_ids - * event_ticket_ids -> triggers _compute_start_sale_date (start_sale_date computation) + * tag_ids """ for event in self: if not event.event_type_id: if not event.seats_max: event.seats_max = 0 - if not event.event_ticket_ids: - event.event_ticket_ids = False continue if event.event_type_id.seats_max: event.seats_max = event.event_type_id.seats_max - if event.event_type_id.auto_confirm: + if event.event_type_id.has_seats_limitation != event.seats_limited: + event.seats_limited = event.event_type_id.has_seats_limitation + + if event.event_type_id.auto_confirm != event.auto_confirm: event.auto_confirm = event.event_type_id.auto_confirm - # compute mailing information (force only if activated and mailing defined) - if event.event_type_id.use_mail_schedule and event.event_type_id.event_type_mail_ids: - event.event_mail_ids = [(5, 0, 0)] + [ + if event.event_type_id.tag_ids: + event.tag_ids = event.event_type_id.tag_ids + + @api.depends('event_type_id') + def _compute_event_mail_ids(self): + """ Update event mails from its event type. Depends are set only on + event_type_id itself to emulate an onchange. Changing event type content + itself should not trigger this method. + + When synchronizing mails: + + * lines that are not sent and have no registrations linked are remove; + * type lines are added; + """ + for event in self: + if not event.event_type_id and not event.event_mail_ids: + event.event_mail_ids = False + continue + + # lines to keep: those with already sent emails or registrations + mails_toremove = event._origin.event_mail_ids.filtered(lambda mail: not mail.mail_sent and not(mail.mail_registration_ids)) + command = [(3, mail.id) for mail in mails_toremove] + if event.event_type_id.use_mail_schedule: + command += [ (0, 0, { attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id for attribute_name in self.env['event.type.mail']._get_event_mail_fields_whitelist() - }) - for line in event.event_type_id.event_type_mail_ids] + }) for line in event.event_type_id.event_type_mail_ids + ] + if command: + event.event_mail_ids = command + + @api.depends('event_type_id') + def _compute_event_ticket_ids(self): + """ Update event tickets from its event type. Depends are set only on + event_type_id itself to emulate an onchange. Changing event type content + itself should not trigger this method. + + When synchronizing tickets: + + * lines that have no registrations linked are remove; + * type lines are added; + + Note that updating event_ticket_ids triggers _compute_start_sale_date + (start_sale_date computation) so ensure result to avoid cache miss. + """ + if self.ids or self._origin.ids: + # lines to keep: those with already sent emails or registrations + tickets_tokeep_ids = self.env['event.registration'].search( + [('event_id', 'in', self.ids or self._origin.ids)] + ).event_ticket_id.ids + else: + tickets_tokeep_ids = [] + for event in self: + if not event.event_type_id and not event.event_ticket_ids: + event.event_ticket_ids = False + continue - # compute tickets information (force only if activated and tickets defined) - if event.event_type_id.use_ticket and event.event_type_id.event_type_ticket_ids: - event.event_ticket_ids = [(5, 0, 0)] + [ + # lines to keep: those with existing registrations + if tickets_tokeep_ids: + tickets_toremove = event._origin.event_ticket_ids.filtered(lambda ticket: ticket.id not in tickets_tokeep_ids) + command = [(3, ticket.id) for ticket in tickets_toremove] + else: + command = [(5, 0)] + if event.event_type_id.use_ticket: + command += [ (0, 0, { attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id for attribute_name in self.env['event.type.ticket']._get_event_ticket_fields_whitelist() - }) - for line in event.event_type_id.event_type_ticket_ids] - - if event.event_type_id.tag_ids: - event.tag_ids = event.event_type_id.tag_ids + }) for line in event.event_type_id.event_type_ticket_ids + ] + event.event_ticket_ids = command @api.constrains('seats_max', 'seats_available', 'seats_limited') def _check_seats_limit(self): diff --git a/addons/event/tests/test_event_internals.py b/addons/event/tests/test_event_internals.py index bd7e537f75f..09a0f9f5a92 100644 --- a/addons/event/tests/test_event_internals.py +++ b/addons/event/tests/test_event_internals.py @@ -139,8 +139,8 @@ class TestEventData(TestEventCommon): registration.write({'event_ticket_id': event_ticket1.id}) self.assertEqual(registration.event_ticket_id, event_ticket1) - # change template to a void one for mails -> do not reset event - # change template to a one with other tickets -> reset event + # change template to a void one for mails -> reset event lines that are void + # change template to a one with other tickets -> keep line linked to a registration event_type.write({ 'use_mail_schedule': False, 'event_type_mail_ids': [(5, 0)], @@ -148,15 +148,16 @@ class TestEventData(TestEventCommon): (0, 0, {'name': 'Registration1'}), (0, 0, {'name': 'Registration2'})], }) - event._compute_from_event_type() - self.assertEqual(len(event.event_mail_ids), 1) - self.assertEqual(len(event.event_ticket_ids), 2) + event._compute_event_ticket_ids() + event._compute_event_mail_ids() + self.assertEqual(event.event_mail_ids, self.env['event.mail']) + self.assertEqual(len(event.event_ticket_ids), 3) self.assertEqual( set(event.mapped('event_ticket_ids.name')), - set(['Registration1', 'Registration2']) + set(['TestRegistration', 'Registration1', 'Registration2']) ) # registration loose its ticket - self.assertEqual(registration.event_ticket_id, self.env['event.event.ticket']) + self.assertEqual(registration.event_ticket_id, event_ticket1) # change template to a one with different mails -> reset event event_type.write({ @@ -165,7 +166,8 @@ class TestEventData(TestEventCommon): 'interval_nbr': 3, 'interval_unit': 'days', 'interval_type': 'after_event', 'template_id': self.env['ir.model.data'].xmlid_to_res_id('event.event_reminder')})] }) - event._compute_from_event_type() + event._compute_event_ticket_ids() + event._compute_event_mail_ids() self.assertEqual(len(event.event_mail_ids), 1) self.assertEqual(event.event_mail_ids.interval_nbr, 3) self.assertEqual(event.event_mail_ids.interval_type, 'after_event') diff --git a/addons/website_event_questions/models/event_event.py b/addons/website_event_questions/models/event_event.py index 95d1d80a0f1..f81f5316908 100644 --- a/addons/website_event_questions/models/event_event.py +++ b/addons/website_event_questions/models/event_event.py @@ -19,18 +19,42 @@ class EventEvent(models.Model): question_ids = fields.One2many( 'event.question', 'event_id', 'Questions', copy=True, - compute='_compute_from_event_type', readonly=False, store=True) + compute='_compute_question_ids', readonly=False, store=True) general_question_ids = fields.One2many('event.question', 'event_id', 'General Questions', domain=[('once_per_order', '=', True)]) specific_question_ids = fields.One2many('event.question', 'event_id', 'Specific Questions', domain=[('once_per_order', '=', False)]) @api.depends('event_type_id') - def _compute_from_event_type(self): - super(EventEvent, self)._compute_from_event_type() + def _compute_question_ids(self): + """ Update event questions from its event type. Depends are set only on + event_type_id itself to emulate an onchange. Changing event type content + itself should not trigger this method. + + When synchronizing questions: + + * lines that no answer are removed; + * type lines are added; + """ + if self._origin.question_ids: + # lines to keep: those with already sent emails or registrations + questions_tokeep_ids = self.env['event.registration.answer'].search( + [('question_id', 'in', self._origin.question_ids.ids)] + ).question_id.ids + else: + questions_tokeep_ids = [] for event in self: - if event.event_type_id.use_questions and event.event_type_id.question_ids: - event.question_ids = [(5, 0, 0)] + [ + if not event.event_type_id and not event.question_ids: + event.question_ids = False + continue + + if questions_tokeep_ids: + questions_toremove = event._origin.question_ids.filtered(lambda question: question.id not in questions_tokeep_ids) + command = [(3, question.id) for question in questions_toremove] + else: + command = [(5, 0)] + if event.event_type_id.use_mail_schedule: + command += [ (0, 0, { 'title': question.title, 'question_type': question.question_type, @@ -40,6 +64,6 @@ class EventEvent(models.Model): 'name': answer.name, 'sequence': answer.sequence }) for answer in question.answer_ids], - }) - for question in event.event_type_id.question_ids + }) for question in event.event_type_id.question_ids ] + event.question_ids = command diff --git a/addons/website_event_questions/tests/test_event_internals.py b/addons/website_event_questions/tests/test_event_internals.py index c2cfe1f40d1..28c9099734a 100644 --- a/addons/website_event_questions/tests/test_event_internals.py +++ b/addons/website_event_questions/tests/test_event_internals.py @@ -13,7 +13,7 @@ from odoo.addons.website_event_questions.tests.common import TestEventQuestionCo class TestEventData(TestEventQuestionCommon): @users('user_eventmanager') - def test_event_type_configuration(self): + def test_event_type_configuration_from_type(self): event_type = self.event_type_complex.with_user(self.env.user) event = self.env['event.event'].create({ |
