[REF] sale_product_matrix: convert configurator to OWL

Task-2918791

X-original-commit: 737bdc0b103df96813dc45feaa4e9c0c9136faf0
Part-of: odoo/odoo#101352
This commit is contained in:
Victor Feyens
2022-09-28 11:14:35 +02:00
parent 7f4013fc30
commit 268797286a
5 changed files with 119 additions and 189 deletions
@@ -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',
}
@@ -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;
});
},
});
@@ -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();
}
});
},
});
@@ -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: `<form>
<field name="grid" invisible="1"/>
<field name="order_line_ids" widget="section_and_note_one2many">
<tree editable="bottom">
<field name="product_template_id" widget="product_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, ' '),
' 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();
});
});
});
@@ -11,6 +11,9 @@
<field name="grid_product_tmpl_id" invisible="1"/>
<field name="grid_update" invisible="1"/>
</field>
<xpath expr="//tree/field[@name='product_template_id']" position="after">
<field name="product_add_mode" invisible="1"/>
</xpath>
<xpath expr="//notebook//group[@name='sales_person']" position="inside">
<field name="report_grids" groups="base.group_no_one"/>
</xpath>