3
0
Fork 0

[IMP] web_widget_one2many_product_picker: Add option to use 'name_search'

13.0
Alexandre D. Díaz 2021-04-13 15:52:46 +02:00
parent b453c9784e
commit 8c128aa3bf
4 changed files with 188 additions and 73 deletions

View File

@ -68,7 +68,7 @@ Widget options:
* price_unit -> The field that represent a price_unit ('price_unit' by default) * price_unit -> The field that represent a price_unit ('price_unit' by default)
* discount -> The field that represent a discount ('discount' by default) * discount -> The field that represent a discount ('discount' by default)
* search > Array of dictionaries or Array of 'triplets' ([[field_map.name, 'ilike', '$search']] by default) * search > Array of dictionaries (defines to use name_search by default)
* name -> The name to display * name -> The name to display
* domain -> The domain to use * domain -> The domain to use
@ -76,6 +76,9 @@ Widget options:
* $search -> Replaces it with the current value of the searchbox * $search -> Replaces it with the current value of the searchbox
* $number_search -> Replaces all the leaf with the current value of the searchbox as a number * $number_search -> Replaces all the leaf with the current value of the searchbox as a number
* name_search_value -> Enables the use of 'name_search' instead of 'search_read' and defines the value to search ('$search' by default)
* operator -> Operator used in 'name_search' ('ilike' by default)
* edit_discount > Enable/Disable discount edits (False by default) * edit_discount > Enable/Disable discount edits (False by default)
* edit_price > Enable/Disable price edits (True by default) * edit_price > Enable/Disable price edits (True by default)
* show_discount > Enable/Disable display discount (False by default) * show_discount > Enable/Disable display discount (False by default)

View File

@ -26,7 +26,7 @@ Widget options:
* price_unit -> The field that represent a price_unit ('price_unit' by default) * price_unit -> The field that represent a price_unit ('price_unit' by default)
* discount -> The field that represent a discount ('discount' by default) * discount -> The field that represent a discount ('discount' by default)
* search > Array of dictionaries or Array of 'triplets' ([[field_map.name, 'ilike', '$search']] by default) * search > Array of dictionaries (defines to use name_search by default)
* name -> The name to display * name -> The name to display
* domain -> The domain to use * domain -> The domain to use
@ -34,6 +34,9 @@ Widget options:
* $search -> Replaces it with the current value of the searchbox * $search -> Replaces it with the current value of the searchbox
* $number_search -> Replaces all the leaf with the current value of the searchbox as a number * $number_search -> Replaces all the leaf with the current value of the searchbox as a number
* name_search_value -> Enables the use of 'name_search' instead of 'search_read' and defines the value to search ('$search' by default)
* operator -> Operator used in 'name_search' ('ilike' by default)
* edit_discount > Enable/Disable discount edits (False by default) * edit_discount > Enable/Disable discount edits (False by default)
* edit_price > Enable/Disable price edits (True by default) * edit_price > Enable/Disable price edits (True by default)
* show_discount > Enable/Disable display discount (False by default) * show_discount > Enable/Disable display discount (False by default)

View File

@ -442,7 +442,7 @@ 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">search &gt; Array of dictionaries or Array of triplets ([[field_map.name, ilike, $search]] by default)</p> <li><p class="first">search &gt; Array of dictionaries (defines to use name_search by default)</p>
<blockquote> <blockquote>
<ul> <ul>
<li><p class="first">name -&gt; The name to display</p> <li><p class="first">name -&gt; The name to display</p>
@ -455,6 +455,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">name_search_value -&gt; Enables the use of name_search instead of search_read and defines the value to search ($search by default)</p>
</li>
<li><p class="first">operator -&gt; Operator used in name_search (ilike by default)</p>
</li>
</ul> </ul>
</blockquote> </blockquote>
</li> </li>

View File

