[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.
This commit is contained in:
Mahendrasinh Barad
2014-12-24 14:23:21 +01:00
committed by Thibault Delavallée
parent 1656a7e503
commit e2e38076aa
10 changed files with 137 additions and 45 deletions
+6 -4
View File
@@ -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):
+2 -2
View File
@@ -305,13 +305,13 @@
</header>
<sheet string="Registration">
<group>
<group string="Attendee Information">
<group string="Attendee Information" name="attendee">
<field name="name"/>
<field name="phone"/>
<field name="email"/>
<field name="partner_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group string="Event Information">
<group string="Event Information" name="event">
<field name="event_id" readonly="1"/>
<field name="date_open" groups="base.group_no_one"/>
<field name="date_closed" groups="base.group_no_one"/>
+12 -1
View File
@@ -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')
+13 -9
View File
@@ -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': '<i>%s</i>' % res.event_id.name,
'ticket': res.event_ticket_id and _(' with ticket %s') % (('<i>%s</i>') % 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
+18 -7
View File
@@ -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):
+8 -2
View File
@@ -32,8 +32,14 @@
<field name="arch" type="xml">
<field name="event_id" position="after">
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
<field name="origin"/>
</field>
<group name="event" position="after">
<group string="Origin">
<field name="origin" attrs="{'invisible': [('sale_order_id', '!=', False)]}"/>
<field name="sale_order_id"/>
<field name="sale_order_line_id" readonly="1" attrs="{'invisible': [('sale_order_id', '=', False)]}"/>
</group>
</group>
</field>
</record>
@@ -43,7 +49,7 @@
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook" position="inside">
<xpath expr="//page[@string='Registrations']" position="after">
<page string="Ticket Types">
<field name="event_ticket_ids" context="{'default_name': name}">
<tree string="Payments" editable="bottom">
@@ -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,
}
+1 -12
View File
@@ -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")
+55 -5
View File
@@ -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
@@ -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('<div class="alert alert-danger alert-dismissable" role="alert" id="data_warning">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning + '</div>');
}
else {
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning);
}
$input.val(data.quantity);
}
});
});