diff --git a/addons/account/static/src/js/section_and_note_fields_backend.js b/addons/account/static/src/js/section_and_note_fields_backend.js index 26328cc045b53..1f232ac0db84e 100644 --- a/addons/account/static/src/js/section_and_note_fields_backend.js +++ b/addons/account/static/src/js/section_and_note_fields_backend.js @@ -6,9 +6,6 @@ odoo.define('account.section_and_note_backend', function (require) { // [UPDATED] now also allows configuring products on sale order. "use strict"; -var pyUtils = require('web.py_utils'); -var core = require('web.core'); -var _t = core._t; var FieldChar = require('web.basic_fields').FieldChar; var FieldOne2Many = require('web.relational_fields').FieldOne2Many; var fieldRegistry = require('web.field_registry'); @@ -72,121 +69,6 @@ var SectionAndNoteListRenderer = ListRenderer.extend({ return this._super.apply(this, arguments).then(function () { self.$('> table').addClass('o_section_and_note_list_view'); }); - }, - /** - * Add support for product configurator - * - * @override - * @private - */ - _onAddRecord: function (ev) { - // we don't want the browser to navigate to a the # url - ev.preventDefault(); - - // we don't want the click to cause other effects, such as unselecting - // the row that we are creating, because it counts as a click on a tr - ev.stopPropagation(); - - // but we do want to unselect current row - var self = this; - this.unselectRow().then(function () { - var context = ev.currentTarget.dataset.context; - - var pricelistId = self._getPricelistId(); - if (context && pyUtils.py_eval(context).open_product_configurator){ - self._rpc({ - model: 'ir.model.data', - method: 'xmlid_to_res_id', - kwargs: {xmlid: 'sale.sale_product_configurator_view_form'}, - }).then(function (res_id) { - self.do_action({ - name: _t('Configure a product'), - type: 'ir.actions.act_window', - res_model: 'sale.product.configurator', - views: [[res_id, 'form']], - target: 'new', - context: { - 'default_pricelist_id': pricelistId - } - }, { - on_close: function (products) { - if (products && products !== 'special'){ - self.trigger_up('add_record', { - context: self._productsToRecords(products), - forceEditable: "bottom" , - allowWarning: true, - onSuccess: function (){ - self.unselectRow(); - } - }); - } - } - }); - }); - } else { - self.trigger_up('add_record', {context: context && [context]}); // TODO write a test, the deferred was not considered - } - }); - }, - - /** - * Will try to get the pricelist_id value from the parent sale_order form - * - * @private - * @returns {integer} pricelist_id's id - */ - _getPricelistId: function () { - var saleOrderForm = this.getParent() && this.getParent().getParent(); - var stateData = saleOrderForm && saleOrderForm.state && saleOrderForm.state.data; - var pricelist_id = stateData.pricelist_id && stateData.pricelist_id.data && stateData.pricelist_id.data.id; - - return pricelist_id; - }, - - /** - * Will map the products to appropriate record objects that are - * ready for the default_get - * - * @private - * @param {Array} products The products to transform into records - */ - _productsToRecords: function (products) { - var records = []; - _.each(products, function (product){ - var record = { - default_product_id: product.product_id, - default_product_uom_qty: product.quantity - }; - - if (product.no_variant_attribute_values) { - var default_product_no_variant_attribute_values = []; - _.each(product.no_variant_attribute_values, function (attribute_value) { - default_product_no_variant_attribute_values.push( - [4, parseInt(attribute_value.value)] - ); - }); - record['default_product_no_variant_attribute_value_ids'] - = default_product_no_variant_attribute_values; - } - - if (product.product_custom_attribute_values) { - var default_custom_attribute_values = []; - _.each(product.product_custom_attribute_values, function (attribute_value) { - default_custom_attribute_values.push( - [0, 0, { - attribute_value_id: attribute_value.attribute_value_id, - custom_value: attribute_value.custom_value - }] - ); - }); - record['default_product_custom_attribute_value_ids'] - = default_custom_attribute_values; - } - - records.push(record); - }); - - return records; } }); diff --git a/addons/event_sale/__manifest__.py b/addons/event_sale/__manifest__.py index 839ef948898e3..5ad00204e0489 100644 --- a/addons/event_sale/__manifest__.py +++ b/addons/event_sale/__manifest__.py @@ -20,8 +20,8 @@ """, 'depends': ['event', 'sale_management'], 'data': [ + 'views/assets.xml', 'views/event_views.xml', - 'views/event_views_template.xml', 'views/product_views.xml', 'views/sale_order_views.xml', 'data/event_sale_data.xml', @@ -29,6 +29,7 @@ 'security/ir.model.access.csv', 'security/event_security.xml', 'wizard/event_edit_registration.xml', + 'wizard/event_configurator_views.xml', ], 'demo': ['data/event_demo.xml'], 'installable': True, 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 0000000000000..3ae81387b7896 --- /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_test_ui.js b/addons/event_sale/static/src/js/event_configurator_test_ui.js new file mode 100644 index 0000000000000..2a748e7b865cd --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_test_ui.js @@ -0,0 +1,92 @@ +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.STEPS.SHOW_APPS_MENU_ITEM, { + 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', + run: function (){ + var $input = $('div[name="product_id"] input'); + $input.click(); + $input.val('EVENT'); + // fake keydown to trigger search + var keyDownEvent = jQuery.Event("keydown"); + keyDownEvent.which = 42; + $input.trigger(keyDownEvent); + }, + id: 'product_selection_step' +}, { + 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_event_sale_js_event_configurator_edit' +}, { + 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 +}]); + +}); 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 0000000000000..67aebba498715 --- /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 0000000000000..5324a1fc16b83 --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_widget.js @@ -0,0 +1,59 @@ +odoo.define('event.configurator', function (require) { + +var relationalFields = require('web.relational_fields'); +var FieldsRegistry = require('web.field_registry'); +var EventConfiguratorWidgetMixin = require('event_sale.EventConfiguratorWidgetMixin'); + +/** + * The event configurator widget is a simple FieldMany2One that adds the capability + * to configure a product_id with event information using the event configurator wizard. + * + * The event information include: + * - event_id + * - event_ticket_id + * + * !!! It should only be used on a product_id field !!! + */ +var EventConfiguratorWidget = relationalFields.FieldMany2One.extend(EventConfiguratorWidgetMixin, { + events: _.extend({}, + relationalFields.FieldMany2One.prototype.events, + EventConfiguratorWidgetMixin.events + ), + + /** + * @see _addEventConfigurationEditButton for more info + * @override + */ + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + self._addEventConfigurationEditButton(); + }); + }, + + /** + * This method is overridden to check if the product_id needs configuration or not: + * + * @override + * @param {OdooEvent} event + * + * @private + */ + _onFieldChanged: function (event) { + var self = this; + + this._super.apply(this, arguments); + if (!event.data.changes.product_id){ + return; + } + + var productId = event.data.changes.product_id.id; + self._checkForEvent(productId, event.data.dataPointID); + } +}); + +FieldsRegistry.add('event_configurator', EventConfiguratorWidget); + +return EventConfiguratorWidget; + +}); diff --git a/addons/event_sale/static/src/js/event_configurator_widget_mixin.js b/addons/event_sale/static/src/js/event_configurator_widget_mixin.js new file mode 100644 index 0000000000000..8e1300fb45154 --- /dev/null +++ b/addons/event_sale/static/src/js/event_configurator_widget_mixin.js @@ -0,0 +1,124 @@ +odoo.define('event_sale.EventConfiguratorWidgetMixin', function (require) { +'use strict'; + +var core = require('web.core'); +var _t = core._t; + +/** + * This mixin intends to gather methods that will be used by the event configurator. + * They're extracted here to be used by other widgets if necessary (namely the product_configurator_widget) + */ +var EventConfiguratorWidgetMixin = { + events: { + 'click .o_event_sale_js_event_configurator_edit': '_onEditEventConfiguration' + }, + /** + * This method will check if the current product set on the SO line is a configurable event. + * -> If so, we add a 'Edit Configuration' button next to the dropdown. + * + */ + _addEventConfigurationEditButton: function () { + var $inputDropdown = this.$('.o_input_dropdown'); + + if (this.recordData.event_ok && + $inputDropdown.length !== 0 && + this.$('.o_event_sale_js_event_configurator_edit').length === 0) { + var $editConfigurationButton = $('