1
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 +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
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user