From e2e38076aa3b33de9b23a3530a0cd104b4e2532f Mon Sep 17 00:00:00 2001 From: Mahendrasinh Barad Date: Tue, 18 Nov 2014 10:53:23 +0100 Subject: [PATCH] [IMP] website_event_sale: validation of seats availability in cart. Attendees are created and deleted on changing the quantity from the cart. Added sale_order_id and sale_order_line_id fields on registrations, allowing to find the order and the order line that created the registration without to rely on origin and ticket. Also fixed check_auto_confirmation, now in multi mode to avoid issues due to the specific behavior of api.one. --- addons/event/event.py | 10 ++-- addons/event/event_view.xml | 4 +- addons/event/tests/test_event_flow.py | 13 +++- addons/event_sale/models/event.py | 22 ++++--- addons/event_sale/models/sale_order.py | 25 +++++--- addons/event_sale/views/event.xml | 10 +++- .../wizard/event_edit_registration.py | 14 ++++- addons/website_event_sale/controllers/main.py | 13 +--- .../website_event_sale/models/sale_order.py | 60 +++++++++++++++++-- .../static/src/js/website_sale.js | 11 ++++ 10 files changed, 137 insertions(+), 45 deletions(-) diff --git a/addons/event/event.py b/addons/event/event.py index 3d07fccc472..97caf7de37e 100644 --- a/addons/event/event.py +++ b/addons/event/event.py @@ -277,13 +277,15 @@ class event_registration(models.Model): if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0): raise Warning(_('No more seats available for this event.')) - @api.one + @api.multi def _check_auto_confirmation(self): if self._context.get('registration_force_draft'): return False - if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available: - return True - return False + if any(registration.event_id.state != 'confirm' or + not registration.event_id.auto_confirm or + not registration.event_id.seats_available for registration in self): + return False + return True @api.model def create(self, vals): diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index b7ca6167362..2bd5a0f5afb 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -305,13 +305,13 @@ - + - + diff --git a/addons/event/tests/test_event_flow.py b/addons/event/tests/test_event_flow.py index ed5219d4d0a..f94567ba50a 100644 --- a/addons/event/tests/test_event_flow.py +++ b/addons/event/tests/test_event_flow.py @@ -74,4 +74,15 @@ class TestEventFlow(TestEventCommon): 'date_end': datetime.datetime.now() + relativedelta(days=1), 'seats_max': 10, }) - self.assertEqual(test_event.state, 'draft', 'Event: new event should be in draft state, no auto confirmation') + self.assertEqual( + test_event.state, 'draft', + 'Event: new event should be in draft state, no auto confirmation') + + # EventUser create registrations for this event -> no auto confirmation + test_reg1 = self.Registration.sudo(self.user_eventuser).create({ + 'name': 'TestReg1', + 'event_id': test_event.id, + }) + self.assertEqual( + test_reg1.state, 'draft', + 'Event: new registration should not be confirmed with auto_confirmation parameter being False') diff --git a/addons/event_sale/models/event.py b/addons/event_sale/models/event.py index 2dc204fafa6..ca214bdfce4 100644 --- a/addons/event_sale/models/event.py +++ b/addons/event_sale/models/event.py @@ -11,7 +11,7 @@ class event_event(models.Model): event_ticket_ids = fields.One2many( 'event.event.ticket', 'event_id', string='Event Ticket', - default=lambda rec: rec._default_tickets(), copy=True) + default=lambda rec: rec._default_tickets(), copy=True) seats_max = fields.Integer( string='Maximum Available Seats', help="The maximum registration level is equal to the sum of the maximum registration of event ticket. " + @@ -149,7 +149,11 @@ class event_registration(models.Model): _inherit = 'event.registration' event_ticket_id = fields.Many2one('event.event.ticket', 'Event Ticket') - # sale_order_line_id = fields.Many2one('sale.order.line', 'Sale Order Line', ondelete='cascade') + # in addition to origin generic fields, add real relational fields to correctly + # handle attendees linked to sale orders and their lines + # TDE FIXME: maybe add an onchange on sale_order_id + origin + sale_order_id = fields.Many2one('sale.order', 'Source Sale Order', ondelete='cascade') + sale_order_line_id = fields.Many2one('sale.order.line', 'Sale Order Line', ondelete='cascade') @api.one @api.constrains('event_ticket_id', 'state') @@ -157,22 +161,22 @@ class event_registration(models.Model): if self.event_ticket_id.seats_max and self.event_ticket_id.seats_available < 0: raise Warning('No more available seats for this ticket') - @api.one + @api.multi def _check_auto_confirmation(self): - res = super(event_registration, self)._check_auto_confirmation()[0] - if res and self.origin: - orders = self.env['sale.order'].search([('name', '=', self.origin)], limit=1) - if orders and orders[0].state == 'draft': + res = super(event_registration, self)._check_auto_confirmation() + if res: + orders = self.env['sale.order'].search([('state', '=', 'draft'), ('id', 'in', self.mapped('sale_order_id').ids)], limit=1) + if orders: res = False return res @api.model def create(self, vals): res = super(event_registration, self).create(vals) - if res.origin: + if res.origin or res.sale_order_id: message = _("The registration has been created for event %(event_name)s%(ticket)s from sale order %(order)s") % ({ 'event_name': '%s' % res.event_id.name, 'ticket': res.event_ticket_id and _(' with ticket %s') % (('%s') % res.event_ticket_id.name) or '', - 'order': res.origin}) + 'order': res.origin or res.sale_order_id.name}) res.message_post(body=message) return res diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index 1b5f5ac03de..ba05138ae74 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -57,23 +57,34 @@ class sale_order_line(osv.osv): return res @api.multi - def _update_registrations(self): + def _update_registrations(self, confirm=True, registration_data=None): """ Create or update registrations linked to a sale order line. A sale order line has a product_uom_qty attribute that will be the number of registrations linked to this line. This method update existing registrations and create new one for missing one. """ - registrations = self.env['event.registration'].search([('origin', 'in', list(set([so.name for line in self for so in line.order_id if line.event_id])))]) + registrations = self.env['event.registration'].search([('sale_order_line_id', 'in', self.ids)]) for so_line in [l for l in self if l.event_id]: - existing_registrations = [r for r in registrations if r.event_id == so_line.event_id and r.origin == so_line.order_id.name] - for registration in existing_registrations: - registration.write({'state': 'open'}) + existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id) + if confirm: + existing_registrations.filtered(lambda self: self.state != 'open').confirm_registration() + else: + existing_registrations.filtered(lambda self: self.state == 'cancel').do_draft() for count in range(int(so_line.product_uom_qty) - len(existing_registrations)): - self.env['event.registration'].create({ + registration = {} + if registration_data: + registration = registration_data.pop() + # TDE CHECK: auto confirmation + self.env['event.registration'].with_context(registration_force_draft=True).create({ + 'name': registration.get('name', so_line.order_id.partner_id.name), + 'phone': registration.get('phone', so_line.order_id.partner_id.phone), + 'email': registration.get('email', so_line.order_id.partner_id.email), 'event_id': so_line.event_id.id, 'event_ticket_id': so_line.event_ticket_id.id, 'partner_id': so_line.order_id.partner_id.id, 'origin': so_line.order_id.name, + 'sale_order_id': so_line.order_id.id, + 'sale_order_line_id': so_line.id, }) return True @@ -84,7 +95,7 @@ class sale_order_line(osv.osv): create registration with sales order ''' res = super(sale_order_line, self).button_confirm(cr, uid, ids, context=context) - self._update_registrations(cr, uid, ids, context=context) + self._update_registrations(cr, uid, ids, confirm=True, context=context) return res def onchange_event_ticket_id(self, cr, uid, ids, event_ticket_id=False, context=None): diff --git a/addons/event_sale/views/event.xml b/addons/event_sale/views/event.xml index 5b8344fb55e..b63006f10bb 100644 --- a/addons/event_sale/views/event.xml +++ b/addons/event_sale/views/event.xml @@ -32,8 +32,14 @@ - + + + + + + + @@ -43,7 +49,7 @@ - + diff --git a/addons/event_sale/wizard/event_edit_registration.py b/addons/event_sale/wizard/event_edit_registration.py index dfc43b0c4a2..d301f984acc 100644 --- a/addons/event_sale/wizard/event_edit_registration.py +++ b/addons/event_sale/wizard/event_edit_registration.py @@ -16,11 +16,14 @@ class SaleOrderEventRegistration(models.TransientModel): sale_order_id = res.get('sale_order_id', self._context.get('active_id')) res['sale_order_id'] = sale_order_id sale_order = self.env['sale.order'].browse(res.get('sale_order_id')) - registrations = self.env['event.registration'].search([('origin', '=', sale_order.name)]) + registrations = self.env['event.registration'].search([ + ('sale_order_id', '=', sale_order.id), + ('event_ticket_id', 'in', sale_order.mapped('order_line.event_ticket_id').ids), + ('state', '!=', 'cancel')]) attendee_list = [] - for so_line in [l for l in sale_order.order_line if l.event_id]: - existing_registrations = [r for r in registrations if r.event_id == so_line.event_id] + for so_line in [l for l in sale_order.order_line if l.event_ticket_id]: + existing_registrations = [r for r in registrations if r.event_ticket_id == so_line.event_ticket_id] for reg in existing_registrations: attendee_list.append({ 'event_id': reg.event_id.id, @@ -29,11 +32,13 @@ class SaleOrderEventRegistration(models.TransientModel): 'name': reg.name, 'email': reg.email, 'phone': reg.phone, + 'sale_order_line_id': so_line.id, }) for count in range(int(so_line.product_uom_qty) - len(existing_registrations)): attendee_list.append({ 'event_id': so_line.event_id.id, 'event_ticket_id': so_line.event_ticket_id.id, + 'sale_order_line_id': so_line.id, }) res['event_registration_ids'] = attendee_list return res @@ -55,6 +60,7 @@ class RegistrationEditorLine(models.TransientModel): _name = "registration.editor.line" editor_id = fields.Many2one('registration.editor') + sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line') event_id = fields.Many2one('event.event', string='Event', required=True) registration_id = fields.Many2one('event.registration', 'Original Registration') event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket') @@ -72,4 +78,6 @@ class RegistrationEditorLine(models.TransientModel): 'phone': self.phone or self.editor_id.sale_order_id.partner_id.phone, 'email': self.email or self.editor_id.sale_order_id.partner_id.email, 'origin': self.editor_id.sale_order_id.name, + 'sale_order_id': self.editor_id.sale_order_id.id, + 'sale_order_line_id': self.sale_order_line_id.id, } diff --git a/addons/website_event_sale/controllers/main.py b/addons/website_event_sale/controllers/main.py index 022066436b0..a34b4558f2c 100644 --- a/addons/website_event_sale/controllers/main.py +++ b/addons/website_event_sale/controllers/main.py @@ -57,20 +57,9 @@ class website_event(website_event): order = request.website.sale_get_order(force_create=1) registrations = self._process_registration_details(post) - registration_ctx = dict(context, registration_force_draft=True) for registration in registrations: ticket = request.registry['event.event.ticket'].browse(cr, SUPERUSER_ID, int(registration['ticket_id']), context=context) - order.with_context(event_ticket_id=ticket.id)._cart_update(product_id=ticket.product_id.id, add_qty=1) - - request.registry['event.registration'].create(cr, SUPERUSER_ID, { - 'name': registration.get('name'), - 'phone': registration.get('phone'), - 'email': registration.get('email'), - 'event_ticket_id': int(registration['ticket_id']), - 'partner_id': order.partner_id.id, - 'event_id': event.id, - 'origin': order.name, - }, context=registration_ctx) + order.with_context(event_ticket_id=ticket.id)._cart_update(product_id=ticket.product_id.id, add_qty=1, registration_data=[registration]) return request.redirect("/shop/checkout") diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py index 127efc949e3..549bdde3bf5 100644 --- a/addons/website_event_sale/models/sale_order.py +++ b/addons/website_event_sale/models/sale_order.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- from openerp import SUPERUSER_ID -from openerp.osv import osv, fields +from openerp.osv import osv from openerp.tools.translate import _ -import openerp.addons.decimal_precision as dp -# defined for access rules + class sale_order(osv.Model): _inherit = "sale.order" @@ -19,7 +18,7 @@ class sale_order(osv.Model): return self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context) def _website_product_id_change(self, cr, uid, ids, order_id, product_id, qty=0, line_id=None, context=None): - values = super(sale_order,self)._website_product_id_change(cr, uid, ids, order_id, product_id, qty=qty, line_id=line_id, context=None) + values = super(sale_order, self)._website_product_id_change(cr, uid, ids, order_id, product_id, qty=qty, line_id=line_id, context=None) event_ticket_id = None if context.get("event_ticket_id"): @@ -36,7 +35,7 @@ class sale_order(osv.Model): if event_ticket_id: ticket = self.pool.get('event.event.ticket').browse(cr, uid, event_ticket_id, context=context) if product_id != ticket.product_id.id: - raise osv.except_osv(_('Error!'),_("The ticket doesn't match with this product.")) + raise osv.except_osv(_('Error!'), _("The ticket doesn't match with this product.")) values['product_id'] = ticket.product_id.id values['event_id'] = ticket.event_id.id @@ -45,3 +44,54 @@ class sale_order(osv.Model): values['name'] = "%s: %s" % (ticket.event_id.name, ticket.name) return values + + def _cart_update(self, cr, uid, ids, product_id=None, line_id=None, add_qty=0, set_qty=0, context=None, **kwargs): + OrderLine = self.pool['sale.order.line'] + Attendee = self.pool['event.registration'] + Ticket = self.pool['event.event.ticket'] + + if line_id: + line = OrderLine.browse(cr, uid, line_id, context=context) + ticket = line.event_ticket_id + old_qty = int(line.product_uom_qty) + else: + line, ticket = None, None + ticket_ids = Ticket.search(cr, uid, [('product_id', '=', product_id)], limit=1, context=context) + if ticket_ids: + ticket = Ticket.browse(cr, uid, ticket_ids[0], context=context) + old_qty = 0 + new_qty = set_qty if set_qty else (add_qty or 0 + old_qty) + + # case: buying tickets for a sold out ticket + values = {} + if ticket and ticket.seats_available <= 0: + values['warning'] = _('Sorry, The %(ticket)s tickets for the %(event)s event are sold out.') % { + 'ticket': ticket.name, + 'event': ticket.event_id.name} + new_qty, set_qty, add_qty = 0, 0, 0 + # case: buying tickets, too much attendees + elif ticket and new_qty > ticket.seats_available: + values['warning'] = _('Sorry, only %(remaining_seats)d seats are still available for the %(ticket)s ticket for the %(event)s event.') % { + 'remaining_seats': ticket.seats_available, + 'ticket': ticket.name, + 'event': ticket.event_id.name} + new_qty, set_qty, add_qty = ticket.seats_available, ticket.seats_available, 0 + + values.update(super(sale_order, self)._cart_update( + cr, uid, ids, product_id, line_id, add_qty, set_qty, context, **kwargs)) + + # removing attendees + if ticket and new_qty < old_qty: + attendees = Attendee.search( + cr, uid, [ + ('state', '!=', 'cancel'), + ('sale_order_id', '=', ids[0]) + ('event_ticket_id', '=', ticket.id) + ], offset=new_qty, limit=(old_qty-new_qty), + order='create_date asc', context=context) + Attendee.button_reg_cancel(cr, uid, attendees, context=context) + # adding attendees + elif ticket and new_qty > old_qty: + line = OrderLine.browse(cr, uid, values['line_id'], context=context) + line._update_registrations(confirm=False, registration_data=kwargs.get('registration_data', [])) + return values diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index d700ef25cec..43c452a4300 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -45,6 +45,17 @@ $('.oe_website_sale').each(function () { $input.val(data.quantity); $('.js_quantity[data-line-id='+line_id+']').val(data.quantity).html(data.quantity); $("#cart_total").replaceWith(data['website_sale.total']); + if (data.warning) { + var cart_alert = $('.oe_cart').parent().find('#data_warning') + if (cart_alert.length == 0) { + $('.oe_cart').prepend(''); + } + else { + cart_alert.html(' ' + data.warning); + } + $input.val(data.quantity); + } }); });