Update repository files:

This commit is contained in:
Владислав Костров 2024-03-29 11:55:50 +05:00
parent 297453dfb0
commit 11996a2709
48 changed files with 1339 additions and 1644 deletions

View File

@ -1,48 +0,0 @@
# DOCX report
Functionality
Allows you to add docx files to the report model as a source template.
You can get reports based on such a template in docx or pdf format.
Currently, the simultaneous creation of multiple reports is not supported.
To convert docx -> pdf, an available gutenberg service is required on localhost:8808.
An example of launching a service in docker-compose next to Odoo:
```yaml
gotenberg:
image: thecodingmachine/gotenberg:6
restart: unless-stopped
environment:
LOG_LEVEL: INFO
DEFAULT_LISTEN_PORT: 8808
DISABLE_GOOGLE_CHROME: 1
DEFAULT_WAIT_TIMEOUT: 30
MAXIMUM_WAIT_TIMEOUT: 60
```
Creating a report
The report creates in the same way as the standard Odoo procedure:
1. In Settings -> Technical -> Reports, you need to create a new record. In the record of the report
choose one of the new types: "DOCX" or "DOCX(PDF)".
You do not need to fill in the "Template name" field, but instead download the docx file of the report.
All other fields are filled in the same as in standard Odoo reports.
2. If custom fields are applied in the report template, then you need to create them on the tab
"Custom fields".
3. In the entry of the specified model, an additional item with the name of the created report will appear in the print menu.
Clicking on it will display a wizard in which you can check the values of custom fields before generating the report file.
4. When generating a report from the portal, the file is generated without displaying the wizard.
Templates creating
1. Templates can be created in any text editor that supports the docx format.
2. All formatting of the template is saved in the generated report.
3. Double curly braces are used to insert variables.
4. Access to the Odoo record for which the report generation is called is performed through the "docs" variable,
accessing attributes and methods as in Odoo: {{docs.attribute_name }}
5. It is possible to call the methods available for the entry in "docs", or passed to the context of the report.
6. By default, the report context contains methods of the "report_monetary_helper" module, which can be called directly by name.
7. Custom fields may also be present in the context of the report.
Such fields must be created in the report record.
In the template, custom fields are available by the name specified in the "tech_name" field of the custom field entry.

View File

@ -1,2 +0,0 @@
from . import controllers
from . import models

View File

@ -1,30 +0,0 @@
{
"name": "DOCX report",
"summary": """Printing reports in docx format from docx templates.""",
"description": """
Adds generation reports from .docx templates like standard Odoo reports
with qweb templates. Standard Odoo reports also available.
For generating .pdf from .docx external service the "Gotenberg" is used,
and it required module for integration with this service: "gotenberg".
If integration module "gotenberg" is absent, or service itself unreachable
there will be only reports in docx format.
This is the beta version, bugs may be present.
""",
"author": "RYDLAB",
"website": "https://rydlab.ru",
"category": "Technical",
"version": "16.0.1.0.0",
"license": "LGPL-3",
"depends": ["base", "web", "custom_report_field", "report_monetary_helpers"],
"external_dependencies": {"python": ["docxcompose", "docxtpl", "bs4"]},
"data": [
"views/ir_actions_report_views.xml",
],
"assets": {
"web.assets_backend": [
"docx_report/static/src/css/mimetypes.css",
"docx_report/static/src/js/action_manager_report.js",
],
},
}

View File

@ -1 +0,0 @@
from . import main

View File

@ -1,123 +0,0 @@
from json import dumps as json_dumps, loads as json_loads
from werkzeug.urls import url_decode
from odoo.http import (
content_disposition,
request,
route,
serialize_exception as _serialize_exception,
)
from odoo.tools import html_escape
from odoo.tools.safe_eval import safe_eval, time
from odoo.addons.web.controllers.report import ReportController
class DocxReportController(ReportController):
@route()
def report_routes(self, reportname, docids=None, converter=None, **data):
"""
Запускает генерацию файла отчета и возвращает его
"""
report = request.env["ir.actions.report"]._get_report_from_name(reportname)
context = dict(request.env.context)
_data = dict()
_docids = [int(i) for i in docids.split(",")] if docids else []
if data.get("options"):
_data.update(json_loads(data.pop("options")))
if data.get("context"):
# Ignore 'lang' here, because the context in data is the one from the webclient *but* if
# the user explicitely wants to change the lang, this mechanism overwrites it.
_data["context"] = json_loads(data["context"])
if _data["context"].get("lang") and not _data.get("force_context_lang"):
del _data["context"]["lang"]
context.update(_data["context"])
if converter == "docx":
docx = report.with_context(context)._render_docx_docx(_docids, data=_data)
docxhttpheaders = [
(
"Content-Type",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
),
]
return request.make_response(docx, headers=docxhttpheaders)
elif converter == "pdf" and "docx" in report.report_type:
pdf = report.with_context(context)._render_docx_pdf(_docids, data=_data)
pdfhttpheaders = [
(
"Content-Type",
"application/pdf",
),
("Content-Length", len(pdf[0])),
]
return request.make_response(pdf, headers=pdfhttpheaders)
else:
return super(DocxReportController, self).report_routes(
reportname, docids, converter, **data
)
@route()
def report_download(self, data, token, context=None):
"""
Обрабатывает запрос на скачивание файла отчета
"""
requestcontent = json_loads(data)
url, type = requestcontent[0], requestcontent[1]
try:
if type in ["docx-docx", "docx-pdf"]:
converter = "docx" if type == "docx-docx" else "pdf"
extension = "docx" if type == "docx-docx" else "pdf"
pattern = "/report/%s/" % ("docx" if type == "docx-docx" else "pdf")
reportname = url.split(pattern)[1].split("?")[0]
docids = None
if "/" in reportname:
reportname, docids = reportname.split("/")
if docids:
# Generic report:
response = self.report_routes(
reportname, docids=docids, converter=converter, context=context
)
else:
# Particular report:
data = dict(
url_decode(url.split("?")[1]).items()
) # decoding the args represented in JSON
if "context" in data:
context, data_context = json_loads(context or "{}"), json_loads(
data.pop("context")
)
context = json_dumps({**context, **data_context})
response = self.report_routes(
reportname, converter=converter, context=context, **data
)
report = request.env["ir.actions.report"]._get_report_from_name(
reportname
)
filename = "%s.%s" % (report.name, extension)
if docids:
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}
)
filename = "%s.%s" % (report_name, extension)
response.headers.add(
"Content-Disposition", content_disposition(filename)
)
# token is dummy in 15 version
# response.set_cookie("fileToken", token)
return response
else:
return super(DocxReportController, self).report_download(
data, context=context
)
except Exception as e:
se = _serialize_exception(e)
error = {"code": 200, "message": "Odoo Server Error", "data": se}
return request.make_response(html_escape(json_dumps(error)))

View File

@ -0,0 +1,18 @@
# Custom report values
Adds custom computed fields for reports.
Adds new tab with custom fields in report form, where custom fields can be
created. Here is possible to write python code block for computing
field's values, and this fields with computed values vill be accessible in report
template.
Also adds wizard where custom fields values can be validated before report creation.
If it is required to change some value, it shoud be changed in record where it stored.
Fields in Custom Field creation form:
"name": Name for showing in interface.
"tech_name": by this literal field's value can be accessed in templates.
"required": If value can't be empty, in fact it must be equivalent to True.
"visible": Will this field be showed in validation wizard or not.
"Description": Text field storing info for users.
"Computing field's value" tab: Place for input python code computing field's value.
Help about code writing can be found in neighbour "Help" tab.

View File

@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@ -0,0 +1,29 @@
{
"name": "Custom report field",
"summary": """Creates custom computed fields for reports""",
"description": """
Adds custom computed fields for reports.
Adds new tab with custom fields in report form, where custom fields can be
created. Here is possible write some python code for computing field's value,
and this field with computed value will be accessible in report template.
Also adds wizard where custom fields values can be validated before report
creation.
""",
"author": "RYDLAB",
"website": "https://rydlab.ru",
"category": "Technical",
"version": "16.0.1.0.2",
"license": "LGPL-3",
"depends": ["base", "web", "report_monetary_helpers"],
"data": [
"security/ir.model.access.csv",
"views/ir_actions_report_views.xml",
"wizard/custom_report_field_values_wizard_views.xml",
],
"assets": {
"web.assets_backend": [
"custom_report_field/static/src/js/action_manager_report.js",
],
},
}

