diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml
index ffc04deca47..1977634b90a 100644
--- a/addons/event/views/event_views.xml
+++ b/addons/event/views/event_views.xml
@@ -468,7 +468,7 @@
-
+
diff --git a/addons/event_sale/models/event.py b/addons/event_sale/models/event.py
index 1f9a7ed53b0..0ab88b3be85 100644
--- a/addons/event_sale/models/event.py
+++ b/addons/event_sale/models/event.py
@@ -160,17 +160,43 @@ class EventTicket(models.Model):
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.
+ It will often be used as the default description of a sales 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):
+ # We reset the ticket when keeping it would lead to an inconstitent state.
+ 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):
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index a4601ac5f76..c41a6218f10 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -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
@@ -60,9 +52,40 @@ class SaleOrderLine(models.Model):
Registration._prepare_attendee_values(registration))
return True
+ @api.onchange('product_id')
+ def _onchange_product_id(self):
+ # We reset the event when keeping it would lead to an inconstitent state.
+ # 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
+
+ @api.onchange('event_id')
+ def _onchange_event_id(self):
+ # We reset the ticket when keeping it would lead to an inconstitent state.
+ 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 sales 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 sales 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)
diff --git a/addons/event_sale/tests/test_event_sale.py b/addons/event_sale/tests/test_event_sale.py
index d73a1cfcb7e..8f17f322780 100644
--- a/addons/event_sale/tests/test_event_sale.py
+++ b/addons/event_sale/tests/test_event_sale.py
@@ -25,6 +25,12 @@ class EventSaleTest(common.TransactionCase):
'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,
@@ -40,7 +46,8 @@ class EventSaleTest(common.TransactionCase):
'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
diff --git a/addons/event_sale/views/event_views.xml b/addons/event_sale/views/event_views.xml
index e8498e562e1..778bd166cb3 100644
--- a/addons/event_sale/views/event_views.xml
+++ b/addons/event_sale/views/event_views.xml
@@ -29,7 +29,15 @@
-
+
diff --git a/addons/event_sale/views/sale_order_views.xml b/addons/event_sale/views/sale_order_views.xml
index 3d2b8530947..712da75ffd8 100644
--- a/addons/event_sale/views/sale_order_views.xml
+++ b/addons/event_sale/views/sale_order_views.xml
@@ -6,8 +6,28 @@
-
-
+
+
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index a42b37cf5e7..ccda4b32511 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -572,6 +572,16 @@ class ProductProduct(models.Model):
)
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"
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index cdae56697db..3a66cc1a1f4 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -1343,10 +1343,7 @@ class SaleOrderLine(models.Model):
self.product_id = False
return result
- name = product.name_get()[0][1]
- if product.description_sale:
- name += '\n' + product.description_sale
- vals['name'] = name
+ vals.update(name=self.get_sale_order_line_multiline_description_sale(product))
self._compute_tax_id()
@@ -1486,3 +1483,12 @@ class SaleOrderLine(models.Model):
def _is_delivery(self):
self.ensure_one()
return False
+
+ def get_sale_order_line_multiline_description_sale(self, product):
+ """ Compute a default multiline description for this sales 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 sales 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()
diff --git a/addons/sale_management/models/sale_order.py b/addons/sale_management/models/sale_order.py
index f2481bc7fc5..b8a8d3ad8e8 100644
--- a/addons/sale_management/models/sale_order.py
+++ b/addons/sale_management/models/sale_order.py
@@ -238,9 +238,7 @@ class SaleOrderOption(models.Model):
return
product = self.product_id.with_context(lang=self.order_id.partner_id.lang)
self.price_unit = product.list_price
- self.name = product.name
- if product.description_sale:
- self.name += '\n' + product.description_sale
+ self.name = product.get_product_multiline_description_sale()
self.uom_id = self.uom_id or product.uom_id
pricelist = self.order_id.pricelist_id
if pricelist and product:
diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py
index ae07f798939..e4e10667dfc 100644
--- a/addons/website_event/models/event.py
+++ b/addons/website_event/models/event.py
@@ -4,12 +4,9 @@ import logging
import pytz
import werkzeug
-from datetime import datetime
-
from odoo import api, fields, models, _
from odoo.addons.http_routing.models.ir_http import slug
from odoo.exceptions import UserError
-from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
@@ -162,8 +159,8 @@ class Event(models.Model):
return result
def _get_event_resource_urls(self, attendees):
- url_date_start = datetime.strptime(self.date_begin, DEFAULT_SERVER_DATETIME_FORMAT).strftime('%Y%m%dT%H%M%SZ')
- url_date_stop = datetime.strptime(self.date_end, DEFAULT_SERVER_DATETIME_FORMAT).strftime('%Y%m%dT%H%M%SZ')
+ url_date_start = self.date_begin.strftime('%Y%m%dT%H%M%SZ')
+ url_date_stop = self.date_end.strftime('%Y%m%dT%H%M%SZ')
params = {
'action': 'TEMPLATE',
'text': self.name,
diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py
index 3cf2d253182..097ce0adbaf 100644
--- a/addons/website_event_sale/models/sale_order.py
+++ b/addons/website_event_sale/models/sale_order.py
@@ -42,7 +42,7 @@ class SaleOrder(models.Model):
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)
@@ -96,3 +96,18 @@ class SaleOrder(models.Model):
# 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
diff --git a/addons/website_event_sale/views/event_templates.xml b/addons/website_event_sale/views/event_templates.xml
index 4be278f9620..bccf05ad8be 100644
--- a/addons/website_event_sale/views/event_templates.xml
+++ b/addons/website_event_sale/views/event_templates.xml
@@ -84,15 +84,54 @@
-
-
+
+
+
line.event_id and ('/event/%s/register' % slug(line.event_id)) or ('/shop/product/%s' % slug(line.product_id.product_tmpl_id))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ticket
+
+
+
N/A
+
+
+
+
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index a48436c1433..8d8fd395045 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -239,3 +239,21 @@ class SaleOrder(models.Model):
self.modified(['create_date'])
if self.env.recompute and self.env.context.get('recompute', True):
self.recompute()
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ name_short = fields.Char(compute="_compute_name_short")
+
+ @api.multi
+ @api.depends('product_id.display_name')
+ def _compute_name_short(self):
+ """ Compute a short name for this sale order line, to be used on the website where we don't have much space.
+ To keep it short, instead of using the first line of the description, we take the product name without the internal reference.
+ """
+ for record in self:
+ record.name_short = record.product_id.with_context(display_default_code=False).display_name
+
+ def get_description_following_lines(self):
+ return self.name.splitlines()[1:]
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 20a4c211015..1d5010bc412 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -817,6 +817,24 @@
+
+
+
+
+
+
+
+
+
+