[IMP] web_widget_one2many_product_picker: Control record per page by group and fix to two the number of decimals

pull/1858/head
Alexandre D. Díaz 2021-06-02 14:48:07 +02:00
parent dd9a97bc65
commit 0543c48c56
10 changed files with 325 additions and 154 deletions

View File

@ -47,7 +47,6 @@ You need to define the view fields. The view must be of ``form`` type.
Widget options: Widget options:
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
* product_per_page > Integer -> Used to control the load more behaviour (16 by default)
* groups > Array of dictionaries -> Declare the groups * groups > Array of dictionaries -> Declare the groups
* name -> The group name * name -> The group name
@ -58,6 +57,9 @@ Widget options:
* name -> The field name to order * name -> The field name to order
* asc -> Flag to use 'asc' order * asc -> Flag to use 'asc' order
* records_per_page > Integer -> Used to control the load more behaviour (16 by default)
* active -> Boolean -> Select the default group to use ('false' by default = 'All' group)
* currency_field > Model field used to format monetary values ('currency_id' by default) * currency_field > Model field used to format monetary values ('currency_id' by default)
* field_map > Dictionary: * field_map > Dictionary:

View File

@ -5,7 +5,6 @@ You need to define the view fields. The view must be of ``form`` type.
Widget options: Widget options:
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
* product_per_page > Integer -> Used to control the load more behaviour (16 by default)
* groups > Array of dictionaries -> Declare the groups * groups > Array of dictionaries -> Declare the groups
* name -> The group name * name -> The group name
@ -16,6 +15,9 @@ Widget options:
* name -> The field name to order * name -> The field name to order
* asc -> Flag to use 'asc' order * asc -> Flag to use 'asc' order
* records_per_page > Integer -> Used to control the load more behaviour (16 by default)
* active -> Boolean -> Select the default group to use ('false' by default = 'All' group)
* currency_field > Model field used to format monetary values ('currency_id' by default) * currency_field > Model field used to format monetary values ('currency_id' by default)
* field_map > Dictionary: * field_map > Dictionary:

View File

@ -406,8 +406,6 @@ You need to define the view fields. The view must be of <tt class="docutils lite
<div class="section" id="widget-options"> <div class="section" id="widget-options">
<h2><a class="toc-backref" href="#id3">Widget options:</a></h2> <h2><a class="toc-backref" href="#id3">Widget options:</a></h2>
<ul> <ul>
<li><p class="first">product_per_page &gt; Integer -&gt; Used to control the load more behaviour (16 by default)</p>
</li>
<li><p class="first">groups &gt; Array of dictionaries -&gt; Declare the groups</p> <li><p class="first">groups &gt; Array of dictionaries -&gt; Declare the groups</p>
<blockquote> <blockquote>
<ul> <ul>
@ -425,6 +423,10 @@ You need to define the view fields. The view must be of <tt class="docutils lite
</ul> </ul>
</blockquote> </blockquote>
</li> </li>
<li><p class="first">records_per_page &gt; Integer -&gt; Used to control the load more behaviour (16 by default)</p>
</li>
<li><p class="first">active -&gt; Boolean -&gt; Select the default group to use (false by default = All group)</p>
</li>
</ul> </ul>
</blockquote> </blockquote>
</li> </li>

View File

@ -34,8 +34,15 @@ odoo.define("web_widget_one2many_product_picker.tools", function(require) {
}); });
} }
function float(value, field_info, digits) {
return field_utils.format.float(value, field_info, {
digits: digits,
});
}
return { return {
monetary: monetary, monetary: monetary,
float: float,
priceReduce: priceReduce, priceReduce: priceReduce,
}; };
}); });

View File