View File

@ -0,0 +1,297 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * custom_report_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:05+0000\n"
"PO-Revision-Date: 2022-12-29 08:05+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>UserError</code>, <code>Warning</code>: Warning Exceptions to use with"
" <code>raise</code>"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "<code>env</code>: Odoo Environment on which the action is triggered"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>format_number</code>: Method for formatting number representation in "
"report."
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>log(message, level='info')</code>: logging function to record debug "
"information in <code>ir.logging</code> table"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>model</code>: Odoo Model of the record on which the action is "
"triggered; is a void recordset"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>record</code>: record on which the action is triggered; may be be void"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>records</code>: recordset of all records on which the action is "
"triggered in multi mode; may be void"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>time</code>, <code>datetime</code>, <code>dateutil</code>, "
"<code>timezone</code>: useful Python libraries"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__default_value
msgid "Code"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Computing field's value"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__create_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__create_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__create_uid
msgid "Created by"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__create_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__create_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__create_date
msgid "Created on"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Custom fields"
msgstr ""
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_custom_report_field
msgid "Custom report field"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__field_values_ids
msgid "Custom report field values"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_ir_actions_report__custom_report_field_ids
msgid "Custom report fields"
msgstr ""
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_custom_report_field_values_wizard
#: model:ir.model,name:custom_report_field.model_custom_report_field_values_wizard_line
msgid "Custom report values field verify wizard"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__description
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__description
msgid "Description"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__description
msgid ""
"Description for this field to be showed in fields list in verify wizard."
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__display_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__display_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__display_name
msgid "Display Name"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"Example of Python code:<br/>\n"
" <code style=\"white-space: pre-wrap;\">\n"
" partner_rec = env['res.partner'].search([('name', '=' record.name)])<br/>\n"
" value = partner_rec.parent_id.vat if partner_rec else \"\"<br/>\n"
" </code>"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.custom_report_values_wizard_view_form
msgid "Get report"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Help"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Help with Python code"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__id
msgid "ID"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__required
msgid ""
"If checked, it will not be possible to generate a document without a default"
" value."
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"It's signature: <code>format_number(number, r_acc, dec_sep, div_by_3)</code><br/>\n"
" Param <code>number</code> - <code>int</code> or <code>float</code> to represent,<br/>\n"
" Param <code>r_acc</code> - <code>int</code> is round accuracy, default value is <code>2</code><br/>\n"
" Param <code>dec_sep</code> - <code>str</code> is decimal separator, default value is <code>,</code><br/>\n"
" Param <code>div_by_3</code> - <code>bool</code> is flag for add whitespase separators after each 3 digits id integer part. Default value is <code>True</code><br/>\n"
" Returns number as <code>str</code>.<br/>"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field____last_update
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard____last_update
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line____last_update
msgid "Last Modified on"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__write_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__write_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__write_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__write_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__name
msgid "Name"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__technical_name
msgid "Name for using in templates"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__ir_actions_report_id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__ir_actions_report_id
msgid "Report"
msgstr ""
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_ir_actions_report
msgid "Report Action"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.custom_report_values_wizard_view_form
msgid "Report values"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__required
msgid "Required"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__sequence
msgid "Sequence"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__visible
msgid "Show in wizard"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__technical_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__technical_name
msgid "Technical Name"
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "The following variables can be used:"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__default_value
msgid ""
"This field contains a Python code which can use the same variables as the "
"'code' field in the server action, except moment that the computation result"
" should be assigned into the 'value' variable.Also available method "
"'format_number' for formatting numbers representation in report. Returns "
"number as string."
msgstr ""
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "To return a value, assign: <code>value = ...</code>"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__visible
msgid "To show this field in fields list in verification wizard."
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_ir_actions_report__validate_custom_report_field
msgid "Validate Custom Report Field"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__value
msgid "Value"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__wizard_id
msgid "Wizard"
msgstr ""

View File

@ -0,0 +1,331 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * custom_report_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:06+0000\n"
"PO-Revision-Date: 2022-12-29 08:06+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>UserError</code>, <code>Warning</code>: Warning Exceptions to use with"
" <code>raise</code>"
msgstr ""
"<code>UserError</code>, <code>Warning</code>: Исключение для использования с"
" <code>raise</code>"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "<code>env</code>: Odoo Environment on which the action is triggered"
msgstr "<code>env</code>: Окружение Odoo, на котором вызывается action."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>format_number</code>: Method for formatting number representation in "
"report."
msgstr ""
"<code>format_number</code>: Метод для форматирования числовых представлений в "
"отчете."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>log(message, level='info')</code>: logging function to record debug "
"information in <code>ir.logging</code> table"
msgstr ""
"<code>log(message, level='info')</code>: Метод логирования для записи "
"отладочной информации<code>ir.logging</code> table"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>model</code>: Odoo Model of the record on which the action is "
"triggered; is a void recordset"
msgstr ""
"<code>model</code>: Модель записи Odoo, на которой вызван action; Является "
"пустым множеством записей."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>record</code>: record on which the action is triggered; may be be void"
msgstr ""
"<code>record</code>: Запись, на которой вызван action; Может быть пустой."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>records</code>: recordset of all records on which the action is "
"triggered in multi mode; may be void"
msgstr ""
"<code>records</code>: Множество записей, на которых вызван action в режиме "
"нескольких записей; Может быть пустым."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"<code>time</code>, <code>datetime</code>, <code>dateutil</code>, "
"<code>timezone</code>: useful Python libraries"
msgstr ""
"<code>time</code>, <code>datetime</code>, <code>dateutil</code>, "
"<code>timezone</code>: полезные библиотеки Python"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__default_value
msgid "Code"
msgstr "Код"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Computing field's value"
msgstr "Вычисление значения поля"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__create_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__create_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__create_uid
msgid "Created by"
msgstr "Создал"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__create_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__create_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__create_date
msgid "Created on"
msgstr "Создан"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Custom fields"
msgstr "Пользовательские поля"
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_custom_report_field
msgid "Custom report field"
msgstr "Пользовательское поле отчета"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__field_values_ids
msgid "Custom report field values"
msgstr "Значения пользовательских полей отчета"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_ir_actions_report__custom_report_field_ids
msgid "Custom report fields"
msgstr "Пользовательские поля отчета"
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_custom_report_field_values_wizard
#: model:ir.model,name:custom_report_field.model_custom_report_field_values_wizard_line
msgid "Custom report values field verify wizard"
msgstr "Мастер проверки значений пользовательских полей отчета"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__description
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__description
msgid "Description"
msgstr "Описание"
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__description
msgid ""
"Description for this field to be showed in fields list in verify wizard."
msgstr ""
"Описание для поля, которое будет отображаться в списке полей мастера "
"проверки."
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__display_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__display_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__display_name
msgid "Display Name"
msgstr "Отображаемое имя"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"Example of Python code:<br/>\n"
" <code style=\"white-space: pre-wrap;\">\n"
" partner_rec = env['res.partner'].search([('name', '=' record.name)])<br/>\n"
" value = partner_rec.parent_id.vat if partner_rec else \"\"<br/>\n"
" </code>"
msgstr ""
"Пример кода Python:<br/>\n"
" <code style=\"white-space: pre-wrap;\">\n"
" partner_rec = env['res.partner'].search([('name', '=' record.name)])<br/>\n"
" value = partner_rec.parent_id.vat if partner_rec else \"\"<br/> \n"
" </code>"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.custom_report_values_wizard_view_form
msgid "Get report"
msgstr "Получить отчет"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Help"
msgstr "Помощь"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "Help with Python code"
msgstr "Помощь с кодом Python"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__id
msgid "ID"
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__required
msgid ""
"If checked, it will not be possible to generate a document without a default"
" value."
msgstr ""
"Если отмечено, то невозможно будет сгенерировать документ без значения для "
"этого поля."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid ""
"It's signature: <code>format_number(number, r_acc, dec_sep, div_by_3)</code><br/>\n"
" Param <code>number</code> - <code>int</code> or <code>float</code> to represent,<br/>\n"
" Param <code>r_acc</code> - <code>int</code> is round accuracy, default value is <code>2</code><br/>\n"
" Param <code>dec_sep</code> - <code>str</code> is decimal separator, default value is <code>,</code><br/>\n"
" Param <code>div_by_3</code> - <code>bool</code> is flag for add whitespase separators after each 3 digits id integer part. Default value is <code>True</code><br/>\n"
" Returns number as <code>str</code>.<br/>"
msgstr ""
"Сигнатура: <code>format_number(number, r_acc, dec_sep, div_by_3)</code><br/>\n"
" Param <code>number</code> - <code>int</code> или <code>float</code> число для отображения,<br/>\n"
" Param <code>r_acc</code> - <code>int</code> точность округления, по умолчанию <code>2</code> знака,<br/>\n"
" Param <code>dec_sep</code> - <code>str</code> разделитель для десятичной части, по умолчанию <code>,</code>,<br/>\n"
" Param <code>div_by_3</code> - <code>bool</code> флаг для добавления пробелов между каждыми тремя цифрами числа в целой части. По умолчанию <code>True</code>,<br/>\n"
" Возвращает число как <code>str</code>.<br/>"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field____last_update
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard____last_update
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line____last_update
msgid "Last Modified on"
msgstr "Последнее изменение"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__write_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__write_uid
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__write_uid
msgid "Last Updated by"
msgstr "Последний раз обновил"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__write_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__write_date
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__write_date
msgid "Last Updated on"
msgstr "Последнее обновление"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__name
msgid "Name"
msgstr "Имя"
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__technical_name
msgid "Name for using in templates"
msgstr "Имя для использования в шаблонах"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__ir_actions_report_id
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard__ir_actions_report_id
msgid "Report"
msgstr "Отчет"
#. module: custom_report_field
#: model:ir.model,name:custom_report_field.model_ir_actions_report
msgid "Report Action"
msgstr "Отчет о действияx "
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.custom_report_values_wizard_view_form
msgid "Report values"
msgstr "Значения отчета"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__required
msgid "Required"
msgstr "Обязательно"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__sequence
msgid "Sequence"
msgstr "Последовательность"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__visible
msgid "Show in wizard"
msgstr "Видимость"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field__technical_name
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__technical_name
msgid "Technical Name"
msgstr "Техническое имя"
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "The following variables can be used:"
msgstr "Могут быть использованы следующие переменные:"
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__default_value
msgid ""
"This field contains a Python code which can use the same variables as the "
"'code' field in the server action, except moment that the computation result"
" should be assigned into the 'value' variable.Also available method "
"'format_number' for formatting numbers representation in report. Returns "
"number as string."
msgstr ""
"Это поле - выражение на Python, которое может использовать те же переменные,"
" что и поле 'code' в серверных действиях, кроме момента, что результат "
"вычисления должен присваиваться в переменную 'value'. Так же, доступен метод"
" 'format_number' для форматирования представлений чисел в отчете. Возвращает"
" число как строку."
#. module: custom_report_field
#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
msgid "To return a value, assign: <code>value = ...</code>"
msgstr ""
"Для возвращения значения выполните присвоение: <code>value = ...</code>"
#. module: custom_report_field
#: model:ir.model.fields,help:custom_report_field.field_custom_report_field__visible
msgid "To show this field in fields list in verification wizard."
msgstr ""
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_ir_actions_report__validate_custom_report_field
msgid "Validate Custom Report Field"
msgstr "Проверка пользовательского поля отчета"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__value
msgid "Value"
msgstr "Значение"
#. module: custom_report_field
#: model:ir.model.fields,field_description:custom_report_field.field_custom_report_field_values_wizard_line__wizard_id
msgid "Wizard"
msgstr "Мастер"

