Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,551 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Advanced search</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="advanced-search">
<h1 class="title">Advanced search</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:65c79a6f6afe59fa74b84abd881a95cb2ab25a8f4adcbf84af205873d9302d23
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/16.0/web_advanced_search"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_advanced_search"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>More powerful and easy to use search, especially for related fields.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-3">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-4">11.0.1.0.2 (2018-10-31)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-5">11.0.1.0.1 (2018-09-18)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-6">11.0.1.0.0 (2018-07-20)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-7">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-8">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-9">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-10">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-11">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module, you need to:</p>
<ul class="simple">
<li>Open <em>Filters</em> in a search view</li>
<li>Select any relational field</li>
<li>Select operator <cite>is equal to</cite> or <cite>is not equal to</cite></li>
<li>The text field changes to a relational selection field where you
can search for the record in question</li>
<li>Click <em>Apply</em></li>
</ul>
<p>To search for properties of linked records (ie invoices for customers
with a credit limit higher than X):</p>
<ul class="simple">
<li>Open <em>Filters</em> in a search view</li>
<li>Select <em>Add Advanced Filter</em></li>
<li>Edit the advanced filter</li>
<li>Click <em>Save</em></li>
</ul>
<p>Note that you can stack searching for properties: Simply add another
advanced search in the selection search window. You can do
this indefinetely, so it is possible to search for moves belonging
to a journal which has a user who is member of a certain group etc.</p>
<dl class="docutils">
<dt>Note also the domain dialog offers an editable preview in debug mode:</dt>
<dd><img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_advanced_search/static/img/debug_mode.png" class="first last" src="https://raw.githubusercontent.com/OCA/web/16.0/web_advanced_search/static/img/debug_mode.png" />
</dd>
</dl>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<p>Improvements to the <tt class="docutils literal">domain</tt> widget, not exclusively related to this addon:</p>
<ul class="simple">
<li>Use relational widgets when filtering a relational field</li>
<li>Allow to filter field names</li>
</ul>
<p>Improvements to the search view in this addon:</p>
<ul class="simple">
<li>Use widgets <tt class="docutils literal">one2many_tags</tt> when searching <tt class="docutils literal">one2many</tt> fields</li>
<li>Use widgets <tt class="docutils literal">many2many_tags</tt> when searching <tt class="docutils literal">many2many</tt> fields</li>
<li>Allow to edit current full search using the advanced domain editor</li>
</ul>
<p>Issues:</p>
<ul class="simple">
<li>Grouped totals can show incorrect values. See <a class="reference external" href="https://github.com/odoo/odoo/issues/47950">https://github.com/odoo/odoo/issues/47950</a></li>
</ul>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#toc-entry-3">Changelog</a></h1>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-4">11.0.1.0.2 (2018-10-31)</a></h2>
<ul>
<li><p class="first">Fix initialization of 1st domain node</p>
<p>Sometime the dialog is not ready yet, like on EE version.
Hence when you inject the 1st domain node
the dialog must be already opened.</p>
<p>[simahawk]</p>
</li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-5">11.0.1.0.1 (2018-09-18)</a></h2>
<ul>
<li><p class="first">Fix <cite>undefined</cite> in x2m fields</p>
<p>Before this patch, when searching with the “equals to” operator in any
x2many field, the searched parameter was always <cite>undefined</cite>.</p>
<p>The problem was that the underlying field manager implementation was
treating those fields as x2many, while the widget used was the <cite>one2many</cite>
one.</p>
<p>This patch simply mocks the underlying fake record to make think that
any relational field is always a <cite>one2many</cite>. This sets all pieces in
place and makes the field manager work as expected, and thus you can
search as expected too.</p>
</li>
<li><p class="first">Make linter happy</p>
<p>[Yajo]</p>
</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-6">11.0.1.0.0 (2018-07-20)</a></h2>
<ul>
<li><p class="first">Rename, refactor, migrate to v11</p>
<p>[Yajo]</p>
</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-7">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_advanced_search%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-8">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-9">Authors</a></h2>
<ul class="simple">
<li>Therp BV</li>
<li>Tecnativa</li>
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-10">Contributors</a></h2>
<ul>
<li><p class="first">Holger Brunn &lt;<a class="reference external" href="mailto:hbrunn&#64;therp.nl">hbrunn&#64;therp.nl</a>&gt;</p>
</li>
<li><p class="first">Rami Alwafaie &lt;<a class="reference external" href="mailto:rami.alwafaie&#64;initos.com">rami.alwafaie&#64;initos.com</a>&gt;</p>
</li>
<li><p class="first">Jose Mª Bernet &lt;<a class="reference external" href="mailto:josemaria.bernet&#64;guadaltech.es">josemaria.bernet&#64;guadaltech.es</a>&gt;</p>
</li>
<li><p class="first">Simone Orsi &lt;<a class="reference external" href="mailto:simone.orsi&#64;camptocamp.com">simone.orsi&#64;camptocamp.com</a>&gt;</p>
</li>
<li><p class="first">Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</p>
</li>
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
<ul class="simple">
<li>Vicent Cubells</li>
<li>Jairo Llopis</li>
<li>Alexandre Díaz</li>
</ul>
</li>
<li><p class="first"><a class="reference external" href="https://www.dynapps.be">DynApps NV</a>:</p>
<ul class="simple">
<li>Raf Ven</li>
</ul>
</li>
<li><p class="first"><a class="reference external" href="https://www.camptocamp.com">Camptocamp</a></p>
<blockquote>
<ul class="simple">
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;camptocamp.com">ivan.todorovich&#64;camptocamp.com</a>&gt;</li>
</ul>
</blockquote>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-11">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/ivantodorovich"><img alt="ivantodorovich" src="https://github.com/ivantodorovich.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/16.0/web_advanced_search">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -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;
},
});

View file

@ -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};

View file

@ -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);
},
});

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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>