Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Master event sale ticket name sht #24772

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions addons/event/models/event.py
Expand Up @@ -426,6 +426,13 @@ def button_reg_close(self):
def button_reg_cancel(self):
self.state = 'cancel'

@api.onchange('event_id')
def _onchange_event_id(self):
""" This method is overriden in event_sale.
It's just added here so super() can be called and we don't risk someone later not paying attention to the override.
"""
pass

@api.onchange('partner_id')
def _onchange_partner(self):
if self.partner_id:
Expand Down
4 changes: 2 additions & 2 deletions addons/event/views/event_views.xml
Expand Up @@ -238,7 +238,7 @@
<div>
<field name="seats_availability" widget='radio'/>
<span attrs="{'invisible': [('seats_availability', '=', 'unlimited')]}" class="oe_read_only">
to
to
</span>
<field name="seats_max" attrs="{'invisible': [('seats_availability', '=', 'unlimited')], 'required': [('seats_availability', '=', 'limited')]}"/>
</div>
Expand Down Expand Up @@ -477,7 +477,7 @@
<field name="email"/>
</group>
<group string="Event Information" name="event">
<field name="event_id" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="event_id" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_open': True, 'no_create': True}"/>
<field name="date_open" groups="base.group_no_one"/>
<field name="date_closed" groups="base.group_no_one"/>
</group>
Expand Down
32 changes: 31 additions & 1 deletion addons/event_sale/models/event.py
Expand Up @@ -61,6 +61,7 @@ def _is_event_registrable(self):
return True
return all(self.event_ticket_ids.with_context(active_test=False).mapped(lambda t: t.product_id.active))


class EventTicket(models.Model):
_name = 'event.event.ticket'
_description = 'Event Ticket'
Expand Down Expand Up @@ -160,17 +161,46 @@ def _constrains_event(self):
def _onchange_product_id(self):
self.price = self.product_id.list_price or 0

def get_ticket_multiline_description_sale(self):
""" Compute a multiline description of this ticket, in the context of sales
(do not use for purchases or other display reasons that don't intend to use "description_sale").
It will often be used as the default description of a sale order line referencing this ticket.

1. the first line is the ticket name
2. the second line is the event name (if it exists, which should be the case with a normal workflow) or the product name (if it exists)

We decided to ignore entirely the product name and the product description_sale because they are considered to be replaced by the ticket name and event name.
-> the workflow of creating a new event also does not lead to filling them correctly, as the product is created through the event interface
"""

name = self.display_name

if self.event_id:
name += '\n' + self.event_id.display_name
elif self.product_id:
name += '\n' + self.product_id.display_name

return name


class EventRegistration(models.Model):
_inherit = 'event.registration'

event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket',
readonly=True, states={'draft': [('readonly', False)]})
# in addition to origin generic fields, add real relational fields to correctly
# handle attendees linked to sales orders and their lines
# TDE FIXME: maybe add an onchange on sale_order_id + origin
sale_order_id = fields.Many2one('sale.order', string='Source Sales Order', ondelete='cascade')
sale_order_line_id = fields.Many2one('sale.order.line', string='Sales Order Line', ondelete='cascade')

@api.onchange('event_id')
def _onchange_event_id(self):
super(EventRegistration, self)._onchange_event_id()

if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id):
self.event_ticket_id = None

@api.multi
@api.constrains('event_ticket_id', 'state')
def _check_ticket_seats_limit(self):
Expand Down
38 changes: 30 additions & 8 deletions addons/event_sale/models/sale_order.py
Expand Up @@ -27,14 +27,6 @@ class SaleOrderLine(models.Model):
"an event ticket and it will automatically create a registration for this event ticket.")
event_ok = fields.Boolean(related='product_id.event_ok', readonly=True)

@api.multi
def _prepare_invoice_line(self, qty):
self.ensure_one()
res = super(SaleOrderLine, self)._prepare_invoice_line(qty)
if self.event_id:
res['name'] = '%s: %s' % (res.get('name', ''), self.event_id.name)
return res