View File

@ -0,0 +1,2 @@
from . import custom_report_field
from . import ir_actions_report

View File

@ -0,0 +1,106 @@
# import logging
from odoo import api, exceptions, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval, test_python_expr
from odoo.addons.report_monetary_helpers.utils.format_number import format_number
# _logger = logging.getLogger(__name__)
class CustomReportField(models.Model):
_name = "custom.report.field"
_description = "Custom report field"
_order = "sequence"
ir_actions_report_id = fields.Many2one(
"ir.actions.report",
string="Report",
)
sequence = fields.Integer(
string="Sequence",
)
name = fields.Char(
string="Name",
required=True,
translate=True,
)
technical_name = fields.Char(
string="Technical Name",
help="Name for using in templates",
required=True,
)
default_value = fields.Text(
string="Code",
help="This field contains a Python code which can use the same variables as"
" the 'code' field in the server action, except moment that the computation"
" result should be assigned into the 'value' variable."
"Also available method 'format_number' for formatting numbers representation in report. Returns number as string.",
required=True,
default="# Write a code for computing value for this field\n# and assign it into 'value' variable.",
)
description = fields.Char(
string="Description",
help="Description for this field to be showed in fields list in verify wizard.",
translate=True,
default="",
)
required = fields.Boolean(
string="Required",
help="If checked, it will not be possible to generate a document without a default value.",
default=False,
)
visible = fields.Boolean(
string="Show in wizard",
help="To show this field in fields list in verification wizard.",
default=True,
)
@api.constrains("default_value")
def _check_default_value(self):
for rec in self.filtered("default_value"):
msg = test_python_expr(expr=rec.default_value.strip(), mode="exec")
if msg:
raise ValidationError(msg)
def _get_eval_context(self, action=None):
eval_context = self.env["ir.actions.actions"]._get_eval_context(action=action)
eval_context.update(self.env.context)
model_name = action.model_id.sudo().model
model = self.env[model_name]
record = None
records = None
if self._context.get("active_model") == model_name and self._context.get(
"active_id"
):
record = model.browse(self._context["active_id"])
if self._context.get("active_model") == model_name and self._context.get(
"active_ids"
):
records = model.browse(self._context["active_ids"])
eval_context.update(
{
# orm
"env": self.env,
"model": model,
# Exceptions
"Warning": exceptions.Warning,
"UserError": exceptions.UserError,
# record
"record": record,
"records": records,
# utils
"format_number": format_number,
}
)
return eval_context
def compute_value(self, report_rec):
_eval_context = self._get_eval_context(report_rec)
safe_eval(self.default_value.strip(), _eval_context, mode="exec", nocopy=True)
return (
_eval_context["value"]
if _eval_context.get("value", "key absent") != "key absent"
else ""
)

View File

@ -0,0 +1,59 @@
from odoo import api, fields, models
from odoo.exceptions import UserError
class IrActionsReport(models.Model):
_inherit = "ir.actions.report"
custom_report_field_ids = fields.One2many(
comodel_name="custom.report.field",
inverse_name="ir_actions_report_id",
string="Custom report fields",
)
validate_custom_report_field = fields.Boolean(
compute="_compute_validate_custom_report_field"
)
@api.depends("custom_report_field_ids")
def _compute_validate_custom_report_field(self):
self.validate_custom_report_field = bool(
self.custom_report_field_ids.filtered("visible")
)
def _get_readable_fields(self):
return super()._get_readable_fields() | {
"validate_custom_report_field",
}
def get_custom_report_field_values(self):
"""
Returns: dict with computed custom fields values.
"""
# Bunch reports creation not supported for a while.
self.ensure_one()
report_custom_field_ids = self.env["custom.report.field"].search(
[("ir_actions_report_id", "=", self.id)]
)
custom_report_field_values = {}
for field_rec in report_custom_field_ids:
field_value = field_rec.compute_value(self)
if field_rec.required and not field_value:
raise UserError(
"""Required custom field %s of the report is not filled. It's value: %s.
Fill it or remove "required" attribute if this value is ok."""
% (field_rec.name, field_value)
)
custom_report_field_values[field_rec.technical_name] = field_value
return custom_report_field_values
@api.model
def _get_rendering_context(self, report, docids, data):
"""
Redefined here to add custom report fields in context.
:param docids: source documents IDs list
:param data: dict containing current context
:return: dict, updated context.
"""
data = super()._get_rendering_context(report, docids, data)
data.update(report.get_custom_report_field_values())
return data

View File

