[IMP] stock: remove dynamically duplicate quant
Creation of a js_class: singleton_list. It's used in the quant list view and its purpose is to avoid duplicate line in list editable. When user creates a new record, if the server responses with a ID corresponding to a record already present in the view, it'll update the old one instead of create a duplicate line. Task #1935921
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
odoo.define('stock.SingletonListController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var ListController = require('web.ListController');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
/**
|
||||
* The purpose of this override is to avoid to have two or more similar records
|
||||
* in the list view.
|
||||
*
|
||||
* It's used in quant list view, a list editable where when you create a new
|
||||
* line about a quant who already exists, we want to update the existing one
|
||||
* instead of create a new one, and then we don't want to have two similar line
|
||||
* in the list view, so we refresh it.
|
||||
*/
|
||||
|
||||
var SingletonListController = ListController.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_confirmSave: function (id) {
|
||||
var newRecord = this.model.localData[id];
|
||||
var model = newRecord.model;
|
||||
var res_id = newRecord.res_id;
|
||||
|
||||
var findSimilarRecords = function (record) {
|
||||
if ((record.groupedBy && record.groupedBy.length > 0) || record.data.length) {
|
||||
var recordsToReturn = [];
|
||||
for (var i in record.data) {
|
||||
var foundRecords = findSimilarRecords(record.data[i]);
|
||||
recordsToReturn = recordsToReturn.concat(foundRecords || []);
|
||||
}
|
||||
return recordsToReturn;
|
||||
} else {
|
||||
if (record.res_id === res_id && record.model === model) {
|
||||
if (record.count === 0){
|
||||
return [record];
|
||||
}
|
||||
else if (record.ref && record.ref.indexOf('virtual') !== -1) {
|
||||
return [record];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var handle = this.model.get(this.handle);
|
||||
var similarRecords = findSimilarRecords(handle);
|
||||
|
||||
if (similarRecords.length > 1) {
|
||||
var notification = _t("You tried to create a record who already exists."+
|
||||
"<br/>This last one has been modified instead.");
|
||||
this.do_notify(_t("This record already exists."), notification);
|
||||
return this.reload();
|
||||
}
|
||||
else {
|
||||
return this._super(id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return SingletonListController;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
odoo.define('stock.SingletonListView', function (require) {
|
||||
'use strict';
|
||||
|
||||
var ListView = require('web.ListView');
|
||||
var SingletonListController = require('stock.SingletonListController');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
var SingletonListView = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
Controller: SingletonListController,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('singleton_list', SingletonListView);
|
||||
|
||||
return SingletonListView;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,254 @@
|
||||
odoo.define('web.singleton_list_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var SingletonListView = require('stock.SingletonListView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
|
||||
QUnit.module('Views', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
person: {
|
||||
fields: {
|
||||
name: {string: "Name", type: "char"},
|
||||
age: {string: "Age", type: "integer"},
|
||||
job: {string: "Profession", type: "char"},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: 'Daniel Fortesque', age: 32, job: 'Soldier'},
|
||||
{id: 2, name: 'Samuel Oak', age: 64, job: 'Professor'},
|
||||
{id: 3, name: 'Leto II Atreides', age: 128, job: 'Emperor'},
|
||||
]
|
||||
},
|
||||
};
|
||||
this.mockRPC = function (route, args) {
|
||||
if (route === '/web/dataset/call_kw/person/create') {
|
||||
var name = args.args[0].name;
|
||||
var age = args.args[0].age;
|
||||
var job = args.args[0].job;
|
||||
for (var d of this.data.person.records) {
|
||||
if (d.name === name) {
|
||||
d.age = age;
|
||||
d.job = job;
|
||||
return Promise.resolve(d.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('SingletonListView');
|
||||
|
||||
QUnit.test('Create new record correctly', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var list = await createView({
|
||||
View: SingletonListView,
|
||||
model: 'person',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top" js_class="singleton_list">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="age"/>'+
|
||||
'</tree>',
|
||||
mockRPC: this.mockRPC,
|
||||
});
|
||||
// Checks we have initially 3 records
|
||||
assert.containsN(list, '.o_data_row', 3, "should have 3 records");
|
||||
|
||||
// Creates a new line...
|
||||
await testUtils.dom.click($('.o_list_button_add'));
|
||||
// ... and fills fields with new values
|
||||
var $input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Bilou');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '24');
|
||||
await testUtils.fields.triggerKeydown($input, 'enter');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
|
||||
// Checks new record is in the list
|
||||
assert.containsN(list, '.o_data_row', 4, "should now have 4 records");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Don\'t duplicate record', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var list = await createView({
|
||||
View: SingletonListView,
|
||||
model: 'person',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top" js_class="singleton_list">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="age"/>'+
|
||||
'</tree>',
|
||||
mockRPC: this.mockRPC,
|
||||
});
|
||||
// Checks we have initially 3 records
|
||||
assert.containsN(list, '.o_data_row', 3, "should have 3 records");
|
||||
|
||||
// Creates a new line...
|
||||
await testUtils.dom.click($('.o_list_button_add'));
|
||||
// ... and fills fields with already existing value
|
||||
var $input = $('.o_selected_row input[name=name]');
|
||||
var name = 'Samuel Oak';
|
||||
await testUtils.fields.editInput($input, name);
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
var age = '72';
|
||||
await testUtils.fields.editInput($input, age);
|
||||
await testUtils.fields.triggerKeydown($input, 'enter');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
|
||||
// Checks we have still only 3 records...
|
||||
assert.containsN(list, '.o_data_row', 3, "should now have 4 records");
|
||||
// ... and verify modification was occured.
|
||||
var nameField = list.$('td[title="' + name + '"]');
|
||||
var ageField = nameField.parent().find('.o_list_number');
|
||||
assert.strictEqual(ageField.text(), age, "The age field must be updated");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Refresh the list only when needed', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var refresh_count = 0;
|
||||
var list = await createView({
|
||||
View: SingletonListView,
|
||||
model: 'person',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top" js_class="singleton_list">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="age"/>'+
|
||||
'</tree>',
|
||||
mockRPC: this.mockRPC,
|
||||
});
|
||||
list.realReload = list.reload;
|
||||
list.reload = function () {
|
||||
refresh_count++;
|
||||
return this.realReload();
|
||||
};
|
||||
// Modify Record
|
||||
await testUtils.dom.click(list.$('.o_data_row:nth-child(2) > .o_list_number'));
|
||||
var $input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '70');
|
||||
await testUtils.fields.triggerKeydown($input, 'enter');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
assert.strictEqual(refresh_count, 0, "don't refresh when edit existing line");
|
||||
|
||||
// Add existing record
|
||||
await testUtils.dom.click($('.o_list_button_add'));
|
||||
$input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Leto II Atreides');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '800');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
assert.strictEqual(refresh_count, 1, "refresh after tried to create an existing record");
|
||||
|
||||
// Add new record
|
||||
await testUtils.dom.click($('.o_list_button_add'));
|
||||
$input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Valentin Cognito');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '37');
|
||||
await testUtils.fields.triggerKeydown($input, 'enter');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
assert.strictEqual(refresh_count, 1, "don't refresh when create entirely new record");
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Work in grouped list', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var refresh_count = 0;
|
||||
var list = await createView({
|
||||
View: SingletonListView,
|
||||
model: 'person',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top" js_class="singleton_list">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="age"/>'+
|
||||
'<field name="job"/>'+
|
||||
'</tree>',
|
||||
mockRPC: this.mockRPC,
|
||||
groupBy: ['job'],
|
||||
});
|
||||
list.realReload = list.reload;
|
||||
list.reload = function () {
|
||||
refresh_count++;
|
||||
return this.realReload();
|
||||
};
|
||||
// Opens 'Professor' group
|
||||
await testUtils.dom.click(list.$('.o_group_header:nth-child(2)'));
|
||||
|
||||
// Creates a new record...
|
||||
await testUtils.dom.click(list.$('.o_add_record_row a'));
|
||||
var $input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Del Tutorial');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '32');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
// ... then checks the list didn't refresh
|
||||
assert.strictEqual(refresh_count, 0,
|
||||
"don't refresh when creating new record");
|
||||
|
||||
// Creates an existing record in same group...
|
||||
await testUtils.dom.click(list.$('.o_add_record_row a'));
|
||||
var $input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Samuel Oak');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
// ... then checks the list has been refreshed
|
||||
assert.strictEqual(refresh_count, 1,
|
||||
"refresh when try to create an existing record");
|
||||
|
||||
// Creates an existing but not displayed record...
|
||||
await testUtils.dom.click(list.$('.o_add_record_row a'));
|
||||
var $input = $('.o_selected_row input[name=name]');
|
||||
await testUtils.fields.editInput($input, 'Daniel Fortesque');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '55');
|
||||
await testUtils.fields.triggerKeydown($input, 'tab');
|
||||
$input = $('.o_selected_row input[name=job]');
|
||||
await testUtils.fields.editInput($input, 'Soldier');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
// .. then checks the list didn't refresh
|
||||
assert.strictEqual(refresh_count, 1,
|
||||
"don't refresh when creating an existing record but this record " +
|
||||
"isn't present in the view");
|
||||
|
||||
// Opens 'Soldier' group
|
||||
await testUtils.dom.click(list.$('.o_group_header:nth-child(1)').first());
|
||||
// Checks the record has been correctly updated
|
||||
var ageCell = $('tr.o_data_row td.o_list_number').first();
|
||||
assert.strictEqual(ageCell.text(), "55",
|
||||
"age of the record must be updated");
|
||||
// Edits the freshly created record...
|
||||
await testUtils.dom.click(list.$('tr.o_data_row td.o_list_number').eq(1));
|
||||
$input = $('.o_selected_row input[name=age]');
|
||||
await testUtils.fields.editInput($input, '66');
|
||||
await testUtils.dom.click($('.o_list_button_save'));
|
||||
// ... then checks the list and data have been refreshed
|
||||
assert.strictEqual(refresh_count, 2,
|
||||
"refresh when try to create an existing record present in the view");
|
||||
ageCell = $('tr.o_data_row td.o_list_number').first();
|
||||
assert.strictEqual(ageCell.text(), "66",
|
||||
"age of the record must be updated");
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -75,7 +75,7 @@
|
||||
<field eval="10" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-danger="inventory_quantity < 0" editable="top"
|
||||
create="1" edit="1">
|
||||
create="1" edit="1" js_class="singleton_list">
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="tracking" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('id', '!=', False)]}"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<template id="stock_assets_backend" name="mrp_workorder assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/stock/static/tests/route_tour.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_singleton_list_controller.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_singleton_list_view.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_validate_button_controller.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/inventory_validate_button_view.js"></script>
|
||||
<script type="text/javascript" src="/stock/static/src/js/stock_traceability_report_backend.js"></script>
|
||||
@@ -12,6 +14,12 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" name="stock tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/stock/static/tests/singleton_list_tests.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="message_head">
|
||||
<t t-if="move.state != 'done'">
|
||||
<strong>The initial demand has been updated.</strong>
|
||||
|
||||
Reference in New Issue
Block a user