Update repository files:
48
README.md
@ -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.
|
@ -1,2 +0,0 @@
|
||||
from . import controllers
|
||||
from . import models
|
@ -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",
|
||||
],
|
||||
},
|
||||
}
|
@ -1 +0,0 @@
|
||||
from . import main
|
@ -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)))
|
18
custom_report_field/README.md
Normal 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.
|
2
custom_report_field/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizard
|
29
custom_report_field/__manifest__.py
Normal 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",
|
||||
],
|
||||
},
|
||||
}
|
297
custom_report_field/i18n/custom_report_field.pot
Normal 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 ""
|
331
custom_report_field/i18n/ru.po
Normal 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 "Мастер"
|
2
custom_report_field/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import custom_report_field
|
||||
from . import ir_actions_report
|
106
custom_report_field/models/custom_report_field.py
Normal 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 ""
|
||||
)
|
59
custom_report_field/models/ir_actions_report.py
Normal 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
|
4
custom_report_field/security/ir.model.access.csv
Normal 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
|
|
40
custom_report_field/static/src/js/action_manager_report.js
Normal 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},
|
||||
);
|
78
custom_report_field/views/ir_actions_report_views.xml
Normal 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>
|
1
custom_report_field/wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import custom_report_field_values_wizard
|
@ -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",
|
||||
)
|
@ -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>
|
@ -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 ""
|
75
i18n/ru.po
@ -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 и загружен пользователем."
|
@ -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
|
31
report_monetary_helpers/README.md
Normal 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.
|
2
report_monetary_helpers/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import utils
|
35
report_monetary_helpers/__manifest__.py
Normal 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": [],
|
||||
}
|
21
report_monetary_helpers/i18n/report_monetary_helpers.pot
Normal 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 ""
|
21
report_monetary_helpers/i18n/ru.po
Normal 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 "
|
0
models/__init__.py → report_monetary_helpers/models/__init__.py
Executable file → Normal file
24
report_monetary_helpers/models/ir_actions_report.py
Normal 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
|
2
report_monetary_helpers/utils/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import format_number
|
||||
from . import num2words
|
63
report_monetary_helpers/utils/format_number.py
Normal 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])
|
51
report_monetary_helpers/utils/num2words.py
Normal 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
|
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 44 KiB |
@ -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&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>
|
||||
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>
|
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 251 KiB |
Before Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 852 KiB |
Before Width: | Height: | Size: 437 KiB |
Before Width: | Height: | Size: 239 KiB |
Before Width: | Height: | Size: 222 KiB |
Before Width: | Height: | Size: 669 KiB |
Before Width: | Height: | Size: 183 KiB |
@ -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');
|
||||
}
|
Before Width: | Height: | Size: 14 KiB |
@ -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);
|
@ -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>
|