This commit is contained in:
李鹏宇
2025-02-24 17:24:14 +08:00
parent ce5be17fb7
commit b6a52ca546
9 changed files with 434 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from . import models
+19
View File
@@ -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
+63
View File
@@ -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>