[REF] sale_product_matrix: convert configurator to OWL
Task-2918791 X-original-commit: 737bdc0b103df96813dc45feaa4e9c0c9136faf0 Part-of: odoo/odoo#101352
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user