This commit is contained in:
李鹏宇
2025-04-21 08:24:42 +08:00
parent e98cd7f0ec
commit 5d9572f152
11 changed files with 208 additions and 50 deletions
+3 -1
View File
@@ -152,7 +152,9 @@ class ReportControllerDocx(ReportController):
ids = [int(x) for x in docids.split(",")]
obj = request.env[report.model].browse(ids)
if report.print_report_name and not len(obj) > 1:
report_name = safe_eval(report.print_report_name, {"object": obj, "time": time})
report_name = safe_eval(
report.print_report_name, {"object": obj, "time": time}
)
filename = "%s.%s" % (report_name, extension)
response.headers.add(
"Content-Disposition", content_disposition(filename)
+18 -1
View File
@@ -171,6 +171,23 @@ class IrActionsReport(models.Model):
def _render_docx(self, report_ref, res_ids, data=None):
return self.render_docx(report_ref, res_ids, data)
def _get_context(self, doc):
# [XSK] TODO(2025/4/20 22:01):
# 改成递归获取关联字段,
# 但是可能会存在无限递归的字段,需要注意
# 可以添加最大递归层级,超过最大递归层级时停止递归
deep_field_type = ['many2one', 'many2many', 'one2many']
doc.ensure_one()
context = doc.sudo().read()[0]
fields_dict = doc.fields_get()
for field, field_info in fields_dict.items():
try:
if field_info['type'] in deep_field_type:
context[field] = doc[field].sudo().read()
except Exception as e:
_logger.error("Error get field " + field + " in model " + doc._name + " " + str(e))
return context
@api.model
def render_docx(self, report_ref, docids, data=None):
self = self.sudo()
@@ -195,7 +212,7 @@ class IrActionsReport(models.Model):
for doc in data["docs"]:
# add all record fields by default data to insert
context = doc.sudo().read()[0]
context = self._get_context(doc)
# add prepare python method data to insert
if self.python_function_prepare:
+4 -7
View File
@@ -15,13 +15,13 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='paperformat_id']" position="after">
<field name="template_docx"
invisible="report_type not in ['docx']" string="报表模板"/>
invisible="report_type not in ['docx']" />
<field name="out_report_type"
invisible="report_type not in ['docx']" string="输出报告类型"/>
invisible="report_type not in ['docx']" />
<field name="python_function_prepare" string="是否用Python代码"/>
<field name="python_function_name" invisible="python_function_prepare == False" string="Python代码"/>
<field name="python_function_prepare"/>
<field name="python_function_name" invisible="python_function_prepare == False" />
<field name="path_source"
invisible="report_type not in ['docx'] or out_report_type in ['docx']" />
<field name="path_convert_folder"
@@ -37,9 +37,6 @@
<p>Change rights and give all users access + writing to the directory you want the documents to be saved in: /tmp/backups</p>
</div>
</xpath>
<xpath expr="//field[@name='report_type']" position="attributes">
<attribute name="string">报表类型</attribute>
</xpath>
</field>
</record>
</data>
+1 -1
View File
@@ -9,7 +9,7 @@
'license': 'LGPL-3',
'depends': ['base', 'sale_management'],
'data': [
'views/product_views.xml',
# 'views/product_views.xml',
],
'assets': {
'web.assets_backend': [
@@ -1,7 +1,7 @@
/** @odoo-module */
import {SearchModel} from "@web/search/search_model";
import {Domain} from "@web/core/domain";
import {patch} from "@web/core/utils/patch";
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) {
@@ -10,16 +10,15 @@ export function childrenSize(children) {
return children.length
} else if (children instanceof Object) {
return Object.keys(children).length > 0
} else {
}else {
return false
}
}
patch(SearchModel.prototype, {
_createFilterTree(sectionId, result) {
debugger;
_createFilterTree(sectionId, result) {
super._createFilterTree(sectionId, result)
let {values} = result;
let { values } = result;
const filter = this.sections.get(sectionId)
const valIds = result['values'].map(item => item.id).filter(item => item)
@@ -28,28 +27,30 @@ patch(SearchModel.prototype, {
groups.forEach((group, key) => {
// 求和
// Array.from(group.values.values()).map(item => item.__count).reduce((acc, cur) => acc + cur, 0)
group.__count = (values.find(item => item.id === key)?.__count ?? 0)
if (!key) {
group.__count = values.find(item => item.id === key)?.__count ?? 0;
if (!key) return;
const currentItem = values.find(item => item.id === key); // 集中获取当前项
const group_id = currentItem?.group_id;
const parentGroup = valIds.includes(group_id)
? groups.get(group_id)
: groups.get(false);
if (!parentGroup) return;
// 统一处理子节点逻辑
if (group_id === key) {
group.parent = false;
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)
if (!parentGroup.children || !childrenSize(parentGroup.children)) {
parentGroup.children = new Map();
}
group.parent = group_id;
parentGroup.children.set(key, group);
group.parent = parentGroup === groups.get(false) ? false : group_id;
})
}
filter.groups = groups;
@@ -96,8 +97,8 @@ patch(SearchModel.prototype, {
if (groups) {
groups.forEach((group, key) => {
// Array.from(group.values.values()).map(item => item.__count).reduce((acc, cur) => acc + cur, 0)
group.__count = (Array.from(section?.values?.values() ?? []).find(item => item.id === key)?.__count ?? 0);
const group_id = Array.from(section?.values?.values() ?? []).find(item => item.id === key)?.group_id;
group.__count = (section.values.values().find(item => item.id === key)?.__count ?? 0)
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;
@@ -109,11 +110,8 @@ patch(SearchModel.prototype, {
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()
// }
if (!childrenSize(groups.get(group_id).children)) {
groups.get(group_id).children = new Map();
if (!childrenSize(groups.get(false).children)) {
groups.get(group_id).children = new Map()
}
groups.get(false).children.set(key, group)
}
@@ -130,7 +128,6 @@ patch(SearchModel.prototype, {
// debugger;
const domain = [];
const domainList = [];
function addCondition(fieldName, valueMap) {
const ids = [];
for (const [valueId, value] of valueMap) {
@@ -143,12 +140,11 @@ patch(SearchModel.prototype, {
domainList.push(it);
}
}
for (const filter of this.filters) {
if (filter.id === excludedFilterId) {
continue;
}
const {fieldName, groups, values} = filter;
const { fieldName, groups, values } = filter;
if (groups) {
for (const group of groups.values()) {
addCondition(fieldName, group.values);
@@ -163,8 +159,8 @@ patch(SearchModel.prototype, {
_getGroupDomain(filter) {
// debugger;
const {fieldName, groups, enableCounters} = filter;
const {type: fieldType} = this.searchViewFields[fieldName];
const { fieldName, groups, enableCounters } = filter;
const { type: fieldType } = this.searchViewFields[fieldName];
const groupDomain = super._getGroupDomain(filter);
@@ -96,8 +96,8 @@ class SoongWorkflowCommentWizard(models.TransientModel):
employee_user_role_id = self.env['hr.employee'].search(
['|', ('user_role_id', '=', link_users_id.id), ('user_role_ids', 'in', link_users_id.id),
'|', ('department_id', '=', record_id.department_id.parent_id.id), ('department_ids', 'in', record_id.department_id.parent_id.id)])
self.employee_trans_domain_ids = employee_user_role_ids if next_is_automatic else employee_user_role_id
self.employee_ids = employee_user_role_ids if next_is_automatic else employee_user_role_id
self.employee_trans_domain_ids = employee_user_role_ids if not next_is_automatic else employee_user_role_id
self.employee_ids = employee_user_role_ids if not next_is_automatic else employee_user_role_id
if '分发' in ret.name:
self.employee_ids = record_id.users_id.employee_id
+1
View File
@@ -1 +1,2 @@
# -*- coding: utf-8 -*-
from . import override_relaxng
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
import os
import logging
from lxml import etree
from odoo import tools
from odoo.tools import view_validation
_logger = logging.getLogger(__name__)
_relaxng_cache = view_validation._relaxng_cache
_relaxng_cache['tree'] = None
with tools.file_open(os.path.join(os.path.dirname(__file__), 'rng', 'tree_view.rng')) as frng:
try:
text = frng.read()
relaxng_doc = etree.fromstring(text.encode('utf-8'))
_relaxng_cache['tree'] = etree.RelaxNG(relaxng_doc)
except Exception as error:
_logger.exception('Failed to load RelaxNG XML schema for views validation, {error}'.format(error=error))
_relaxng_cache['search'] = None
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://relaxng.org/ns/annotation/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<!-- Handling of element overloading when inheriting from a base
template
-->
<rng:include href="common.rng"/>
<rng:define name="groupby">
<rng:element name="groupby">
<rng:attribute name="name"/>
<rng:optional><rng:attribute name="expand"/></rng:optional>
<rng:zeroOrMore>
<rng:ref name="field"/>
</rng:zeroOrMore>
<rng:zeroOrMore>
<rng:ref name="button"/>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="tree">
<rng:element name="tree">
<rng:ref name="overload"/>
<rng:optional><rng:attribute name="name"/></rng:optional>
<rng:optional><rng:attribute name="create"/></rng:optional>
<rng:optional><rng:attribute name="delete"/></rng:optional>
<rng:optional><rng:attribute name="edit"/></rng:optional>
<rng:optional><rng:attribute name="multi_edit"/></rng:optional>
<rng:optional><rng:attribute name="export_xlsx"/></rng:optional>
<rng:optional><rng:attribute name="duplicate"/></rng:optional>
<rng:optional><rng:attribute name="import"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional> <!-- deprecated, has no effect anymore -->
<rng:optional><rng:attribute name="class"/></rng:optional>
<!-- Allows to take a custom View widget for handling -->
<rng:optional><rng:attribute name="js_class"/></rng:optional>
<rng:optional><rng:attribute name="default_order"/></rng:optional>
<rng:optional><rng:attribute name="default_group_by"/></rng:optional>
<rng:optional><rng:attribute name="decoration-bf"/></rng:optional>
<rng:optional><rng:attribute name="decoration-it"/></rng:optional>
<rng:optional><rng:attribute name="decoration-danger"/></rng:optional>
<rng:optional><rng:attribute name="decoration-info"/></rng:optional>
<rng:optional><rng:attribute name="decoration-muted"/></rng:optional>
<rng:optional><rng:attribute name="decoration-primary"/></rng:optional>
<rng:optional><rng:attribute name="decoration-success"/></rng:optional>
<rng:optional><rng:attribute name="decoration-warning"/></rng:optional>
<rng:optional><rng:attribute name="banner_route"/></rng:optional>
<rng:optional><rng:attribute name="sample"/></rng:optional>
<rng:optional><rng:attribute name="action"/></rng:optional>
<rng:optional><rng:attribute name="type"/></rng:optional>
<rng:optional><rng:attribute name="open_form_view"/></rng:optional>
<rng:optional><rng:attribute name="advanced_filter"/></rng:optional>
<rng:optional>
<rng:attribute name="limit">
<rng:data type="int"/>
</rng:attribute>
</rng:optional>
<rng:optional>
<rng:attribute name="count_limit">
<rng:data type="int"/>
</rng:attribute>
</rng:optional>
<rng:optional>
<rng:attribute name="groups_limit">
<rng:data type="int"/>
</rng:attribute>
</rng:optional>
<rng:optional>
<rng:attribute name="editable">
<rng:choice>
<rng:value>top</rng:value>
<rng:value>bottom</rng:value>
</rng:choice>
</rng:attribute>
</rng:optional>
<rng:optional><rng:attribute name="expand"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:element name="header">
<rng:zeroOrMore>
<rng:ref name="button"/>
</rng:zeroOrMore>
</rng:element>
<rng:ref name="control"/>
<rng:ref name="field"/>
<rng:ref name="widget"/>
<rng:ref name="separator"/>
<rng:ref name="tree"/>
<rng:ref name="groupby"/>
<rng:ref name="button"/>
<rng:ref name="filter"/>
<rng:ref name="html"/>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:choice>
<rng:ref name="tree" />
</rng:choice>
</rng:start>
</rng:grammar>
@@ -0,0 +1,19 @@
/** @odoo-module **/
import { archParseBoolean } from "@web/views/utils";
import { patch } from "@web/core/utils/patch";
import { visitXML, parseXML } from "@web/core/utils/xml";
import { ListArchParser } from "@web/views/list/list_arch_parser";
patch(ListArchParser.prototype, {
parse(arch, models, modelName) {
let parse_return = super.parse(arch, models, modelName);
visitXML(arch, (node) => {
if (["tree", "list"].includes(node.tagName)){
parse_return.advanced_filter = archParseBoolean(arch.getAttribute("advanced_filter"), false);
}
});
return parse_return
}
})
@@ -26,6 +26,7 @@ export const ListRendererCust = {
},
_renderSearchPanel: async function () {
if (!this.props.archInfo.advanced_filter) return;
// await loadJS("https://cdn.jsdelivr.net/jquery/latest/jquery.min.js")
// await loadJS("https://cdn.jsdelivr.net/momentjs/latest/moment.min.js")
// await loadJS("https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js")