Merge PR #2003 into 14.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2021-09-22 07:21:04 +00:00
4 changed files with 278 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
odoo.define("web_advanced_search.CustomFilterItem", function (require) {
"use strict";
const CustomFilterItem = require("web.CustomFilterItem");
const FieldMany2One = require("web.relational_fields").FieldMany2One;
const Relational = require("web_advanced_search.RelationalOwl");
const {FIELD_TYPES} = require("web.searchUtils");
const {useListener} = require("web.custom_hooks");
const Domain = require("web.Domain");
const field_utils = require("web.field_utils");
CustomFilterItem.patch("web_advanced_search.CustomFilterItem", (T) => {
class AdvancedCustomFilterItem extends T {
constructor() {
super(...arguments);
this.state.field = false;
this.OPERATORS.relational = this.OPERATORS.char;
this.FIELD_TYPES.many2one = "relational";
useListener("m2xchange", this._onM2xDataChanged);
}
_addDefaultCondition() {
super._addDefaultCondition(...arguments);
const condition = this.state.conditions[
this.state.conditions.length - 1
];
condition.index = _.uniqueId("condition_");
}
/**
* @private
* @param {Object} condition
*/
_setDefaultValue(condition) {
const fieldType = this.fields[condition.field].type;
const genericType = FIELD_TYPES[fieldType];
if (genericType === "relational") {
condition.displayedValue = "";
} else {
super._setDefaultValue(...arguments);
}
}
/**
* @private
* @param {Object} condition
* @param {Event} ev
*/
_onFieldSelect(condition, ev) {
super._onFieldSelect(...arguments);
this.state.field = this.fields[ev.target.selectedIndex];
this.state.fieldindex = ev.target.selectedIndex;
this.state.conditionIndex = condition.index;
}
/**
* @private
* @param {Object} condition
* @param {Event} ev
*/
_onOperatorSelect(condition, ev) {
this.trigger("operatorChange");
this.state.operator = ev.target[ev.target.selectedIndex].value;
super._onOperatorSelect(...arguments);
}
_onM2xDataChanged(event) {
const fieldindex = this.fields
.map((field) => field.name)
.indexOf(event.detail.field);
const condition = this.state.conditions.filter(
(con) =>
con.field === fieldindex &&
con.index === this.state.conditionIndex
);
if (condition.length) {
condition[0].value = event.detail.changes.id;
condition[0].displayedValue = event.detail.changes.display_name;
}
}
_onApply() {
/* Patch onApply to add displayedValue to discriptionArray */
const preFilters = this.state.conditions.map((condition) => {
const field = this.fields[condition.field];
const type = this.FIELD_TYPES[field.type];
const operator = this.OPERATORS[type][condition.operator];
const descriptionArray = [field.string, operator.description];
const domainArray = [];
let domainValue = [];
// Field type specifics
if ("value" in operator) {
domainValue = [operator.value];
// No description to push here
} else if (["date", "datetime"].includes(type)) {
domainValue = condition.value.map((val) =>
field_utils.parse[type](val, {type}, {timezone: true})
);
const dateValue = condition.value.map((val) =>
field_utils.format[type](val, {type}, {timezone: false})
);
descriptionArray.push(
`"${dateValue.join(" " + this.env._t("and") + " ")}"`
);
} else {
domainValue = [condition.value];
descriptionArray.push(
`"${condition.displayedValue || condition.value}"`
);
}
// Operator specifics
if (operator.symbol === "between") {
domainArray.push(
[field.name, ">=", domainValue[0]],
[field.name, "<=", domainValue[1]]
);
} else {
domainArray.push([field.name, operator.symbol, domainValue[0]]);
}
const preFilter = {
description: descriptionArray.join(" "),
domain: Domain.prototype.arrayToString(domainArray),
type: "filter",
};
return preFilter;
});
this.model.dispatch("createNewFilters", preFilters);
// Reset state
this.state.open = false;
this.state.conditions = [];
this._addDefaultCondition();
}
}
return AdvancedCustomFilterItem;
});
// Extends HomeMenuWrapper components
CustomFilterItem.components = Object.assign({}, CustomFilterItem.components, {
FieldMany2One,
Relational,
});
});

View File