@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_custom_report_field","custom_report_field user","model_custom_report_field","base.group_user",1,1,1,1
"access_custom_report_field_values_wizard","custom_report_field_values_wizard user","model_custom_report_field_values_wizard",,1,1,1,1
"access_custom_report_field_values_wizard_line","custom_report_field_values_wizard_line user","model_custom_report_field_values_wizard_line",,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_custom_report_field custom_report_field user model_custom_report_field base.group_user 1 1 1 1
3 access_custom_report_field_values_wizard custom_report_field_values_wizard user model_custom_report_field_values_wizard 1 1 1 1
4 access_custom_report_field_values_wizard_line custom_report_field_values_wizard_line user model_custom_report_field_values_wizard_line 1 1 1 1

View File

@ -0,0 +1,40 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
/**
* Adds new handler for showing wizard with custom fields values for report.
*
* @returns {Promise<*>}
*/
async function customReportFieldWizardHandler(action, options, env) {
if (action.type === "ir.actions.report"
&& action.validate_custom_report_field
&& !action.context.report_values_validated) {
await env.services.action.doAction({
type: "ir.actions.act_window",
view_mode: "form",
views: [[false, "form"]],
res_model: "custom.report.field.values.wizard",
target: "new",
context: Object.assign(
{ "default_ir_actions_report_id": action.id },
action.context
),
});
return Promise.resolve(true);
}
else if (action.type === "ir.actions.report"
&& action.validate_custom_report_field
&& action.context.report_values_validated) {
return env.services.action.doAction({"type": "ir.actions.act_window_close"});
}
return Promise.resolve(false);
}
registry.category("ir.actions.report handlers").add(
"custom_report_field_wizard_handler",
customReportFieldWizardHandler,
{sequence: 10},
);

View File

@ -0,0 +1,78 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="act_report_xml_inherit_view_form" model="ir.ui.view">
<field name="name">ir.actions.report.inherit.view.form</field>
<field name="model">ir.actions.report</field>
<field name="inherit_id" ref="base.act_report_xml_view"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='advanced']" position="after">
<page name="custom_report_field" string="Custom fields">
<field name="custom_report_field_ids">
<tree>
<field name="ir_actions_report_id" invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="technical_name"/>
<field name="required" widget="boolean_toggle"/>
<field name="visible" widget="boolean_toggle"/>
</tree>
<form>
<group>
<group>
<field name="name"/>
<field name="technical_name"/>
<field name="required" widget="boolean_toggle"/>
</group>
<group>
<field name="visible" widget="boolean_toggle"/>
<field name="description" widget="text"/>
</group>
</group>
<notebook>
<page string="Computing field's value">
<field name="default_value" widget="ace" options="{'mode': 'python'}"/>
</page>
<page string="Help">
<div style="margin-top: 4px;">
<h3>Help with Python code</h3>
<p>The following variables can be used:</p>
<ul>
<li><code>env</code>: Odoo Environment on which the action is triggered</li>
<li><code>model</code>: Odoo Model of the record on which the action is triggered; is a void recordset</li>
<li><code>record</code>: record on which the action is triggered; may be be void</li>
<li><code>records</code>: recordset of all records on which the action is triggered in multi mode; may be void</li>
<li><code>time</code>, <code>datetime</code>, <code>dateutil</code>, <code>timezone</code>: useful Python libraries</li>
<li><code>log(message, level='info')</code>: logging function to record debug information in <code>ir.logging</code> table</li>
<li><code>UserError</code>, <code>Warning</code>: Warning Exceptions to use with <code>raise</code></li>
<li><code>format_number</code>: Method for formatting number representation in report.
<div style="padding-left: 10px;">
It's signature: <code>format_number(number, r_acc, dec_sep, div_by_3)</code><br/>
Param <code>number</code> - <code>int</code> or <code>float</code> to represent,<br/>
Param <code>r_acc</code> - <code>int</code> is round accuracy, default value is <code>2</code><br/>
Param <code>dec_sep</code> - <code>str</code> is decimal separator, default value is <code>,</code><br/>
Param <code>div_by_3</code> - <code>bool</code> is flag for add whitespase separators after each 3 digits id integer part. Default value is <code>True</code><br/>
Returns number as <code>str</code>.<br/>
</div>
</li>
<li>To return a value, assign: <code>value = ...</code></li>
</ul>
<div>
Example of Python code:<br/>
<code style="white-space: pre-wrap;">
partner_rec = env['res.partner'].search([('name', '=' record.name)])<br/>
value = partner_rec.parent_id.vat if partner_rec else ""<br/>
</code>
</div>
</div>
</page>
</notebook>
</form>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1 @@
from . import custom_report_field_values_wizard

View File

@ -0,0 +1,95 @@
from odoo import api, fields, models
from odoo.addons.web.controllers.utils import clean_action
from odoo.exceptions import ValidationError
class CustomReportFieldValuesWizard(models.TransientModel):
_name = "custom.report.field.values.wizard"
_description = "Custom report values field verify wizard"
ir_actions_report_id = fields.Many2one(
"ir.actions.report",
string="Report",
)
field_values_ids = fields.One2many(
comodel_name="custom.report.field.values.wizard.line",
inverse_name="wizard_id",
string="Custom report field values",
)
def _check_records_amount(self):
"""
Checks source records for report generation. If more than one - raise error.
"""
active_model = self._context.get("active_model")
if active_model:
record_id = self.env[active_model].browse(self._context.get("active_id"))
record_ids = self.env[active_model].browse(self._context.get("active_ids"))
records_for_report = record_id | record_ids
if len(records_for_report) > 1:
raise ValidationError(
"Reports with custom fields do not supports bunch report generation.\nPrint them one at a time."
)
@api.onchange("ir_actions_report_id")
def _onchange_ir_actions_report_id(self):
self._check_records_amount()
custom_values_for_report = self.env["custom.report.field"].search(
[
("ir_actions_report_id", "=", self.ir_actions_report_id.id),
("visible", "=", True),
]
)
self.field_values_ids = [
(
6,
False,
self.env["custom.report.field.values.wizard.line"]
.create(
[
{
"name": field_rec.name,
"value": field_rec.compute_value(self.ir_actions_report_id),
"technical_name": field_rec.technical_name,
"description": field_rec.description,
}
for field_rec in custom_values_for_report
]
)
.ids,
)
]
def get_report(self):
self.ensure_one()
cleaned_report = False
action_id = self.ir_actions_report_id.id
ctx = dict(self._context)
ctx.update({"report_values_validated": True})
report = self.env["ir.actions.report"].sudo().browse([action_id]).read()
if report:
cleaned_report = clean_action(report[0], env=self.env)
cleaned_report["context"] = ctx
return cleaned_report
class CustomReportFieldValuesWizardLine(models.TransientModel):
_name = "custom.report.field.values.wizard.line"
_description = "Custom report values field verify wizard"
wizard_id = fields.Many2one(
"custom.report.field.values.wizard",
)
name = fields.Char(
string="Name",
)
value = fields.Char(
string="Value",
)
technical_name = fields.Char(
string="Technical Name",
)
description = fields.Char(
string="Description",
)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="custom_report_values_wizard_view_form" model="ir.ui.view">
<field name="name">Verify report values</field>
<field name="model">custom.report.field.values.wizard</field>
<field name="arch" type="xml">
<form create="0" delete="0">
<group string="Report values">
<field name="ir_actions_report_id" invisible="1"/>
<field name="field_values_ids" nolabel="1" colspan="4" readonly="1" view_mode="tree">
<tree >
<field name="name"/>
<field name="value"/>
<field name="description"/>
<field name="technical_name" invisible="1"/>
</tree>
</field>
</group>
<footer>
<button name="get_report" type="object" string="Get report"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -1,70 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * docx_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:24+0000\n"
"PO-Revision-Date: 2022-12-29 08:24+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: docx_report
#: model:ir.model.fields.selection,name:docx_report.selection__ir_actions_report__report_type__docx-docx
msgid "DOCX"
msgstr ""
#. module: docx_report
#: model:ir.model.fields.selection,name:docx_report.selection__ir_actions_report__report_type__docx-pdf
msgid "DOCX(PDF)"
msgstr ""
#. module: docx_report
#. odoo-python
#: code:addons/docx_report/models/ir_actions_report.py:0
#, python-format
msgid ""
"Gotenberg converting service not available. The PDF can not be created."
msgstr ""
#. module: docx_report
#. odoo-python
#: code:addons/docx_report/models/ir_actions_report.py:0
#, python-format
msgid "One of the documents you try to merge caused failure."
msgstr ""
#. module: docx_report
#: model:ir.model,name:docx_report.model_ir_actions_report
msgid "Report Action"
msgstr ""
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_type
msgid "Report Type"
msgstr ""
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_docx_template
msgid "Report docx template"
msgstr ""
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_name
msgid "Template Name"
msgstr ""
#. module: docx_report
#: model:ir.model.fields,help:docx_report.field_ir_actions_report__report_type
msgid ""
"The type of the report that will be rendered, each one having its own "
"rendering method. HTML means the report will be opened directly in your "
"browser PDF means the report will be rendered using Wkhtmltopdf and "
"downloaded by the user."
msgstr ""

