From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- .../event_sale/static/img/event_product-image.jpg | Bin 0 -> 3845 bytes .../static/src/js/event_configurator_controller.js | 38 ++++++ .../static/src/js/event_configurator_view.js | 21 +++ .../static/src/js/event_configurator_widget.js | 144 +++++++++++++++++++++ .../static/tests/event_configurator.test.js | 137 ++++++++++++++++++++ .../static/tests/tours/event_configurator_ui.js | 85 ++++++++++++ 6 files changed, 425 insertions(+) create mode 100644 addons/event_sale/static/img/event_product-image.jpg create mode 100644 addons/event_sale/static/src/js/event_configurator_controller.js create mode 100644 addons/event_sale/static/src/js/event_configurator_view.js create mode 100644 addons/event_sale/static/src/js/event_configurator_widget.js create mode 100644 addons/event_sale/static/tests/event_configurator.test.js create mode 100644 addons/event_sale/static/tests/tours/event_configurator_ui.js (limited to 'addons/event_sale/static') diff --git a/addons/event_sale/static/img/event_product-image.jpg b/addons/event_sale/static/img/event_product-image.jpg new file mode 100644 index 00000000..95e0da4e Binary files /dev/null and b/addons/event_sale/static/img/event_product-image.jpg differ diff --git a/addons/event_sale/static/src/js/event_configurator_controller.js b/addons/event_sale/static/src/js/event_configurator_controller.js new file mode 100644 index 00000000..3ae81387 --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_controller.js @@ -0,0 +1,38 @@ +odoo.define('event.EventConfiguratorFormController', function (require) { +"use strict"; + +var FormController = require('web.FormController'); + +/** + * This controller is overridden to allow configuring sale_order_lines through a popup + * window when a product with 'event_ok' is selected. + * + * This allows keeping an editable list view for sales order and remove the noise of + * those 2 fields ('event_id' + 'event_ticket_id') + */ +var EventConfiguratorFormController = FormController.extend({ + /** + * We let the regular process take place to allow the validation of the required fields + * to happen. + * + * Then we can manually close the window, providing event information to the caller. + * + * @override + */ + saveRecord: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + var state = self.renderer.state.data; + self.do_action({type: 'ir.actions.act_window_close', infos: { + eventConfiguration: { + event_id: {id: state.event_id.data.id}, + event_ticket_id: {id: state.event_ticket_id.data.id} + } + }}); + }); + } +}); + +return EventConfiguratorFormController; + +}); diff --git a/addons/event_sale/static/src/js/event_configurator_view.js b/addons/event_sale/static/src/js/event_configurator_view.js new file mode 100644 index 00000000..67aebba4 --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_view.js @@ -0,0 +1,21 @@ +odoo.define('event.EventConfiguratorFormView', function (require) { +"use strict"; + +var EventConfiguratorFormController = require('event.EventConfiguratorFormController'); +var FormView = require('web.FormView'); +var viewRegistry = require('web.view_registry'); + +/** + * @see EventConfiguratorFormController for more information + */ +var EventConfiguratorFormView = FormView.extend({ + config: _.extend({}, FormView.prototype.config, { + Controller: EventConfiguratorFormController + }), +}); + +viewRegistry.add('event_configurator_form', EventConfiguratorFormView); + +return EventConfiguratorFormView; + +}); diff --git a/addons/event_sale/static/src/js/event_configurator_widget.js b/addons/event_sale/static/src/js/event_configurator_widget.js new file mode 100644 index 00000000..eff0edd3 --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_widget.js @@ -0,0 +1,144 @@ +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, ['event_ok']], + }).then(function (result) { + if (Array.isArray(result) && result.length && result[0].event_ok) { + 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/tests/event_configurator.test.js b/addons/event_sale/static/tests/event_configurator.test.js new file mode 100644 index 00000000..9ed0c98c --- /dev/null +++ b/addons/event_sale/static/tests/event_configurator.test.js @@ -0,0 +1,137 @@ +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'}, + event_ok: {type: 'boolean'}, + rent_ok: {type: 'boolean'}//sale_rental purposes + }, + records: [{ + id: 1, + display_name: "Customizable Event", + event_ok: true, + rent_ok: false//sale_rental purposes + }, { + id: 2, + display_name: "Desk", + event_ok: false, + 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) { + assert.expect(1); + + 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] === 'event_ok') { + assert.ok(true); + return Promise.resolve([{event_ok: false}]); + } + 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] === 'event_ok') { + assert.ok(true); + return Promise.resolve([{event_ok: true}]); + } + 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 new file mode 100644 index 00000000..a7419871 --- /dev/null +++ b/addons/event_sale/static/tests/tours/event_configurator_ui.js @@ -0,0 +1,85 @@ +odoo.define('event.event_configurator_tour', function (require) { +"use strict"; + +var tour = require('web_tour.tour'); + +tour.register('event_configurator_tour', { + url: "/web", + test: true, +}, [tour.stepUtils.showAppsMenuItem(), { + trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', + edition: 'community' +}, { + trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', + edition: 'enterprise' +}, { + trigger: ".o_list_button_add", + extra_trigger: ".o_sale_order" +}, { + trigger: "a:contains('Add a product')" +}, { + trigger: 'div[name="product_id"] input, div[name="product_template_id"] input', + run: function (actions) { + actions.text('Event'); + } +}, { + trigger: 'ul.ui-autocomplete a:contains("Event")', + run: 'click' +}, { + trigger: 'div[name="event_id"] input', + run: 'click' +}, { + trigger: 'ul.ui-autocomplete a:contains("Design")', + run: 'click', + in_modal: false +}, { + trigger: 'div[name="event_ticket_id"] input', + run: 'click' +}, { + trigger: 'ul.ui-autocomplete a:contains("VIP")', + run: 'click', + in_modal: false +}, { + 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', + run: function () {} // check +}, { + trigger: 'ul.nav a:contains("Order Lines")', + run: 'click' +}, { + trigger: 'td:contains("Event")', + run: 'click' +}, { + trigger: '.o_edit_product_configuration' +}, { + trigger: 'div[name="event_ticket_id"] input', + run: 'click' +}, { + trigger: 'ul.ui-autocomplete a:contains("Standard")', + run: 'click', + in_modal: false +}, { + 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', + run: function () {} // check +}]); + +}); -- cgit v1.2.3