[ADD] report,base: custom report layouts
External layout for reports can be customized. 4 Layout are currently proposed, selectable from General Settings. An onboarding popup is shown to select the layout to used if none has been shown nor a company logo chosen. Joint works by Fabien Pinckaers, Antony Lesuisse and Christophe Simonis. Design by Csaba Fazekas.
This commit is contained in:
committed by
Christophe Simonis
parent
1d6fa1b2fd
commit
3d04e44880
@@ -9,16 +9,19 @@
|
||||
Report
|
||||
""",
|
||||
'depends': ['base', 'web', 'base_setup'],
|
||||
'demo': [
|
||||
'data/report_demo.xml',
|
||||
],
|
||||
'data': [
|
||||
'data/report_paperformat_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/layout_templates.xml',
|
||||
'views/report_paperformat_views.xml',
|
||||
'views/report_templates.xml',
|
||||
'views/base_config_settings_views.xml',
|
||||
'views/ir_actions_report_views.xml',
|
||||
'views/res_company_view.xml',
|
||||
'views/templates.xml',
|
||||
],
|
||||
'qweb' : [
|
||||
'qweb': [
|
||||
'static/src/xml/*.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="external_report_layout">standard</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,21 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class BaseConfigSettings(models.TransientModel):
|
||||
_inherit = 'base.config.settings'
|
||||
|
||||
paperformat_id = fields.Many2one(related="company_id.paperformat_id", string='Paper format')
|
||||
default_custom_header_footer = fields.Boolean("Custom Layout",
|
||||
help='Define your custom header & footer.', default_model='base.config.settings')
|
||||
external_report_layout = fields.Selection(related="company_id.external_report_layout")
|
||||
|
||||
def _prepare_report_view_action(self, template):
|
||||
template_id = self.env.ref(template)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'ir.ui.view',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': template_id.id,
|
||||
}
|
||||
|
||||
def edit_external_header(self):
|
||||
return self.company_id.edit_external_header()
|
||||
return self._prepare_report_view_action('report.layout_'+self.external_report_layout)
|
||||
|
||||
def edit_external_footer(self):
|
||||
return self.company_id.edit_external_footer()
|
||||
def change_report_template(self):
|
||||
self.ensure_one()
|
||||
template = self.env.ref('report.view_company_report_form', False)
|
||||
return {
|
||||
'name': _('Choose Your Report Layout'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': self.env.user.company_id.id,
|
||||
'res_model': 'res.company',
|
||||
'views': [(template.id, 'form')],
|
||||
'view_id': template.id,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def edit_internal_header(self):
|
||||
return self.company_id.edit_internal_header()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.sql_db import TestCursor
|
||||
from odoo.tools import config
|
||||
@@ -224,7 +224,7 @@ class Report(models.Model):
|
||||
footer = render_minimal(dict(subst=True, body=body, base_url=base_url))
|
||||
footerhtml.append(footer)
|
||||
|
||||
for node in root.xpath(match_klass.format('page')):
|
||||
for node in root.xpath(match_klass.format('article')):
|
||||
# Previously, we marked some reports to be saved in attachment via their ids, so we
|
||||
# must set a relation between report ids and report's content. We use the QWeb
|
||||
# branding in order to do so: searching after a node having a data-oe-model
|
||||
@@ -266,12 +266,27 @@ class Report(models.Model):
|
||||
)
|
||||
|
||||
@api.noguess
|
||||
def get_action(self, docids, report_name, data=None):
|
||||
def get_action(self, docids, report_name, data=None, config=True):
|
||||
"""Return an action of type ir.actions.report.xml.
|
||||
|
||||
:param docids: id/ids/browserecord of the records to print (if not used, pass an empty list)
|
||||
:param report_name: Name of the template to generate an action for
|
||||
"""
|
||||
if (self.env.uid == SUPERUSER_ID) and ((not self.env.user.company_id.external_report_layout) or (not self.env.user.company_id.logo)) and config:
|
||||
template = self.env.ref('report.view_company_report_form', False)
|
||||
return {
|
||||
'name': _('Choose Your Report Layout'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': {'default_report_name': report_name},
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': self.env.user.company_id.id,
|
||||
'res_model': 'res.company',
|
||||
'views': [(template.id, 'form')],
|
||||
'view_id': template.id,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
context = self.env.context
|
||||
if docids:
|
||||
if isinstance(docids, models.Model):
|
||||
|
||||
@@ -7,20 +7,29 @@ class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
paperformat_id = fields.Many2one('report.paperformat', 'Paper format')
|
||||
report_footer_default = fields.Html('Standard Report Foorter', compute='_compute_report_footer_default', readonly=True)
|
||||
external_report_layout = fields.Selection([
|
||||
('background', 'Background'),
|
||||
('boxed', 'Boxed'),
|
||||
('clean', 'Clean'),
|
||||
('standard', 'Standard'),
|
||||
], string='Report Template')
|
||||
|
||||
@api.multi
|
||||
def _compute_report_footer_default(self):
|
||||
for company in self:
|
||||
company.report_footer_default = self.env['ir.ui.view'].render_template('report.external_layout_footer_default', {
|
||||
'company': company,
|
||||
})
|
||||
def open_company_edit_report(self):
|
||||
self.ensure_one()
|
||||
return self.env['base.config.settings'].open_company()
|
||||
|
||||
def set_report_template(self):
|
||||
self.ensure_one()
|
||||
if self.env.context.get('report_template', False):
|
||||
self.external_report_layout = self.env.context['report_template']
|
||||
if self.env.context.get('default_report_name'):
|
||||
document = self.env.get(self.env.context['active_model']).browse(self.env.context['active_id'])
|
||||
return self.env['report'].get_action(document, self.env.context['default_report_name'], config=False)
|
||||
return False
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
|
||||
# set a default paperformat based on rml one.
|
||||
|
||||
for company in self.search([('paperformat_id', '=', False)]):
|
||||
paperformat_euro = self.env.ref('report.paperformat_euro', False)
|
||||
paperformat_us = self.env.ref('report.paperformat_us', False)
|
||||
@@ -36,25 +45,4 @@ class ResCompany(models.Model):
|
||||
if hasattr(sup, 'init'):
|
||||
sup.init()
|
||||
|
||||
@api.model
|
||||
def _prepare_report_view_action(self, template):
|
||||
template_id = self.env.ref(template)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'ir.ui.view',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': template_id.id,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def edit_external_header(self):
|
||||
return self._prepare_report_view_action('report.external_layout_header')
|
||||
|
||||
@api.multi
|
||||
def edit_external_footer(self):
|
||||
return self._prepare_report_view_action('report.external_layout_footer')
|
||||
|
||||
@api.multi
|
||||
def edit_internal_header(self):
|
||||
return self._prepare_report_view_action('report.internal_layout')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,68 @@
|
||||
.o_background_footer, .o_background_header, .o_report_layout_background {
|
||||
font-family: 'Lato';
|
||||
color: @gray;
|
||||
}
|
||||
.o_background_header {
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
.company_address {
|
||||
min-width: 300px;
|
||||
}
|
||||
img {
|
||||
max-height: 96px;
|
||||
max-width: 300px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
h3 {
|
||||
color: @odoo-brand-primary;
|
||||
font-weight: 700;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
.o_background_footer {
|
||||
ul {
|
||||
border-top: 1px solid @odoo-brand-primary;
|
||||
border-bottom: 1px solid @odoo-brand-primary;
|
||||
padding: 4px 0;
|
||||
margin: 0 0 4px 0;
|
||||
li {
|
||||
color: @odoo-brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_report_layout_background {
|
||||
background-image: url(/report/static/src/img/bg_background_template.jpg);
|
||||
background-size: cover;
|
||||
background-position: bottom center;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 1000px;
|
||||
thead tr th, h2 {
|
||||
color: @odoo-brand-primary;
|
||||
}
|
||||
tbody {
|
||||
color: @gray;
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgba(184, 171, 180, 0.2);
|
||||
}
|
||||
tr td {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
/*Total table*/
|
||||
.row > .col-xs-4 > table {
|
||||
tr:nth-child(odd) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
tr:first-child td {
|
||||
/*we could get rid of this rule if .border-black td would be terminated in report.less*/
|
||||
border-top: none !important;
|
||||
}
|
||||
tr:last-child td {
|
||||
color: @odoo-brand-primary;
|
||||
}
|
||||
}
|
||||
#total {
|
||||
table tr:last-child td {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
.o_boxed_footer, .o_boxed_header, .o_report_layout_boxed {
|
||||
font-family: 'Lato';
|
||||
color: @gray;
|
||||
font-size: 12px;
|
||||
}
|
||||
.o_boxed_header {
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
img {
|
||||
max-height: 100px;
|
||||
}
|
||||
h4 {
|
||||
color: @o-we-color-text-normal;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
.o_boxed_footer {
|
||||
border-top: 3px solid @gray;
|
||||
ul {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
.o_report_layout_boxed {
|
||||
// This is not a general rule since the id is not used in all the reports,
|
||||
// we should make a general structure for report body to use it everywhere
|
||||
#informations {
|
||||
border: 1px solid @gray;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
> h2 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
table {
|
||||
border: 1px solid @gray;
|
||||
thead {
|
||||
tr th {
|
||||
border-bottom: 3px solid @gray !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
color: @gray;
|
||||
tr {
|
||||
td {
|
||||
border-top: none;
|
||||
border-right: 1px solid @gray;
|
||||
}
|
||||
td:last-child {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*Total table*/
|
||||
.row > .col-xs-4 > table {
|
||||
td {
|
||||
border-right: none;
|
||||
}
|
||||
tr:last-child td, tr:first-child td {
|
||||
/*we could get rid of this rule if .border-black td would be terminated in report.less*/
|
||||
border-top: none !important;
|
||||
}
|
||||
tr:first-child td {
|
||||
background-color: transparent;
|
||||
}
|
||||
tr:last-child td {
|
||||
background-color: @gray;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
.o_clean_footer, .o_clean_header, .o_report_layout_clean {
|
||||
font-family: 'Lato';
|
||||
color: #000;
|
||||
}
|
||||
.o_clean_header {
|
||||
img {
|
||||
max-height: 90px;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
.o_clean_footer {
|
||||
margin: 0 3px;
|
||||
border-top: 3px solid @odoo-brand-primary;
|
||||
h4 {
|
||||
color: @odoo-brand-primary;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.pagenumber {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border: 3px solid @odoo-brand-primary;
|
||||
background-color: @odoo-brand-optional;
|
||||
color: white;
|
||||
padding-top: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.o_report_layout_clean {
|
||||
h1, h2, h3 {
|
||||
color: @odoo-brand-optional;
|
||||
font-weight: bolder;
|
||||
}
|
||||
strong {
|
||||
color: @odoo-brand-primary;
|
||||
}
|
||||
table {
|
||||
thead {
|
||||
color: @odoo-brand-primary;
|
||||
tr th {
|
||||
text-align: left !important;
|
||||
border-top: 3px solid @odoo-brand-primary !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
tr th:last-child {
|
||||
text-align: right !important;
|
||||
}
|
||||
tr th:first-child {
|
||||
width: 48%;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
color: #000;
|
||||
tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
tr:last-child td {
|
||||
border-bottom: 3px solid @odoo-brand-primary;
|
||||
}
|
||||
tr {
|
||||
td {
|
||||
padding: 15px 5px;
|
||||
text-align: left !important;
|
||||
}
|
||||
td:last-child {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*Total table*/
|
||||
.row > .col-xs-4 {
|
||||
width: 43%;
|
||||
margin-top: -20px;
|
||||
> table {
|
||||
tr:last-child td {
|
||||
border-top: 1px solid @gray-lighter !important;
|
||||
}
|
||||
tr:first-child td {
|
||||
/*we could get rid of this rule if .border-black td would be terminated in report.less*/
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,15 @@ body {
|
||||
.border-black td {
|
||||
border-top: 1px solid black !important;
|
||||
}
|
||||
|
||||
/* See https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1524 */
|
||||
.table-condensed {
|
||||
> thead > tr > th {
|
||||
border-bottom: 1px solid black !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
> tbody > tr {
|
||||
page-break-inside: avoid !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
.zero_min_height {
|
||||
@@ -26,81 +28,8 @@ body {
|
||||
.jumbotron, .panel, .carousel, section{
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Wkhtmltopdf doesn't support very well the media-print CSS (depends on the version) */
|
||||
.hidden-print{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Missing css rules since the website/web_editor split */
|
||||
.mt128 {
|
||||
margin-top: 128px!important;
|
||||
}
|
||||
.mt92 {
|
||||
margin-top: 92px!important;
|
||||
}
|
||||
.mt64 {
|
||||
margin-top: 64px!important;
|
||||
}
|
||||
.mt48 {
|
||||
margin-top: 48px!important;
|
||||
}
|
||||
.mt32 {
|
||||
margin-top: 32px!important;
|
||||
}
|
||||
.mt16 {
|
||||
margin-top: 16px!important;
|
||||
}
|
||||
.mt8 {
|
||||
margin-top: 8px!important;
|
||||
}
|
||||
.mt4 {
|
||||
margin-top: 4px!important;
|
||||
}
|
||||
.mt0 {
|
||||
margin-top: 0px!important;
|
||||
}
|
||||
.mb128 {
|
||||
margin-bottom: 128px!important;
|
||||
}
|
||||
.mb92 {
|
||||
margin-bottom: 92px!important;
|
||||
}
|
||||
.mb64 {
|
||||
margin-bottom: 64px!important;
|
||||
}
|
||||
.mb48 {
|
||||
margin-bottom: 48px!important;
|
||||
}
|
||||
.mb32 {
|
||||
margin-bottom: 32px!important;
|
||||
}
|
||||
.mb16 {
|
||||
margin-bottom: 16px!important;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px!important;
|
||||
}
|
||||
.mb4 {
|
||||
margin-bottom: 4px!important;
|
||||
}
|
||||
.mb0 {
|
||||
margin-bottom: 0px!important;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px!important;
|
||||
}
|
||||
.ml4 {
|
||||
margin-left: 4px!important;
|
||||
}
|
||||
.ml0 {
|
||||
margin-left: 0px!important;
|
||||
}
|
||||
.mr8 {
|
||||
margin-right: 8px!important;
|
||||
}
|
||||
.mr4 {
|
||||
margin-right: 4px!important;
|
||||
}
|
||||
.mr0 {
|
||||
margin-right: 0px!important;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,46 +24,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="default_custom_report_footer"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="Custom Footer" for="default_custom_report_footer"/>
|
||||
<label string="Report Template"/>
|
||||
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
|
||||
<div class="text-muted">
|
||||
Set a custom footer
|
||||
Choose your reports's header and footer layout.
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('default_custom_report_footer', '=', False)]}">
|
||||
<div class="content-group">
|
||||
<div class="mt16" groups="base.group_no_one">
|
||||
<label for="external_report_layout" string="Template" class="col-xs-3 col-md-3 o_light_label"/>
|
||||
<field name="external_report_layout" class="oe_inline"/>
|
||||
</div>
|
||||
<div class="mt16">
|
||||
<field name="rml_footer"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" groups="base.group_no_one">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="default_custom_header_footer"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="default_custom_header_footer"/>
|
||||
<div class="text-muted">
|
||||
Customize header & footer in HTML (advanced)
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('default_custom_header_footer', '=', False)]}">
|
||||
<div class="mt16">
|
||||
<button type="object" name="edit_external_header" string="External Header" icon="fa-arrow-right" class="btn-link"/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="object" name="edit_external_footer" string="External Footer" icon="fa-arrow-right" class="btn-link"/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="object" name="edit_internal_header" string="Internal Header" icon="fa-arrow-right" class="btn-link"/>
|
||||
</div>
|
||||
<div>
|
||||
<button name="%(action_report_internalpreview)d" string="Preview Internal Report" type="action" icon="fa-arrow-right" class="btn-link"/>
|
||||
</div>
|
||||
<div>
|
||||
<button name="%(action_report_externalpreview)d" string="Preview External Report" type="action" icon="fa-arrow-right" class="btn-link"/>
|
||||
<button name="change_report_template" string="Change Report Template" type="object" class="oe_link"/>
|
||||
<button name="edit_external_header" string="Edit Header" type="object" class="oe_link" groups="base.group_no_one"/>
|
||||
<button name="%(action_report_externalpreview)d" string="Preview Report" type="action" class="oe_link" groups="base.group_no_one"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="report assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/report/static/src/js/qwebactionmanager.js"></script>
|
||||
<script type="text/javascript" src="/report/static/src/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/report/static/src/js/client_action.js"></script>
|
||||
<link rel="stylesheet" type="text/less" href="/report/static/src/less/report_backend.less"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_company_report_form" model="ir.ui.view">
|
||||
<field name="name">res.company.report.form</field>
|
||||
<field name="model">res.company</field>
|
||||
<field name="priority">1024</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Company">
|
||||
<field name="logo" invisible="1"/>
|
||||
<div class="alert alert-warning" attrs="{'invisible': [('logo','<>',False)]}">
|
||||
<p>
|
||||
<strong>No logo found!</strong>
|
||||
To get a beautiful report, you might review your
|
||||
company data: upload a logo, set
|
||||
bank accounts, phone numbers, etc.
|
||||
</p>
|
||||
<button name="open_company_edit_report" type="object" string="⇒ Review Company Data" class="btn-link"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<div class="col-xs-3 text-center">
|
||||
<button type="object" name="set_report_template" context="{'report_template': 'background'}">
|
||||
<img src="/report/static/src/img/preview_background.png" class="img img-responsive img-thumbnail" alt="Preview Background Template"/>
|
||||
</button>
|
||||
<a href="/report/static/src/pdf/preview_background.pdf" target="_blank">preview</a>
|
||||
</div><div class="col-xs-3 text-center">
|
||||
<button type="object" name="set_report_template" context="{'report_template': 'boxed'}">
|
||||
<img src="/report/static/src/img/preview_boxed.png" class="img img-responsive img-thumbnail" alt="Preview Boxed Template"/>
|
||||
</button>
|
||||
<a href="/report/static/src/pdf/preview_boxed.pdf" target="_blank">preview</a>
|
||||
</div><div class="col-xs-3 text-center">
|
||||
<button type="object" name="set_report_template" context="{'report_template': 'clean'}">
|
||||
<img src="/report/static/src/img/preview_clean.png" class="img img-responsive img-thumbnail" alt="Preview Clean Template"/>
|
||||
</button>
|
||||
<a href="/report/static/src/pdf/preview_clean.pdf" target="_blank">preview</a>
|
||||
</div><div class="col-xs-3 text-center">
|
||||
<button type="object" name="set_report_template" context="{'report_template': 'standard'}">
|
||||
<img src="/report/static/src/img/preview_standard.png" class="img img-responsive img-thumbnail" alt="Preview Minimalist Template"/>
|
||||
</button>
|
||||
<a href="/report/static/src/pdf/preview_standard.pdf" target="_blank">preview</a>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<button string="Close" class="btn-default" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,5 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Assets for webclient -->
|
||||
|
||||
<template id="assets_backend" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/report/static/src/js/qwebactionmanager.js"></script>
|
||||
<script type="text/javascript" src="/report/static/src/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/report/static/src/js/client_action.js"></script>
|
||||
<link rel="stylesheet" type="text/less" href="/report/static/src/less/report_backend.less"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Assets for reports -->
|
||||
|
||||
<template id="report.assets_common">
|
||||
<t t-call="web.less_helpers"/>
|
||||
|
||||
@@ -12,8 +26,15 @@
|
||||
<link href="/website/static/src/less/website.wrapwrap.less" rel="stylesheet" type="text/less"/>
|
||||
<link href="/website/static/src/less/website.snippets.less" rel="stylesheet" type="text/less"/>
|
||||
|
||||
<link href="/report/static/src/less/report.less" rel="stylesheet" type="text/less"/>
|
||||
<!-- TODO: Put in the right report ..? -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Work+Sans:thin,light,regular,medium,bold,semi-bold" rel="stylesheet"/>
|
||||
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
<link href="/report/static/src/less/report.less" rel="stylesheet" type="text/less"/>
|
||||
<link href="/report/static/src/less/layout_background.less" rel="stylesheet" type="text/less"/>
|
||||
<link href="/report/static/src/less/layout_boxed.less" rel="stylesheet" type="text/less"/>
|
||||
<link href="/report/static/src/less/layout_clean.less" rel="stylesheet" type="text/less"/>
|
||||
</template>
|
||||
|
||||
<template id="report.assets_pdf">
|
||||
@@ -30,6 +51,8 @@
|
||||
<script type="text/javascript" src='/report/static/src/js/report.editor.js' />
|
||||
</template>
|
||||
|
||||
<!-- Private templates used internally by reports -->
|
||||
|
||||
<template id="layout" name="Report layout"><!DOCTYPE html>
|
||||
<html t-att-lang="lang and lang.replace('_', '-')"
|
||||
t-att-data-editable="'1' if editable else None"
|
||||
@@ -75,123 +98,6 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="external_layout">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="not o and doc">
|
||||
<t t-set="o" t-value="doc"/>
|
||||
</t>
|
||||
<t t-if="o and 'company_id' in o">
|
||||
<t t-set="company" t-value="o.company_id.sudo()"></t>
|
||||
</t>
|
||||
<t t-if="not o or not 'company_id' in o">
|
||||
<t t-set="company" t-value="res_company"></t>
|
||||
</t>
|
||||
|
||||
<t t-call="report.external_layout_header" />
|
||||
<t t-raw="0" />
|
||||
<t t-call="report.external_layout_footer" />
|
||||
</template>
|
||||
|
||||
<template id="external_layout_header">
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" style="max-height: 45px;"/>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right" style="margin-top:20px;" t-field="company.rml_header1"/>
|
||||
</div>
|
||||
<div t-if="company.logo or company.rml_header1" class="row zero_min_height">
|
||||
<div class="col-xs-12">
|
||||
<div style="border-bottom: 1px solid black;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6" name="company_address">
|
||||
<t t-if="company.street and company.zip">
|
||||
<span t-field="company.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'
|
||||
style="border-bottom: 1px solid black; display:inline-block;"/>
|
||||
</t>
|
||||
<t t-if="not (company.street and company.zip)">
|
||||
<p name="report_company_data_warning">
|
||||
<i class="fa fa-warning"/> Company address is not defined. Please, got to <i>Settings > General Settings > configure your company data </i>, to set the missing informations.
|
||||
</p>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="external_layout_footer_default">
|
||||
<ul class="list-inline">
|
||||
<t t-set="company" t-value="company.sudo()"/>
|
||||
<li t-if="company.phone">Phone: <span t-field="company.phone"/></li>
|
||||
|
||||
<li t-if="company.fax and company.phone">&bull;</li>
|
||||
<li t-if="company.fax">Fax: <span t-field="company.fax"/></li>
|
||||
|
||||
<li t-if="company.email and company.fax or company.email and company.phone">&bull;</li>
|
||||
<li t-if="company.email">Email: <span t-field="company.email"/></li>
|
||||
|
||||
<li t-if="company.website and company.email or company.website and company.fax or company.website and company.phone">&bull;</li>
|
||||
<li t-if="company.website">Website: <span t-field="company.website"/></li>
|
||||
</ul>
|
||||
|
||||
<ul class="list-inline" name="financial_infos">
|
||||
<li t-if="company.vat">TIN: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template id="external_layout_footer">
|
||||
<div class="footer">
|
||||
<div class="text-center" style="border-top: 1px solid black;">
|
||||
<t t-if="not company.custom_footer">
|
||||
<t t-call="report.external_layout_footer_default"/>
|
||||
</t>
|
||||
|
||||
<t t-if="company.custom_footer">
|
||||
<span t-raw="company.rml_footer" style="white-space: pre-wrap;"/>
|
||||
</t>
|
||||
|
||||
<ul class="list-inline">
|
||||
<li>Page:</li>
|
||||
<li><span class="page"/></li>
|
||||
<li>/</li>
|
||||
<li><span class="topage"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="internal_layout">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="o and 'company_id' in o">
|
||||
<t t-set="company" t-value="o.company_id"></t>
|
||||
</t>
|
||||
<t t-if="not o or not 'company_id' in o">
|
||||
<t t-set="company" t-value="res_company"></t>
|
||||
</t>
|
||||
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</div>
|
||||
<div class="col-xs-2 col-xs-offset-2 text-center">
|
||||
<span t-esc="company.name"/>
|
||||
</div>
|
||||
<div class="col-xs-2 col-xs-offset-3 text-right">
|
||||
<ul class="list-inline">
|
||||
<li><span class="page"/></li>
|
||||
<li>/</li>
|
||||
<li><span class="topage"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t t-raw="0" />
|
||||
</template>
|
||||
|
||||
<template id="minimal_layout">
|
||||
<t t-raw="'<base href=%s>' % base_url"/>
|
||||
<!DOCTYPE html>
|
||||
@@ -227,4 +133,233 @@
|
||||
</html>
|
||||
</template>
|
||||
|
||||
<!-- External layouts styles -->
|
||||
|
||||
<template id="external_layout_background">
|
||||
<div class="header o_background_header">
|
||||
<div class="pull-right">
|
||||
<h3 class="mt0 text-right" t-field="company.rml_header1"/>
|
||||
</div>
|
||||
<img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" class="pull-left"/>
|
||||
<div class="pull-left company_address">
|
||||
<div>
|
||||
<strong t-field="company.partner_id.name"/>
|
||||
</div>
|
||||
<span t-field="company.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address"], "no_marker": true}'/>
|
||||
</div>
|
||||
<div class="clearfix mb8"/>
|
||||
</div>
|
||||
|
||||
<div class="article o_report_layout_background">
|
||||
<t t-raw="0" />
|
||||
</div>
|
||||
|
||||
<div class="footer o_background_footer">
|
||||
<div class="text-center">
|
||||
<ul class="list-inline">
|
||||
<li t-if="company.phone"><i class="fa fa-phone"></i> <span t-field="company.phone"/></li>
|
||||
<li t-if="company.fax"><i class="fa fa-fax"></i> <span t-field="company.fax"/></li>
|
||||
<li t-if="company.email"><i class="fa fa-at"></i> <span t-field="company.email"/></li>
|
||||
<li t-if="company.website"><i class="fa fa-globe"></i> <span t-field="company.website"/></li>
|
||||
<li t-if="company.vat"><i class="fa fa-building-o"></i> VAT: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
<div t-field="company.rml_footer"/>
|
||||
<div class="text-muted">
|
||||
Page:
|
||||
<span class="page"/>
|
||||
of
|
||||
<span class="topage"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="external_layout_boxed">
|
||||
<div class="header o_boxed_header">
|
||||
<div class="row mb8">
|
||||
<div class="col-xs-6">
|
||||
<img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo"/>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right mb4">
|
||||
<h4 class="mt0" t-field="company.rml_header1"/>
|
||||
<div name="company_address" class="mb4">
|
||||
<span class="company_address" t-field="company.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article o_report_layout_boxed">
|
||||
<t t-raw="0" />
|
||||
</div>
|
||||
|
||||
<div class="footer o_boxed_footer">
|
||||
<div class="text-center">
|
||||
<ul class="list-inline">
|
||||
<li t-if="company.phone">Tel: <span t-field="company.phone"/></li>
|
||||
<li t-if="company.fax">Fax: <span t-field="company.fax"/></li>
|
||||
<li t-if="company.email">Mail: <span t-field="company.email"/></li>
|
||||
<li t-if="company.website">Web: <span t-field="company.website"/></li>
|
||||
<li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
<div t-field="company.rml_footer"/>
|
||||
<div>
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="external_layout_clean">
|
||||
<div class="header o_clean_header">
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo"/>
|
||||
</div>
|
||||
<div class="col-xs-5 col-xs-offset-1" name="company_address">
|
||||
<ul class="list-unstyled">
|
||||
<strong><li t-if="company.name"><span t-field="company.name"/></li></strong>
|
||||
<li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
|
||||
<li t-if="company.phone">Tel: <span t-field="company.phone"/></li>
|
||||
<li t-if="company.fax">Fax: <span t-field="company.fax"/></li>
|
||||
<li t-if="company.email"><span t-field="company.email"/></li>
|
||||
<li t-if="company.website"><span t-field="company.website"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article o_report_layout_clean">
|
||||
<t t-raw="0" />
|
||||
</div>
|
||||
|
||||
<div class="footer o_clean_footer">
|
||||
<div class="row mt8">
|
||||
<div class="col-xs-3">
|
||||
<span t-field="company.rml_footer"/>
|
||||
</div>
|
||||
<div class="col-xs-4 text-right">
|
||||
<span class="company_address" t-field="company.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address"], "no_marker": true}'/>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<h4 class="mt0 mb0 text-uppercase" t-field="company.rml_header1"/>
|
||||
</div>
|
||||
<div class="col-xs-1">
|
||||
<ul class="list-inline pagenumber pull-right text-center">
|
||||
<li><strong><span class="page"/></strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="external_layout_standard">
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-xs-3 mb4">
|
||||
<img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" style="max-height: 45px;"/>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right" style="margin-top:22px;" t-field="company.rml_header1" name="moto"/>
|
||||
</div>
|
||||
<div t-if="company.logo or company.rml_header1" class="row zero_min_height">
|
||||
<div class="col-xs-12">
|
||||
<div style="border-bottom: 1px solid black;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6" name="company_address">
|
||||
<span t-field="company.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'
|
||||
style="border-bottom: 1px solid black; display:inline-block;"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article o_report_layout_standard">
|
||||
<t t-raw="0" />
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="text-center" style="border-top: 1px solid black;">
|
||||
<ul class="list-inline mb4">
|
||||
<li t-if="company.phone">Phone: <span t-field="company.phone"/></li>
|
||||
<li t-if="company.fax">Fax: <span t-field="company.fax"/></li>
|
||||
<li t-if="company.email">Email: <span t-field="company.email"/></li>
|
||||
<li t-if="company.website">Web: <span t-field="company.website"/></li>
|
||||
<li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
|
||||
<div name="financial_infos">
|
||||
<span t-field="company.rml_footer"/>
|
||||
</div>
|
||||
|
||||
<div class="text-muted">
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Public api: layout to t-call from reports -->
|
||||
|
||||
<template id="external_layout">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="not o and doc">
|
||||
<t t-set="o" t-value="doc"/>
|
||||
</t>
|
||||
<t t-if="o and 'company_id' in o">
|
||||
<t t-set="company" t-value="o.company_id.sudo()"></t>
|
||||
</t>
|
||||
<t t-if="not o or not 'company_id' in o">
|
||||
<t t-set="company" t-value="res_company"></t>
|
||||
</t>
|
||||
|
||||
<t t-if="company.external_report_layout == 'background'">
|
||||
<t t-call="report.external_layout_background"><t t-raw="0"/></t>
|
||||
</t>
|
||||
<t t-if="company.external_report_layout == 'boxed'">
|
||||
<t t-call="report.external_layout_boxed"><t t-raw="0"/></t>
|
||||
</t>
|
||||
<t t-if="company.external_report_layout == 'clean'">
|
||||
<t t-call="report.external_layout_clean"><t t-raw="0"/></t>
|
||||
</t>
|
||||
<t t-if="company.external_report_layout in (False, 'standard')">
|
||||
<t t-call="report.external_layout_standard"><t t-raw="0"/></t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="internal_layout">
|
||||
<!-- Multicompany -->
|
||||
<t t-if="o and 'company_id' in o">
|
||||
<t t-set="company" t-value="o.company_id"></t>
|
||||
</t>
|
||||
<t t-if="not o or not 'company_id' in o">
|
||||
<t t-set="company" t-value="res_company"></t>
|
||||
</t>
|
||||
|
||||
<div class="header">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</div>
|
||||
<div class="col-xs-2 col-xs-offset-2 text-center">
|
||||
<span t-esc="company.name"/>
|
||||
</div>
|
||||
<div class="col-xs-2 col-xs-offset-3 text-right">
|
||||
<ul class="list-inline">
|
||||
<li><span class="page"/></li>
|
||||
<li>/</li>
|
||||
<li><span class="topage"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article">
|
||||
<t t-raw="0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -26,6 +26,7 @@
|
||||
<field name="phone">+1 555 123 8069</field>
|
||||
<field name="email">info@yourcompany.example.com</field>
|
||||
<field name="website">www.example.com</field>
|
||||
<field name="image" type="base64" file="base/res/res_company_logo.png"/>
|
||||
</record>
|
||||
|
||||
<record id="main_company" model="res.company">
|
||||
|
||||
@@ -122,18 +122,14 @@ class Company(models.Model):
|
||||
child_ids = fields.One2many('res.company', 'parent_id', string='Child Companies')
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', required=True)
|
||||
rml_header = fields.Text(required=True, default=_get_header)
|
||||
rml_header1 = fields.Char(string='Company Tagline', help="Appears by default on the top right corner of your printed documents (report header).")
|
||||
rml_header1 = fields.Text(string='Company Tagline', help="Appears by default on the top right corner of your printed documents (report header).")
|
||||
rml_header2 = fields.Text(string='RML Internal Header', required=True, default=_header2)
|
||||
rml_header3 = fields.Text(string='RML Internal Header for Landscape Reports', required=True, default=_header3)
|
||||
rml_footer = fields.Text(string='Custom Report Footer', translate=True, help="Footer text displayed at the bottom of all reports.")
|
||||
custom_footer = fields.Selection([
|
||||
(0, 'Use standard footer'),
|
||||
(1, 'Use custom footer')
|
||||
], "Report Footer", default=0, help="""Set to 'custom' this to define the report footer manually. Otherwise it will be filled in automatically.""")
|
||||
rml_footer = fields.Text(string='Report Footer', translate=True, help="Footer text displayed at the bottom of all reports.")
|
||||
font = fields.Many2one('res.font', string="Font", default=lambda self: self._get_font(),
|
||||
domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
|
||||
help="Set the font into the report header, it will be used as default font in the RML reports of the user company")
|
||||
logo = fields.Binary(related='partner_id.image', default=_get_logo)
|
||||
logo = fields.Binary(related='partner_id.image', default=_get_logo, string="Company Logo")
|
||||
# logo_web: do not store in attachments, since the image is retrieved in SQL for
|
||||
# performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
|
||||
logo_web = fields.Binary(compute='_compute_logo_web', store=True)
|
||||
@@ -208,21 +204,6 @@ class Company(models.Model):
|
||||
for company in self:
|
||||
company.logo_web = tools.image_resize_image(company.partner_id.image, (180, None))
|
||||
|
||||
@api.onchange('custom_footer', 'phone', 'fax', 'email', 'website', 'vat', 'company_registry')
|
||||
def onchange_footer(self):
|
||||
if not self.custom_footer:
|
||||
# first line (notice that missing elements are filtered out before the join)
|
||||
res = ' | '.join(filter(bool, [
|
||||
self.phone and '%s: %s' % (_('Phone'), self.phone),
|
||||
self.fax and '%s: %s' % (_('Fax'), self.fax),
|
||||
self.email and '%s: %s' % (_('Email'), self.email),
|
||||
self.website and '%s: %s' % (_('Website'), self.website),
|
||||
self.vat and '%s: %s' % (_('TIN'), self.vat),
|
||||
self.company_registry and '%s: %s' % (_('Reg'), self.company_registry),
|
||||
]))
|
||||
self.rml_footer_readonly = res
|
||||
self.rml_footer = res
|
||||
|
||||
@api.onchange('state_id')
|
||||
def _onchange_state(self):
|
||||
self.country_id = self.state_id.country_id
|
||||
|
||||
@@ -13,12 +13,8 @@
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" class="oe_inline"/>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<label for="rml_header1" class="oe_edit_only"/>
|
||||
<h3>
|
||||
<field name="rml_header1" placeholder="e.g. Global Business Solutions"/>
|
||||
</h3>
|
||||
</div>
|
||||
<notebook colspan="4">
|
||||
<page string="General Information">
|
||||
@@ -35,6 +31,8 @@
|
||||
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True}'/>
|
||||
</div>
|
||||
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
|
||||
<field name="rml_footer" placeholder="e.g. Your Bank Accounts, one per line"/>
|
||||
<field name="rml_header1" placeholder="e.g. Global Business Solutions"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="phone"/>
|
||||
|
||||
Reference in New Issue
Block a user