@ -28,6 +28,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}, },
_click_card_delayed_time: 250, _click_card_delayed_time: 250,
_onchange_delay: 250,
/** /**
* @override * @override
@ -48,6 +49,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/** /**
* Generates a new virtual state and recreates the product card * Generates a new virtual state and recreates the product card
* *
* @param {Boolean} simple_mode
* @returns {Object} * @returns {Object}
*/ */
generateVirtualState: function(simple_mode) { generateVirtualState: function(simple_mode) {
@ -186,6 +188,19 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
); );
}, },
/**
* Prints the given field value using the selected format
*
* @private
* @param {String} price_field
* @returns {String}
*/
_getFloatFieldValue: function(field) {
const field_name = this.options.fieldMap[field];
const value = this.state.data[field_name];
return tools.float(value, this.state.fields[field_name]);
},
/** /**
* @private * @private
* @param {String} d a stringified domain * @param {String} d a stringified domain
@ -241,6 +256,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
this._setMasterUomMap(); this._setMasterUomMap();
}, },
/**
* Used to know what is the "main" uom
* @private
*/
_setMasterUomMap: function() { _setMasterUomMap: function() {
this.master_uom_map = { this.master_uom_map = {
field_uom: "product_uom", field_uom: "product_uom",
@ -269,9 +288,13 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
field_map: this.options.fieldMap, field_map: this.options.fieldMap,
widget: this, widget: this,
monetary: this._getMonetaryFieldValue.bind(this), monetary: this._getMonetaryFieldValue.bind(this),
floatFixed: this._getFloatFieldValue.bind(this),
show_discount: this.options.showDiscount, show_discount: this.options.showDiscount,
is_virtual: this.is_virtual, is_virtual: this.is_virtual,
modified: record && record.context.product_picker_modified, modified:
record &&
model.hasChanges(record.id) &&
!model.isPureVirtual(record.id),
active_model: "", active_model: "",
auto_save: this.options.autoSave, auto_save: this.options.autoSave,
is_saving: record && record.context.saving, is_saving: record && record.context.saving,
@ -310,6 +333,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
return {}; return {};
}, },
/**
* This generates a virtual record with delayed call to "get_default" & "onchange"
* used in "instant search" mode
*
* @private
* @param {Object} context
* @param {Object} def_values
* @returns {Promise}
*/
_generateVirtualStateSimple: function(context, def_values) { _generateVirtualStateSimple: function(context, def_values) {
const model = this.options.basicFieldParams.model; const model = this.options.basicFieldParams.model;
return new Promise(resolve => { return new Promise(resolve => {
@ -349,7 +381,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
(current_batch_id, record_def) => { (current_batch_id, record_def) => {
this._timerOnChange = false; this._timerOnChange = false;
if ( if (
current_batch_id != current_batch_id !==
this.options.basicFieldParams this.options.basicFieldParams
.current_batch_id || .current_batch_id ||
record_def.record.context.aborted record_def.record.context.aborted
@ -376,7 +408,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
); );
}); });
}, },
750, this._onchange_delay,
this.options.basicFieldParams.current_batch_id, this.options.basicFieldParams.current_batch_id,
record_def record_def
); );
@ -387,6 +419,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}); });
}, },
/**
* Generates a complete virtual record
*
* @private
* @param {Object} data
* @param {Object} context
* @param {Object} def_values
* @returns {Promise}
*/
_generateVirtualStateFull: function(data, context, def_values) { _generateVirtualStateFull: function(data, context, def_values) {
const model = this.options.basicFieldParams.model; const model = this.options.basicFieldParams.model;
return new Promise(resolve => { return new Promise(resolve => {
@ -441,6 +482,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @private * @private
* @param {Object} data * @param {Object} data
* @param {Object} context * @param {Object} context
* @param {Boolean} simple_mode
* @returns {Object} * @returns {Object}
*/ */
_generateVirtualState: function(data, context, simple_mode) { _generateVirtualState: function(data, context, simple_mode) {
@ -468,6 +510,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @param {Integer/String} record_id * @param {Integer/String} record_id
* @param {Object} changes * @param {Object} changes
* @param {Object} options * @param {Object} options
* @returns {Promise}
*/ */
_applyChanges: function(record_id, changes, options) { _applyChanges: function(record_id, changes, options) {
const model = this.options.basicFieldParams.model; const model = this.options.basicFieldParams.model;
@ -479,6 +522,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}); });
}, },
/**
* @private
*/
_detachAllWidgets: function() { _detachAllWidgets: function() {
_.invoke(this.widgets.front, "on_detach_callback"); _.invoke(this.widgets.front, "on_detach_callback");
_.invoke(this.widgets.back, "on_detach_callback"); _.invoke(this.widgets.back, "on_detach_callback");
@ -633,6 +679,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}); });
}, },
/**
* @private
*/
_updateLazyQty: function() { _updateLazyQty: function() {
var model = this.options.basicFieldParams.model; var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id); var record = model.get(this.state.id);
@ -664,12 +713,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
const state_data = record.data; const state_data = record.data;
let to_find = []; let to_find = [];
if (!_.isEmpty(fields)) { if (_.isEmpty(fields)) {
to_find = ["[data-field]"];
} else {
to_find = _.map(fields, field => { to_find = _.map(fields, field => {
return _.str.sprintf("[data-field=%s]", [field]); return _.str.sprintf("[data-field=%s]", [field]);
}); });
} else {
to_find = ["[data-field]"];
} }
this.$el.find(to_find.join()).each((key, value) => { this.$el.find(to_find.join()).each((key, value) => {

View File

@ -107,6 +107,14 @@ odoo.define(
}); });
}, },
/**
* Because this widget doesn't support comments/sections line types
* we need check if the line is valid to be shown.
*
* @private
* @param {Object} state
* @returns {Boolean}
*/
_isValidLineState: function(state) { _isValidLineState: function(state) {
return ( return (
state.data[this.options.field_map.product] && state.data[this.options.field_map.product] &&
@ -114,6 +122,12 @@ odoo.define(
); );
}, },
/**
* @private
* @param {Object} state_a
* @param {Object} state_b
* @returns {Boolean}
*/
_isEqualState: function(state_a, state_b) { _isEqualState: function(state_a, state_b) {
if (state_a.id === state_b.id) { if (state_a.id === state_b.id) {
return true; return true;
@ -133,6 +147,11 @@ odoo.define(
); );
}, },
/**
* @private
* @param {Object} state
* @returns {Boolean}
*/
_existsWidgetWithState: function(state) { _existsWidgetWithState: function(state) {
for (let eb = this.widgets.length - 1; eb >= 0; --eb) { for (let eb = this.widgets.length - 1; eb >= 0; --eb) {
const widget = this.widgets[eb]; const widget = this.widgets[eb];
@ -388,6 +407,7 @@ odoo.define(
}, },
/** /**
* @private
* @param {Array} datas * @param {Array} datas
* @returns {Array} * @returns {Array}
*/ */
@ -540,8 +560,7 @@ odoo.define(
* *
* @private * @private
* @param {Array} search_records * @param {Array} search_records
* @param {Boolean} no_process_records * @param {Object} options
* @param {Number} position
*/ */
_appendSearchRecords: function(search_records, options) { _appendSearchRecords: function(search_records, options) {
const processed_info = options.no_process_records const processed_info = options.no_process_records
@ -569,8 +588,8 @@ odoo.define(
} }
// At this point the widget will use the existing state (line) or // At this point the widget will use the existing state (line) or
// the search data. Using search data instead of waiting for // a simple state data. Using simple state data instead of waiting for
// simulated state gives a low FCP time. // complete state (default + onchange) gives a low FCP time.
const def = $.Deferred(); const def = $.Deferred();
ProductPickerRecord.appendTo(this.$recordsContainer).then( ProductPickerRecord.appendTo(this.$recordsContainer).then(
function(widget, widget_position) { function(widget, widget_position) {
@ -622,9 +641,7 @@ odoo.define(
* Append search records to the view * Append search records to the view
* *
* @param {Array} search_records * @param {Array} search_records
* @param {Boolean} no_attach_widgets * @param {Object} options
* @param {Boolean} no_process_records
* @param {Number} position
* @returns {Array} * @returns {Array}
*/ */
appendSearchRecords: function(search_records, options = {}) { appendSearchRecords: function(search_records, options = {}) {
@ -690,6 +707,7 @@ odoo.define(
* Handle card flip. * Handle card flip.
* Used to create/update the record * Used to create/update the record
* *
* @private
* @param {CustomEvent} evt * @param {CustomEvent} evt
*/ */
_onRecordFlip: function(evt) { _onRecordFlip: function(evt) {

View File

@ -12,22 +12,19 @@ odoo.define("web_widget_one2many_product_picker.BasicController", function(requi
* @override * @override
*/ */
_confirmChange: function(id, fields, e) { _confirmChange: function(id, fields, e) {
id = id || this.handle;
return this._super.apply(this, arguments).then(() => { return this._super.apply(this, arguments).then(() => {
const product_picker_widgets = _.filter( if (this.renderer && !_.isEmpty(this.renderer.allFieldWidgets)) {
this.renderer.allFieldWidgets[this.handle], const product_picker_widgets = _.filter(
item => item.attrs.widget === "one2many_product_picker" this.renderer.allFieldWidgets[id],
); item => item.attrs.widget === "one2many_product_picker"
for (const widget of product_picker_widgets) { );
const trigger_fields = widget.options.trigger_refresh_fields || []; _.invoke(
if ( product_picker_widgets,
_.difference(trigger_fields, fields).length !== "onDocumentConfirmChanges",
trigger_fields.length fields,
) { e
widget._reset(this.model.get(this.handle), e); );
// Force re-launch onchanges on 'pure virtual' records
widget.renderer.clearRecords();
widget._render();
}
} }
}); });
}, },

View File

@ -74,6 +74,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
* elements so can be removed safesly * elements so can be removed safesly
* *
* @param {String} id * @param {String} id
* @returns {Boolean}
*/ */
removeVirtualRecord: function(id) { removeVirtualRecord: function(id) {
if (!this.isPureVirtual(id)) { if (!this.isPureVirtual(id)) {
@ -91,6 +92,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
this.removeLine(remove_id); this.removeLine(remove_id);
delete this.localData[remove_id]; delete this.localData[remove_id];
} }
return true;
}, },
/** /**
@ -158,6 +160,10 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
return Promise.reject(); return Promise.reject();
} }
var def = new Promise(function(resolve, reject) { var def = new Promise(function(resolve, reject) {
// Interrupt point (used in instant search)
if (!self.exists(record.id)) {
return Promise.reject();
}
var always = function() { var always = function() {
if (record._warning) { if (record._warning) {
if (params.allowWarning) { if (params.allowWarning) {
@ -329,28 +335,142 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
}, },
/** /**
* This happens when the user discard main document changes (isn't a rollback) * Because records can be removed at any time we
* need check if the record still existing.
* Necessary for 'instant search' feature.
* *
* @override * @override
*/ */
discardChanges: function(id, options) { _applyOnChange: function(values, record) {
this._super.apply(this, arguments); if (!this.exists(record.id)) {
options = options || {}; return Promise.reject();
var isNew = this.isNew(id);
var rollback = "rollback" in options ? options.rollback : isNew;
if (rollback) {
return;
} }
const element = this.localData[id]; return this._super.apply(this, arguments);
this._visitChildren(element, function(elem) { },
if (
elem && /**
elem.context && * @param {String} recordID
elem.context.product_picker_modified && * @returns {Boolean}
_.isEmpty(elem._changes) */
) { hasChanges: function(recordID) {
elem.context.product_picker_modified = false; const record = this.localData[recordID];
return record && !_.isEmpty(record._changes);
},
/**
* @param {Object} model_fields
* @param {String} model
* @param {String} search_val
* @param {Array} domain
* @param {Array} fields
* @param {Object} orderby
* @param {String} operator
* @param {Number} limit
* @param {Number} offset
* @param {Object} context
* @returns {Promise}
*/
fetchNameSearchFull: function(
model_fields,
model,
search_val,
domain,
fields,
orderby,
operator,
limit,
offset,
context
) {
return this._rpc({
model: model,
method: "name_search",
kwargs: {
name: search_val,
args: domain || [],
operator: operator || "ilike",
limit: this.limit,
context: context || {},
},
}).then(results => {
const record_ids = results.map(item => item[0]);
return this.fetchGenericRecords(
model_fields,
model,
[["id", "in", record_ids]],
fields,
orderby,
limit,
offset,
context
);
});
},
/**
* @param {Object} model_fields
* @param {String} model
* @param {Array} domain
* @param {Array} fields
* @param {Array} orderby
* @param {Number} limit
* @param {Number} offset
* @param {Object} context
* @returns {Promise}
*/
fetchGenericRecords: function(
model_fields,
model,
domain,
fields,
orderby,
limit,
offset,
context
) {
return this._rpc({
model: model,
method: "search_read",
fields: fields,
domain: domain,
limit: limit,
offset: offset,
orderBy: orderby,
kwargs: {context: context},
}).then(result => {
for (const index in result) {
const record = result[index];
for (const fieldName in record) {
const field = model_fields[fieldName];
if (field.type !== "many2one") {
record[fieldName] = this._parseServerValue(
model_fields[fieldName],
record[fieldName]
);
}
}
} }
return result;
});
},
fetchModelFieldsInfo: function(model) {
return this._rpc({
model: model,
method: "fields_get",
args: [
false,
[
"store",
"searchable",
"type",
"string",
"relation",
"selection",
"related",
],
],
context: this.getSession().user_context,
}); });
}, },
}); });

View File

@ -42,7 +42,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}), }),
_auto_search_delay: 450, _auto_search_delay: 450,
_input_instant_search_time: 150, _input_instant_search_time: 100,
// Model product.product fields // Model product.product fields
search_read_fields: ["id", "display_name", "uom_id"], search_read_fields: ["id", "display_name", "uom_id"],
@ -88,14 +88,25 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}, },
willStart: function() { willStart: function() {
return this._super.apply(this, arguments).then(() => { return this._super
if (this.isReadonly) { .apply(this, arguments)
// Show Lines .then(() => {
this._updateSearchContext(-1); const arch = this.view.arch;
} else { const field_name = this.options.field_map.product;
this._updateSearchContext(0); const field_info = this.view.fieldsInfo[arch.tag][field_name];
} const model = this.view.viewFields[field_info.name].relation;
}); this._modelName = model;
return this.parent_controller.model.fetchModelFieldsInfo(model);
})
.then(fields_info => {
this._fieldsInfo = fields_info;
if (this.isReadonly) {
// Show Lines
this._updateSearchContext(-1);
} else {
this._updateSearchContext(0);
}
});
}, },
/** /**
@ -169,6 +180,26 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}; };
}, },
/**
* Because the widget shows "pure virtual" information, we don't have any 'onchange' linked.
* This method forces 'refresh' the widget if the selected fields was changed.
*
* @param {Array} fields
* @param {Event} e
*/
onDocumentConfirmChanges: function(fields, e) {
const trigger_fields = this.options.trigger_refresh_fields || [];
if (_.difference(trigger_fields, fields).length !== trigger_fields.length) {
this._reset(
this.parent_controller.model.get(this.parent_controller.handle),
e
);
// Force re-launch onchanges on 'pure virtual' records
this.renderer.clearRecords();
this._render();
}
},
/** /**
* @override * @override
*/ */
@ -191,6 +222,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
group_def.active = !hasUserActive; group_def.active = !hasUserActive;
hasUserActive = true; hasUserActive = true;
} }
if (!group_def.records_per_page) {
group_def.records_per_page = 16;
}
this.searchGroups.push(group_def); this.searchGroups.push(group_def);
} }
@ -200,6 +234,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
domain: this.options.all_domain, domain: this.options.all_domain,
order: false, order: false,
active: !hasUserActive, active: !hasUserActive,
records_per_page: 16,
}); });
this._activeSearchGroup = this.searchGroups[0]; this._activeSearchGroup = this.searchGroups[0];
}, },
@ -305,6 +340,16 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
this.updateSubtotalPrice(); this.updateSubtotalPrice();
}, },
/**
* Replace placeholders for search
* - $number_search -> Is a number
* - $search -> Is a string
*
* @private
* @param {Number/String} value
* @param {String} format
* @returns {Number/String}
*/
_getSearchValue: function(value, format) { _getSearchValue: function(value, format) {
if (format === "$number_search") { if (format === "$number_search") {
return Number(value); return Number(value);
@ -320,15 +365,10 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* *
* @private * @private
* @param {Dictionary} options * @param {Dictionary} options
* @param {Boolean} merge
* @returns {Deferred} * @returns {Deferred}
*/ */
_getSearchRecords: function(options) { _getSearchRecords: function(options) {
const arch = this.view.arch;
const search_mode = this.options.search[this._searchMode]; const search_mode = this.options.search[this._searchMode];
const field_name = this.options.field_map.product;
const field_info = this.view.fieldsInfo[arch.tag][field_name];
const model = this.view.viewFields[field_info.name].relation;
const orderby = this._searchContext.order; const orderby = this._searchContext.order;
const fields = this.search_read_fields; const fields = this.search_read_fields;
@ -344,7 +384,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}, },
this.value.getContext() this.value.getContext()
); );
const limit = soptions.limit || this.options.records_per_page; const limit = soptions.limit || this._activeSearchGroup.records_per_page;
const offset = soptions.offset || 0; const offset = soptions.offset || 0;
return new Promise(resolve => { return new Promise(resolve => {
@ -355,8 +395,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
search_mode.name_search_value search_mode.name_search_value
); );
const operator = search_mode.operator; const operator = search_mode.operator;
task = this._doSearchRecordsNameSearch( task = this.parent_controller.model.fetchNameSearchFull(
model, this._fieldsInfo,
this._modelName,
search_val, search_val,
domain, domain,
fields, fields,
@ -367,8 +408,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
context context
); );
} else { } else {
task = this._doSearchRecords( task = this.parent_controller.model.fetchGenericRecords(
model, this._fieldsInfo,
this._modelName,
domain, domain,
fields, fields,
orderby, orderby,
@ -386,76 +428,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}); });
}, },
_doSearchRecordsNameSearch: function(
model,
search_val,
domain,
fields,
orderby,
operator,
limit,
offset,
context
) {
return new Promise(resolve => {
this._rpc({
model: model,
method: "name_search",
kwargs: {
name: search_val,
args: domain || [],
operator: operator || "ilike",
limit: this.limit,
context: context || {},
},
}).then(results => {
const record_ids = results.map(item => item[0]);
this._doSearchRecords(
model,
[["id", "in", record_ids]],
fields,
orderby,
limit,
offset,
context
).then(records => {
resolve(records);
});
});
});
},
/**
* @param {String} model
* @param {Array} domain
* @param {Array} fields
* @param {Array} orderby
* @param {Number} limit
* @param {Number} offset
* @param {Object} context
* @returns {Promise}
*/
_doSearchRecords: function(
model,
domain,
fields,
orderby,
limit,
offset,
context
) {
return this._rpc({
model: model,
method: "search_read",
fields: fields,
domain: domain,
limit: limit,
offset: offset,
orderBy: orderby,
kwargs: {context: context},
});
},
/** /**
* @private * @private
* @param {MouseEvent} evt * @param {MouseEvent} evt
@ -514,7 +486,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
_getDefaultOptions: function() { _getDefaultOptions: function() {
return { return {
currency_field: "currency_id", currency_field: "currency_id",
records_per_page: 16,
show_subtotal: true, show_subtotal: true,
show_discount: false, show_discount: false,
edit_discount: false, edit_discount: false,
@ -540,7 +511,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* This domain is used to get the records to display. * This domain is used to get the records to display.
* *
* @private * @private
* @param {Object} active_search * @param {Object} search_mode
* @returns {Array} * @returns {Array}
*/ */
_getFullSearchDomain: function(search_mode) { _getFullSearchDomain: function(search_mode) {
@ -671,18 +642,24 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
}, },
/**
* @private
* @param {SearchEvent} evt
*/
_onSearch: function(evt) { _onSearch: function(evt) {
this._searchContext.text = evt.target.value; this._searchContext.text = evt.target.value;
this.doRenderSearchRecords(); this.doRenderSearchRecords();
}, },
/**
* @private
* @param {InputEvent} evt
*/
_onInputSearch: function(evt) { _onInputSearch: function(evt) {
if (!this.options.instant_search) { if (this.options.instant_search) {
return; this._searchContext.text = evt.target.value;
this._lazyRenderSearchRecords();
} }
this._searchContext.text = evt.target.value;
this._lazyRenderSearchRecords();
// This.doRenderSearchRecords()
}, },
/** /**
@ -751,10 +728,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
} }
}); });
} else { } else {
// This is used to know when need use 'yellow' color
this.parent_controller.model.updateRecordContext(evt.data.id, {
product_picker_modified: true,
});
// This will trigger an "state" update // This will trigger an "state" update
this._setValue({operation: "ADD", id: evt.data.id}).then(() => { this._setValue({operation: "ADD", id: evt.data.id}).then(() => {
if (evt.data.callback) { if (evt.data.callback) {
@ -764,6 +737,11 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
} }
}, },
/**
* @param {Number} id
* @param {Object} data
* @param {Function} callback
*/
_doUpdateQuickRecord: function(id, data, callback) { _doUpdateQuickRecord: function(id, data, callback) {
if (this.options.auto_save) { if (this.options.auto_save) {
var self = this; var self = this;
@ -790,10 +768,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
} }
}); });
} else { } else {
// This is used to know when need use 'yellow' color
this.parent_controller.model.updateRecordContext(id, {
product_picker_modified: true,
});
// This will trigger an "state" update // This will trigger an "state" update
this._setValue({operation: "UPDATE", id: id, data: data}).then( this._setValue({operation: "UPDATE", id: id, data: data}).then(
function() { function() {

View File

@ -98,7 +98,7 @@
<t t-elif="!is_virtual"> <t t-elif="!is_virtual">
<span <span
t-att-data-field="field_map[field_uom_qty]" t-att-data-field="field_map[field_uom_qty]"
t-attf-data-esc="str({{field_map[field_uom_qty]}}) + ' x ' + {{field_map[field_uom]}}.data.display_name" t-attf-data-esc="str({{floatFixed(field_map[field_uom_qty])}}) + ' x ' + {{field_map[field_uom]}}.data.display_name"
t-attf-class="badge {{modified &amp;&amp; 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty" t-attf-class="badge {{modified &amp;&amp; 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
/> />
</t> </t>