@@ -0,0 +1,111 @@
odoo.define("web_advanced_search.RelationalOwl", function (require) {
"use strict";
const BasicModel = require("web.BasicModel");
const patchMixin = require("web.patchMixin");
const {ComponentAdapter} = require("web.OwlCompatibility");
const relationalFields = require("web.relational_fields");
const FieldMany2One = relationalFields.FieldMany2One;
const FieldManagerMixin = require("web.FieldManagerMixin");
const {useListener} = require("web.custom_hooks");
/* global owl */
const {Component} = owl;
const {xml} = owl.tags;
const AdvancedSearchWidget = FieldMany2One.extend(FieldManagerMixin, {
init: function (parent) {
const field = parent.__owl__.parent.field;
const model = new BasicModel(field.relation);
// Create dummy record with only the field the user is searching
const params = {
fieldNames: [field.name],
modelName: field.relation,
context: field.context,
type: "record",
viewType: "default",
fieldsInfo: {
default: {},
},
fields: {
[field.name]: _.omit(
field,
// User needs all records, to actually produce a new domain
"domain",
// Onchanges make no sense in this context, there's no record
"onChange"
),
},
};
if (field.type.endsWith("2many")) {
// X2many fields behave like m2o in the search context
params.fields[field.name].type = "many2one";
}
params.fieldsInfo.default[field.name] = {};
// Emulate `model.load()`, without RPC-calling `default_get()`
this.dataPointID = model._makeDataPoint(params).id;
model.generateDefaultValues(this.dataPointID, {});
this._super(parent, field.name, this._get_record(model), {
mode: "edit",
attrs: {
options: {
no_create_edit: true,
no_create: true,
no_open: true,
no_quick_create: true,
},
},
});
FieldManagerMixin.init.call(this, model);
},
_get_record: function (model) {
return model.get(this.dataPointID);
},
/**
* @override
*/
_confirmChange: function (id, fields, event) {
this.trigger_up("m2xchange", {
data: event.data,
changes: event.data.changes[fields[0]],
field: fields[0],
});
this.dataPointID = id;
return this.reset(this._get_record(this.model), event);
},
});
/**
* A search field for relational fields.
*
* It implements and extends the `FieldManagerMixin`, and acts as if it
* were a reduced dummy controller. Some actions "mock" the underlying
* model, since sometimes we use a char widget to fill related fields
* (which is not supported by that widget), and fields need an underlying
* model implementation, which can only hold fake data, given a search view
* has no data on it by definition.
*/
class Relational extends Component {
// eslint-disable-next-line no-unused-vars
constructor(parent, component, props) {
super(...arguments);
this.field = parent.state.field;
this.operator = parent.state.operator;
this.FieldWidget = false;
this.set_widget();
useListener("operatorChange", this.set_widget);
}
/**
* @override
*/
set_widget() {
this.FieldWidget = AdvancedSearchWidget;
}
}
Relational.template = xml`
<div>
<ComponentAdapter Component="FieldWidget" />
</div>`;
Relational.components = {ComponentAdapter};
return patchMixin(Relational);
});

View File

@@ -8,6 +8,24 @@
<AdvancedFilterItem fields="props.fields" />
</xpath>
</t>
<t t-inherit="web.CustomFilterItem" t-inherit-mode="extension" owl="1">
<xpath expr="//select[@t-elif]" position="after">
<t t-elif="fieldType === 'many2one'">
<t
t-if="selectedOperator.symbol === '=' || selectedOperator.symbol === '!='"
>
<Relational />
</t>
<input
t-else=""
type="text"
class="o_input"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
</t>
</xpath>
</t>
<t t-name="web_advanced_search.AdvancedFilterItem" owl="1">
<div class="o_generator_menu">
<button

View File

@@ -12,10 +12,18 @@
type="text/javascript"
src="/web_advanced_search/static/src/js/control_panel/filter_menu.js"
/>
<script
type="text/javascript"
src="/web_advanced_search/static/src/js/control_panel/custom_filter_item.js"
/>
<script
type="text/javascript"
src="/web_advanced_search/static/src/js/human_domain.js"
/>
<script
type="text/javascript"
src="/web_advanced_search/static/src/js/relational.js"
/>
</xpath>
</template>
</odoo>