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 @@ - + + + + + + + + +