diff --git a/README.md b/README.md
deleted file mode 100755
index 93ff078..0000000
--- a/README.md
+++ /dev/null
@@ -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.
diff --git a/__init__.py b/__init__.py
deleted file mode 100755
index 91c5580..0000000
--- a/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from . import controllers
-from . import models
diff --git a/__manifest__.py b/__manifest__.py
deleted file mode 100755
index a1dec89..0000000
--- a/__manifest__.py
+++ /dev/null
@@ -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",
- ],
- },
-}
diff --git a/controllers/__init__.py b/controllers/__init__.py
deleted file mode 100644
index 12a7e52..0000000
--- a/controllers/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from . import main
diff --git a/controllers/main.py b/controllers/main.py
deleted file mode 100644
index 40ff84d..0000000
--- a/controllers/main.py
+++ /dev/null
@@ -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)))
diff --git a/custom_report_field/README.md b/custom_report_field/README.md
new file mode 100644
index 0000000..5595b93
--- /dev/null
+++ b/custom_report_field/README.md
@@ -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.
diff --git a/custom_report_field/__init__.py b/custom_report_field/__init__.py
new file mode 100644
index 0000000..9b42961
--- /dev/null
+++ b/custom_report_field/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/custom_report_field/__manifest__.py b/custom_report_field/__manifest__.py
new file mode 100644
index 0000000..6442d24
--- /dev/null
+++ b/custom_report_field/__manifest__.py
@@ -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",
+ ],
+ },
+}
diff --git a/custom_report_field/i18n/custom_report_field.pot b/custom_report_field/i18n/custom_report_field.pot
new file mode 100644
index 0000000..4defd67
--- /dev/null
+++ b/custom_report_field/i18n/custom_report_field.pot
@@ -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 ""
+"UserError, Warning: Warning Exceptions to use with"
+" raise"
+msgstr ""
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid "env: 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 ""
+"format_number: 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 ""
+"log(message, level='info'): logging function to record debug "
+"information in ir.logging table"
+msgstr ""
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"model: 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 ""
+"record: 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 ""
+"records: 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 ""
+"time, datetime, dateutil, "
+"timezone: 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: \n"
+" \n"
+" partner_rec = env['res.partner'].search([('name', '=' record.name)]) \n"
+" value = partner_rec.parent_id.vat if partner_rec else \"\" \n"
+" "
+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: format_number(number, r_acc, dec_sep, div_by_3) \n"
+" Param number - int or float to represent, \n"
+" Param r_acc - int is round accuracy, default value is 2 \n"
+" Param dec_sep - str is decimal separator, default value is , \n"
+" Param div_by_3 - bool is flag for add whitespase separators after each 3 digits id integer part. Default value is True \n"
+" Returns number as str. "
+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: value = ..."
+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 ""
diff --git a/custom_report_field/i18n/ru.po b/custom_report_field/i18n/ru.po
new file mode 100644
index 0000000..bab1f68
--- /dev/null
+++ b/custom_report_field/i18n/ru.po
@@ -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 ""
+"UserError, Warning: Warning Exceptions to use with"
+" raise"
+msgstr ""
+"UserError, Warning: Исключение для использования с"
+" raise"
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid "env: Odoo Environment on which the action is triggered"
+msgstr "env: Окружение Odoo, на котором вызывается action."
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"format_number: Method for formatting number representation in "
+"report."
+msgstr ""
+"format_number: Метод для форматирования числовых представлений в "
+"отчете."
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"log(message, level='info'): logging function to record debug "
+"information in ir.logging table"
+msgstr ""
+"log(message, level='info'): Метод логирования для записи "
+"отладочной информацииir.logging table"
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"model: Odoo Model of the record on which the action is "
+"triggered; is a void recordset"
+msgstr ""
+"model: Модель записи Odoo, на которой вызван action; Является "
+"пустым множеством записей."
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"record: record on which the action is triggered; may be be void"
+msgstr ""
+"record: Запись, на которой вызван action; Может быть пустой."
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"records: recordset of all records on which the action is "
+"triggered in multi mode; may be void"
+msgstr ""
+"records: Множество записей, на которых вызван action в режиме "
+"нескольких записей; Может быть пустым."
+
+#. module: custom_report_field
+#: model_terms:ir.ui.view,arch_db:custom_report_field.act_report_xml_inherit_view_form
+msgid ""
+"time, datetime, dateutil, "
+"timezone: useful Python libraries"
+msgstr ""
+"time, datetime, dateutil, "
+"timezone: полезные библиотеки 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: \n"
+" \n"
+" partner_rec = env['res.partner'].search([('name', '=' record.name)]) \n"
+" value = partner_rec.parent_id.vat if partner_rec else \"\" \n"
+" "
+msgstr ""
+"Пример кода Python: \n"
+" \n"
+" partner_rec = env['res.partner'].search([('name', '=' record.name)]) \n"
+" value = partner_rec.parent_id.vat if partner_rec else \"\" \n"
+" "
+
+#. 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: format_number(number, r_acc, dec_sep, div_by_3) \n"
+" Param number - int or float to represent, \n"
+" Param r_acc - int is round accuracy, default value is 2 \n"
+" Param dec_sep - str is decimal separator, default value is , \n"
+" Param div_by_3 - bool is flag for add whitespase separators after each 3 digits id integer part. Default value is True \n"
+" Returns number as str. "
+msgstr ""
+"Сигнатура: format_number(number, r_acc, dec_sep, div_by_3) \n"
+" Param number - int или float число для отображения, \n"
+" Param r_acc - int точность округления, по умолчанию 2 знака, \n"
+" Param dec_sep - str разделитель для десятичной части, по умолчанию ,, \n"
+" Param div_by_3 - bool флаг для добавления пробелов между каждыми тремя цифрами числа в целой части. По умолчанию True, \n"
+" Возвращает число как str. "
+
+#. 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: value = ..."
+msgstr ""
+"Для возвращения значения выполните присвоение: value = ..."
+
+#. 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 "Мастер"
diff --git a/custom_report_field/models/__init__.py b/custom_report_field/models/__init__.py
new file mode 100644
index 0000000..7e788b4
--- /dev/null
+++ b/custom_report_field/models/__init__.py
@@ -0,0 +1,2 @@
+from . import custom_report_field
+from . import ir_actions_report
diff --git a/custom_report_field/models/custom_report_field.py b/custom_report_field/models/custom_report_field.py
new file mode 100644
index 0000000..9f6cd30
--- /dev/null
+++ b/custom_report_field/models/custom_report_field.py
@@ -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 ""
+ )
diff --git a/custom_report_field/models/ir_actions_report.py b/custom_report_field/models/ir_actions_report.py
new file mode 100644
index 0000000..4bcb74a
--- /dev/null
+++ b/custom_report_field/models/ir_actions_report.py
@@ -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
diff --git a/custom_report_field/security/ir.model.access.csv b/custom_report_field/security/ir.model.access.csv
new file mode 100644
index 0000000..4b775f9
--- /dev/null
+++ b/custom_report_field/security/ir.model.access.csv
@@ -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
diff --git a/custom_report_field/static/src/js/action_manager_report.js b/custom_report_field/static/src/js/action_manager_report.js
new file mode 100644
index 0000000..e760217
--- /dev/null
+++ b/custom_report_field/static/src/js/action_manager_report.js
@@ -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},
+);
diff --git a/custom_report_field/views/ir_actions_report_views.xml b/custom_report_field/views/ir_actions_report_views.xml
new file mode 100644
index 0000000..329fa4a
--- /dev/null
+++ b/custom_report_field/views/ir_actions_report_views.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ ir.actions.report.inherit.view.form
+ ir.actions.report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/custom_report_field/wizard/__init__.py b/custom_report_field/wizard/__init__.py
new file mode 100644
index 0000000..b578d62
--- /dev/null
+++ b/custom_report_field/wizard/__init__.py
@@ -0,0 +1 @@
+from . import custom_report_field_values_wizard
diff --git a/custom_report_field/wizard/custom_report_field_values_wizard.py b/custom_report_field/wizard/custom_report_field_values_wizard.py
new file mode 100644
index 0000000..c8c52d4
--- /dev/null
+++ b/custom_report_field/wizard/custom_report_field_values_wizard.py
@@ -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",
+ )
diff --git a/custom_report_field/wizard/custom_report_field_values_wizard_views.xml b/custom_report_field/wizard/custom_report_field_values_wizard_views.xml
new file mode 100644
index 0000000..5fe4b8f
--- /dev/null
+++ b/custom_report_field/wizard/custom_report_field_values_wizard_views.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ Verify report values
+ custom.report.field.values.wizard
+
+
+
+
+
+
diff --git a/i18n/docx_report.pot b/i18n/docx_report.pot
deleted file mode 100644
index 2912961..0000000
--- a/i18n/docx_report.pot
+++ /dev/null
@@ -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 ""
diff --git a/i18n/ru.po b/i18n/ru.po
deleted file mode 100644
index 3007c1f..0000000
--- a/i18n/ru.po
+++ /dev/null
@@ -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 и загружен пользователем."
diff --git a/models/ir_actions_report.py b/models/ir_actions_report.py
deleted file mode 100644
index 0e9d591..0000000
--- a/models/ir_actions_report.py
+++ /dev/null
@@ -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
diff --git a/report_monetary_helpers/README.md b/report_monetary_helpers/README.md
new file mode 100644
index 0000000..b4b9a34
--- /dev/null
+++ b/report_monetary_helpers/README.md
@@ -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.
diff --git a/report_monetary_helpers/__init__.py b/report_monetary_helpers/__init__.py
new file mode 100644
index 0000000..af38f37
--- /dev/null
+++ b/report_monetary_helpers/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import utils
diff --git a/report_monetary_helpers/__manifest__.py b/report_monetary_helpers/__manifest__.py
new file mode 100644
index 0000000..61ee7bb
--- /dev/null
+++ b/report_monetary_helpers/__manifest__.py
@@ -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": [],
+}
diff --git a/report_monetary_helpers/i18n/report_monetary_helpers.pot b/report_monetary_helpers/i18n/report_monetary_helpers.pot
new file mode 100644
index 0000000..5d0e15f
--- /dev/null
+++ b/report_monetary_helpers/i18n/report_monetary_helpers.pot
@@ -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 ""
diff --git a/report_monetary_helpers/i18n/ru.po b/report_monetary_helpers/i18n/ru.po
new file mode 100644
index 0000000..07e4d0a
--- /dev/null
+++ b/report_monetary_helpers/i18n/ru.po
@@ -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 "
diff --git a/models/__init__.py b/report_monetary_helpers/models/__init__.py
old mode 100755
new mode 100644
similarity index 100%
rename from models/__init__.py
rename to report_monetary_helpers/models/__init__.py
diff --git a/report_monetary_helpers/models/ir_actions_report.py b/report_monetary_helpers/models/ir_actions_report.py
new file mode 100644
index 0000000..24a62c3
--- /dev/null
+++ b/report_monetary_helpers/models/ir_actions_report.py
@@ -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
diff --git a/report_monetary_helpers/utils/__init__.py b/report_monetary_helpers/utils/__init__.py
new file mode 100644
index 0000000..fccf8c7
--- /dev/null
+++ b/report_monetary_helpers/utils/__init__.py
@@ -0,0 +1,2 @@
+from . import format_number
+from . import num2words
diff --git a/report_monetary_helpers/utils/format_number.py b/report_monetary_helpers/utils/format_number.py
new file mode 100644
index 0000000..9672599
--- /dev/null
+++ b/report_monetary_helpers/utils/format_number.py
@@ -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])
diff --git a/report_monetary_helpers/utils/num2words.py b/report_monetary_helpers/utils/num2words.py
new file mode 100644
index 0000000..a43bf29
--- /dev/null
+++ b/report_monetary_helpers/utils/num2words.py
@@ -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
diff --git a/static/description/company_logo.jpg b/static/description/company_logo.jpg
deleted file mode 100644
index 4e826eb..0000000
Binary files a/static/description/company_logo.jpg and /dev/null differ
diff --git a/static/description/divider.png b/static/description/divider.png
deleted file mode 100644
index b5269ea..0000000
Binary files a/static/description/divider.png and /dev/null differ
diff --git a/static/description/index.html b/static/description/index.html
deleted file mode 100644
index 1a494c7..0000000
--- a/static/description/index.html
+++ /dev/null
@@ -1,753 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
DOCX REPORT
-
-
- 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.
-
- 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.
-
-
-
-
Access to all attributes of the model
-
- 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.
-
-
-
-
Easy template maintenance
-
- There is no need to change the template in the Odoo code; it is enough to
- upload a new template through the user interface.
-
-
-
-
The amount-to-words methods are available
-
- Thanks to these methods, we can insert numbers and sums with currencies in
- words and round the numbers to the desired accuracy.
-
-
-
-
-
-
-
-
-
How to use
-
-
Step 1: Install the module
-
-
-
-
-
- Open the Apps menu in your Odoo and install the module "DOCX report".
-
-
-
-
-
-
-
-
Step 2: Activate the Developer Mode
-
-
-
-
-
- To create a new report in the user interface, we need to activate the
- developer mode.
-
-
-
-
-
-
-
-
Step 3: Open Reports
-
-
-
-
-
- Now you should go back to the settings. Click "Technical", scroll down the
- list, and click "Reports".
-
-
-
-
-
-
-
-
Step 4: Create Docx template
-
-
-
-
-
- 1. To get model attributes like model field values, use the word "docs" +
- (dot) + model field name.
-
-
- 2. To call a model method that returns a value, use the word "record" + .
- (dot) + model method name + () to call it.
-
-
3. Use double curly braces "{{ }}" to call methods and attributes.
-
- 4. Use curly braces with the percentage sign "{% %}" to create local
- variables and use loops and if conditions.
-
-
-
-
-
-
-
-
Step 5: Reports list view
-
-
-
-
-
- When the Reports list view will be opened, click the "New" button to create a
- new report.
-
-
-
-
-
-
-
-
Step 6: Create a Report
-
-
-
-
-
1. To create a new report, you should fill out the form.
-
- 2. "Action name" is the name that will be shown in the Print menu of the
- model.
-
-
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.
-
- 4. "Model name" is the name of the model. Fields and methods will be derived
- from this model.
-
-
- 5. "Report docx template" is the template file that was created at step 4.
-
-
- 6. "Printed report name" is the name of the file after generation.
-
-
- 7. To add this report to the Print menu, you should click on the button "Add
- in the Print".
-
-
-
-
-
-
-
-
Step 7: Create a custom field
-
-
-
-
-
- 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.
-
- 2. To create a custom field you should click "Custom Fields" menu on the form
- and then click "Add a line"
-
-
3. After that, write your Python code for the new variable.
-
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 }}.
-
-
-
-
-
-
-
Step 8: Print the Report
-
-
-
-
-
- After printing the report the file will be saved. The information from the
- model will complete the template.
-
-
-
-
-
-
-
-
-
Step 9: Make a report from the Python code
-
-
-
-
-
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.
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/static/description/screenshots/1_intall_app.jpg b/static/description/screenshots/1_intall_app.jpg
deleted file mode 100644
index 0c53487..0000000
Binary files a/static/description/screenshots/1_intall_app.jpg and /dev/null differ
diff --git a/static/description/screenshots/2_activate_developer_mode.jpg b/static/description/screenshots/2_activate_developer_mode.jpg
deleted file mode 100644
index 48fc97e..0000000
Binary files a/static/description/screenshots/2_activate_developer_mode.jpg and /dev/null differ
diff --git a/static/description/screenshots/3_open_reports.jpg b/static/description/screenshots/3_open_reports.jpg
deleted file mode 100644
index e0ee81d..0000000
Binary files a/static/description/screenshots/3_open_reports.jpg and /dev/null differ
diff --git a/static/description/screenshots/4_docx_template.jpg b/static/description/screenshots/4_docx_template.jpg
deleted file mode 100644
index 554a47b..0000000
Binary files a/static/description/screenshots/4_docx_template.jpg and /dev/null differ
diff --git a/static/description/screenshots/5_reporst_list.jpg b/static/description/screenshots/5_reporst_list.jpg
deleted file mode 100644
index b8320c7..0000000
Binary files a/static/description/screenshots/5_reporst_list.jpg and /dev/null differ
diff --git a/static/description/screenshots/6_report_form.jpg b/static/description/screenshots/6_report_form.jpg
deleted file mode 100644
index dce6037..0000000
Binary files a/static/description/screenshots/6_report_form.jpg and /dev/null differ
diff --git a/static/description/screenshots/7_cutom_fields.jpg b/static/description/screenshots/7_cutom_fields.jpg
deleted file mode 100644
index 0ff23c3..0000000
Binary files a/static/description/screenshots/7_cutom_fields.jpg and /dev/null differ
diff --git a/static/description/screenshots/8_result.jpg b/static/description/screenshots/8_result.jpg
deleted file mode 100644
index bf5c62b..0000000
Binary files a/static/description/screenshots/8_result.jpg and /dev/null differ
diff --git a/static/description/screenshots/9_report_code.jpg b/static/description/screenshots/9_report_code.jpg
deleted file mode 100644
index ffe9052..0000000
Binary files a/static/description/screenshots/9_report_code.jpg and /dev/null differ
diff --git a/static/src/css/mimetypes.css b/static/src/css/mimetypes.css
deleted file mode 100755
index e7b0185..0000000
--- a/static/src/css/mimetypes.css
+++ /dev/null
@@ -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');
-}
diff --git a/static/src/img/msword.png b/static/src/img/msword.png
deleted file mode 100755
index 5ac980f..0000000
Binary files a/static/src/img/msword.png and /dev/null differ
diff --git a/static/src/js/action_manager_report.js b/static/src/js/action_manager_report.js
deleted file mode 100644
index 012a629..0000000
--- a/static/src/js/action_manager_report.js
+++ /dev/null
@@ -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);
diff --git a/views/ir_actions_report_views.xml b/views/ir_actions_report_views.xml
deleted file mode 100755
index c0f56f9..0000000
--- a/views/ir_actions_report_views.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- ir.actions.report.inherit.view.form
- ir.actions.report
-
-
-
- {'readonly': [('report_type', 'in', ['docx-docx', 'docx-pdf'])], 'required': [('report_type', 'not in', ['docx-docx', 'docx-pdf'])]}
-
-
-
-
-
-
-
-