mirror of https://github.com/OCA/web.git
[IMP] web_widget_one2many_product_picker: Control record per page by group and fix to two the number of decimals
parent
dd9a97bc65
commit
0543c48c56
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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 > Integer -> Used to control the load more behaviour (16 by default)</p>
|
|
||||||
</li>
|
|
||||||
<li><p class="first">groups > Array of dictionaries -> Declare the groups</p>
|
<li><p class="first">groups > Array of dictionaries -> 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 > Integer -> Used to control the load more behaviour (16 by default)</p>
|
||||||
|
</li>
|
||||||
|
<li><p class="first">active -> Boolean -> Select the default group to use (‘false’ by default = ‘All’ group)</p>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 && 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
t-attf-class="badge {{modified && 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||||
/>
|
/>
|
||||||
</t>
|
</t>
|
||||||
|
|
Loading…
Reference in New Issue