View File

@ -1,75 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * docx_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:24+0000\n"
"PO-Revision-Date: 2022-12-29 08:24+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: docx_report
#: model:ir.model.fields.selection,name:docx_report.selection__ir_actions_report__report_type__docx-docx
msgid "DOCX"
msgstr "DOCX"
#. module: docx_report
#: model:ir.model.fields.selection,name:docx_report.selection__ir_actions_report__report_type__docx-pdf
msgid "DOCX(PDF)"
msgstr "DOCX(PDF)"
#. module: docx_report
#. odoo-python
#: code:addons/docx_report/models/ir_actions_report.py:0
#, python-format
msgid ""
"Gotenberg converting service not available. The PDF can not be created."
msgstr ""
"Файл PDF не может быть создан, так как сервис конвертации Gotenberg не доступен."
#. module: docx_report
#. odoo-python
#: code:addons/docx_report/models/ir_actions_report.py:0
#, python-format
msgid "One of the documents you try to merge caused failure."
msgstr "Один из документов, которые вы пытаетесь соединить, вызывает ошибку."
#. module: docx_report
#: model:ir.model,name:docx_report.model_ir_actions_report
msgid "Report Action"
msgstr "Действие для отчета."
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_type
msgid "Report Type"
msgstr "Тип отчета"
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_docx_template
msgid "Report docx template"
msgstr "Шаблон для отчета docx"
#. module: docx_report
#: model:ir.model.fields,field_description:docx_report.field_ir_actions_report__report_name
msgid "Template Name"
msgstr "Имя шаблона"
#. module: docx_report
#: model:ir.model.fields,help:docx_report.field_ir_actions_report__report_type
msgid ""
"The type of the report that will be rendered, each one having its own "
"rendering method. HTML means the report will be opened directly in your "
"browser PDF means the report will be rendered using Wkhtmltopdf and "
"downloaded by the user."
msgstr ""
"Тип генерируемого отчета. Каждый тип имеет свой собственный"
" метод генерации. HTML означает, что отчет будет открыт непосредственно в"
" вашем браузере, PDF означает, что отчет будет сгенерирован с помощью "
"Wkhtmltopdf и загружен пользователем."

View File

