diff --git a/addons/sale_product_matrix/__manifest__.py b/addons/sale_product_matrix/__manifest__.py index d83fcbff18a..00705b9fc95 100644 --- a/addons/sale_product_matrix/__manifest__.py +++ b/addons/sale_product_matrix/__manifest__.py @@ -22,9 +22,6 @@ 'web.assets_backend': [ 'sale_product_matrix/static/src/**/*', ], - 'web.qunit_suite_tests': [ - 'sale_product_matrix/static/tests/section_and_note_widget_tests.js', - ], }, 'license': 'LGPL-3', } diff --git a/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js b/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js deleted file mode 100644 index e9ff37cb564..00000000000 --- a/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js +++ /dev/null @@ -1,80 +0,0 @@ -/** @odoo-module */ -import { ProductConfiguratorWidget } from "@sale_product_configurator/js/product_configurator_widget"; - -/** - * Extension of the ProductConfiguratorWidget to support product configuration - * as variant batches to add to the SO.. - * It opens when a configurable product_template is set - * (multiple variants, or custom attributes) - * and its configuration mode is matrix. - * - */ -ProductConfiguratorWidget.include({ - /** - * @override - */ - _openConfigurator: function (result, productTemplateId, dataPointId) { - var self = this; - var mode = result.mode; - this._super.apply(this, arguments).then(function (configuratorOpened) { - if (!configuratorOpened && mode === "matrix") { - self._openGridConfigurator(productTemplateId, dataPointId); - return Promise.resolve(true); - } - return Promise.resolve(configuratorOpened); - }); - }, - - _openGridConfigurator: function (productTemplateId, dataPointId, edit) { - var attribs = edit ? this._getPTAVS() : []; - this.trigger_up("open_matrix", { - product_template_id: productTemplateId, - model: "sale.order", - dataPointId: dataPointId, - edit: edit, - editedCellAttributes: attribs, - }); - }, - - _onEditProductConfiguration: function () { - const { _super } = this; - if (!this.recordData.is_configurable_product) { - // if line should be edited by another configurator - // or simply inline. - _super.apply(this, arguments); - return; - } - var self = this; - var productTemplateId = this.recordData.product_template_id.data.id; - this._rpc({ - model: "product.template", - method: "read", - args: [productTemplateId, ["product_add_mode"]], - }).then(function (result) { - if (result && result[0].product_add_mode === "matrix") { - self._openGridConfigurator(productTemplateId, self.dataPointID, true); - } else { - // Call super only if product_add_mode different than matrix - // to avoid product configurator opening (which is the default case). - return _super.apply(self, arguments); - } - }); - }, - - /** - * Returns the list of attribute ids (product.template.attribute.value) - * from the current SOLine. - */ - _getPTAVS: function () { - var PTAVSIDS = []; - _.each(this.recordData.product_no_variant_attribute_value_ids.res_ids, function (id) { - PTAVSIDS.push(id); - }); - _.each(this.recordData.product_template_attribute_value_ids.res_ids, function (id) { - PTAVSIDS.push(id); - }); - return PTAVSIDS.sort(function (a, b) { - return a - b; - }); - }, -}); diff --git a/addons/sale_product_matrix/static/src/js/sale_product_field.js b/addons/sale_product_matrix/static/src/js/sale_product_field.js new file mode 100644 index 00000000000..2c21ba3d6a1 --- /dev/null +++ b/addons/sale_product_matrix/static/src/js/sale_product_field.js @@ -0,0 +1,116 @@ +/** @odoo-module **/ + +import Dialog from 'web.Dialog'; +import { qweb } from "web.core"; +import { patch } from "@web/core/utils/patch"; +import { SaleOrderLineProductField } from '@sale/js/sale_product_field'; +import { formatMonetary } from "@web/views/fields/formatters"; + + +patch(SaleOrderLineProductField.prototype, 'sale_product_matrix', { + + async _openGridConfigurator(mode) { + const saleOrderRecord = this.props.record.model.root; + + // fetch matrix information from server; + await saleOrderRecord.update({ + grid_product_tmpl_id: this.props.record.data.product_template_id, + }); + + let updatedLineAttributes = []; + if (mode === 'edit') { + // provide attributes of edited line to automatically focus on matching cell in the matrix + for (let ptnvav of this.props.record.data.product_no_variant_attribute_value_ids.records) { + updatedLineAttributes.push(ptnvav.data.id); + } + for (let ptav of this.props.record.data.product_template_attribute_value_ids.records) { + updatedLineAttributes.push(ptav.data.id); + } + updatedLineAttributes.sort((a, b) => { return a - b; }); + } + + this._openMatrixConfigurator( + saleOrderRecord.data.grid, + this.props.record.data.product_template_id[0], + updatedLineAttributes, + ); + + if (mode !== 'edit') { + // remove new line used to open the matrix + saleOrderRecord.data.order_line.removeRecord(this.props.record); + } + }, + + async _openProductConfigurator(mode) { + if (mode === 'edit' && this.props.record.data.product_add_mode == 'matrix') { + this._openGridConfigurator('edit'); + } else { + this._super(...arguments); + } + }, + + /** + * Triggers Matrix Dialog opening + * + * @param {String} jsonInfo matrix dialog content + * @param {integer} productTemplateId product.template id + * @param {editedCellAttributes} list of product.template.attribute.value ids + * used to focus on the matrix cell representing the edited line. + * + * @private + */ + _openMatrixConfigurator: function (jsonInfo, productTemplateId, editedCellAttributes) { + const infos = JSON.parse(jsonInfo); + const saleOrderRecord = this.props.record.model.root; + const MatrixDialog = new Dialog(this, { + title: this.env._t('Choose Product Variants'), + size: 'extra-large', // adapt size depending on matrix size? + $content: $(qweb.render( + 'product_matrix.matrix', { + header: infos.header, + rows: infos.matrix, + format({price, currency_id}) { + if (!price) { return ""; } + return formatMonetary(price, { + currencyId: currency_id, + }); + } + } + )), + buttons: [ + {text: this.env._t('Confirm'), classes: 'btn-primary', close: true, click: function (result) { + const $inputs = this.$('.o_matrix_input'); + var matrixChanges = []; + _.each($inputs, function (matrixInput) { + if (matrixInput.value && matrixInput.value !== matrixInput.attributes.value.nodeValue) { + matrixChanges.push({ + qty: parseFloat(matrixInput.value), + ptav_ids: matrixInput.attributes.ptav_ids.nodeValue.split(",").map(function (id) { + return parseInt(id); + }), + }); + } + }); + if (matrixChanges.length > 0) { + // NB: server also removes current line opening the matrix + saleOrderRecord.update({ + grid: JSON.stringify({changes: matrixChanges, product_template_id: productTemplateId}), + grid_update: true // to say that the changes to grid have to be applied to the SO. + }); + } + }}, + {text: this.env._t('Close'), close: true}, + ], + }).open(); + + MatrixDialog.opened(function () { + MatrixDialog.$content.closest('.o_dialog_container').removeClass('d-none'); + if (editedCellAttributes.length > 0) { + const str = editedCellAttributes.toString(); + MatrixDialog.$content.find('.o_matrix_input').filter((k, v) => v.attributes.ptav_ids.nodeValue === str)[0].focus(); + } else { + MatrixDialog.$content.find('.o_matrix_input:first()').focus(); + } + }); + }, +}); diff --git a/addons/sale_product_matrix/static/tests/section_and_note_widget_tests.js b/addons/sale_product_matrix/static/tests/section_and_note_widget_tests.js deleted file mode 100644 index 08be54b2070..00000000000 --- a/addons/sale_product_matrix/static/tests/section_and_note_widget_tests.js +++ /dev/null @@ -1,106 +0,0 @@ -odoo.define('sale_product_matrix.section_and_note_widget_tests', function (require) { -"use strict"; - -const FormView = require('web.FormView'); -const testUtils = require('web.test_utils'); -const createView = testUtils.createView; - -QUnit.module('section_and_note: sale_product_matrix', { - beforeEach: function () { - this.data = { - sale_order: { - fields: { - order_line_ids: { - string: "Lines", - type: 'one2many', - relation: 'order_line', - relation_field: 'order_id', - }, - grid: {string: "Grid", type: 'char'}, - }, - }, - order_line: { - fields: { - order_id: {string: "Invoice", type: 'many2one', relation: 'invoice'}, - product_template_id: {string: "Product", type: 'many2one', relation: 'product'}, - }, - }, - product: { - fields: { - name: {string: "Name", type: 'char'}, - }, - records: [ - {id: 1, name: 'A configurable product'}, - ], - }, - }; - - this.grid = JSON.stringify({ - header: [{name: "My Company Tshirt (GRID)"}, {name: "M"}, {name: "L"}], - matrix: [[ - {name: "Men"}, - {ptav_ids: [10, 13], qty: 0, is_possible_combination: true}, - {ptav_ids: [11, 13], qty: 0, is_possible_combination: true}, - ], [ - {name: "Women"}, - {ptav_ids: [10, 14], qty: 0, is_possible_combination: true}, - {ptav_ids: [11, 14], qty: 0, is_possible_combination: true}, - ]], - }); - }, -}, function () { - QUnit.test('can configure a product with the matrix', async function (assert) { - assert.expect(4); - - this.data.sale_order.onchanges = { - order_line_ids: obj => { - obj.grid = this.grid; - }, - grid: () => {}, - }; - const form = await createView({ - View: FormView, - model: 'sale_order', - data: this.data, - arch: `
`, - mockRPC: function (route, args) { - if (args.method === 'onchange' && args.args[2] === 'grid') { - // should trigger an onchange on the grid field and let the - // business logic create rows according to the matrix content - assert.deepEqual(args.args[1].grid, JSON.stringify({ - changes: [{qty: 2, ptav_ids: [10, 13]}, {qty: 3, ptav_ids: [11, 14]}], - product_template_id: 1, - })); - } - if (args.method === 'get_single_product_variant') { - assert.strictEqual(args.args[0], 1); - return Promise.resolve({mode: 'matrix'}); - } - return this._super.apply(this, arguments); - }, - }); - - await testUtils.dom.click('.o_field_x2many_list_row_add a'); - await testUtils.fields.many2one.searchAndClickItem("product_template_id", {item: 'configurable'}); - - assert.containsOnce(document.body, '.modal .o_product_variant_matrix'); - const $matrix = $('.modal .o_product_variant_matrix'); - assert.strictEqual($matrix.text().replace(/[\n\r\s\u00a0]+/g, ' '), - ' My Company Tshirt (GRID) M L Men Women '); - - // select 2 M-Men and 3 L-Women - await testUtils.fields.editInput($matrix.find('.o_matrix_input[ptav_ids="10,13"]'), '2'); - await testUtils.fields.editInput($matrix.find('.o_matrix_input[ptav_ids="11,14"]'), '3'); - await testUtils.dom.click($('.modal .modal-footer .btn-primary')); - - form.destroy(); - }); -}); -}); diff --git a/addons/sale_product_matrix/views/sale_order_views.xml b/addons/sale_product_matrix/views/sale_order_views.xml index 71261563ef6..0545a3a436a 100644 --- a/addons/sale_product_matrix/views/sale_order_views.xml +++ b/addons/sale_product_matrix/views/sale_order_views.xml @@ -11,6 +11,9 @@