From da71f04db3ca38bd2713c9b7478ae7e06874791a Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 27 Sep 2022 17:12:14 +0000 Subject: [PATCH] [REF] event_sale: convert configurator to OWL And stop relying on dumb event_ok field, when the product_type already holds the information (recently moved from sale_stock to sale). Task-2918791 X-original-commit: 10255e549d071c1ceee5faae7be0e1a0486a3639 Part-of: odoo/odoo#101352 --- addons/event_sale/__manifest__.py | 3 - addons/event_sale/models/sale_order.py | 15 +- .../static/src/js/event_configurator_model.js | 4 +- .../src/js/event_configurator_widget.js | 144 ------------------ .../static/src/js/sale_product_field.js | 61 ++++++++ .../static/tests/event_configurator.test.js | 140 ----------------- .../tests/tours/event_configurator_ui.js | 31 ++-- addons/event_sale/views/sale_order_views.xml | 8 +- 8 files changed, 87 insertions(+), 319 deletions(-) delete mode 100644 addons/event_sale/static/src/js/event_configurator_widget.js create mode 100644 addons/event_sale/static/src/js/sale_product_field.js delete mode 100644 addons/event_sale/static/tests/event_configurator.test.js diff --git a/addons/event_sale/__manifest__.py b/addons/event_sale/__manifest__.py index caff9de6f23..120d5d3f682 100644 --- a/addons/event_sale/__manifest__.py +++ b/addons/event_sale/__manifest__.py @@ -48,9 +48,6 @@ this event. 'web.assets_tests': [ 'event_sale/static/tests/tours/**/*', ], - 'web.qunit_suite_tests': [ - 'event_sale/static/tests/event_configurator.test.js', - ], }, 'license': 'LGPL-3', } diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index a80ad830e98..879b56916ba 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -21,12 +21,14 @@ class SaleOrder(models.Model): def action_confirm(self): res = super(SaleOrder, self).action_confirm() for so in self: + if not any(line.product_type == 'event' for line in so.order_line): + continue # confirm registration if it was free (otherwise it will be confirmed once invoice fully paid) so.order_line._update_registrations(confirm=so.amount_total == 0, cancel_to_draft=False) - if any(line.event_ok for line in so.order_line): - return self.env['ir.actions.act_window'] \ - .with_context(default_sale_order_id=so.id) \ - ._for_xml_id('event_sale.action_sale_order_event_registration') + if len(self) == 1: + return self.env['ir.actions.act_window'].with_context( + default_sale_order_id=so.id + )._for_xml_id('event_sale.action_sale_order_event_registration') return res def _action_cancel(self): @@ -68,6 +70,7 @@ class SaleOrderLine(models.Model): 'event.event.ticket', string='Event Ticket', compute="_compute_event_ticket_id", store=True, readonly=False, precompute=True, help="Choose an event ticket and it will automatically create a registration for this event ticket.") + # TODO in master: remove this field, unused anymore event_ok = fields.Boolean(compute='_compute_event_ok') @api.depends('product_id.detailed_type') @@ -89,7 +92,9 @@ class SaleOrderLine(models.Model): RegistrationSudo = self.env['event.registration'].sudo() registrations = RegistrationSudo.search([('sale_order_line_id', 'in', self.ids)]) registrations_vals = [] - for so_line in self.filtered('event_ok'): + for so_line in self: + if not so_line.product_type == 'event': + continue existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id) if confirm: existing_registrations.filtered(lambda self: self.state not in ['open', 'cancel']).action_confirm() diff --git a/addons/event_sale/static/src/js/event_configurator_model.js b/addons/event_sale/static/src/js/event_configurator_model.js index 79995b110f5..bbbdb882b96 100644 --- a/addons/event_sale/static/src/js/event_configurator_model.js +++ b/addons/event_sale/static/src/js/event_configurator_model.js @@ -28,8 +28,8 @@ class EventConfiguratorRecord extends Record { await super.save(options); this.model.action.doAction({type: 'ir.actions.act_window_close', infos: { eventConfiguration: { - event_id: {id: this.data.event_id[0]}, - event_ticket_id: {id: this.data.event_ticket_id[0]} + event_id: this.data.event_id, + event_ticket_id: this.data.event_ticket_id, } }}); } diff --git a/addons/event_sale/static/src/js/event_configurator_widget.js b/addons/event_sale/static/src/js/event_configurator_widget.js deleted file mode 100644 index 7d2f130c66a..00000000000 --- a/addons/event_sale/static/src/js/event_configurator_widget.js +++ /dev/null @@ -1,144 +0,0 @@ -odoo.define('event_sale.product_configurator', function (require) { -var ProductConfiguratorWidget = require('sale.product_configurator'); - -/** - * Extension of the ProductConfiguratorWidget to support event product configuration. - * It opens when an event product_product is set. - * - * The event information include: - * - event_id - * - event_ticket_id - * - */ -ProductConfiguratorWidget.include({ - /** - * @returns {boolean} - * - * @override - * @private - */ - _isConfigurableLine: function () { - return this.recordData.event_ok || this._super.apply(this, arguments); - }, - - /** - * @param {integer} productId - * @param {String} dataPointID - * @returns {Promise} stopPropagation true if a suitable configurator has been found. - * - * @override - * @private - */ - _onProductChange: function (productId, dataPointId) { - var self = this; - return this._super.apply(this, arguments).then(function (stopPropagation) { - if (stopPropagation || productId === undefined) { - return Promise.resolve(true); - } else { - return self._checkForEvent(productId, dataPointId); - } - }); - }, - - /** - * This method will check if the productId needs configuration or not: - * - * @param {integer} productId - * @param {string} dataPointID - * @returns {Promise} stopPropagation true if the product is an event ticket. - * - * @private - */ - _checkForEvent: function (productId, dataPointId) { - var self = this; - return this._rpc({ - model: 'product.product', - method: 'read', - args: [productId, ['detailed_type']], - }).then(function (result) { - if (Array.isArray(result) && result.length && result[0].detailed_type === 'event') { - self._openEventConfigurator({ - default_product_id: productId - }, - dataPointId - ); - return Promise.resolve(true); - } - return Promise.resolve(false); - }); - }, - - /** - * Opens the event configurator in 'edit' mode. - * - * @override - * @private - */ - _onEditLineConfiguration: function () { - if (this.recordData.event_ok) { - var defaultValues = { - default_product_id: this.recordData.product_id.data.id - }; - - if (this.recordData.event_id) { - defaultValues.default_event_id = this.recordData.event_id.data.id; - } - - if (this.recordData.event_ticket_id) { - defaultValues.default_event_ticket_id = this.recordData.event_ticket_id.data.id; - } - - this._openEventConfigurator(defaultValues, this.dataPointID); - } else { - this._super.apply(this, arguments); - } - }, - - /** - * Opens the event configurator to allow configuring the SO line with events information. - * - * When the window is closed, configured values are used to trigger a 'field_changed' - * event to modify the current SO line. - * - * If the window is closed without providing the required values 'event_id' and - * 'event_ticket_id', the product_id field is cleaned. - * - * @param {Object} data various "default_" values - * @param {string} dataPointId - * - * @private - */ - _openEventConfigurator: function (data, dataPointId) { - var self = this; - this.do_action('event_sale.event_configurator_action', { - additional_context: data, - on_close: function (result) { - if (result && !result.special) { - self.trigger_up('field_changed', { - dataPointID: dataPointId, - changes: result.eventConfiguration, - onSuccess: function () { - // Call post-init function. - self._onLineConfigured(); - } - }); - } else { - if (!self.recordData.event_id || !self.recordData.event_ticket_id) { - self.trigger_up('field_changed', { - dataPointID: dataPointId, - changes: { - product_id: false, - name: '' - }, - }); - } - } - } - }); - } -}); - - -return ProductConfiguratorWidget; - -}); diff --git a/addons/event_sale/static/src/js/sale_product_field.js b/addons/event_sale/static/src/js/sale_product_field.js new file mode 100644 index 00000000000..3540cf1e754 --- /dev/null +++ b/addons/event_sale/static/src/js/sale_product_field.js @@ -0,0 +1,61 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { SaleOrderLineProductField } from '@sale/js/sale_product_field'; + + +patch(SaleOrderLineProductField.prototype, 'event_sale', { + + async _onProductUpdate() { + this._super(...arguments); + if (this.props.record.data.product_type === 'event') { + this._openEventConfigurator(); + } + }, + + _editLineConfiguration() { + this._super(...arguments); + if (this.props.record.data.product_type === 'event') { + this._openEventConfigurator(); + } + }, + + get isConfigurableLine() { + return this._super(...arguments) || Boolean(this.props.record.data.event_ticket_id); + }, + + async _openEventConfigurator() { + let actionContext = { + 'default_product_id': this.props.record.data.product_id[0], + }; + if (this.props.record.data.event_id) { + actionContext.default_event_id = this.props.record.data.event_id[0]; + } + if (this.props.record.data.event_ticket_id) { + actionContext.default_event_ticket_id = this.props.record.data.event_ticket_id[0]; + } + this.action.doAction( + 'event_sale.event_configurator_action', + { + additionalContext: actionContext, + onClose: async (closeInfo) => { + if (!closeInfo || closeInfo.special) { + // wizard popup closed or 'Cancel' button triggered + if (!this.props.record.data.event_ticket_id) { + // remove product if event configuration was cancelled. + this.props.record.update({ + [this.props.name]: undefined, + }); + } + } else { + const eventConfiguration = closeInfo.eventConfiguration; + this.props.record.update({ + 'event_id': eventConfiguration.event_id, + 'event_ticket_id': eventConfiguration.event_ticket_id, + }); + } + } + } + ); + }, +}); diff --git a/addons/event_sale/static/tests/event_configurator.test.js b/addons/event_sale/static/tests/event_configurator.test.js deleted file mode 100644 index 7a462379bb7..00000000000 --- a/addons/event_sale/static/tests/event_configurator.test.js +++ /dev/null @@ -1,140 +0,0 @@ -odoo.define('event.configurator.tests', function (require) { - "use strict"; - - var FormView = require('web.FormView'); - var testUtils = require('web.test_utils'); - var createView = testUtils.createView; - - var getArch = function (){ - return '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
'; - }; - - QUnit.module('Event Configurator', { - beforeEach: function () { - this.data = { - 'product.product': { - fields: { - id: {type: 'integer'}, - detailed_type: {type: 'selection'}, - rent_ok: {type: 'boolean'}//sale_rental purposes - }, - records: [{ - id: 1, - display_name: "Customizable Event", - detailed_type: 'event', - rent_ok: false//sale_rental purposes - }, { - id: 2, - display_name: "Desk", - detailed_type: 'service', - rent_ok: false//sale_rental purposes - }] - }, - sale_order: { - fields: { - id: {type: 'integer'}, - sale_order_line: { - string: 'lines', - type: 'one2many', - relation: 'sale_order_line' - }, - } - }, - sale_order_line: { - fields: { - product_id: { - string: 'product', - type: 'many2one', - relation: 'product.product' - }, - event_ok: {type: 'boolean'}, - rent_ok: {type: 'boolean'},//sale_rental purposes - event_id: { - string: 'event', - type: 'many2one', - relation: 'event' - }, - event_ticket_id: { - string: 'event_ticket', - type: 'many2one', - relation: 'event_ticket' - } - } - } - }; - } - }, function (){ - QUnit.test('Select a regular product and verify that the event configurator is not opened', async function (assert) { - // When the module event_booth_sale is installed, it will also call the rpc: read/detailed_type when selecting a product. - // This means that 2 asserts are done when this module is installed. - // Note that: this test will fail if the module event_booth_sale is not installed. - assert.expect(2); - - var form = await createView({ - View: FormView, - model: 'sale_order', - data: this.data, - arch: getArch(), - mockRPC: function (route, params) { - if (params.method === 'read' && params.args[1][0] === 'detailed_type') { - assert.ok(true); - return Promise.resolve([{detailed_type: 'service'}]); - } - return this._super.apply(this, arguments); - }, - intercepts: { - do_action: function (ev) { - if (ev.data.action === 'event_sale.event_configurator_action') { - assert.ok(false, "Should not execute the configure action"); - } - }, - } - }); - - await testUtils.dom.click(form.$("a:contains('Add a product')")); - await testUtils.fields.many2one.searchAndClickItem("product_id", {item: 'Desk'}) - form.destroy(); - }); - - QUnit.test('Select a configurable event and verify that the event configurator is opened', async function (assert) { - assert.expect(2); - - var form = await createView({ - View: FormView, - model: 'sale_order', - data: this.data, - arch: getArch(), - mockRPC: function (route, params) { - if (params.method === 'read' && params.args[1][0] === 'detailed_type') { - assert.ok(true); - return Promise.resolve([{detailed_type: 'event'}]); - } - return this._super.apply(this, arguments); - }, - intercepts: { - do_action: function (ev) { - if (ev.data.action === 'event_sale.event_configurator_action') { - assert.ok(true); - } - }, - } - }); - - await testUtils.dom.click(form.$("a:contains('Add a product')")); - await testUtils.fields.many2one.searchAndClickItem("product_id", {item: 'Customizable Event'}); - form.destroy(); - }); - }); -}); diff --git a/addons/event_sale/static/tests/tours/event_configurator_ui.js b/addons/event_sale/static/tests/tours/event_configurator_ui.js index 5bf4fc4ffaf..a24d3ee9795 100644 --- a/addons/event_sale/static/tests/tours/event_configurator_ui.js +++ b/addons/event_sale/static/tests/tours/event_configurator_ui.js @@ -42,24 +42,23 @@ tour.register('event_configurator_tour', { }, { trigger: '.o_event_sale_js_event_configurator_ok' }, { - trigger: 'textarea[name="name"]', - run: function () { - var $textarea = $('textarea[name="name"]'); - if ($textarea.val().includes('Design Fair Los Angeles') && $textarea.val().includes('VIP')) { - $textarea.addClass('tour_success'); - } - } -}, { - trigger: 'textarea[name="name"].tour_success', + trigger: "td[name='name'][data-tooltip*='VIP']", run: function () {} // check }, { trigger: 'ul.nav a:contains("Order Lines")', run: 'click' +}, { + content: "search the partner", + trigger: 'div[name="partner_id"] input', + run: 'text Azure' +}, { + content: "select the partner", + trigger: 'ul.ui-autocomplete > li > a:contains(Azure)', }, { trigger: 'td:contains("Event")', run: 'click' }, { - trigger: '.o_edit_product_configuration' + trigger: 'button.fa-pencil' }, { trigger: 'div[name="event_ticket_id"] input', run: 'click' @@ -70,17 +69,9 @@ tour.register('event_configurator_tour', { }, { trigger: '.o_event_sale_js_event_configurator_ok' }, { - trigger: 'textarea[name="name"]', - run: function () { - var $textarea = $('textarea[name="name"]'); - if ($textarea.val().includes('Standard')) { - $textarea.addClass('tour_success_2'); - } - } -}, { - trigger: 'textarea[name="name"].tour_success_2', + trigger: "td[name='name'][data-tooltip*='Standard']", run: function () {} // check -}, ...tour.stepUtils.discardForm() +}, ...tour.stepUtils.saveForm() ]); }); diff --git a/addons/event_sale/views/sale_order_views.xml b/addons/event_sale/views/sale_order_views.xml index 6bf4ddeae4e..7b48def1f3b 100644 --- a/addons/event_sale/views/sale_order_views.xml +++ b/addons/event_sale/views/sale_order_views.xml @@ -19,7 +19,7 @@ ('date_end','>=',time.strftime('%Y-%m-%d 00:00:00')), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id) ]" - attrs="{'invisible': [('event_ok', '=', False)], 'required': [('event_ok', '!=', False)]}" + attrs="{'invisible': [('product_type', '!=', 'event')], 'required': [('product_type', '=', 'event')]}" options="{'no_open': True, 'no_create': True}" /> - -