From b6a52ca546caf653d2edc2f43b9fbc25f6a396bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Mon, 24 Feb 2025 17:24:14 +0800 Subject: [PATCH] 1 --- search_panel_extension/__init__.py | 1 + search_panel_extension/__manifest__.py | 19 +++ search_panel_extension/models/__init__.py | 1 + search_panel_extension/models/models.py | 63 +++++++++ .../static/src/js/search_model.js | 121 ++++++++++++++++++ .../static/src/js/search_panel.js | 96 ++++++++++++++ .../static/src/xml/search_panel.xml | 106 +++++++++++++++ .../views/product_views.xml | 15 +++ .../views/soong_hr_employee_views.xml | 12 ++ 9 files changed, 434 insertions(+) create mode 100644 search_panel_extension/__init__.py create mode 100644 search_panel_extension/__manifest__.py create mode 100644 search_panel_extension/models/__init__.py create mode 100644 search_panel_extension/models/models.py create mode 100644 search_panel_extension/static/src/js/search_model.js create mode 100644 search_panel_extension/static/src/js/search_panel.js create mode 100644 search_panel_extension/static/src/xml/search_panel.xml create mode 100644 search_panel_extension/views/product_views.xml diff --git a/search_panel_extension/__init__.py b/search_panel_extension/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/search_panel_extension/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/search_panel_extension/__manifest__.py b/search_panel_extension/__manifest__.py new file mode 100644 index 000000000..8b5b188c7 --- /dev/null +++ b/search_panel_extension/__manifest__.py @@ -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", +} \ No newline at end of file diff --git a/search_panel_extension/models/__init__.py b/search_panel_extension/models/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/search_panel_extension/models/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/search_panel_extension/models/models.py b/search_panel_extension/models/models.py new file mode 100644 index 000000000..febf035f2 --- /dev/null +++ b/search_panel_extension/models/models.py @@ -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, } diff --git a/search_panel_extension/static/src/js/search_model.js b/search_panel_extension/static/src/js/search_model.js new file mode 100644 index 000000000..c71c4b68d --- /dev/null +++ b/search_panel_extension/static/src/js/search_model.js @@ -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); + } + +}) diff --git a/search_panel_extension/static/src/js/search_panel.js b/search_panel_extension/static/src/js/search_panel.js new file mode 100644 index 000000000..8d0f5fbd6 --- /dev/null +++ b/search_panel_extension/static/src/js/search_panel.js @@ -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, + +}) \ No newline at end of file diff --git a/search_panel_extension/static/src/xml/search_panel.xml b/search_panel_extension/static/src/xml/search_panel.xml new file mode 100644 index 000000000..65332a470 --- /dev/null +++ b/search_panel_extension/static/src/xml/search_panel.xml @@ -0,0 +1,106 @@ + + + + + + +
  • + + + + + + + + + + +
  • +
    + +
    + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
  • + +
    + + +
    +
  • +
    + +
    \ No newline at end of file diff --git a/search_panel_extension/views/product_views.xml b/search_panel_extension/views/product_views.xml new file mode 100644 index 000000000..34204c876 --- /dev/null +++ b/search_panel_extension/views/product_views.xml @@ -0,0 +1,15 @@ + + + + product.template.search.inherit + product.template + + + + + + + + + + \ No newline at end of file diff --git a/soong_workflow/views/soong_hr_employee_views.xml b/soong_workflow/views/soong_hr_employee_views.xml index 28b73e92a..798fa55a0 100644 --- a/soong_workflow/views/soong_hr_employee_views.xml +++ b/soong_workflow/views/soong_hr_employee_views.xml @@ -25,4 +25,16 @@ + + + inherit.view.employee.filter + hr.employee + + + + multi + parent_id + + + \ No newline at end of file