@ -1,466 +0,0 @@
from base64 import b64decode
from bs4 import BeautifulSoup
from collections import OrderedDict
from io import BytesIO
from logging import getLogger
from docx import Document
from docxcompose.composer import Composer
from docxtpl import DocxTemplate
from requests import codes as codes_request, post as post_request
from requests.exceptions import RequestException
from odoo import _, api, fields, models
from odoo.exceptions import AccessError, UserError
from odoo.http import request
from odoo.tools.safe_eval import safe_eval, time
try:
from odoo.addons.gotenberg.service.utils import (
get_auth, # noqa
convert_pdf_from_office_url, # noqa
check_gotenberg_installed, # noqa
)
gotenberg_imported = True
except ImportError:
gotenberg_imported = False
_logger = getLogger(__name__)
class IrActionsReport(models.Model):
_inherit = "ir.actions.report"
report_name = fields.Char(
compute="_compute_report_name",
inverse="_inverse_report_name",
store=True,
required=False,
)
report_type = fields.Selection(
selection_add=[("docx-docx", "DOCX"), ("docx-pdf", "DOCX(PDF)")],
ondelete={"docx-docx": "cascade", "docx-pdf": "cascade"},
)
report_docx_template = fields.Binary(
string="Report docx template",
)
@api.depends("report_type", "model")
def _compute_report_name(self):
for record in self:
if (
record.report_type in ["docx-docx", "docx-pdf"]
and record.model
and record.id
):
record.report_name = "%s-docx_report+%s" % (record.model, record.id)
else:
record.report_name = False
def _inverse_report_name(self):
"""TODO: write this method"""
pass
def retrieve_attachment(self, record):
"""
Searc for existing report file in record's attachments by fields:
1. name
2. res_model
3. res_id
"""
result = super().retrieve_attachment(record)
if result:
if self.report_type == "docx-docx":
result = (
result.filtered(
lambda r: r.mimetype
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
or None
)
elif self.report_type == "docx-pdf":
result = (
result.filtered(lambda r: r.mimetype == "application/pdf") or None
)
return result
@api.model
def _render_docx_pdf(self, res_ids=None, data=None):
"""
Prepares the data for report file rendering, calls for the render method
and handle rendering result.
"""
if not data:
data = {}
data.setdefault("report_type", "pdf")
# access the report details with sudo() but evaluation context as current user
self_sudo = self.sudo()
save_in_attachment = OrderedDict()
# Maps the streams in `save_in_attachment` back to the records they came from
# stream_record = dict()
if res_ids:
Model = self.env[self_sudo.model]
record_ids = Model.browse(res_ids)
docx_record_ids = Model
if self_sudo.attachment:
for record_id in record_ids:
attachment = self_sudo.retrieve_attachment(record_id)
if attachment and self_sudo.attachment_use:
# stream = self_sudo._retrieve_stream_from_attachment(attachment)
stream = BytesIO(attachment.raw)
save_in_attachment[record_id.id] = stream
# stream_record[stream] = record_id
if not self_sudo.attachment_use or not attachment:
docx_record_ids += record_id
else:
docx_record_ids = record_ids
res_ids = docx_record_ids.ids
if save_in_attachment: # and not res_ids:
_logger.info("The PDF report has been generated from attachment.")
# self._raise_on_unreadable_pdfs(save_in_attachment.values(), stream_record)
return self_sudo._post_pdf(save_in_attachment), "pdf"
docx_content = self._render_docx(res_ids, data=data)
pdf_content = (
self._get_pdf_from_office(docx_content)
if gotenberg_imported and check_gotenberg_installed()
else None
)
if not pdf_content:
raise UserError(
_(
"Gotenberg converting service not available. The PDF can not be created."
)
)
if res_ids:
# self._raise_on_unreadable_pdfs(save_in_attachment.values(), stream_record)
# saving pdf in attachment.
return (
self_sudo._post_pdf(
save_in_attachment, pdf_content=pdf_content, res_ids=res_ids
),
"pdf",
)
return pdf_content, "pdf"
@api.model
def _render_docx_docx(self, res_ids=None, data=None):
"""
Prepares the data for report file rendering, calls for the render method
and handle rendering result.
"""
if not data:
data = {}
data.setdefault("report_type", "docx")
# access the report details with sudo() but evaluation context as current user
self_sudo = self.sudo()
save_in_attachment = OrderedDict()
# Maps the streams in `save_in_attachment` back to the records they came from
stream_record = dict()
if res_ids:
Model = self.env[self_sudo.model]
record_ids = Model.browse(res_ids)
docx_record_ids = Model
if self_sudo.attachment:
for record_id in record_ids:
attachment = self_sudo.retrieve_attachment(record_id)
if attachment:
# stream = self_sudo._retrieve_stream_from_attachment(attachment)
stream = BytesIO(attachment.raw)
save_in_attachment[record_id.id] = stream
stream_record[stream] = record_id
if not self_sudo.attachment_use or not attachment:
docx_record_ids += record_id
else:
docx_record_ids = record_ids
res_ids = docx_record_ids.ids
if save_in_attachment and not res_ids:
_logger.info("The DOCS report has been generated from attachment.")
return self_sudo._post_docx(save_in_attachment), "docx"
docx_content = self._render_docx(res_ids, data=data)
if res_ids:
_logger.info(
"The DOCS report has been generated for model: %s, records %s."
% (self_sudo.model, str(res_ids))
)
return (
self_sudo._post_docx(
save_in_attachment, docx_content=docx_content, res_ids=res_ids
),
"docx",
)
return docx_content, "docx"
def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
"""
Adds pdf file in record's attachments.
TODO: For now bunch generation is not supported.
2 execution ways:
- save_in_attachment and not res_ids - when get reports from attachments
- res_ids and not save_in_attachment - when generate report.
"""
self_sudo = self.sudo()
attachment_vals_list = []
if save_in_attachment:
# here get streams from save_in_attachment, make pdf file and return it
# bunch generation here is already realized.
reports_data = list(save_in_attachment.values())
if len(reports_data) == 1:
# If only one report, no need to merge files. Returns as is.
return reports_data[0].getvalue()
else:
return self._merge_pdfs(reports_data)
for res_id in res_ids:
record = self.env[self_sudo.model].browse(res_id)
attachment_name = safe_eval(
self_sudo.attachment, {"object": record, "time": time}
)
# Unable to compute a name for the attachment.
if not attachment_name:
continue
attachment_vals_list.append(
{
"name": attachment_name,
"raw": pdf_content, # stream_data['stream'].getvalue(),
"res_model": self_sudo.model,
"res_id": record.id,
"type": "binary",
}
)
if attachment_vals_list:
attachment_names = ", ".join(x["name"] for x in attachment_vals_list)
try:
self.env["ir.attachment"].create(attachment_vals_list)
except AccessError:
_logger.info(
"Cannot save PDF report %r attachments for user %r",
attachment_names,
self.env.user.display_name,
)
else:
_logger.info(
"The PDF documents %r are now saved in the database",
attachment_names,
)
return pdf_content
def _post_docx(self, save_in_attachment, docx_content=None, res_ids=None):
"""
Adds generated file in attachments.
"""
def close_streams(streams):
for stream in streams:
try:
stream.close()
except Exception:
pass
if len(save_in_attachment) == 1 and not docx_content:
return list(save_in_attachment.values())[0].getvalue()
streams = []
if docx_content:
# Build a record_map mapping id -> record
record_map = {
r.id: r
for r in self.env[self.model].browse(
[res_id for res_id in res_ids if res_id]
)
}
# If no value in attachment or no record specified, only append the whole docx.
if not record_map or not self.attachment:
streams.append(docx_content)
else:
if len(res_ids) == 1:
# Only one record, so postprocess directly and append the whole docx.
if (
res_ids[0] in record_map
and not res_ids[0] in save_in_attachment
):
new_stream = self._postprocess_docx_report(
record_map[res_ids[0]], docx_content
)
# If the buffer has been modified, mark the old buffer to be closed as well.
if new_stream and new_stream != docx_content:
close_streams([docx_content])
docx_content = new_stream
streams.append(docx_content)
else:
streams.append(docx_content)
if self.attachment_use:
for stream in save_in_attachment.values():
streams.append(stream)
if len(streams) == 1:
result = streams[0].getvalue()
else:
try:
result = self._merge_docx(streams)
except Exception as e:
_logger.exception(e)
raise UserError(
_("One of the documents you try to merge caused failure.")
)
close_streams(streams)
return result
def _postprocess_docx_report(self, record, buffer):
"""
Creates the record in the "ir.attachment" model.
"""
attachment_name = safe_eval(self.attachment, {"object": record, "time": time})
if not attachment_name:
return None
attachment_vals = {
"name": attachment_name,
"raw": buffer.getvalue(),
"res_model": self.model,
"res_id": record.id,
"type": "binary",
}
try:
self.env["ir.attachment"].create(attachment_vals)
except AccessError:
_logger.info(
"Cannot save DOCX report %r as attachment", attachment_vals["name"]
)
else:
_logger.info(
"The DOCX document %s is now saved in the database",
attachment_vals["name"],
)
return buffer
@staticmethod
def _merge_docx(streams):
"""
Joins several docx files into one.
"""
if streams:
writer = Document(streams[0])
composer = Composer(writer)
for stream in streams[1:]:
reader = Document(stream)
composer.append(reader)
return composer.getvalue()
else:
return streams
def _render_docx(self, docids: list, data: dict = None):
"""
Receive the data for rendering and calls for it.
docids: list of record's ids for which report is generated.
data: dict, conains "context", "report_type".
"""
if not data:
data = {}
data.setdefault("report_type", "docx")
data = self._get_rendering_context(
self, docids, data
) # self contains current record of ir.actions.report model.
return self._render_docx_template(self.report_docx_template, values=data)
def _render_docx_template(self, template: bytes, values: dict = None):
"""
docx file rendering itself.
"""
if values is None:
values = {}
context = dict(self.env.context, inherit_branding=False)
# Browse the user instead of using the sudo self.env.user
user = self.env["res.users"].browse(self.env.uid)
website = None
if request and hasattr(request, "website"):
if request.website is not None:
website = request.website
context = dict(
context,
translatable=context.get("lang")
!= request.env["ir.http"]._get_default_lang().code,
)
values.update(
record=values["docs"],
time=time,
context_timestamp=lambda t: fields.Datetime.context_timestamp(
self.with_context(tz=user.tz), t
),
user=user,
res_company=user.company_id,
website=website,
web_base_url=self.env["ir.config_parameter"]
.sudo()
.get_param("web.base.url", default=""),
)
record_to_render = values["docs"]
docs = {
key: record_to_render[key]
for key in record_to_render._fields.keys()
if not isinstance(record_to_render[key], fields.Markup)
}
docs.update(
{
key: self._parse_markup(record_to_render[key])
for key in record_to_render._fields.keys()
if isinstance(record_to_render[key], fields.Markup)
}
)
values["docs"] = docs
docx_content = BytesIO()
with BytesIO(b64decode(template)) as template_file:
doc = DocxTemplate(template_file)
doc.render(values)
doc.save(docx_content)
docx_content.seek(0)
return docx_content
@staticmethod
def _parse_markup(markup_data: fields.Markup):
"""
Extracts data from field of Html type and returns them in text format,
without html tags.
"""
soup = BeautifulSoup(markup_data.__str__())
data_arr = list(soup.strings)
return "\n".join(data_arr)
@staticmethod
def _get_pdf_from_office(content_stream):
"""
Converting docx into pdf with Gotenberg service.
"""
result = None
url = convert_pdf_from_office_url()
auth = get_auth()
try:
response = post_request(
url,
files={"file": ("converted_file.docx", content_stream.read())},
auth=auth,
)
if response.status_code == codes_request.ok:
result = response.content
else:
_logger.warning(
"Gotenberg response: %s - %s"
% (response.status_code, response.content)
)
except RequestException as e:
_logger.exception(e)
finally:
return result

View File

@ -0,0 +1,31 @@
# Report monetary helpers
Adds in report's rendering context 2 methods for printing amount in words and
1 method for formatting numbers representation/
They are accessible from template like this:
number2words(amount_variable, lang="en", to="cardinal")
currency2words(amount_variable, lang="en", to="cardinal", currency="RUB")
format_number(amount_variable, r_acc=2, dec_sep=",", div_by_3=False)
"amount_variable" should be of "int", "float" or validate "string" type.
Variants for "to" attribute:
'cardinal', 'ordinal', 'ordinal_num', 'year', 'currency'.
"cardinal" is default value.
"lang" attribute. 25 languages are supported:
'ar', 'en', 'en_IN', 'fr', 'fr_CH', 'fr_DZ', 'de', 'es', 'es_CO', 'es_VE',
'id', 'lt', 'lv', 'pl', 'ru', 'sl', 'no', 'dk', 'pt_BR', 'he', 'it',
'vi_VN', 'tr', 'nl', 'uk'.
"ru" is default value.
"currency" attribute: for russian language there are "RUB" and "EUR" currencies.
"RUB" is default value.
Full info about currencies features see in "num2words" python module.
"r_acc" attribute: Round accuracy for amount_variable, type int. Default is 2.
"dec_sep" attribute: Decimal separator symbol to set, type str. Default is ",".
"div_by_3" attribute: Bool flag to divide number's integer part by 3 digits with whitespaces.
Default is True.

View File

@ -0,0 +1,2 @@
from . import models
from . import utils

View File

@ -0,0 +1,35 @@
{
"name": "Report monetary helpers",
"summary": """""",
"description": """
Adds in report's rendering context 2 methods for printing amount in words.
They are accessible from template like this:
number2words(amount_variable, lang="en", to="cardinal")
currency2words(amount_variable, lang="en", to="cardinal", currency="RUB")
"amount_variable" should be of "int", "float" or validate "string" type.
Variants for "to" attribute:
'cardinal', 'ordinal', 'ordinal_num', 'year', 'currency'.
"cardinal" is default value.
"lang" attribute. 25 languages are supported:
'ar', 'en', 'en_IN', 'fr', 'fr_CH', 'fr_DZ', 'de', 'es', 'es_CO', 'es_VE',
'id', 'lt', 'lv', 'pl', 'ru', 'sl', 'no', 'dk', 'pt_BR', 'he', 'it',
'vi_VN', 'tr', 'nl', 'uk'.
"ru" is default value.
"currency" attribute: for russian language there are "RUB" and "EUR" currencies.
"RUB" is default value.
Full info about currencies features see in "num2words" python module.
""",
"author": "RYDLAB",
"website": "http://rydlab.ru",
"category": "Technical",
"version": "16.0.1.0.0",
"license": "LGPL-3",
"depends": ["base"],
"external_dependencies": {"python": ["num2words"]},
"data": [],
}

View File

@ -0,0 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * report_monetary_helpers
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:17+0000\n"
"PO-Revision-Date: 2022-12-29 08:17+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: report_monetary_helpers
#: model:ir.model,name:report_monetary_helpers.model_ir_actions_report
msgid "Report Action"
msgstr ""

View File

@ -0,0 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * report_monetary_helpers
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:18+0000\n"
"PO-Revision-Date: 2022-12-29 08:18+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: report_monetary_helpers
#: model:ir.model,name:report_monetary_helpers.model_ir_actions_report
msgid "Report Action"
msgstr "Отчет о действияx "

View File

View File

@ -0,0 +1,24 @@
from logging import getLogger
from odoo import api, models
from ..utils.num2words import num2words_, num2words_currency
from ..utils.format_number import format_number
_logger = getLogger(__name__)
class IrActionsReport(models.Model):
_inherit = "ir.actions.report"
@api.model
def _get_rendering_context(self, report, docids, data):
data = super()._get_rendering_context(report, docids, data)
data.update(
{
"number2words": num2words_,
"currency2words": num2words_currency,
"format_number": format_number,
}
)
return data

View File

@ -0,0 +1,2 @@
from . import format_number
from . import num2words

View File

@ -0,0 +1,63 @@
from math import modf
from odoo.exceptions import ValidationError
def _validate_number_arg(number: int or float or str) -> int or float:
"""
Raise ValidationError if number have wrong type or string is not a valid number.
Returns int or float.
"""
if type(number) == str:
check_comma = number.split(",")
check_dot = number.split(".")
if len(check_comma) in (1, 2) and all([part.isdigit() for part in check_comma]):
return float(".".join(check_comma))
elif len(check_dot) in (1, 2) and all([part.isdigit() for part in check_dot]):
return float(number)
else:
raise ValidationError(
f"'format_number' method got an argument of string type which is not valid number: {number}"
)
if type(number) in (int, float):
return number
raise ValidationError(
f"'format_number' method got an argument of wrong type '{type(number)}'"
)
def format_number(
number: int or float or str,
r_acc: int = 2,
dec_sep: str = ",",
div_by_3: bool = True,
) -> str:
"""
Formats float and int values representation. Returns string.
:param r_acc: int, Round accuracy, default is 2.
:param dec_sep: str, separator between integer and fractional parts
:param div_by_3: bool, inserts space after each 3 digits in integer part.
}
"""
valid_number = _validate_number_arg(number)
fract_part, int_part = modf(valid_number)
new_fract_part = str(round(fract_part, r_acc))[2:].ljust(r_acc, "0")
if div_by_3:
# convert to str, cut off ".0" and reverse
int_part = str(int_part)[:-2][::-1]
counter_3 = 0
new_int_part = ""
for num in int_part:
if counter_3 < 3:
divider = ""
else:
divider = " "
counter_3 = 0
new_int_part = divider.join([new_int_part, num])
counter_3 += 1
# Reverse backward
new_int_part = new_int_part[::-1]
else:
new_int_part = str(int_part)[:-2]
return dec_sep.join([new_int_part, new_fract_part])

View File

@ -0,0 +1,51 @@
from decimal import Decimal
from num2words import num2words
from num2words import CONVERTES_TYPES
# Can use params:
# ~ number: int, float or validate string
# ~ to: num2words.CONVERTES_TYPES
# ~ lang: num2words.CONVERTER_CLASSES
# ~ currency: num2words.CONVERTER_CLASSES.CURRENCY_FORMS
def num2words_(number, **kwargs):
if _perform_convert(number):
if "lang" not in kwargs:
kwargs["lang"] = "ru"
if "to" not in kwargs or kwargs["to"] not in CONVERTES_TYPES:
kwargs["to"] = "cardinal"
return num2words(number, **kwargs)
def num2words_currency(number, **kwargs):
if _perform_convert(number):
if "lang" not in kwargs:
kwargs["lang"] = "ru"
if "to" not in kwargs or kwargs["to"] not in CONVERTES_TYPES:
kwargs["to"] = "currency"
if "currency" not in kwargs:
kwargs["currency"] = "RUB"
result = num2words(number, **kwargs)
total = result.split(",")[0]
part_word = result.split()[-1]
part_number = Decimal(str(number)) % 1
return "{total}, {part_n} {part_w}".format(
total=total.capitalize(),
part_n="{:02d}".format(int(part_number * 100)),
part_w=part_word,
)
def _perform_convert(number):
if isinstance(number, int) or isinstance(number, float):
return True
if isinstance(number, str):
try:
number = float(number)
return True
except ValueError:
return False
return False

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,753 +0,0 @@
<!DOCTYPE html>
<html lang="en-US" data-website-id="1" data-oe-company-name="Rydlab">
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<header>
<!-- Module name and short description of the module. -->
<section>
<h1 class="display-3" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
color: #eb8900;
">DOCX REPORT</h1>
<div style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
<p style="text-align: center">
The DOCX REPORT module is a tool for creating templates using the Jinja
template engine. The module allows you to add docx files to the report model as a source template.
Thanks to this, you can create automatically filled-out documents in docx and pdf formats.
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 40%" src="divider.png">
</div>
</section>
<section>
<div style="width: 15%; margin: 0 auto">
<a href="https://rydlab.ru/" target="_blank" style="color: black; text-decoration: none">
<img style="display: block; max-width: 100%; margin: 0 auto" src="company_logo.jpg"
alt="Rydlab company logo">
</a>
</div>
<div style="
width: 30%;
margin: 0 auto;
text-align: center;
margin-top: 20px;
">
<a href="mailto:company@rydlab.ru?subject=Odoo Support / Development / Module: Contract&amp;body="
style="
border-radius: 42px;
display: inline-block;
cursor: pointer;
color: #ffffff !important;
font-family: 'Roboto', sans-serif;
font-size: 23px;
padding: 10px 33px;
background-color: black;
text-decoration: none;
border-radius: 45px;
">
<i class="fa fa-envelope"></i>
&nbsp; Have questions or need support?
</a>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</section>
</header>
<main>
<section>
<h3 class="display-5" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Features</h3>
<div class="d-flex flex-wrap" style="display: flex; flex-wrap: wrap; width: 70%; margin: 0 auto">
<div style="
box-sizing: border-box;
padding: 10px 10px;
border: 2px solid #dfdee8;
text-align: center;
width: 48%;
word-wrap: break-word;
font-family: FontAwesome;
font-size: 20px;
margin: 0 auto;
margin-top: 10px;
margin-bottom: 10px;
">
<h5 style="
text-align: center;
font-family: FontAwesome;
font-weight: 700;
font-size: 22px;
margin-bottom: 1px;
">Easy creation of auto-filled documents</h5>
<p style="margin-top: 1px; margin-bottom: 2px">
There is no need to create a complex HTML template that is difficult to edit
and customize. It is enough to take a template in Docx format and insert the necessary values in
the right places.
</p>
</div>
<div style="
box-sizing: border-box;
padding: 10px 10px;
border: 2px solid #dfdee8;
text-align: center;
width: 48%;
word-wrap: break-word;
font-family: FontAwesome;
font-size: 20px;
margin: 0 auto;
margin-top: 10px;
margin-bottom: 10px;
">
<h5 style="
text-align: center;
font-family: FontAwesome;
font-weight: 700;
font-size: 22px;
margin-bottom: 1px;
">Access to all attributes of the model</h5>
<p style="margin-top: 1px; margin-bottom: 2px">
During the creation of the report, the model to which this report belongs is
specified. When creating a report template, we can refer to any attribute of the specified
model.
</p>
</div>
<div style="
box-sizing: border-box;
padding: 10px 10px;
border: 2px solid #dfdee8;
text-align: center;
width: 48%;
word-wrap: break-word;
font-family: FontAwesome;
font-size: 20px;
margin: 0 auto;
margin-top: 10px;
margin-bottom: 10px;
">
<h5 style="
text-align: center;
font-family: FontAwesome;
font-weight: 700;
font-size: 22px;
margin-bottom: 1px;
">Easy template maintenance</h5>
<p style="margin-top: 1px; margin-bottom: 2px">
There is no need to change the template in the Odoo code; it is enough to
upload a new template through the user interface.
</p>
</div>
<div style="
box-sizing: border-box;
padding: 10px 10px;
border: 2px solid #dfdee8;
text-align: center;
width: 48%;
word-wrap: break-word;
font-family: FontAwesome;
font-size: 20px;
margin: 0 auto;
margin-top: 10px;
margin-bottom: 10px;
">
<h5 style="
text-align: center;
font-family: FontAwesome;
font-weight: 700;
font-size: 22px;
margin-bottom: 1px;
">The amount-to-words methods are available</h5>
<p style="margin-top: 1px; margin-bottom: 2px">
Thanks to these methods, we can insert numbers and sums with currencies in
words and round the numbers to the desired accuracy.
</p>
</div>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</section>
<section>
<h3 class="display-5" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">How to use</h3>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 1: Install the module</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/1_intall_app.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
Open the Apps menu in your Odoo and install the module "DOCX report".
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 2: Activate the Developer Mode</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/2_activate_developer_mode.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
To create a new report in the user interface, we need to activate the
developer mode.
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 3: Open Reports</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/3_open_reports.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
Now you should go back to the settings. Click "Technical", scroll down the
list, and click "Reports".
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 4: Create Docx template</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/4_docx_template.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
1. To get model attributes like model field values, use the word "docs" +
(dot) + model field name.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
2. To call a model method that returns a value, use the word "record" + .
(dot) + model method name + () to call it.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">3. Use double curly braces "{{ }}" to call methods and attributes.</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
4. Use curly braces with the percentage sign "{% %}" to create local
variables and use loops and if conditions.
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 5: Reports list view</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/5_reporst_list.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
When the Reports list view will be opened, click the "New" button to create a
new report.
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 6: Create a Report</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/6_report_form.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">1. To create a new report, you should fill out the form.</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
2. "Action name" is the name that will be shown in the Print menu of the
model.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">3. "Report Type" should be DOCS or DOCX (PDF). The gotenberg service is used
to create pdf files. To use it, you need to install the Odoo module to communicate with the
service and enter the access details.</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
4. "Model name" is the name of the model. Fields and methods will be derived
from this model.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
5. "Report docx template" is the template file that was created at step 4.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
6. "Printed report name" is the name of the file after generation.
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
7. To add this report to the Print menu, you should click on the button "Add
in the Print".
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 7: Create a custom field</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/7_cutom_fields.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
1. Custom fields are needed to get data that is not in the fields of the model associated with
the
report. Thanks to them, you can get data from other models, for example, through the reference
fields of the current model.
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
2. To create a custom field you should click "Custom Fields" menu on the form
and then click "Add a line"
</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">3. After that, write your Python code for the new variable.</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">4. In the template, custom fields are available by the name specified in the
"tech_name" field of the custom field entry. For exapmle: {{ contract_date }}.</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 8: Print the Report</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/8_result.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">
After printing the report the file will be saved. The information from the
model will complete the template.
</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
</div>
<div>
<h5 class="display-6" style="
text-align: center;
font-family: FontAwesome;
margin-bottom: 10px;
font-weight: 700;
">Step 9: Make a report from the Python code</h5>
<div>
<img style="
display: block;
border: none;
width: 70%;
position: relative;
margin: 0 auto;
margin-top: 20px;
" src="screenshots/9_report_code.jpg">
</div>
<div>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">If you want to make the report from the Python code, you should make an
ir.action.report record and an ir.attachment record that is connected to the first one. Add the
path to the Docx template from step 4.</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png" />
</div>
</div>
</section>
</main>
<footer style="margin-bottom: 70px">
<div style="width: 15%; margin: 0 auto">
<a href="https://rydlab.ru/" target="_blank" style="color: black; text-decoration: none">
<img style="display: block; max-width: 100%; margin: 0 auto" src="company_logo.jpg"
alt="Rydlab company logo">
</a>
</div>
<div style="margin: 0 auto; margin-top: 10px; margin-bottom: 5px; padding: 0">
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">Need any help for this module?</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
">Contact us for your queries</p>
<p style="
font-family: FontAwesome;
font-size: 26px;
margin: 0 auto;
width: 70%;
text-align: center;
margin-top: 5px;
margin-bottom: 5px;
font-weight: 700;
"> Email: company@rydlab.ru</p>
</div>
<div style="
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
">
<img style="height: 10px; width: 70%" src="divider.png">
</div>
</footer>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

View File

@ -1,4 +0,0 @@
.o_image[data-mimetype$='msword'],
.o_image[data-mimetype$='application/vnd.openxmlformats-officedocument.wordprocessingml.document'] {
background-image: url('/docx_report/static/src/img/msword.png');
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,54 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { download } from "@web/core/network/download";
async function docxHandler(action, options, env) {
let reportType = null;
if (action.report_type === "docx-docx") {
reportType = "docx";
} else if (action.report_type === "docx-pdf") {
reportType = "pdf";
}
if (reportType) {
// Make URL
let url = `/report/${reportType}/${action.report_name}`;
const actionContext = action.context || {};
if (action.data && JSON.stringify(action.data) !== "{}") {
// build a query string with `action.data` (it's the place where reports
// using a wizard to customize the output traditionally put their options)
const options = encodeURIComponent(JSON.stringify(action.data));
const context = encodeURIComponent(JSON.stringify(actionContext));
url += `?options=${options}&context=${context}`;
} else {
if (actionContext.active_ids) {
url += `/${actionContext.active_ids.join(",")}`;
}
}
// Download report
env.services.ui.block();
try {
const template_type = (action.report_type && action.report_type.split("-")[0]) || "docx";
const type = template_type + "-" + url.split("/")[2];
await download({
url: "/report/download",
data: {
data: JSON.stringify([url, type]),
context: JSON.stringify(Object.assign({}, action.context, env.services.user.context)),
},
});
} finally {
env.services.ui.unblock();
}
const onClose = options.onClose;
if (action.close_on_report_download) {
return env.services.action.doAction({type: "ir.actions.act_window_close"}, {onClose});
} else if (onClose) {
onClose();
}
return Promise.resolve(true);
}
return Promise.resolve(false);
}
registry.category("ir.actions.report handlers").add("docx_handler", docxHandler);

View File

@ -1,18 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="act_report_xml_inherit_view_form" model="ir.ui.view">
<field name="name">ir.actions.report.inherit.view.form</field>
<field name="model">ir.actions.report</field>
<field name="inherit_id" ref="base.act_report_xml_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='report_name']" position="attributes">
<attribute name="attrs">{'readonly': [('report_type', 'in', ['docx-docx', 'docx-pdf'])], 'required': [('report_type', 'not in', ['docx-docx', 'docx-pdf'])]}</attribute>
</xpath>
<xpath expr="//field[@name='report_name']" position="after">
<field name="report_docx_template" attrs="{'required': [('report_type', 'in', ['docx-docx', 'docx-pdf'])]}"/>
</xpath>
</field>
</record>
</odoo>