@api.multi
def _update_registrations(self, confirm=True, cancel_to_draft=False, registration_data=None):
""" Create or update registrations linked to a sales order line. A sale
Expand All @@ -60,9 +52,39 @@ def _update_registrations(self, confirm=True, cancel_to_draft=False, registratio
Registration._prepare_attendee_values(registration))
return True

@api.onchange('product_id')
def _onchange_product_id(self):
# We need to do it this way because the only relation between the product and the event is through the corresponding tickets.
if self.event_id and (not self.product_id or self.product_id.id not in self.event_id.mapped('event_ticket_ids.product_id.id')):
self.event_id = None
# This will cascade the onchange below to also unset the ticket.

@api.onchange('event_id')
def _onchange_event_id(self):
if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id):
self.event_ticket_id = None

@api.onchange('event_ticket_id')
def _onchange_event_ticket_id(self):
company = self.event_id.company_id or self.env.user.company_id
currency = company.currency_id
self.price_unit = currency._convert(
self.event_ticket_id.price, self.order_id.currency_id, self.order_id.company_id, self.order_id.date_order or fields.Date.today())

# we call this to force update the default name
self.product_id_change()

def _get_sale_order_line_multiline_description_sale(self, product):
""" We override this method because we decided that:
The default description of a sale order line containing a ticket must be different than the default description when no ticket is present.
So in that case we use the description computed from the ticket, instead of the description computed from the product.
We need this override to be defined here in sale order line (and not in product) because here is the only place where the event_ticket_id is referenced.
"""
if self.event_ticket_id:
ticket = self.event_ticket_id.with_context(
lang=self.order_id.partner_id.lang,
)

return ticket.get_ticket_multiline_description_sale()
else:
return super(SaleOrderLine, self)._get_sale_order_line_multiline_description_sale(product)
9 changes: 8 additions & 1 deletion addons/event_sale/tests/test_event_sale.py
Expand Up @@ -25,6 +25,12 @@ def setUp(self):
'date_begin': '2012-01-01 18:05:15'
})

ticket = self.env['event.event.ticket'].create({
'name': 'test_ticket',
'product_id': product.id,
'event_id': event.id,
})

# I create a sales order
self.sale_order = self.env['sale.order'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
Expand All @@ -40,7 +46,8 @@ def setUp(self):
'product_uom_qty': 8.0,
'order_id': self.sale_order.id,
'name': 'sales order line',
'event_id': event.id
'event_id': event.id,
'event_ticket_id': ticket.id,
})

# In the event registration I add some attendee detail lines. i choose event product
Expand Down
10 changes: 9 additions & 1 deletion addons/event_sale/views/event_views.xml
Expand Up @@ -29,7 +29,15 @@
<field name="inherit_id" ref="event.view_event_registration_form" />
<field name="arch" type="xml">
<field name="event_id" position="after">
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
<field
name="event_ticket_id"
domain="[
('event_id', '=', event_id),
'|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
]"
attrs="{'invisible': [('event_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"
/>
</field>
<group name="event" position="after">
<group string="Origin">
Expand Down
23 changes: 21 additions & 2 deletions addons/event_sale/views/sale_order_views.xml
Expand Up @@ -6,8 +6,27 @@
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']//form//field[@name='product_id']" position="after">
<field name="event_id" domain="[('event_ticket_ids.product_id','=', product_id),('date_end','&gt;=',time.strftime('%Y-%m-%d 00:00:00'))]" attrs="{'invisible': [('event_ok', '=', False)],'required': [('event_ok', '!=', False)]}"/>
<field name="event_ticket_id" domain="[('event_id', '=', event_id), ('product_id','=',product_id), '|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)]" attrs="{'invisible': [('event_ok', '=', False)], 'required': [('event_ok', '!=', False)]}"/>
<field
name="event_id"
domain="[
('event_ticket_ids.product_id','=', product_id),
('date_end','&gt;=',time.strftime('%Y-%m-%d 00:00:00'))
]"
attrs="{'invisible': [('event_ok', '=', False)],'required': [('event_ok', '!=', False)]}"
options="{'no_open': True, 'no_create': True}"
/>
<field
name="event_ticket_id"
domain="[
('event_id', '=', event_id),
('product_id','=',product_id),
'|', ('seats_availability', '=', 'unlimited'), ('seats_available', '>', 0)
]"
attrs="{'invisible': [
'|', ('event_ok', '=', False), ('event_id', '=', False)
], 'required': [('event_ok', '!=', False), ('event_id', '!=', False)]}"
options="{'no_open': True, 'no_create': True}"
/>
<field name="event_ok" invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="after">
Expand Down
11 changes: 10 additions & 1 deletion addons/product/models/product.py
Expand Up @@ -532,7 +532,6 @@ def price_compute(self, price_type, uom=False, currency=False, company=False):

return prices


# compatibility to remove after v10 - DEPRECATED
@api.multi
def price_get(self, ptype='list_price'):
Expand Down Expand Up @@ -564,6 +563,16 @@ def get_empty_list_help(self, help):
)
return super(ProductProduct, self).get_empty_list_help(help)

def get_product_multiline_description_sale(self):
""" Compute a multiline description of this product, in the context of sales
(do not use for purchases or other display reasons that don't intend to use "description_sale").
It will often be used as the default description of a sale order line referencing this product.
"""
name = self.display_name
if self.description_sale:
name += '\n' + self.description_sale
return name


class ProductPackaging(models.Model):
_name = "product.packaging"
Expand Down
18 changes: 14 additions & 4 deletions addons/sale/models/sale.py
Expand Up @@ -1134,10 +1134,11 @@ def product_id_change(self):
self.product_id = False
return result

name = product.name_get()[0][1]
if product.description_sale:
name += '\n' + product.description_sale
vals['name'] = name
""" Here we call a method on the sale order line (= self) instead of directly calling product.get_product_multiline_description_sale()
Why? -> So in other modules the description (= vals['name']) can depend on other fields of the sale order line, a feature notably used by event_sale tickets
which override this method (_get_sale_order_line_multiline_description_sale) and read the event_ticket_id present on the sale order line to generate the description.
"""
vals['name'] = self._get_sale_order_line_multiline_description_sale(product)

self._compute_tax_id()

Expand Down Expand Up @@ -1264,3 +1265,12 @@ def _onchange_discount(self):
discount = (new_list_price - price) / new_list_price * 100
if discount > 0:
self.discount = discount

def _get_sale_order_line_multiline_description_sale(self, product):
""" Compute a default multiline description for this sale order line.
This method exists so it can be overridden in other modules to change how the default name is computed.
In general only the product is used to compute the name, and this method would not be necessary (we could directly override the method in product).
BUT in event_sale we need to know specifically the sale order line as well as the product to generate the name:
the product is not sufficient because we also need to know the event_id and the event_ticket_id (both which belong to the sale order line)
"""
return product.get_product_multiline_description_sale()
2 changes: 2 additions & 0 deletions addons/website_event/views/event_templates.xml
Expand Up @@ -615,6 +615,7 @@
<thead>
<tr>
<th>Reference</th>
<!-- Note: we override this on website_event_sale to add the ticket here. -->
<th>Name</th>
<th>E-mail</th>
<th>Phone</th>
Expand All @@ -624,6 +625,7 @@
<t t-foreach="attendees" t-as="attendee">
<tr>
<td><t t-esc="attendee.id"/></td>
<!-- Note: we override this on website_event_sale to add the ticket here. -->
<td><i class="fa fa-user"></i> <t t-if='attendee.name'><t t-esc="attendee.name"/></t><t t-if='not attendee.name'>N/A</t></td>
<td><i class="fa fa-envelope"></i> <t t-if='attendee.email'><t t-esc="attendee.email"/></t><t t-if='not attendee.email'>N/A</t></td>
<td><i class="fa fa-phone"></i> <t t-if='attendee.phone'><t t-esc="attendee.phone"/></t><t t-if='not attendee.phone'>N/A</t></td>
Expand Down
17 changes: 16 additions & 1 deletion addons/website_event_sale/models/sale_order.py
Expand Up @@ -42,7 +42,7 @@ def _website_product_id_change(self, order_id, product_id, qty=0):
values['event_id'] = ticket.event_id.id
values['event_ticket_id'] = ticket.id
values['price_unit'] = ticket.price_reduce or ticket.price
values['name'] = "%s\n%s" % (ticket.event_id.display_name, ticket.name)
values['name'] = ticket.get_ticket_multiline_description_sale()

# avoid writing related values that end up locking the product record
values.pop('event_ok', None)
Expand Down Expand Up @@ -96,3 +96,18 @@ def _cart_update(self, product_id=None, line_id=None, add_qty=0, set_qty=0, **kw
# add in return values the registrations, to display them on website (or not)
values['attendee_ids'] = self.env['event.registration'].search([('sale_order_line_id', '=', line.id), ('state', '!=', 'cancel')]).ids
return values


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

@api.multi
@api.depends('product_id.display_name', 'event_ticket_id.display_name')
def _compute_name_short(self):
""" If the sale order line concerns a ticket, we don't want the product name, but the ticket name instead.
"""
super(SaleOrderLine, self)._compute_name_short()

for record in self:
if record.event_ticket_id:
record.name_short = record.event_ticket_id.display_name
43 changes: 41 additions & 2 deletions addons/website_event_sale/views/event_templates.xml
Expand Up @@ -84,15 +84,54 @@
</xpath>
</template>

<template id="cart" inherit_id="website_sale.cart_lines" name="Hide product reduction for event tickets">
<xpath expr="//td[hasclass('td-product_name')]/div/a" position="attributes">
<!-- If the sale order line concerns an event, we want the "product" link to point to the event itself and not to the product on the ecommerce -->
<template id="event_cart_line_product_link" inherit_id="website_sale.cart_line_product_link" name="Event Shopping Cart Line Product Link">
<xpath expr="//a" position="attributes">
<attribute name="t-attf-href"/>
<attribute name="t-att-href">
line.event_id and ('/event/%s/register' % slug(line.event_id)) or ('/shop/product/%s' % slug(line.product_id.product_tmpl_id))
</attribute>
</xpath>
</template>

<!-- If the sale order line concerns an event, we want to show an additional line with the event name even on small screens -->
<template id="event_cart_lines" inherit_id="website_sale.cart_lines" name="Event Shopping Cart Lines">
<xpath expr="//t[@t-call='website_sale.cart_line_description_following_lines']/t[@t-set='div_class']" position="after">
<t t-if="line.event_id">
<t t-set="div_class" t-value="''"/>
</t>
</xpath>
</template>

<!-- If the sale order line concerns an event, we want to show an additional line with the event name -->
<template id="event_cart_popover" inherit_id="website_sale.cart_popover" name="Event Cart Popover">
<xpath expr="//t[@t-call='website_sale.cart_line_product_link']" position="after">
<t t-if="line.event_id" t-call="website_sale.cart_line_description_following_lines"/>
</xpath>
</template>

<!-- If the sale order line concerns an event, we want to show an additional line with the event name -->
<template id="event_cart_summary" inherit_id="website_sale.cart_summary" name="Event Cart right column">
<xpath expr="//td[hasclass('td-product_name')]/div/strong" position="after">
<t t-if="line.event_id" t-call="website_sale.cart_line_description_following_lines"/>
</xpath>
</template>

<template id="cart" inherit_id="website_sale.cart_lines" name="Hide product reduction for event tickets">
<xpath expr="//del" position="attributes">
<attribute name="t-attf-class" separator=" " add="#{line.event_id and 'hidden' or ''}"/>
</xpath>
</template>

<!-- Add the ticket name on the registration complete page. -->
<template id="sale_registration_complete" inherit_id="website_event.registration_complete" name="Sale Registration Completed">
<xpath expr="//thead/tr/th[1]" position="after">
<th>Ticket</th>
</xpath>
<xpath expr="//tbody/t/tr/td[1]" position="after">
<td><i class="fa fa-ticket"></i> <t t-if='attendee.event_ticket_id'><t t-esc="attendee.event_ticket_id.name"/></t><t t-if='not attendee.event_ticket_id'>N/A</t></td>
</xpath>
</template>

</odoo>