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 + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+

Help with Python code

+

The following variables can be used:

+
    +
  • env: Odoo Environment on which the action is triggered
  • +
  • model: Odoo Model of the record on which the action is triggered; is a void recordset
  • +
  • record: record on which the action is triggered; may be be void
  • +
  • records: recordset of all records on which the action is triggered in multi mode; may be void
  • +
  • time, datetime, dateutil, timezone: useful Python libraries
  • +
  • log(message, level='info'): logging function to record debug information in ir.logging table
  • +
  • UserError, Warning: Warning Exceptions to use with raise
  • +
  • format_number: Method for formatting number representation in report. +
    + It's signature: format_number(number, r_acc, dec_sep, div_by_3)
    + Param number - int or float to represent,
    + Param r_acc - int is round accuracy, default value is 2
    + Param dec_sep - str is decimal separator, default value is ,
    + Param div_by_3 - bool is flag for add whitespase separators after each 3 digits id integer part. Default value is True
    + Returns number as str.
    +
    +
  • +
  • To return a value, assign: value = ...
  • +
+
+ Example of Python code:
+ + partner_rec = env['res.partner'].search([('name', '=' record.name)])
+ value = partner_rec.parent_id.vat if partner_rec else ""
+
+
+
+
+
+
+
+
+
+
+
+ +
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. -

-
-
- -
-
-
-
- - Rydlab company logo - -
-
- - -   Have questions or need support? - -
-
- -
-
-
-
-
-

Features

-
-
-
Easy creation of auto-filled documents
-

- 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'])]} - - - - - - - -