1
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'search_panel_extension',
|
||||
'version': '17.0.0.1',
|
||||
'summary': 'Search Panel Extension',
|
||||
'description': 'Search Panel Extension',
|
||||
'category': 'tool',
|
||||
'author': 'TX',
|
||||
'website': 'https://www.17tongx.com/',
|
||||
'depends': ['base'],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'search_panel_extension/static/src/xml/search_panel.xml',
|
||||
'search_panel_extension/static/src/js/search_model.js',
|
||||
'search_panel_extension/static/src/js/search_panel.js',
|
||||
]
|
||||
},
|
||||
"license": "OPL-1",
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,63 @@
|
||||
from odoo import models, _, api
|
||||
|
||||
class Base(models.AbstractModel):
|
||||
_inherit = 'base'
|
||||
|
||||
@api.model
|
||||
def search_panel_select_all_multi_range(self, field_name, **kwargs):
|
||||
res = self.search_panel_select_multi_range(field_name, **kwargs)
|
||||
field_range = res['values']
|
||||
field = self._fields[field_name]
|
||||
if field.type == 'selection':
|
||||
return { 'values': field_range, }
|
||||
Comodel = self.env.get(field.comodel_name).with_context(hierarchical_naming=False)
|
||||
limit = kwargs.get('limit')
|
||||
enable_counters = kwargs.get('enable_counters')
|
||||
group_by = kwargs.get('group_by')
|
||||
field_names = ['display_name']
|
||||
|
||||
if group_by:
|
||||
id_list = [record['id'] for record in field_range]
|
||||
id_set = set(id_list)
|
||||
group_set = set([record['group_id'] for record in field_range])
|
||||
need_add_group_set = group_set - id_set
|
||||
|
||||
group_by_field = Comodel._fields[group_by]
|
||||
|
||||
field_names.append(group_by)
|
||||
|
||||
if group_by_field.type == 'many2one':
|
||||
def group_id_name(value):
|
||||
return value or (False, _("Not Set"))
|
||||
|
||||
elif group_by_field.type == 'selection':
|
||||
desc = Comodel.fields_get([group_by])[group_by]
|
||||
group_by_selection = dict(desc['selection'])
|
||||
group_by_selection[False] = _("Not Set")
|
||||
|
||||
def group_id_name(value):
|
||||
return value, group_by_selection[value]
|
||||
|
||||
else:
|
||||
def group_id_name(value):
|
||||
return (value, value) if value else (False, _("Not Set"))
|
||||
|
||||
while True:
|
||||
if need_add_group_set.__len__() == 0:
|
||||
break
|
||||
group_ids = list(need_add_group_set)
|
||||
need_add_group_set.clear()
|
||||
comodel_records = Comodel.search_read([('id', 'in', group_ids)], field_names, limit=limit)
|
||||
for record in comodel_records:
|
||||
group_id, group_name = group_id_name(record[group_by])
|
||||
values = {
|
||||
'id': record['id'],
|
||||
'display_name': record['display_name'],
|
||||
'group_id': group_id,
|
||||
'group_name': group_name, }
|
||||
if enable_counters:
|
||||
values['__count'] = 0
|
||||
field_range.append(values)
|
||||
if group_id and group_id not in id_list:
|
||||
need_add_group_set.add(group_id)
|
||||
return { 'values': field_range, }
|
||||
@@ -0,0 +1,121 @@
|
||||
/** @odoo-module */
|
||||
import { SearchModel } from "@web/search/search_model";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
export function childrenSize(children) {
|
||||
if (children instanceof Map) {
|
||||
return children.size
|
||||
} else if (children instanceof Array) {
|
||||
return children.length
|
||||
} else if (children instanceof Object) {
|
||||
return Object.keys(children).length > 0
|
||||
}else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
patch(SearchModel.prototype, {
|
||||
_createFilterTree(sectionId, result) {
|
||||
// debugger;
|
||||
super._createFilterTree(sectionId, result)
|
||||
let { values } = result;
|
||||
const filter = this.sections.get(sectionId)
|
||||
|
||||
const valIds = result['values'].map(item => item.id).filter(item => item)
|
||||
const groups = filter.groups
|
||||
if (groups) {
|
||||
groups.forEach((group, key) => {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const group_id = values.find(item => item.id === key)?.group_id
|
||||
if (valIds.includes(group_id)) {
|
||||
if (group_id === key) {
|
||||
group.parent = false;
|
||||
return;
|
||||
}
|
||||
if (!childrenSize(groups.get(group_id).children)) {
|
||||
groups.get(group_id).children = new Map()
|
||||
}
|
||||
groups.get(group_id).children.set(key, group)
|
||||
} else {
|
||||
if (!groups.get(false)) return;
|
||||
if (!childrenSize(groups.get(false).children)) {
|
||||
groups.get(group_id).children = new Map()
|
||||
}
|
||||
groups.get(false).children.set(key, group)
|
||||
}
|
||||
group.parent = group_id;
|
||||
})
|
||||
}
|
||||
filter.groups = groups;
|
||||
},
|
||||
|
||||
async _fetchFilters(filters) {
|
||||
// debugger;
|
||||
const evalContext = {};
|
||||
for (const category of this.categories) {
|
||||
evalContext[category.fieldName] = category.activeValueId;
|
||||
}
|
||||
const categoryDomain = this._getCategoryDomain();
|
||||
const searchDomain = this.searchDomain;
|
||||
await Promise.all(
|
||||
filters.map(async (filter) => {
|
||||
const result = await this.orm.call(
|
||||
this.resModel,
|
||||
"search_panel_select_all_multi_range",
|
||||
[filter.fieldName],
|
||||
{
|
||||
category_domain: categoryDomain,
|
||||
comodel_domain: new Domain(filter.domain).toList(evalContext),
|
||||
context: this.globalContext,
|
||||
enable_counters: filter.enableCounters,
|
||||
filter_domain: this._getFilterDomain(filter.id),
|
||||
expand: filter.expand,
|
||||
group_by: filter.groupBy || false,
|
||||
group_domain: this._getGroupDomain(filter),
|
||||
limit: filter.limit,
|
||||
search_domain: searchDomain,
|
||||
}
|
||||
);
|
||||
this._createFilterTree(filter.id, result);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
getSections(predicate) {
|
||||
const sections = super.getSections(predicate)
|
||||
// debugger
|
||||
for (const section of sections) {
|
||||
const ids = [...section.values?.keys()]
|
||||
const groups = section.groups
|
||||
if (groups) {
|
||||
groups.forEach((group, key) => {
|
||||
const group_id = section.values.values().find(item => item.id === key)?.group_id
|
||||
if (ids.includes(group_id)) {
|
||||
if (group_id === key) {
|
||||
group.parent = false;
|
||||
return;
|
||||
}
|
||||
if (!childrenSize(groups.get(group_id).children)) {
|
||||
groups.get(group_id).children = new Map()
|
||||
}
|
||||
groups.get(group_id).children.set(key, group)
|
||||
} else {
|
||||
if (!groups.get(false)) return;
|
||||
if (!childrenSize(groups.get(false).children)) {
|
||||
groups.get(group_id).children = new Map()
|
||||
}
|
||||
groups.get(false).children.set(key, group)
|
||||
}
|
||||
group.parent = group_id
|
||||
})
|
||||
}
|
||||
section.groups = groups
|
||||
}
|
||||
|
||||
return sections.sort((s1, s2) => s1.index - s2.index);
|
||||
}
|
||||
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
/** @odoo-module **/
|
||||
import {SearchPanel} from "@web/search/search_panel/search_panel";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
import { onPatched } from "@odoo/owl";
|
||||
import {childrenSize} from "./search_model";
|
||||
|
||||
patch(SearchPanel.prototype, {
|
||||
|
||||
setup(...args) {
|
||||
super.setup(...args)
|
||||
onPatched(() => {
|
||||
this.updateGroupHeadersChecked();
|
||||
});
|
||||
},
|
||||
|
||||
async toggleFilterGroup(filterId, group) {
|
||||
/**
|
||||
* 递归
|
||||
*/
|
||||
// debugger;
|
||||
const valueIds = [];
|
||||
|
||||
const recurve_values = (child) => {
|
||||
const {children, values} = child;
|
||||
const childIdList = children ? new Set([...children.keys()]): new Set();
|
||||
const valList = [...Array.from(values?.values() ?? []).filter((v) => !childIdList.has(v.id))]
|
||||
for (const child of children?.values() ?? []) {
|
||||
valList.push(...recurve_values(child))
|
||||
}
|
||||
return valList
|
||||
};
|
||||
|
||||
const checked = [...recurve_values(group)].every(
|
||||
(value) => this.state.active[filterId][value.id]
|
||||
);
|
||||
const recurve_group = (child, checked) => {
|
||||
const {id, children, values} = child;
|
||||
|
||||
values.forEach(({id}) => {
|
||||
const child_id_set = children ? new Set([...children.keys()]): new Set();
|
||||
if (child_id_set.has(id)) {
|
||||
recurve_group(children.get(id), checked)
|
||||
}
|
||||
else {
|
||||
valueIds.push(id);
|
||||
this.state.active[filterId][id] = !checked;
|
||||
}
|
||||
});
|
||||
this.state.active[filterId][id] = !checked;
|
||||
};
|
||||
|
||||
recurve_group(group, checked);
|
||||
|
||||
this.env.searchModel.toggleFilterValues(filterId, valueIds, !checked);
|
||||
},
|
||||
|
||||
updateGroupHeadersChecked() {
|
||||
// 三种状态 1.全部选中 2.部分选中 3.全部不选中
|
||||
debugger;
|
||||
const recurve_child = (child) => {
|
||||
const header = child.querySelector(":scope .o_search_panel_group_header input");
|
||||
const vals = Array.from(child.children)
|
||||
.filter(child => child.classList.contains("o_search_panel_filter_value"))
|
||||
.map((child) => child.querySelector(":scope input"));
|
||||
const children = Array.from(child.children)
|
||||
.filter(child => child.classList.contains("o_search_panel_group_child"));
|
||||
|
||||
for (const item of children) {
|
||||
const childVals = recurve_child(item)
|
||||
vals.push(...childVals)
|
||||
}
|
||||
|
||||
if (vals.every((v) => v.checked)) {
|
||||
header.checked = true;
|
||||
} else if (vals.some((v) => v.checked)) {
|
||||
header.indeterminate = true;
|
||||
} else {
|
||||
header.checked = false;
|
||||
header.indeterminate = false;
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
const groups = this.root.el.querySelectorAll(":scope .o_search_panel_filter_group");
|
||||
for (const group of groups) {
|
||||
const child = group.querySelector(":scope .o_search_panel_group_child")
|
||||
if (child) {
|
||||
recurve_child(child)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
childrenSize,
|
||||
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-inherit="web.SearchPanel.Regular" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//li[hasclass('o_search_panel_filter_group')]" position="replace">
|
||||
|
||||
<li
|
||||
t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId"
|
||||
class="o_search_panel_filter_group list-group-item p-0 border-0"
|
||||
t-att-class="groupId_last? 'mb-0' : 'mb-3'"
|
||||
>
|
||||
<t t-set="_section" t-value="section"/>
|
||||
<t t-set="group" t-value="section.groups.get(groupId)"/>
|
||||
<t t-if="!group.parent">
|
||||
<t t-call="web.SearchPanel.Group">
|
||||
<t t-set="group" t-value="group"/>
|
||||
<t t-set="valueId" t-value="groupId"/>
|
||||
<t t-set="section" t-value="_section"/>
|
||||
<t t-set="isChildList" t-value="false"/>
|
||||
</t>
|
||||
</t>
|
||||
</li>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif='section.groups']/ul" position="replace"/>
|
||||
</t>
|
||||
|
||||
<t t-name="web.SearchPanel.Group" owl="1">
|
||||
<div class="o_search_panel_group_child"
|
||||
t-att-class="{ 'ps-2' : isChildList }"
|
||||
>
|
||||
<header class="o_search_panel_group_header pb-1">
|
||||
<div class="form-check w-100">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
t-att-checked="state.active[section.id][valueId]"
|
||||
t-attf-id="{{ section.id }}_input_{{ valueId }})"
|
||||
t-on-click="() => this.toggleFilterGroup(section.id, group)"
|
||||
/>
|
||||
<label
|
||||
t-attf-for="{{ section.id }}_input_{{ valueId }})"
|
||||
class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
|
||||
t-att-class="{ o_with_counters: group.enableCounters }"
|
||||
t-att-title="group.tooltip or false"
|
||||
>
|
||||
<span class="o_search_panel_label_title text-truncate">
|
||||
<span t-if="group.hex_color" class="me-1"
|
||||
t-attf-style="color: {{ group.hex_color }};">●</span>
|
||||
<t t-esc="group.name"/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
<t t-set="children" t-value="group?.children"/>
|
||||
<t t-set="values" t-value="group?.values"/>
|
||||
<t t-if="childrenSize(children)">
|
||||
<t t-set="childIdList" t-value="[...children.keys()]"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="childIdList" t-value="[]"/>
|
||||
</t>
|
||||
<t t-set="valueIdList" t-value="[...values.keys()]"/>
|
||||
<t t-foreach="valueIdList" t-as="vid" t-key="vid">
|
||||
<t t-if="childIdList.includes(vid)">
|
||||
<t t-call="web.SearchPanel.Group">
|
||||
<t t-set="group" t-value="children.get(vid)"/>
|
||||
<t t-set="valueId" t-value="vid"/>
|
||||
<t t-set="section" t-value="section"/>
|
||||
<t t-set="isChildList" t-value="true"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="web.SearchPanel.GroupValue">
|
||||
<t t-set="value" t-value="values.get(vid)"/>
|
||||
<t t-set="isChildList" t-value="true"/>
|
||||
<t t-set="section" t-value="section"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="web.SearchPanel.GroupValue" owl="1">
|
||||
<li class="o_search_panel_filter_value list-group-item p-0 mb-1 border-0 o_cursor_pointer"
|
||||
t-att-class="{ 'ps-2' : isChildList }"
|
||||
>
|
||||
<t t-set="valueId" t-value="value.id"/>
|
||||
<div class="form-check w-100">
|
||||
<input type="checkbox"
|
||||
t-attf-id="{{ section.id }}_input_{{ valueId }}"
|
||||
t-att-checked="state.active[section.id][valueId]"
|
||||
class="form-check-input"
|
||||
t-on-click="ev => this.toggleFilterValue(section.id, valueId, ev)"
|
||||
/>
|
||||
<label class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 o_cursor_pointer"
|
||||
t-attf-for="{{ section.id }}_input_{{ valueId }}"
|
||||
t-att-title="(group and group.tooltip) or false">
|
||||
<span class="o_search_panel_label_title text-truncate" t-esc="value.display_name"/>
|
||||
<span t-if="section.enableCounters and value.__count gt 0"
|
||||
class="o_search_panel_counter text-muted mx-2 small"
|
||||
t-esc="value.__count"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="product_template_search_view" model="ir.ui.view">
|
||||
<field name="name">product.template.search.inherit</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_search_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search" position="inside">
|
||||
<searchpanel>
|
||||
<field name="categ_id" string="Categories" select='multi' enable_counters="1" groupby='parent_id'/>
|
||||
</searchpanel>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -25,4 +25,16 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="inherit_view_employee_filter_work" model="ir.ui.view">
|
||||
<field name="name">inherit.view.employee.filter</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='department_id']" position="attributes">
|
||||
<attribute name="select">multi</attribute>
|
||||
<attribute name="groupby">parent_id</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user