mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-19 10:51:59 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,57 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {_t} from "web.core";
|
||||
import Domain from "web.Domain";
|
||||
import DomainSelector from "web.DomainSelector";
|
||||
import basic_fields from "web.basic_fields";
|
||||
/**
|
||||
* The redraw in the Debug Field does not trigger correctly
|
||||
* so we overwrite it with the v14 Version
|
||||
*
|
||||
*/
|
||||
patch(DomainSelector.prototype, "web.DomainSelector", {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onDebugInputChange(e) {
|
||||
if (!$(".o_add_advanced_search").length) {
|
||||
return this._super(...arguments);
|
||||
}
|
||||
const rawDomain = e.currentTarget.value;
|
||||
try {
|
||||
Domain.prototype.stringToArray(rawDomain);
|
||||
} catch (err) {
|
||||
// If there is a syntax error, just ignore the change
|
||||
this.displayNotification({
|
||||
title: _t("Syntax error"),
|
||||
message: _t("Domain not properly formed"),
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._redraw(Domain.prototype.stringToArray(rawDomain)).then(
|
||||
function () {
|
||||
this.trigger_up("domain_changed", {
|
||||
child: this,
|
||||
alreadyRedrawn: true,
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
patch(basic_fields.FieldDomain.prototype, "web.basic_fields", {
|
||||
/**
|
||||
* Odoo restricts re-rendering the domain from the debug editor for supposedly
|
||||
* performance reasons. We didn't ever came up with those and in v17 it's supported
|
||||
* in the new advanced search.
|
||||
* @override
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
_onDomainSelectorValueChange(event) {
|
||||
this._super(...arguments);
|
||||
// Deactivate all debug conditions that cripple the functionality
|
||||
this.debugEdition = false;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import BasicModel from "web.BasicModel";
|
||||
import {ComponentAdapter} from "web.OwlCompatibility";
|
||||
import {Dropdown} from "@web/core/dropdown/dropdown";
|
||||
import FieldManagerMixin from "web.FieldManagerMixin";
|
||||
import {FieldMany2One} from "web.relational_fields";
|
||||
import {SelectCreateDialog} from "web.view_dialogs";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {session} from "@web/session";
|
||||
|
||||
const {Component, xml} = owl;
|
||||
|
||||
patch(Dropdown.prototype, "dropdown", {
|
||||
onWindowClicked(ev) {
|
||||
// This patch is created to prevent the closing of the Filter menu
|
||||
// when a selection is made in the RecordPicker
|
||||
if ($(ev.target.closest("ul.dropdown-menu")).attr("id") !== undefined) {
|
||||
const dropdown = $("body > ul.dropdown-menu");
|
||||
for (let i = 0; i < dropdown.length; i++) {
|
||||
if (
|
||||
$(ev.target.closest("ul.dropdown-menu")).attr("id") ===
|
||||
$(dropdown[i]).attr("id")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._super(ev);
|
||||
},
|
||||
});
|
||||
|
||||
export const FakeMany2oneFieldWidget = FieldMany2One.extend(FieldManagerMixin, {
|
||||
supportedFieldTypes: ["many2many", "many2one", "one2many"],
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function (parent) {
|
||||
this.componentAdapter = parent;
|
||||
const options = this.componentAdapter.props.attrs;
|
||||
// Create a dummy record with only a dummy m2o field to search on
|
||||
const model = new BasicModel("dummy");
|
||||
const params = {
|
||||
fieldNames: ["dummy"],
|
||||
modelName: "dummy",
|
||||
context: {},
|
||||
type: "record",
|
||||
viewType: "default",
|
||||
fieldsInfo: {default: {dummy: {}}},
|
||||
fields: {
|
||||
dummy: {
|
||||
string: options.string,
|
||||
relation: options.model,
|
||||
context: options.context,
|
||||
domain: options.domain,
|
||||
type: "many2one",
|
||||
},
|
||||
},
|
||||
};
|
||||
// Emulate `model.load()`, without RPC-calling `default_get()`
|
||||
this.dataPointID = model._makeDataPoint(params).id;
|
||||
model.generateDefaultValues(this.dataPointID, {});
|
||||
this._super(this.componentAdapter, "dummy", 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
|
||||
*
|
||||
* @param {BasicModel} model
|
||||
* @returns {String}
|
||||
*/
|
||||
_get_record: function (model) {
|
||||
return model.get(this.dataPointID);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_confirmChange: function (id, fields, event) {
|
||||
this.componentAdapter.trigger("change", event.data.changes[fields[0]]);
|
||||
this.dataPointID = id;
|
||||
return this.reset(this._get_record(this.model), event);
|
||||
},
|
||||
/**
|
||||
* Stop propagation of the 'Search more..' dialog click event.
|
||||
* Otherwise, the filter's dropdown will be closed after a selection.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_searchCreatePopup: function (view, ids, context, dynamicFilters) {
|
||||
const options = this._getSearchCreatePopupOptions(
|
||||
view,
|
||||
ids,
|
||||
context,
|
||||
dynamicFilters
|
||||
);
|
||||
const dialog = new SelectCreateDialog(
|
||||
this,
|
||||
_.extend({}, this.nodeOptions, options)
|
||||
);
|
||||
// Hack to stop click event propagation
|
||||
dialog._opened.then(() =>
|
||||
dialog.$el
|
||||
.get(0)
|
||||
.addEventListener("click", (event) => event.stopPropagation())
|
||||
);
|
||||
return dialog.open();
|
||||
},
|
||||
_onFieldChanged: function (event) {
|
||||
const self = this;
|
||||
event.stopPropagation();
|
||||
if (event.data.changes.dummy.display_name === undefined) {
|
||||
return this._rpc({
|
||||
model: this.field.relation,
|
||||
method: "name_get",
|
||||
args: [event.data.changes.dummy.id],
|
||||
context: session.user_context,
|
||||
}).then(function (result) {
|
||||
event.data.changes.dummy.display_name = result[0][1];
|
||||
return (
|
||||
self
|
||||
._applyChanges(
|
||||
event.data.dataPointID,
|
||||
event.data.changes,
|
||||
event
|
||||
)
|
||||
// eslint-disable-next-line no-empty-function
|
||||
.then(event.data.onSuccess || function () {})
|
||||
// eslint-disable-next-line no-empty-function
|
||||
.guardedCatch(event.data.onFailure || function () {})
|
||||
);
|
||||
});
|
||||
}
|
||||
return (
|
||||
this._applyChanges(event.data.dataPointID, event.data.changes, event)
|
||||
// eslint-disable-next-line no-empty-function
|
||||
.then(event.data.onSuccess || function () {})
|
||||
// eslint-disable-next-line no-empty-function
|
||||
.guardedCatch(event.data.onFailure || function () {})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export class FakeMany2oneFieldWidgetAdapter extends ComponentAdapter {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.env = Component.env;
|
||||
}
|
||||
|
||||
renderWidget() {
|
||||
this.widget._render();
|
||||
}
|
||||
|
||||
get widgetArgs() {
|
||||
if (this.props.widgetArgs) {
|
||||
return this.props.widgetArgs;
|
||||
}
|
||||
return [this.props.attrs];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A record selector widget.
|
||||
*
|
||||
* Underneath, 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.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export class RecordPicker extends Component {
|
||||
setup() {
|
||||
this.attrs = {
|
||||
string: this.props.string,
|
||||
model: this.props.model,
|
||||
domain: this.props.domain,
|
||||
context: this.props.context,
|
||||
};
|
||||
this.FakeMany2oneFieldWidget = FakeMany2oneFieldWidget;
|
||||
}
|
||||
}
|
||||
|
||||
RecordPicker.template = xml`
|
||||
<div>
|
||||
<FakeMany2oneFieldWidgetAdapter
|
||||
Component="FakeMany2oneFieldWidget"
|
||||
class="d-block"
|
||||
attrs="attrs"
|
||||
/>
|
||||
</div>`;
|
||||
RecordPicker.components = {FakeMany2oneFieldWidgetAdapter};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {Dropdown} from "@web/core/dropdown/dropdown";
|
||||
import {patch} from "web.utils";
|
||||
|
||||
patch(Dropdown.prototype, "web.Dropdown", {
|
||||
/**
|
||||
* Our many2one widget in the filter menus has a dropdown that propagates some
|
||||
* custom events through the bus to the search more pop-up. This is not replicable
|
||||
* in core but we can simply cut it here
|
||||
* @override
|
||||
*/
|
||||
onDropdownStateChanged(args) {
|
||||
const direct_siblings =
|
||||
args.emitter.rootRef.el.parentElement === this.rootRef.el.parentElement;
|
||||
if (!direct_siblings && args.emitter.myActiveEl !== this.myActiveEl) {
|
||||
return;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/** @odoo-module **/
|
||||
/*
|
||||
Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2020 Tecnativa - Alexandre Díaz
|
||||
Copyright 2022 Camptocamp SA - Iván Todorovich
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
*/
|
||||
|
||||
import {_t} from "web.core";
|
||||
const JOIN_MAPPING = {
|
||||
"&": _t(" and "),
|
||||
"|": _t(" or "),
|
||||
"!": _t(" is not "),
|
||||
};
|
||||
|
||||
const HUMAN_DOMAIN_METHODS = {
|
||||
DomainTree: function () {
|
||||
const human_domains = [];
|
||||
_.each(this.children, (child) => {
|
||||
human_domains.push(HUMAN_DOMAIN_METHODS[child.template].apply(child));
|
||||
});
|
||||
return `(${human_domains.join(JOIN_MAPPING[this.operator])})`;
|
||||
},
|
||||
|
||||
DomainSelector: function () {
|
||||
const result = HUMAN_DOMAIN_METHODS.DomainTree.apply(this, arguments);
|
||||
// Remove surrounding parenthesis
|
||||
return result.slice(1, -1);
|
||||
},
|
||||
|
||||
DomainLeaf: function () {
|
||||
const chain = [];
|
||||
let operator = this.operator_mapping[this.operator],
|
||||
value = `"${this.value}"`;
|
||||
// Humanize chain
|
||||
const chain_splitted = this.chain.split(".");
|
||||
const len = chain_splitted.length;
|
||||
for (let x = 0; x < len; ++x) {
|
||||
const element = chain_splitted[x];
|
||||
chain.push(
|
||||
_.findWhere(this.fieldSelector.popover.pages[x], {name: element})
|
||||
.string || element
|
||||
);
|
||||
}
|
||||
// Special beautiness for some values
|
||||
if (this.operator === "=" && _.isBoolean(this.value)) {
|
||||
operator = this.operator_mapping[this.value ? "set" : "not set"];
|
||||
value = "";
|
||||
} else if (_.isArray(this.value)) {
|
||||
value = `["${this.value.join('", "')}"]`;
|
||||
}
|
||||
return `${chain.join("→")} ${operator || this.operator} ${value}`.trim();
|
||||
},
|
||||
};
|
||||
|
||||
export function getHumanDomain(domainSelector) {
|
||||
return HUMAN_DOMAIN_METHODS.DomainSelector.apply(domainSelector);
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import Domain from "web.Domain";
|
||||
import DomainSelectorDialog from "web.DomainSelectorDialog";
|
||||
import config from "web.config";
|
||||
import {getHumanDomain} from "../../../js/utils.esm";
|
||||
import {standaloneAdapter} from "web.OwlCompatibility";
|
||||
import {useModel} from "web.Model";
|
||||
const {Component, useRef} = owl;
|
||||
|
||||
class AdvancedFilterItem extends Component {
|
||||
setup() {
|
||||
this.itemRef = useRef("dropdown-item");
|
||||
this.model = useModel("searchModel");
|
||||
}
|
||||
/**
|
||||
* Prevent propagation of dropdown-item-selected event, so that it
|
||||
* doesn't reach the FilterMenu onFilterSelected event handler.
|
||||
*/
|
||||
mounted() {
|
||||
$(this.itemRef.el).on("dropdown-item-selected", (event) =>
|
||||
event.stopPropagation()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Open advanced search dialog
|
||||
*
|
||||
* @returns {DomainSelectorDialog} The opened dialog itself.
|
||||
*/
|
||||
onClick() {
|
||||
const adapterParent = standaloneAdapter({Component});
|
||||
const dialog = new DomainSelectorDialog(
|
||||
adapterParent,
|
||||
this.model.config.modelName,
|
||||
"[]",
|
||||
{
|
||||
debugMode: config.isDebug(),
|
||||
readonly: false,
|
||||
}
|
||||
);
|
||||
// Add 1st domain node by default
|
||||
dialog.opened(() => dialog.domainSelector._onAddFirstButtonClick());
|
||||
// Configure handler
|
||||
dialog.on("domain_selected", this, function (e) {
|
||||
const preFilter = {
|
||||
description: getHumanDomain(dialog.domainSelector),
|
||||
domain: Domain.prototype.arrayToString(e.data.domain),
|
||||
type: "filter",
|
||||
};
|
||||
this.model.dispatch("createNewFilters", [preFilter]);
|
||||
});
|
||||
return dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
AdvancedFilterItem.components = {AdvancedFilterItem};
|
||||
|
||||
AdvancedFilterItem.template = "web_advanced_search.AdvancedFilterItem";
|
||||
export default AdvancedFilterItem;
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import CustomFilterItem from "web.CustomFilterItem";
|
||||
import {RecordPicker} from "../../../js/RecordPicker.esm";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* Patches the CustomFilterItem for legacy widgets.
|
||||
*
|
||||
* Tree views still use this old legacy widget, so we need to patch it.
|
||||
* This is likely to disappear in 17.0
|
||||
*/
|
||||
patch(CustomFilterItem.prototype, "web_advanced_search.legacy.CustomFilterItem", {
|
||||
/**
|
||||
* Ideally we'd want this in setup, but CustomFilterItem does its initialization
|
||||
* in the constructor, which can't be patched.
|
||||
*
|
||||
* Doing it here works just as well.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async willStart() {
|
||||
this.OPERATORS.relational = this.OPERATORS.char;
|
||||
this.FIELD_TYPES.many2one = "relational";
|
||||
this.FIELD_TYPES.many2many = "relational";
|
||||
this.FIELD_TYPES.one2many = "relational";
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setDefaultValue(condition) {
|
||||
const res = this._super(...arguments);
|
||||
const fieldType = this.fields[condition.field].type;
|
||||
const genericType = this.FIELD_TYPES[fieldType];
|
||||
if (genericType === "relational") {
|
||||
condition.value = 0;
|
||||
condition.displayedValue = "";
|
||||
}
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* Add displayed value to preFilters for "relational" types.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
onApply() {
|
||||
// To avoid the complete override, we patch this.conditions.map()
|
||||
const originalMapFn = this.conditions.map;
|
||||
const self = this;
|
||||
this.conditions.map = function () {
|
||||
const preFilters = originalMapFn.apply(this, arguments);
|
||||
for (const condition of this) {
|
||||
const field = self.fields[condition.field];
|
||||
const type = self.FIELD_TYPES[field.type];
|
||||
if (type === "relational") {
|
||||
const idx = this.indexOf(condition);
|
||||
const preFilter = preFilters[idx];
|
||||
const operator = self.OPERATORS[type][condition.operator];
|
||||
const descriptionArray = [
|
||||
field.string,
|
||||
operator.description,
|
||||
`"${condition.displayedValue}"`,
|
||||
];
|
||||
preFilter.description = descriptionArray.join(" ");
|
||||
}
|
||||
}
|
||||
return preFilters;
|
||||
};
|
||||
const res = this._super(...arguments);
|
||||
// Restore original map()
|
||||
this.conditions.map = originalMapFn;
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} condition
|
||||
* @param {OwlEvent} ev
|
||||
*/
|
||||
onRelationalChanged(condition, ev) {
|
||||
if (ev.detail) {
|
||||
condition.value = ev.detail.id;
|
||||
condition.displayedValue = ev.detail.display_name;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
patch(CustomFilterItem, "web_advanced_search.legacy.CustomFilterItem", {
|
||||
components: {
|
||||
...CustomFilterItem.components,
|
||||
RecordPicker,
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomFilterItem;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import AdvancedFilterItem from "./advanced_filter_item.esm";
|
||||
import FilterMenu from "web.FilterMenu";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* Patches the FilterMenu for legacy widgets.
|
||||
*
|
||||
* Tree views still use this old legacy widget, so we need to patch it.
|
||||
* This is likely to disappear in 17.0
|
||||
*/
|
||||
patch(FilterMenu, "web_advanced_search.legacy.FilterMenu", {
|
||||
components: {
|
||||
...FilterMenu.components,
|
||||
AdvancedFilterItem,
|
||||
},
|
||||
});
|
||||
|
||||
export default FilterMenu;
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import Domain from "web.Domain";
|
||||
import DomainSelectorDialog from "web.DomainSelectorDialog";
|
||||
import config from "web.config";
|
||||
import {getHumanDomain} from "../../js/utils.esm";
|
||||
import {standaloneAdapter} from "web.OwlCompatibility";
|
||||
const {Component, useRef} = owl;
|
||||
|
||||
class AdvancedFilterItem extends Component {
|
||||
setup() {
|
||||
this.itemRef = useRef("dropdown-item");
|
||||
}
|
||||
/**
|
||||
* Prevent propagation of dropdown-item-selected event, so that it
|
||||
* doesn't reach the FilterMenu onFilterSelected event handler.
|
||||
*/
|
||||
mounted() {
|
||||
$(this.itemRef.el).on("dropdown-item-selected", (event) =>
|
||||
event.stopPropagation()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Open advanced search dialog
|
||||
*
|
||||
* @returns {DomainSelectorDialog} The opened dialog itself.
|
||||
*/
|
||||
onClick() {
|
||||
const adapterParent = standaloneAdapter({Component});
|
||||
const dialog = new DomainSelectorDialog(
|
||||
adapterParent,
|
||||
this.env.searchModel.resModel,
|
||||
"[]",
|
||||
{
|
||||
debugMode: config.isDebug(),
|
||||
readonly: false,
|
||||
}
|
||||
);
|
||||
// Add 1st domain node by default
|
||||
dialog.opened(() => dialog.domainSelector._onAddFirstButtonClick());
|
||||
// Configure handler
|
||||
dialog.on("domain_selected", this, function (e) {
|
||||
const preFilter = {
|
||||
description: getHumanDomain(dialog.domainSelector),
|
||||
domain: Domain.prototype.arrayToString(e.data.domain),
|
||||
type: "filter",
|
||||
};
|
||||
this.env.searchModel.createNewFilters([preFilter]);
|
||||
});
|
||||
return dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
AdvancedFilterItem.components = {AdvancedFilterItem};
|
||||
|
||||
AdvancedFilterItem.template = "web_advanced_search.AdvancedFilterItem";
|
||||
export default AdvancedFilterItem;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2018 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||
Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<templates>
|
||||
<t t-name="web_advanced_search.AdvancedFilterItem" owl="1">
|
||||
<a
|
||||
role="menuitem"
|
||||
t-on-click="onClick"
|
||||
class=" dropdown-item o_add_advanced_search"
|
||||
> Add Advanced Filter </a>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {CustomFilterItem} from "@web/search/filter_menu/custom_filter_item";
|
||||
import {RecordPicker} from "../../js/RecordPicker.esm";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* Patches the CustomFilterItem for owl widgets.
|
||||
*/
|
||||
patch(CustomFilterItem.prototype, "web_advanced_search.CustomFilterItem", {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
this._super.apply(this, arguments);
|
||||
this.OPERATORS.relational = this.OPERATORS.char;
|
||||
this.FIELD_TYPES.many2one = "relational";
|
||||
this.FIELD_TYPES.many2many = "relational";
|
||||
this.FIELD_TYPES.one2many = "relational";
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setDefaultValue(condition) {
|
||||
const fieldType = this.fields[condition.field].type;
|
||||
const genericType = this.FIELD_TYPES[fieldType];
|
||||
if (genericType === "relational") {
|
||||
condition.value = 0;
|
||||
condition.displayedValue = "";
|
||||
return;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Add displayed value to preFilters for "relational" types.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
onApply() {
|
||||
// To avoid the complete override, we patch this.conditions.map()
|
||||
const originalMapFn = this.conditions.map;
|
||||
const self = this;
|
||||
this.conditions.map = function () {
|
||||
const preFilters = originalMapFn.apply(this, arguments);
|
||||
for (const condition of this) {
|
||||
const field = self.fields[condition.field];
|
||||
const type = self.FIELD_TYPES[field.type];
|
||||
if (type === "relational") {
|
||||
const idx = this.indexOf(condition);
|
||||
const preFilter = preFilters[idx];
|
||||
const operator = self.OPERATORS[type][condition.operator];
|
||||
if (
|
||||
["=", "!="].includes(operator.symbol) &&
|
||||
operator.value === undefined
|
||||
) {
|
||||
const descriptionArray = [
|
||||
field.string,
|
||||
operator.description,
|
||||
`"${condition.displayedValue}"`,
|
||||
];
|
||||
preFilter.description = descriptionArray.join(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return preFilters;
|
||||
};
|
||||
const res = this._super.apply(this, arguments);
|
||||
// Restore original map()
|
||||
this.conditions.map = originalMapFn;
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} condition
|
||||
* @param {OwlEvent} ev
|
||||
*/
|
||||
onRelationalChanged(condition, ev) {
|
||||
if (ev.detail) {
|
||||
condition.value = ev.detail.id;
|
||||
condition.displayedValue = ev.detail.display_name;
|
||||
}
|
||||
},
|
||||
onValueChange(condition, ev) {
|
||||
if (!ev.target.value) {
|
||||
return this.setDefaultValue(condition);
|
||||
}
|
||||
const field = this.fields[condition.field];
|
||||
const type = this.FIELD_TYPES[field.type];
|
||||
if (type === "relational") {
|
||||
condition.value = ev.target.value;
|
||||
condition.displayedValue = ev.target.value;
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
patch(CustomFilterItem, "web_advanced_search.CustomFilterItem", {
|
||||
components: {
|
||||
...CustomFilterItem.components,
|
||||
RecordPicker,
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomFilterItem;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2018 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||
Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<templates>
|
||||
<t t-inherit="web.CustomFilterItem" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//select[@t-elif]" position="after">
|
||||
<t
|
||||
t-elif="['many2one', 'many2many', 'one2many'].includes(fieldType) and ['=', '!='].includes(selectedOperator.symbol)"
|
||||
>
|
||||
<RecordPicker
|
||||
model="fields[condition.field].relation"
|
||||
string="fields[condition.field].string"
|
||||
context="fields[condition.field].context"
|
||||
t-on-change="(ev) => this.onRelationalChanged(condition,ev)"
|
||||
/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import AdvancedFilterItem from "./advanced_filter_item.esm";
|
||||
import {FilterMenu} from "@web/search/filter_menu/filter_menu";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
/**
|
||||
* Patches the FilterMenu for owl widgets.
|
||||
*/
|
||||
patch(FilterMenu, "web_advanced_search.FilterMenu", {
|
||||
components: {
|
||||
...FilterMenu.components,
|
||||
AdvancedFilterItem,
|
||||
},
|
||||
});
|
||||
|
||||
export default FilterMenu;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright 2017-2018 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||
Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<templates>
|
||||
<t t-inherit="web.legacy.FilterMenu" t-inherit-mode="extension" owl="1">
|
||||
<CustomFilterItem position="after">
|
||||
<AdvancedFilterItem />
|
||||
</CustomFilterItem>
|
||||
</t>
|
||||
<t t-inherit="web.FilterMenu" t-inherit-mode="extension" owl="1">
|
||||
<CustomFilterItem position="after">
|
||||
<AdvancedFilterItem />
|
||||
</CustomFilterItem>
|
||||
</t>
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue