diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase_product_matrix/static | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/purchase_product_matrix/static')
3 files changed, 554 insertions, 0 deletions
diff --git a/addons/purchase_product_matrix/static/src/js/product_matrix_configurator.js b/addons/purchase_product_matrix/static/src/js/product_matrix_configurator.js new file mode 100644 index 00000000..907b2d4b --- /dev/null +++ b/addons/purchase_product_matrix/static/src/js/product_matrix_configurator.js @@ -0,0 +1,183 @@ +odoo.define('purchase.product_matrix_configurator', function (require) { + +var relationalFields = require('web.relational_fields'); +var FieldsRegistry = require('web.field_registry'); +var core = require('web.core'); +var _t = core._t; + +/** + * The purchase.product_matrix_configurator widget is a widget extending FieldMany2One + * It triggers the opening of the matrix edition when the product has multiple variants. + * + * + * !!! WARNING !!! + * + * This widget is only designed for Purchase Order Lines. + * !!! It should only be used on a product_template field !!! + */ +var MatrixConfiguratorWidget = relationalFields.FieldMany2One.extend({ + events: _.extend({}, relationalFields.FieldMany2One.prototype.events, { + 'click .o_edit_product_configuration': '_onEditProductConfiguration' + }), + + /** + * @override + */ + _render: function () { + this._super.apply(this, arguments); + if (this.mode === 'edit' && this.value && + (this._isConfigurableProduct())) { + this._addProductLinkButton(); + this._addConfigurationEditButton(); + } else if (this.mode === 'edit' && this.value) { + this._addProductLinkButton(); + } else { + this.$('.o_edit_product_configuration').hide(); + } + }, + + /** + * Add button linking to product_id/product_template_id form. + */ + _addProductLinkButton: function () { + if (this.$('.o_external_button').length === 0) { + var $productLinkButton = $('<button>', { + type: 'button', + class: 'fa fa-external-link btn btn-secondary o_external_button', + tabindex: '-1', + draggable: false, + 'aria-label': _t('External Link'), + title: _t('External Link') + }); + + var $inputDropdown = this.$('.o_input_dropdown'); + $inputDropdown.after($productLinkButton); + } + }, + + /** + * If current product is configurable, + * Show edit button (in Edit Mode) after the product/product_template + */ + _addConfigurationEditButton: function () { + var $inputDropdown = this.$('.o_input_dropdown'); + + if ($inputDropdown.length !== 0 && + this.$('.o_edit_product_configuration').length === 0) { + var $editConfigurationButton = $('<button>', { + type: 'button', + class: 'fa fa-pencil btn btn-secondary o_edit_product_configuration', + tabindex: '-1', + draggable: false, + 'aria-label': _t('Edit Configuration'), + title: _t('Edit Configuration') + }); + + $inputDropdown.after($editConfigurationButton); + } + }, + + /** + * Hook to override with _onEditProductConfiguration + * to know if edit pencil button has to be put next to the field + * + * @private + */ + _isConfigurableProduct: function () { + return this.recordData.is_configurable_product; + }, + + /** + * Override catching changes on product_id or product_template_id. + * Calls _onTemplateChange in case of product_template change. + * Calls _onProductChange in case of product change. + * Shouldn't be overridden by product configurators + * or only to setup some data for further computation + * before calling super. + * + * @override + */ + reset: async function (record, ev) { + await this._super(...arguments); + if (ev && ev.target === this && ev.data.changes && ev.data.changes.product_template_id && record.data.product_template_id.data.id) { + this._onTemplateChange(record.data.product_template_id.data.id, ev.data.dataPointID); + } + }, + + /** + * Hook for product_template based configurators + * (product configurator, matrix, ...). + * + * @param {integer} productTemplateId + * @param {String} dataPointID + * + * @private + */ + _onTemplateChange: function (productTemplateId, dataPointId) { + var self = this; + this._rpc({ + model: 'product.template', + method: 'get_single_product_variant', + args: [ + productTemplateId + ] + }).then(function (result) { + if (result.product_id) { + self.trigger_up('field_changed', { + dataPointID: dataPointId, + changes: { + product_id: {id: result.product_id}, + }, + }); + } else { + self._openMatrix(productTemplateId, dataPointId, false); + } + }); + }, + + /** + * Hook for editing a configured line. + * The button triggering this function is only shown in Edit mode, + * when _isConfigurableProduct is True. + * + * @private + */ + _onEditProductConfiguration: function () { + if (this.recordData.is_configurable_product) { + this._openMatrix(this.recordData.product_template_id.data.id, this.dataPointID, true); + } + }, + + _openMatrix: function (productTemplateId, dataPointId, edit) { + var attribs = edit ? this._getPTAVS() : []; + this.trigger_up('open_matrix', { + product_template_id: productTemplateId, + model: 'purchase.order', + dataPointId: dataPointId, + edit: edit, + editedCellAttributes: attribs, + // used to focus the cell representing the line on which the pencil was clicked. + }); + }, + + /** + * Returns the list of attribute ids (product.template.attribute.value) + * from the current POLine. + */ + _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;}); + } +}); + +FieldsRegistry.add('matrix_configurator', MatrixConfiguratorWidget); + +return MatrixConfiguratorWidget; + +}); diff --git a/addons/purchase_product_matrix/static/tests/section_and_note_widget_tests.js b/addons/purchase_product_matrix/static/tests/section_and_note_widget_tests.js new file mode 100644 index 00000000..7dfc29e6 --- /dev/null +++ b/addons/purchase_product_matrix/static/tests/section_and_note_widget_tests.js @@ -0,0 +1,260 @@ +odoo.define('purchase_product_matrix.section_and_note_widget_tests', function (require) { +"use strict"; + +var FormView = require('web.FormView'); +var testUtils = require('web.test_utils'); +var createView = testUtils.createView; + +function getGrid(product) { + return JSON.stringify({ + header: [{name: product.name}, {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}, + ]], + }); +} + +QUnit.module('section_and_note: purchase_product_matrix', { + beforeEach: function () { + this.data = { + purchase_order: { + fields: { + order_line_ids: { + string: "Lines", + type: 'one2many', + relation: 'order_line', + relation_field: 'order_id', + }, + grid: {string: "Grid", type: 'char'}, + grid_product_tmpl_id: {string: "Grid Product", type: 'many2one', relation: 'product'}, + }, + onchanges: { + grid_product_tmpl_id: (obj) => { + const product = this.data.product.records.find((p) => { + return p.id === obj.grid_product_tmpl_id; + }); + obj.grid = product ? getGrid(product) : false; + }, + grid: () => {}, + }, + }, + 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'}, + ], + }, + }; + }, +}, function () { + QUnit.test('can configure a product with the matrix', async function (assert) { + assert.expect(4); + + var form = await createView({ + View: FormView, + model: 'purchase_order', + data: this.data, + arch: `<form> + <field name="grid" invisible="1"/> + <field name="grid_product_tmpl_id" invisible="1"/> + <field name="order_line_ids" widget="section_and_note_one2many"> + <tree editable="bottom"> + <field name="product_template_id" widget="matrix_configurator"/> + </tree> + </field> + </form>`, + 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, ' '), + ' A configurable product 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(); + }); + + QUnit.test('can open the matrix twice with 2 different products', async function (assert) { + assert.expect(5); + + this.data.product.records.push({ id: 101, name: "Product A" }); + this.data.product.records.push({ id: 102, name: "Product B" }); + + const form = await createView({ + View: FormView, + model: 'purchase_order', + data: this.data, + arch: `<form> + <field name="grid" invisible="1"/> + <field name="grid_product_tmpl_id" invisible="1"/> + <field name="order_line_ids" widget="section_and_note_one2many"> + <tree editable="bottom"> + <field name="product_template_id" widget="matrix_configurator"/> + </tree> + </field> + </form>`, + 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: 102, + })); + } + if (args.method === 'get_single_product_variant') { + return Promise.resolve({mode: 'matrix'}); + } + return this._super.apply(this, arguments); + }, + }); + + // open the matrix with "Product A" and close it + await testUtils.dom.click('.o_field_x2many_list_row_add a'); + await testUtils.fields.many2one.searchAndClickItem("product_template_id", {item: 'Product A'}); + + assert.containsOnce(document.body, '.modal .o_product_variant_matrix'); + let $matrix = $('.modal .o_product_variant_matrix'); + assert.strictEqual($matrix.text().replace(/[\n\r\s\u00a0]+/g, ' '), + ' Product A M L Men Women '); + + await testUtils.dom.click($('.modal .modal-footer .btn-secondary')); // close + + // re-open the matrix with "Product B" + await testUtils.dom.click('.o_field_x2many_list_row_add a'); + await testUtils.fields.many2one.searchAndClickItem("product_template_id", {item: 'Product B'}); + + assert.containsOnce(document.body, '.modal .o_product_variant_matrix'); + $matrix = $('.modal .o_product_variant_matrix'); + assert.strictEqual($matrix.text().replace(/[\n\r\s\u00a0]+/g, ' '), + ' Product B 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(); + }); + + QUnit.test('_onTemplateChange is executed after product template quick create', async function (assert) { + assert.expect(1); + + let created_product_template; + + const form = await createView({ + View: FormView, + model: 'purchase_order', + data: this.data, + arch: `<form> + <field name="order_line_ids" widget="section_and_note_one2many"> + <tree editable="bottom"> + <field name="product_template_id" widget="matrix_configurator"/> + </tree> + </field> + </form>`, + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/product.template/get_single_product_variant') { + assert.strictEqual(args.args[0], created_product_template[0]); + } + + const result = await this._super(...arguments); + if (args.method === 'name_create') { + created_product_template = result; + } + return result; + }, + }); + + await testUtils.dom.click('.o_field_x2many_list_row_add a'); + await testUtils.fields.many2one.searchAndClickItem("product_template_id", {search: 'new product'}); + + form.destroy(); + }); + + QUnit.test('drag and drop rows containing matrix_configurator many2one', async function (assert) { + assert.expect(4); + + this.data.order_line.fields.sequence = {string: "Sequence", type: 'number'}; + this.data.order_line.fields.order_id.relation = 'purchase_order'; + this.data.purchase_order.records = [ + {id: 1, order_line_ids: [1, 2]} + ]; + this.data.order_line.records = [ + {id: 1, sequence: 4, product_template_id: 1, order_id: 1}, + {id: 2, sequence: 14, product_template_id: 2, order_id: 1}, + ]; + this.data.product.records.push( + {id: 1, name: "Chair"}, + {id: 2, name: "Table"} + ); + + const form = await createView({ + View: FormView, + model: 'purchase_order', + data: this.data, + arch: `<form> + <field name="order_line_ids" widget="section_and_note_one2many"> + <tree editable="bottom"> + <field name="sequence" widget="handle"/> + <field name="product_template_id" widget="matrix_configurator"/> + </tree> + </field> + </form>`, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.containsN(form, '.o_data_row', 2); + assert.strictEqual(form.$('.o_data_row').text(), 'ChairTable'); + assert.containsN(form, '.o_data_row .o_row_handle', 2); + + // move first row below second + const $firstHandle = form.$('.o_data_row:nth(0) .o_row_handle'); + const $secondHandle = form.$('.o_data_row:nth(1) .o_row_handle'); + await testUtils.dom.dragAndDrop($firstHandle, $secondHandle); + + assert.strictEqual(form.$('.o_data_row').text(), 'TableChair'); + + form.destroy(); + }); +}); +}); diff --git a/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js new file mode 100644 index 00000000..010c0b18 --- /dev/null +++ b/addons/purchase_product_matrix/static/tests/tours/purchase_product_matrix_tour.js @@ -0,0 +1,111 @@ +odoo.define('purchase_product_matrix.purchase_matrix_tour', function (require) { +"use strict"; + +var tour = require('web_tour.tour'); + +tour.register('purchase_matrix_tour', { + url: "/web", + test: true, +}, [tour.stepUtils.showAppsMenuItem(), { + trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]', +}, { + trigger: ".o_list_button_add", + extra_trigger: ".o_purchase_order" +}, { + trigger: "a:contains('Add a product')" +}, { + trigger: 'div[name="product_template_id"] input', + run: function () { + var $input = $('div[name="product_template_id"] input'); + $input.click(); + $input.val('Matrix'); + var keyDownEvent = jQuery.Event("keydown"); + keyDownEvent.which = 42; + $input.trigger(keyDownEvent); + } +}, { + trigger: 'ul.ui-autocomplete a:contains("Matrix")', + run: 'click' +}, { + trigger: '.o_product_variant_matrix', + run: function () { + // fill the whole matrix with 1's + $('.o_matrix_input').val(1); + } +}, { + trigger: 'span:contains("Confirm")', + run: 'click' +}, { + trigger: ".o_form_editable .o_field_many2one[name='partner_id'] input", + extra_trigger: ".o_purchase_order", + run: 'text Agrolait' +}, { + trigger: ".ui-menu-item > a", + auto: true, + in_modal: false, +}, { + trigger: '.o_form_button_save:contains("Save")', + run: 'click' // SAVE Sales Order. +}, +// Open the matrix through the pencil button next to the product in line edit mode. +{ + trigger: '.o_form_button_edit:contains("Edit")', + run: 'click' // Edit Sales Order. +}, { + trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\nPA4: PAV41")', + extra_trigger: '.o_form_editable', + run: 'click' +}, { + trigger: '.o_edit_product_configuration', + run: 'click' // edit the matrix +}, { + trigger: '.o_product_variant_matrix', + run: function () { + // update some of the matrix values. + $('.o_matrix_input').slice(8, 16).val(4); + } // set the qty to 4 for half of the matrix products. +}, { + trigger: 'span:contains("Confirm")', + run: 'click' // apply the matrix +}, { + trigger: '.o_form_button_save:contains("Save")', + extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("4.00")', + run: 'click' // SAVE Sales Order, after matrix has been applied (extra_trigger). +}, { + trigger: '.o_form_button_edit:contains("Edit")', + run: 'click' // Edit Sales Order. +}, +// Ensures the matrix is opened with the values, when adding the same product. +{ + trigger: "a:contains('Add a product')" +}, { + trigger: 'div[name="product_template_id"] input', + run: function () { + var $input = $('div[name="product_template_id"] input'); + $input.click(); + $input.val('Matrix'); + var keyDownEvent = jQuery.Event("keydown"); + keyDownEvent.which = 42; + $input.trigger(keyDownEvent); + } +}, { + trigger: 'ul.ui-autocomplete a:contains("Matrix")', + run: 'click' +}, { + trigger: "input[value='4']", + run: function () { + // update some values of the matrix + $("input[value='4']").slice(0, 4).val(8.2); + } +}, { + trigger: 'span:contains("Confirm")', + run: 'click' // apply the matrix +}, { + trigger: '.o_form_button_save:contains("Save")', + extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("8.20")', + run: 'click' // SAVE Sales Order, after matrix has been applied (extra_trigger). +}, +]); + + +}); |