@ -59,15 +59,18 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
this.attrs.options this.attrs.options
); );
if (!this.options.search) { if (!this.options.search) {
// Default search domain
this.options.search = [ this.options.search = [
[this.options.field_map.name, "ilike", "$search"], {
name: _t("By Name"),
domain: [],
name_search_value: "$search",
},
]; ];
} }
this._searchMode = 0; this._searchMode = 0;
this._searchCategoryNames = [];
if (!(this.options.search[0] instanceof Array)) {
this._searchCategoryNames = _.map(this.options.search, "name"); this._searchCategoryNames = _.map(this.options.search, "name");
} this._searchContext = {};
// FIXME: Choose a better way to get the active controller or model objects // FIXME: Choose a better way to get the active controller or model objects
this.parent_controller = parent.getParent(); this.parent_controller = parent.getParent();
@ -84,18 +87,10 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
return Promise.resolve(); return Promise.resolve();
} }
// Uses to work with searchs, so we can mix properties with the user values.
this._searchContext = {
domain: this.mode === "readonly" ? this._getLinesDomain() : false,
text: false,
order: false,
activeTest: true,
};
if (this.mode === "readonly") { if (this.mode === "readonly") {
this._activeSearchGroup = { this._updateSearchContext(-1);
name: "main_lines", } else {
}; this._updateSearchContext(0);
this._searchContext.activeTest = false;
} }
return Promise.all([ return Promise.all([
this._super.apply(this, arguments), this._super.apply(this, arguments),
@ -310,6 +305,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
this.updateSubtotalPrice(); this.updateSubtotalPrice();
}, },
_getSearchValue: function(value, format) {
if (format === "$number_search") {
return Number(value);
} else if (typeof value === "string") {
return format.replace(/\$search/, value);
}
return value;
},
/** /**
* Obtain the linked records defined in the options. * Obtain the linked records defined in the options.
* If merge is true the current records aren't removed. * If merge is true the current records aren't removed.
@ -321,13 +325,16 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
*/ */
_getSearchRecords: function(options, merge) { _getSearchRecords: function(options, merge) {
const arch = this.view.arch; const arch = this.view.arch;
const search_mode = this.options.search[this._searchMode];
const field_name = this.options.field_map.product; const field_name = this.options.field_map.product;
const field_info = this.view.fieldsInfo[arch.tag][field_name]; const field_info = this.view.fieldsInfo[arch.tag][field_name];
const model = this.view.viewFields[field_info.name].relation; const model = this.view.viewFields[field_info.name].relation;
const orderby = this._searchContext.order;
const fields = this.search_read_fields;
// Launch the rpc request and ensures that we wait for the reply // Launch the rpc request and ensures that we wait for the reply
// to continue // to continue
const domain = this._getFullSearchDomain(); const domain = this._getFullSearchDomain(search_mode);
const soptions = options || {}; const soptions = options || {};
const context = _.extend( const context = _.extend(
{ {
@ -337,20 +344,41 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}, },
this.value.getContext() this.value.getContext()
); );
return new Promise(resolve => {
const limit = soptions.limit || this.options.records_per_page; const limit = soptions.limit || this.options.records_per_page;
const offset = soptions.offset || 0; const offset = soptions.offset || 0;
this._rpc({
model: model, return new Promise(resolve => {
method: "search_read", let task = false;
fields: this.search_read_fields, if (search_mode.name_search_value) {
domain: domain, const search_val = this._getSearchValue(
limit: limit, this._searchContext.text,
offset: offset, search_mode.name_search_value
orderBy: this._searchContext.order, );
kwargs: {context: context}, const operator = search_mode.operator;
}).then(results => { task = this._doSearchRecordsNameSearch(
model,
search_val,
domain,
fields,
orderby,
operator,
limit,
offset,
context
);
} else {
task = this._doSearchRecords(
model,
domain,
fields,
orderby,
limit,
offset,
context
);
}
task.then(results => {
if (merge) { if (merge) {
this._searchRecords = _.union( this._searchRecords = _.union(
this._searchRecords || [], this._searchRecords || [],
@ -368,11 +396,82 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
this._activeSearchGroup this._activeSearchGroup
); );
} }
resolve(results); resolve(results);
}); });
}); });
}, },
_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
@ -380,12 +479,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
_onClickSearchGroup: function(evt) { _onClickSearchGroup: function(evt) {
const $btn = $(evt.target); const $btn = $(evt.target);
const groupIndex = Number($btn.data("group")) || 0; const groupIndex = Number($btn.data("group")) || 0;
this._activeSearchGroup = this.searchGroups[groupIndex]; this.showGroup(groupIndex);
this._searchContext.domain = this._activeSearchGroup.domain;
this._searchContext.order = this._activeSearchGroup.order;
this._searchContext.activeTest = true;
this.doRenderSearchRecords();
this.$btnLines.removeClass("active");
$btn.parent() $btn.parent()
.find(".active") .find(".active")
.removeClass("active"); .removeClass("active");
@ -459,16 +553,14 @@ 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
* @returns {Array} * @returns {Array}
*/ */
_getFullSearchDomain: function() { _getFullSearchDomain: function(search_mode) {
this._searchContext.involvedFields = []; this._searchContext.involvedFields = [];
const domain = _.clone(this._searchContext.domain) || []; const domain = _.clone(this._searchContext.domain) || [];
if (this._searchContext.text) { if (this._searchContext.text) {
let search_domain = this.options.search; const search_domain = search_mode.domain;
if (!(search_domain[0] instanceof Array)) {
search_domain = search_domain[this._searchMode].domain;
}
const involved_fields = []; const involved_fields = [];
// Iterate domain triplets and logic operators // Iterate domain triplets and logic operators
@ -478,27 +570,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
// Is a triplet // Is a triplet
if (domain_cloned instanceof Array) { if (domain_cloned instanceof Array) {
// Replace right leaf with the current value of the search input // Replace right leaf with the current value of the search input
if (domain_cloned[2] === "$number_search") { domain_cloned[2] = this._getSearchValue(
domain_cloned[2] = Number(this._searchContext.text); domain_cloned[2],
this._searchContext.text
);
involved_fields.push({ involved_fields.push({
type: "number", type: "number",
field: domain_cloned[0], field: domain_cloned[0],
oper: domain_cloned[1], oper: domain_cloned[1],
}); });
} else if (
typeof domain_cloned[2] === "string" &&
domain_cloned[2].includes("$search")
) {
domain_cloned[2] = domain_cloned[2].replace(
/\$search/,
this._searchContext.text
);
involved_fields.push({
type: "text",
field: domain_cloned[0],
oper: domain_cloned[1],
});
}
} }
domain.push(domain_cloned); domain.push(domain_cloned);
} }
@ -529,31 +609,56 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
}, },
/** /**
* The lines are special data, so we need display it in a other way * @param {Number} group_id
* that the search results. Use directy in-memory values.
*/ */
showLines: function() { _updateSearchContext: function(group_id) {
this._clearSearchInput(); if (group_id >= 0) {
this.$btnLines this._activeSearchGroup = this.searchGroups[group_id];
.parent() this._searchContext.domain = this._activeSearchGroup.domain;
.find(".active") this._searchContext.order = this._activeSearchGroup.order;
.removeClass("active"); this._searchContext.activeTest = this._activeSearchGroup.active_test;
this.$btnLines.addClass("active"); } else {
this._activeSearchGroup = { this._activeSearchGroup = {
name: "main_lines", name: "main_lines",
}; };
this._searchContext.domain = this._getLinesDomain(); this._searchContext.domain = this._getLinesDomain();
this._searchContext.order = [{name: "sequence"}, {name: "id"}]; this._searchContext.order = [{name: "sequence"}, {name: "id"}];
this._searchContext.activeTest = false; this._searchContext.activeTest = false;
}
},
/**
* The lines are special data, so we need display it in a other way
* that the search results. Use directy in-memory values.
*/
showLines: function() {
this._updateSearchContext(-1);
this._clearSearchInput();
this.$btnLines
.parent()
.find(".active")
.removeClass("active");
this.$btnLines.addClass("active");
this.doRenderSearchRecords(); this.doRenderSearchRecords();
}, },
/**
* @param {Number} group_id
*/
showGroup: function(group_id) {
this._updateSearchContext(group_id);
this.doRenderSearchRecords();
this.$btnLines.removeClass("active");
},
/** /**
* @private * @private
*/ */
_clearSearchInput: function() { _clearSearchInput: function() {
if (this.$searchInput) {
this.$searchInput.val(""); this.$searchInput.val("");
this._searchContext.text = ""; this._searchContext.text = "";
}
}, },
/** /**