From 98abc08333b41cded6a9e11dd919cb45ae1e5c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Tue, 12 Mar 2024 14:45:24 +0100 Subject: [PATCH] [ADD] base_import_pdf_simple: New module TT48213 --- base_import_pdf_simple/README.rst | 190 ++++++ base_import_pdf_simple/__init__.py | 2 + base_import_pdf_simple/__manifest__.py | 34 + .../demo/base_import_pdf_template.xml | 84 +++ .../i18n/base_import_pdf_simple.pot | 611 +++++++++++++++++ base_import_pdf_simple/i18n/es.po | 621 ++++++++++++++++++ base_import_pdf_simple/models/__init__.py | 1 + .../models/base_import_pdf_template.py | 407 ++++++++++++ base_import_pdf_simple/readme/CONFIGURE.rst | 46 ++ .../readme/CONTRIBUTORS.rst | 4 + base_import_pdf_simple/readme/DESCRIPTION.rst | 4 + base_import_pdf_simple/readme/ROADMAP.rst | 36 + base_import_pdf_simple/readme/USAGE.rst | 6 + .../security/ir.model.access.csv | 10 + base_import_pdf_simple/security/security.xml | 17 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 546 +++++++++++++++ .../static/src/js/import_menu.js | 35 + .../static/src/xml/import_menu.xml | 11 + base_import_pdf_simple/tests/__init__.py | 3 + .../tests/data/res-partner.pdf | Bin 0 -> 13556 bytes .../tests/test_sale_import_pdf_simple.py | 145 ++++ .../base_import_pdf_template_line_views.xml | 105 +++ .../views/base_import_pdf_template_views.xml | 110 ++++ base_import_pdf_simple/wizards/__init__.py | 3 + .../wizards/wizard_base_import_pdf_mixin.py | 65 ++ .../wizards/wizard_base_import_pdf_preview.py | 54 ++ .../wizard_base_import_pdf_preview_views.xml | 46 ++ .../wizards/wizard_base_import_pdf_upload.py | 210 ++++++ .../wizard_base_import_pdf_upload_views.xml | 38 ++ requirements.txt | 1 + .../odoo/addons/base_import_pdf_simple | 1 + setup/base_import_pdf_simple/setup.py | 6 + 33 files changed, 3452 insertions(+) create mode 100644 base_import_pdf_simple/README.rst create mode 100644 base_import_pdf_simple/__init__.py create mode 100644 base_import_pdf_simple/__manifest__.py create mode 100644 base_import_pdf_simple/demo/base_import_pdf_template.xml create mode 100644 base_import_pdf_simple/i18n/base_import_pdf_simple.pot create mode 100644 base_import_pdf_simple/i18n/es.po create mode 100644 base_import_pdf_simple/models/__init__.py create mode 100644 base_import_pdf_simple/models/base_import_pdf_template.py create mode 100644 base_import_pdf_simple/readme/CONFIGURE.rst create mode 100644 base_import_pdf_simple/readme/CONTRIBUTORS.rst create mode 100644 base_import_pdf_simple/readme/DESCRIPTION.rst create mode 100644 base_import_pdf_simple/readme/ROADMAP.rst create mode 100644 base_import_pdf_simple/readme/USAGE.rst create mode 100644 base_import_pdf_simple/security/ir.model.access.csv create mode 100644 base_import_pdf_simple/security/security.xml create mode 100644 base_import_pdf_simple/static/description/icon.png create mode 100644 base_import_pdf_simple/static/description/index.html create mode 100644 base_import_pdf_simple/static/src/js/import_menu.js create mode 100644 base_import_pdf_simple/static/src/xml/import_menu.xml create mode 100644 base_import_pdf_simple/tests/__init__.py create mode 100644 base_import_pdf_simple/tests/data/res-partner.pdf create mode 100644 base_import_pdf_simple/tests/test_sale_import_pdf_simple.py create mode 100644 base_import_pdf_simple/views/base_import_pdf_template_line_views.xml create mode 100644 base_import_pdf_simple/views/base_import_pdf_template_views.xml create mode 100644 base_import_pdf_simple/wizards/__init__.py create mode 100644 base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py create mode 100644 base_import_pdf_simple/wizards/wizard_base_import_pdf_preview.py create mode 100644 base_import_pdf_simple/wizards/wizard_base_import_pdf_preview_views.xml create mode 100644 base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py create mode 100644 base_import_pdf_simple/wizards/wizard_base_import_pdf_upload_views.xml create mode 120000 setup/base_import_pdf_simple/odoo/addons/base_import_pdf_simple create mode 100644 setup/base_import_pdf_simple/setup.py diff --git a/base_import_pdf_simple/README.rst b/base_import_pdf_simple/README.rst new file mode 100644 index 00000000000..1eb3b09cbe0 --- /dev/null +++ b/base_import_pdf_simple/README.rst @@ -0,0 +1,190 @@ +====================== +Base Import Pdf Simple +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:04414b052ed70db05b830c912a89562becbfa0e319073ddff9a663e855a0a954 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/15.0/base_import_pdf_simple + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-15-0/edi-15-0-base_import_pdf_simple + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to import PDF files and generate records based on the data +contained in those PDF files. +It also allows you to define a pattern that indicates how to recognize and extract +the data from the PDF to generate a record. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure a PDF document template for import, the first thing to do is to have the +document defined with a specific structure. + +#. Go to Settings > Technical > Base Import PDF Simple > Templates +#. Create a new template by entering a characteristic name and the model on which + the record will be generated. + + Fields to consider completing on template: + + - Main Model: model on which the record will be generated. Example: purchase.order + - Child field: One2many field that will create records from selected template. + Example: Order Lines (purchase.order) + - Auto detect pattern: Define a characteristic pattern of the document so that + it recognizes that it corresponds to the template we are creating. Need to use + regular expression. Example: (?<=ESA79935607)[\S\s]* + - Header Items: Complete this field if the template has a header table to extract + information lines. Example: Reference,Quantity,Price + - Company: Set the company that will use the template. If it is empty, template + will apply for all companies set on the environment. + +#. Add new lines. + + - Related model: When adding new line, the section where to locate the data; "header" + which, as its name indicates, refers to the header of the document and "lines" refers + to the structure of lines or table of the document. + - Field: Map the field to be completed. Example: product + - Pattern: Optional field to complete. Define pattern of the document so that it + recognizes the place to get the field selected on PDF template. Need to use regular + expression. Example: ([0-9]{7}) [0-7]{1} + - Value type: + - Fixed: Select this value, if the field mapped will always have an specific + value and not extract the information from template. In this case Pattern field + must be empty. + - Variable: Select variable to get the information from template. In this case, + Pattern field must be completed. + - For Value type "Variable" will appear extra fields to complete: + - Search value: Indicates the field by which the value obtained in the PDF will + be searched on the system. + - Default value: If the search result is empty for the search value option, you + can set default value to create a record and not getting error message. + - Log distint value?: This option is useful when getting prices in order to + compare prices inside system and prices obtained from PDF. This will create lines + with prices obtained from the system but create log on chatter to see the + differences obtained from PDF. + +Check demo data to further information. + +Usage +===== + +This module allows to upload PDF files in any Odoo model. It processes each of the files +and converts it into a new record. +Technically, the pdf is transformed into text and that text is processed to create the +record. +The module incorporates an option in Favorites Import PDF and Template configuration in +order to recognized any document structure. + +Known issues / Roadmap +====================== + +- Add operator in template lines (= or ilike) +- Add support for selection fields as default value. +- Simplify auto-detection (defining a text only to search the system should search the + corresponding regular expression). +- Allow compatibility with registration process created from email alias (for purchase + order for example). +- Remove error if some file is not auto-detected template, options: boolean (default + option according to system parameter) to omit error for not found files or change + process to 2 steps, auto-detect and show lines (each one with respect to a file) with + template applied (similar to dms_auto_classification). +- Create test_base_import_pdf_simple module with sale, purchase and account dependencies + to leave templates created in runboat and tests more useful for testers. +- Display a more readable error if there is an error in Preview process, example: wrong + pattern. Message: "Please check template defined, some items are not correctly set". +- Add a progress bar (widget=“gauge”) in the import wizard process, useful if we import + for example sales orders with 20 lines and thus know the progress. + +Compatibility with csv, xls, etc: + +- Separate much of the logic to new module base_import_simple that would contain the logic + of templates, type of files (csv, excel, etc) in the templates and wizard and this module + would depend on the other adding only what relates to PDF. +- The base module should take into account for each template whether each line is a new + record or not, and start line (in case you want to omit any), only page 1 would be imported. +- The preview smart-btton would serve exactly the same purpose. +- In the case of csv and Excel that each record is a line, the document will NOT be attached + to the record. +- If you indicate that each record is a line the column will be the key, otherwise you must + specify to which line each line of the template refers. +- In the case of csv it will try to auto-detect the lines and columns (no need to complicate + delimiters configuration). +- The menu "Import PDF" of the favorite menu would become "Import file", and the allowed file + extensions would be those obtained from a method (it would be extended by other modules that + add other formats such as PDF). +- Add queue_job_base_import_simple module to process everything by queues (example: Excel + with hundreds of lines, each one a record). + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Víctor Martínez + * Pedro M. Baeza + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-victoralmau| image:: https://github.com/victoralmau.png?size=40px + :target: https://github.com/victoralmau + :alt: victoralmau + +Current `maintainer `__: + +|maintainer-victoralmau| + +This module is part of the `OCA/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_import_pdf_simple/__init__.py b/base_import_pdf_simple/__init__.py new file mode 100644 index 00000000000..aee8895e7a3 --- /dev/null +++ b/base_import_pdf_simple/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/base_import_pdf_simple/__manifest__.py b/base_import_pdf_simple/__manifest__.py new file mode 100644 index 00000000000..63e036e80c2 --- /dev/null +++ b/base_import_pdf_simple/__manifest__.py @@ -0,0 +1,34 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Base Import Pdf Simple", + "version": "15.0.1.0.0", + "website": "https://github.com/OCA/edi", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["mail"], + "installable": True, + "data": [ + "security/ir.model.access.csv", + "security/security.xml", + "views/base_import_pdf_template_line_views.xml", + "views/base_import_pdf_template_views.xml", + "wizards/wizard_base_import_pdf_preview_views.xml", + "wizards/wizard_base_import_pdf_upload_views.xml", + ], + "demo": [ + "demo/base_import_pdf_template.xml", + ], + "external_dependencies": { + "python": ["pypdf"], + }, + "assets": { + "web.assets_backend": [ + "base_import_pdf_simple/static/src/**/*.js", + ], + "web.assets_qweb": [ + "base_import_pdf_simple/static/src/**/*.xml", + ], + }, + "maintainers": ["victoralmau"], +} diff --git a/base_import_pdf_simple/demo/base_import_pdf_template.xml b/base_import_pdf_simple/demo/base_import_pdf_template.xml new file mode 100644 index 00000000000..34be4935149 --- /dev/null +++ b/base_import_pdf_simple/demo/base_import_pdf_template.xml @@ -0,0 +1,84 @@ + + + + Partner Template + + + Name,Address,Child Country + Test partner info.* + + + + header + + Partner name:[\n] [\n](.*) + + + + header + + + [A-Z].* [(]([A-Z]{1,2})[)][\n]Industry + + + + header + + + Industry:[\n] [\n](.*) + + + + header + + fixed + + + + + lines + + 0 + (.*),.*, + + + + lines + + 1 + .*,(.*), + + + + lines + + + .*,.*, [A-Z].*[(]([A-Z]{1,2})[)] + 2 + + + diff --git a/base_import_pdf_simple/i18n/base_import_pdf_simple.pot b/base_import_pdf_simple/i18n/base_import_pdf_simple.pot new file mode 100644 index 00000000000..1d971c7b6ac --- /dev/null +++ b/base_import_pdf_simple/i18n/base_import_pdf_simple.pot @@ -0,0 +1,611 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_import_pdf_simple +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-05-14 07:58+0000\n" +"PO-Revision-Date: 2024-05-14 07:58+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: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "" +"

%(item_name)s has been set with %(new_value)s instead of\n" +" %(old_value)s

" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +msgid "Name" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__log_distinct_value +msgid "" +"A note will be added with the previous value and the indicated value if\n" +" they are different." +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__active +msgid "Active" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__allowed_template_ids +msgid "Allowed Template" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_search +msgid "Archived" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_mixin__attachment_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__attachment_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__attachment_id +msgid "Attachment" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__auto_detect_pattern +msgid "Auto detect pattern" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.ui.menu,name:base_import_pdf_simple.menu_base_import_pdf_simple_root +msgid "Base Import Pdf Simple" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template +msgid "Base Import Pdf Template" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template_line +msgid "Base Import Pdf Template Line" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template_line_mapped +msgid "Base Import Pdf Template Line Mapped" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Cancel" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_field_id +msgid "Child Field" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_model +msgid "Child Model" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__column +msgid "Column" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__decimal_separator__comma +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__comma +#, python-format +msgid "Comma (,)" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__company_id +msgid "Company" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__create_uid +msgid "Created by" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__create_date +msgid "Created on" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__data +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__data +msgid "Data" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__date_format +msgid "Date Format" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__decimal_separator +msgid "Decimal Separator" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__default_value +msgid "Default value" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__display_name +msgid "Display Name" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__decimal_separator__dot +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__dot +#, python-format +msgid "Dot (.)" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "Error to set %(field_name)s with value %(value)s" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_mixin__extraction_mode +msgid "Extraction Mode" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__extraction_mode +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__extraction_mode +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__extraction_mode +msgid "Extraction mode" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_id +msgid "Field" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_field_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_name +msgid "Field Name" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_ttype +msgid "Field Type" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__data_file +msgid "File" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__attachment_ids +msgid "Files" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__value_type__fixed +#, python-format +msgid "Fixed" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__fixed_value +msgid "Fixed value" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__field_relation +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_relation +msgid "For relationship fields, the technical name of the target model" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "Generated Documents" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__time_format__-h:-m:-s +#, python-format +msgid "H:M:S" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__related_model__header +#, python-format +msgid "Header" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__header_items +msgid "Header Items" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template__header_items +msgid "Header columns separated by commas" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__header_values +msgid "Header values" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__id +msgid "ID" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Import" +msgstr "" + +#. module: base_import_pdf_simple +#. openerp-web +#: code:addons/base_import_pdf_simple/static/src/xml/import_menu.xml:0 +#, python-format +msgid "Import PDF" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.action_wizard_base_import_pdf_upload +msgid "Import PDFs" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template__auto_detect_pattern +msgid "" +"It will be necessary to set a patter that only finds something\n" +" in the documents for this template." +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__line_ids +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__line_ids +msgid "Line" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__related_model__lines +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +#, python-format +msgid "Lines" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__lines_values +msgid "Lines values" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__log_text +msgid "Log Text" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__log_distinct_value +msgid "Log distint value?" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-m--d--y +#, python-format +msgid "MM-dd-YY" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-m/-d/-y +#, python-format +msgid "MM/dd/YY" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__model_id +msgid "Main Model" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__mapped_ids +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_line_view_form +msgid "Mapped" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__model +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__model +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__model +msgid "Model" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__name +msgid "Name" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "" +"No template has been auto-detected from %s, it may be necessary to create a " +"new one." +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__none +#, python-format +msgid "None" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py:0 +#, python-format +msgid "Odoo could not extract the text from the PDF." +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__origin +msgid "Origin" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__parent_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__parent_id +msgid "Parent" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__pattern +msgid "Pattern" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +msgid "Preview" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.action_wizard_base_import_pdf_preview +msgid "Preview PDF" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template__extraction_mode__pypdf +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__wizard_base_import_pdf_preview__extraction_mode__pypdf +msgid "Pypdf" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__data +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_preview_form +msgid "RAW data" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_relation +msgid "Related Model" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__related_model +msgid "Related model" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_id +msgid "Search field" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_name +msgid "Search field name" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_relation +msgid "Search field relation" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_ttype +msgid "Search field type" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_subfield_id +msgid "Search subfield" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__space +#, python-format +msgid "Space ( )" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__table_info +msgid "Table info" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_preview_form +msgid "Technical information" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__template_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__template_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__template_id +msgid "Template" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.base_import_pdf_template_action +#: model:ir.ui.menu,name:base_import_pdf_simple.menu_base_import_pdf_template +msgid "Templates" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "There is no template that can be applied" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__thousand_separator +msgid "Thousand Separator" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__time_format +msgid "Time Format" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__total_pages +msgid "Total pages" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Upload Files" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__value +msgid "Value" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__value_type +msgid "Value type" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__value_type__variable +#, python-format +msgid "Variable" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_mixin +msgid "Wizard Base Import Pdf Mixin" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_preview +msgid "Wizard Base Import Pdf Preview" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_upload +msgid "Wizard Base Import Pdf Upload" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_upload_line +msgid "Wizard Base Import Pdf upload Line" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-y--d--m +#, python-format +msgid "YY-dd-MM" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-y/-d/-m +#, python-format +msgid "YY/dd/MM" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-d_-m_-y +#, python-format +msgid "dd.MM.YY" +msgstr "" diff --git a/base_import_pdf_simple/i18n/es.po b/base_import_pdf_simple/i18n/es.po new file mode 100644 index 00000000000..80bb5391f7a --- /dev/null +++ b/base_import_pdf_simple/i18n/es.po @@ -0,0 +1,621 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_import_pdf_simple +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-05-14 07:58+0000\n" +"PO-Revision-Date: 2024-05-14 09:59+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.0.1\n" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "" +"

%(item_name)s has been set with %(new_value)s instead of\n" +" %(old_value)s

" +msgstr "" +"

%(item_name)s se ha definido con %(new_value)s en lugar de\n" +" %(old_value)s

" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +msgid "Name" +msgstr "Nombre" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__log_distinct_value +msgid "" +"A note will be added with the previous value and the indicated value if\n" +" they are different." +msgstr "" +"Se añadirá una nota con el valor anterior y el valor indicado si son " +"diferentes." + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__active +msgid "Active" +msgstr "Activo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__allowed_template_ids +msgid "Allowed Template" +msgstr "Plantillas permitidas" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_search +msgid "Archived" +msgstr "Archivado" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_mixin__attachment_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__attachment_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__attachment_id +msgid "Attachment" +msgstr "Adjunto" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__auto_detect_pattern +msgid "Auto detect pattern" +msgstr "Patrón de auto-detección" + +#. module: base_import_pdf_simple +#: model:ir.ui.menu,name:base_import_pdf_simple.menu_base_import_pdf_simple_root +msgid "Base Import Pdf Simple" +msgstr "Importador PDF Simple" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template +msgid "Base Import Pdf Template" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template_line +msgid "Base Import Pdf Template Line" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_base_import_pdf_template_line_mapped +msgid "Base Import Pdf Template Line Mapped" +msgstr "" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_field_id +msgid "Child Field" +msgstr "Campo hijo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_model +msgid "Child Model" +msgstr "Modelo hijo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__column +msgid "Column" +msgstr "Columna" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__decimal_separator__comma +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__comma +#, python-format +msgid "Comma (,)" +msgstr "Coma (,)" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__company_id +msgid "Company" +msgstr "Compañía" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__create_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__create_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__data +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__data +msgid "Data" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__date_format +msgid "Date Format" +msgstr "Formato de fecha" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__decimal_separator +msgid "Decimal Separator" +msgstr "Separador decimal" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__default_value +msgid "Default value" +msgstr "Valor por defecto" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__display_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__display_name +msgid "Display Name" +msgstr "Nombre para mostrar" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__decimal_separator__dot +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__dot +#, python-format +msgid "Dot (.)" +msgstr "Punto (.)" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "Error to set %(field_name)s with value %(value)s" +msgstr "Error al definir %(field_name)s con el valor %(value)s" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_mixin__extraction_mode +msgid "Extraction Mode" +msgstr "Modo de extracción" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__extraction_mode +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__extraction_mode +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__extraction_mode +msgid "Extraction mode" +msgstr "Modo de extracción" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_id +msgid "Field" +msgstr "Campo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__child_field_name +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_name +msgid "Field Name" +msgstr "Nombre del campo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_ttype +msgid "Field Type" +msgstr "Tipo de campo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__data_file +msgid "File" +msgstr "Archivo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__attachment_ids +msgid "Files" +msgstr "Archivos" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__value_type__fixed +#, python-format +msgid "Fixed" +msgstr "Fijo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__fixed_value +msgid "Fixed value" +msgstr "Valor fijo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__field_relation +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_relation +msgid "For relationship fields, the technical name of the target model" +msgstr "Para los campos relacionados, el nombre técnico del modelo de destino" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "Generated Documents" +msgstr "Documentos generados" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__time_format__-h:-m:-s +#, python-format +msgid "H:M:S" +msgstr "H:M:S" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__related_model__header +#, python-format +msgid "Header" +msgstr "Cabecera" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__header_items +msgid "Header Items" +msgstr "Elemento de cabecera" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template__header_items +msgid "Header columns separated by commas" +msgstr "Columnas de la cabecera separadas por comas" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__header_values +msgid "Header values" +msgstr "Valores de la cabecera" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__id +msgid "ID" +msgstr "ID" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Import" +msgstr "Importar" + +#. module: base_import_pdf_simple +#. openerp-web +#: code:addons/base_import_pdf_simple/static/src/xml/import_menu.xml:0 +#, python-format +msgid "Import PDF" +msgstr "Importar PDF" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.action_wizard_base_import_pdf_upload +msgid "Import PDFs" +msgstr "Importar PDFs" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,help:base_import_pdf_simple.field_base_import_pdf_template__auto_detect_pattern +msgid "" +"It will be necessary to set a patter that only finds something\n" +" in the documents for this template." +msgstr "" +"Será necesario definir un patrón que solo encuentre algo en los documentos " +"de esta plantilla." + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload____last_update +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__write_uid +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__write_date +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__write_date +msgid "Last Updated on" +msgstr "Última actualización" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__line_ids +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__line_ids +msgid "Line" +msgstr "Línea" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__related_model__lines +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +#, python-format +msgid "Lines" +msgstr "Líneas" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__lines_values +msgid "Lines values" +msgstr "Valores de las líneas" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__log_text +msgid "Log Text" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__log_distinct_value +msgid "Log distint value?" +msgstr "¿Guardar registro si el valor es distinto?" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-m--d--y +#, python-format +msgid "MM-dd-YY" +msgstr "MM-dd-YY" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-m/-d/-y +#, python-format +msgid "MM/dd/YY" +msgstr "MM/dd/YY" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__model_id +msgid "Main Model" +msgstr "Modelo principal" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__mapped_ids +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_line_view_form +msgid "Mapped" +msgstr "Mapeado" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__model +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__model +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload__model +msgid "Model" +msgstr "Modelo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template__name +msgid "Name" +msgstr "Nombre" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "" +"No template has been auto-detected from %s, it may be necessary to create a " +"new one." +msgstr "" +"No se ha auto-detectado plantilla para %s, quizás sea necesario crear una " +"nueva." + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__none +#, python-format +msgid "None" +msgstr "Nada" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py:0 +#, python-format +msgid "Odoo could not extract the text from the PDF." +msgstr "Odoo no puede extraer el texto del PDF." + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__origin +msgid "Origin" +msgstr "Origen" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__parent_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__parent_id +msgid "Parent" +msgstr "Padre" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__pattern +msgid "Pattern" +msgstr "Patrón" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.base_import_pdf_template_view_form +msgid "Preview" +msgstr "Previsualizar" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.action_wizard_base_import_pdf_preview +msgid "Preview PDF" +msgstr "Previsualizar PDF" + +#. module: base_import_pdf_simple +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template__extraction_mode__pypdf +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__wizard_base_import_pdf_preview__extraction_mode__pypdf +msgid "Pypdf" +msgstr "Pypdf" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__data +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_preview_form +msgid "RAW data" +msgstr "Datos RAW" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__field_relation +msgid "Related Model" +msgstr "Modelo relacionado" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__related_model +msgid "Related model" +msgstr "Modelo relacionado" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_id +msgid "Search field" +msgstr "Campo de búsqueda" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_name +msgid "Search field name" +msgstr "Nombre del campo de búsqueda" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_relation +msgid "Search field relation" +msgstr "Relación del campo de búsqueda" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_field_ttype +msgid "Search field type" +msgstr "Tipo de campo de búsqueda" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__search_subfield_id +msgid "Search subfield" +msgstr "Subcampo de búsqueda" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__thousand_separator__space +#, python-format +msgid "Space ( )" +msgstr "Espacio ( )" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__table_info +msgid "Table info" +msgstr "Información de la tabla" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_preview_form +msgid "Technical information" +msgstr "Información técnica" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__template_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__template_id +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_upload_line__template_id +msgid "Template" +msgstr "Plantilla" + +#. module: base_import_pdf_simple +#: model:ir.actions.act_window,name:base_import_pdf_simple.base_import_pdf_template_action +#: model:ir.ui.menu,name:base_import_pdf_simple.menu_base_import_pdf_template +msgid "Templates" +msgstr "Plantillas" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py:0 +#, python-format +msgid "There is no template that can be applied" +msgstr "No hay ninguna plantilla que pueda aplicar" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__thousand_separator +msgid "Thousand Separator" +msgstr "Separador de miles" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__time_format +msgid "Time Format" +msgstr "Formato de tiempo" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_wizard_base_import_pdf_preview__total_pages +msgid "Total pages" +msgstr "Total páginas" + +#. module: base_import_pdf_simple +#: model_terms:ir.ui.view,arch_db:base_import_pdf_simple.view_wizard_base_import_pdf_upload_form +msgid "Upload Files" +msgstr "Subir archivos" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line_mapped__value +msgid "Value" +msgstr "Valor" + +#. module: base_import_pdf_simple +#: model:ir.model.fields,field_description:base_import_pdf_simple.field_base_import_pdf_template_line__value_type +msgid "Value type" +msgstr "Tipo de valor" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__value_type__variable +#, python-format +msgid "Variable" +msgstr "Variable" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_mixin +msgid "Wizard Base Import Pdf Mixin" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_preview +msgid "Wizard Base Import Pdf Preview" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_upload +msgid "Wizard Base Import Pdf Upload" +msgstr "" + +#. module: base_import_pdf_simple +#: model:ir.model,name:base_import_pdf_simple.model_wizard_base_import_pdf_upload_line +msgid "Wizard Base Import Pdf upload Line" +msgstr "" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-y--d--m +#, python-format +msgid "YY-dd-MM" +msgstr "YY-dd-MM" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-y/-d/-m +#, python-format +msgid "YY/dd/MM" +msgstr "YY/dd/MM" + +#. module: base_import_pdf_simple +#: code:addons/base_import_pdf_simple/models/base_import_pdf_template.py:0 +#: model:ir.model.fields.selection,name:base_import_pdf_simple.selection__base_import_pdf_template_line__date_format__-d_-m_-y +#, python-format +msgid "dd.MM.YY" +msgstr "dd.MM.YY" diff --git a/base_import_pdf_simple/models/__init__.py b/base_import_pdf_simple/models/__init__.py new file mode 100644 index 00000000000..104223633cc --- /dev/null +++ b/base_import_pdf_simple/models/__init__.py @@ -0,0 +1 @@ +from . import base_import_pdf_template diff --git a/base_import_pdf_simple/models/base_import_pdf_template.py b/base_import_pdf_simple/models/base_import_pdf_template.py new file mode 100644 index 00000000000..386c9822b50 --- /dev/null +++ b/base_import_pdf_simple/models/base_import_pdf_template.py @@ -0,0 +1,407 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import re +from datetime import datetime + +from odoo import _, api, fields, models + + +class BaseImportPdfTemplate(models.Model): + _name = "base.import.pdf.template" + _description = "Base Import Pdf Template" + _order = "name desc" + + active = fields.Boolean(default=True) + name = fields.Char(required=True) + extraction_mode = fields.Selection( + selection=[("pypdf", "Pypdf")], + default="pypdf", + string="Extraction mode", + ) + model_id = fields.Many2one( + comodel_name="ir.model", + string="Main Model", + domain=[("transient", "=", False), ("model", "not like", "base.import.pdf")], + required=True, + ondelete="cascade", + ) + model = fields.Char(compute="_compute_model", store=True, compute_sudo=True) + child_field_id = fields.Many2one( + comodel_name="ir.model.fields", + string="Child Field", + domain="[('model_id.model', '=', model), ('ttype', '=', 'one2many')]", + ondelete="cascade", + ) + child_model = fields.Char( + compute="_compute_child_model", store=True, compute_sudo=True + ) + child_field_name = fields.Char(related="child_field_id.name") + auto_detect_pattern = fields.Char( + string="Auto detect pattern", + help="""It will be necessary to set a patter that only finds something + in the documents for this template.""", + ) + header_items = fields.Char(help="Header columns separated by commas") + company_id = fields.Many2one(comodel_name="res.company", string="Company") + line_ids = fields.One2many( + comodel_name="base.import.pdf.template.line", + inverse_name="template_id", + copy=True, + ) + + @api.depends("model_id") + def _compute_model(self): + for item in self.filtered("model_id"): + item.model = item.model_id.model + + @api.depends("child_field_id") + def _compute_child_model(self): + for item in self.filtered("child_field_id"): + item.child_model = item.child_field_id.relation + + def button_preview(self): + self.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "base_import_pdf_simple.action_wizard_base_import_pdf_preview" + ) + ctx = self._context.copy() + ctx.update({"default_extraction_mode": self.extraction_mode}) + action["context"] = ctx + return action + + def _get_items_from_model(self, model): + return self.search( + [ + ("company_id", "in", [False] + self.env.companies.ids), + ("model", "=", model), + ] + ) + + def _auto_detect_from_text(self, text): + """Look for if any of the templates find the pattern in the text.""" + for item in self.filtered("auto_detect_pattern"): + if text and re.findall(item.auto_detect_pattern, text): + return item + return False + + def _get_table_info(self, text): + """Convert table data to a readable dict.""" + res = False + if text and self.header_items: + res = {"header": self.header_items, "data": []} + data = self._get_table_info_data(text) + res["data"].extend(data) + return res + + def _get_table_info_data(self, text): + """Set table using lines: column + pattern (groups).""" + data = [] + data_map_column = {} + child_lines = self.line_ids.filtered( + lambda x: x.related_model == "lines" + and x.value_type != "fixed" + and x.pattern + ) + for child_line in child_lines: + data_column = [] + matches = re.finditer(child_line.pattern, text, re.MULTILINE) + for _matchNum, match in enumerate(matches, start=1): + match_group = match.groups(0)[0] + data_column.append(match_group.strip()) + data_map_column[int(child_line.column)] = data_column + # Convert data column to lines (table lines "split" in pages not supported) + data_keys = list(data_map_column.keys()) + data_key_0 = data_keys[0] + for x in range(len(data_map_column[data_key_0])): + line_data = [] + for data_key in data_keys: + total_items = len(data_map_column[data_key]) - 1 + if total_items >= x: + line_data.append(data_map_column[data_key][x]) + data.append(line_data) + return data + + def _prepare_ctx_from_model(self, model): + ctx = dict(self.env.context) + fixed_fields = self._get_fixed_fields_from_model(model) + for fixed_key in list(fixed_fields.keys()): + ctx_key = "default_%s" % fixed_key + fixed_value = fixed_fields[fixed_key] + ctx.update({ctx_key: fixed_value.id}) + return ctx + + def _get_fixed_fields_from_model(self, model): + res = {} + fixed_fields = self.line_ids.filtered( + lambda x: x.model == model and x.value_type == "fixed" + ) + for fixed_field in fixed_fields: + res[fixed_field.field_name] = fixed_field.fixed_value + return res + + def _get_field_header_values(self, text): + return self._get_field_values("header", text) + + def _get_field_child_values(self, table_info): + """Process the information from the _get_table_info() method..""" + res = [] + if table_info and table_info["data"]: + for data_line in table_info["data"]: + res_line = self._get_field_values_from_table_item(data_line) + if res_line: + res.append(res_line) + return res + + def _get_field_values_from_table_item(self, item): + res = False + child_lines = self.line_ids.filtered( + lambda x: x.related_model == "lines" + and x.value_type != "fixed" + and x.column + ) + if item and child_lines: + item_lenght = len(item) - 1 + res = {} + for child_line in child_lines: + column = int(child_line.column) + if item_lenght >= column and item[column]: + value = child_line._process_value(item[column]) + res[child_line.field_name] = value + return res + + def _get_field_values(self, related_model, text): + res = {} + for field in self.line_ids.filtered( + lambda x: x.related_model == related_model and x.value_type != "fixed" + ): + value = field._get_field_value(text) + if value: + res[field.field_name] = value + return res + + +class BaseImportPdfTemplateLine(models.Model): + _name = "base.import.pdf.template.line" + _description = "Base Import Pdf Template Line" + _order = "model asc, id" + + template_id = fields.Many2one( + comodel_name="base.import.pdf.template", + string="Template", + required=True, + ondelete="cascade", + ) + related_model = fields.Selection( + selection=[("header", _("Header")), ("lines", _("Lines"))], + default="header", + string="Related model", + ) + model = fields.Char(compute="_compute_model", store=True) + field_id = fields.Many2one( + comodel_name="ir.model.fields", + string="Field", + domain="[('model_id.model', '=', model), ('store', '=', True)]", + required=True, + ondelete="cascade", + ) + field_name = fields.Char(related="field_id.name") + field_ttype = fields.Selection(related="field_id.ttype") + field_relation = fields.Char(related="field_id.relation") + column = fields.Char() + pattern = fields.Char() + search_field_id = fields.Many2one( + comodel_name="ir.model.fields", + domain="[('model_id.model', '=', field_relation)]", + string="Search field", + ) + search_field_name = fields.Char( + related="search_field_id.name", string="Search field name" + ) + search_field_ttype = fields.Selection( + related="search_field_id.ttype", string="Search field type" + ) + search_field_relation = fields.Char( + related="search_field_id.relation", string="Search field relation" + ) + search_subfield_id = fields.Many2one( + comodel_name="ir.model.fields", + domain="[('model_id.model', '=', search_field_relation)]", + string="Search subfield", + ) + default_value = fields.Reference( + selection="_selection_reference_value", + string="Default value", + ) + date_format = fields.Selection( + selection=[ + ("*Y-*d-*m", _("YY-dd-MM")), + ("*m-*d-*Y", _("MM-dd-YY")), + ("*Y/*d/*m", _("YY/dd/MM")), + ("*m/*d/*Y", _("MM/dd/YY")), + ("*d.*m.*Y", _("dd.MM.YY")), + ], + ) + time_format = fields.Selection( + selection=[ + ("*H:*M:*S", _("H:M:S")), + ], + ) + decimal_separator = fields.Selection( + selection=[ + ("dot", "Dot (.)"), + ("comma", "Comma (,)"), + ], + default="dot", + ) + thousand_separator = fields.Selection( + selection=[ + ("none", _("None")), + ("space", _("Space ( )")), + ("dot", _("Dot (.)")), + ("comma", _("Comma (,)")), + ], + default="none", + ) + log_distinct_value = fields.Boolean( + string="Log distint value?", + help="""A note will be added with the previous value and the indicated value if + they are different.""", + ) + value_type = fields.Selection( + selection=[ + ("fixed", _("Fixed")), + ("variable", _("Variable")), + ], + default="variable", + string="Value type", + ) + fixed_value = fields.Reference( + selection="_selection_reference_value", + string="Fixed value", + ) + mapped_ids = fields.One2many( + comodel_name="base.import.pdf.template.line.mapped", + inverse_name="parent_id", + ) + + @api.model + def _selection_reference_value(self): + models = ( + self.env["ir.model"] + .sudo() + .search([("transient", "=", False)], order="name asc") + ) + return [(model.model, model.name) for model in models] + + @api.onchange("value_type") + def _onchange_value_type(self): + if self.value_type == "fixed" and self.field_relation: + record = self.env[self.field_relation].search([], limit=1) + self.fixed_value = "%s,%s" % (self.field_relation, record.id) + + @api.onchange("search_field_id") + def _onchange_search_field_id(self): + """Leave the search_subfield_id field empty to avoid inconsistencies.""" + self.search_subfield_id = False + + @api.depends("related_model") + def _compute_model(self): + for item in self: + item.model = ( + item.template_id.model + if item.related_model == "header" + else item.template_id.child_model + ) + + def _replace_text(self, text, letters, prefix): + for letter in letters: + text = text.replace(letter, prefix + letter) + text = text.replace(letter.upper(), prefix + letter.upper()) + return text + + def _process_datetime_value(self, value): + if self.field_ttype not in ("date", "datetime") or not self.date_format: + return value + date_format = self.date_format.replace("*", "%") + if self.field_ttype == "datetime": + time_format = self.time_format.replace("*", "%") + date_format += " " + time_format + datetime_object = datetime.strptime(value, date_format) + value = datetime_object.strftime("%Y-%m-%d %H:%M:%S") + return value + + def _process_float_value(self, value): + if self.field_ttype != "float": + return value + separator_mapped = {"dot": ".", "comma": ","} + if self.thousand_separator and self.thousand_separator != "none": + value = value.replace(separator_mapped[self.thousand_separator], "") + if self.decimal_separator: + value = value.replace(separator_mapped[self.decimal_separator], ".") + return float(value) + + def _get_record_search_from_value(self, value): + """This method can be overridden in some use cases if needed.""" + if not self.search_field_id: + return False + domain_field_name = self.search_field_name + if self.search_subfield_id: + domain_field_name += ".%s" % (self.search_subfield_id.name) + return self.env[self.field_relation].search( + [(domain_field_name, "=", value)], limit=1 + ) + + def _process_value(self, value): + # Apply pattern + if self.pattern: + new_value = re.findall(self.pattern, value) + if new_value: + value = new_value[0] + # Extra changes (dates or float) + if self.field_ttype in ("date", "datetime"): + value = self._process_datetime_value(value) + elif self.field_ttype == "float": + value = self._process_float_value(value) + # Apply mapping (if any is found, we return that) + mapped_items = self.mapped_ids.filtered(lambda x: x.origin == value) + if mapped_items: + return fields.first(mapped_items).value + # Search and return the record only if found + if self.search_field_id: + record = self._get_record_search_from_value(value) + if record: + return record + if self.default_value: + return self.default_value + return value + + def _get_field_value(self, text): + field_value = False + if self.pattern: + res = re.findall(self.pattern, text) + if res: + field_value = self._process_value(res[0]) + return field_value + + +class BaseImportPdfTemplateLineMapped(models.Model): + _name = "base.import.pdf.template.line.mapped" + _description = "Base Import Pdf Template Line Mapped" + _order = "value asc, id" + + parent_id = fields.Many2one( + comodel_name="base.import.pdf.template.line", required=True + ) + origin = fields.Char() + value = fields.Reference( + selection="_selection_reference_value", + ) + + @api.model + def _selection_reference_value(self): + models = ( + self.env["ir.model"] + .sudo() + .search([("transient", "=", False)], order="name asc") + ) + return [(model.model, model.name) for model in models] diff --git a/base_import_pdf_simple/readme/CONFIGURE.rst b/base_import_pdf_simple/readme/CONFIGURE.rst new file mode 100644 index 00000000000..3d90ecdb4cd --- /dev/null +++ b/base_import_pdf_simple/readme/CONFIGURE.rst @@ -0,0 +1,46 @@ +To configure a PDF document template for import, the first thing to do is to have the +document defined with a specific structure. + +#. Go to Settings > Technical > Base Import PDF Simple > Templates +#. Create a new template by entering a characteristic name and the model on which + the record will be generated. + + Fields to consider completing on template: + + - Main Model: model on which the record will be generated. Example: purchase.order + - Child field: One2many field that will create records from selected template. + Example: Order Lines (purchase.order) + - Auto detect pattern: Define a characteristic pattern of the document so that + it recognizes that it corresponds to the template we are creating. Need to use + regular expression. Example: (?<=ESA79935607)[\S\s]* + - Header Items: Complete this field if the template has a header table to extract + information lines. Example: Reference,Quantity,Price + - Company: Set the company that will use the template. If it is empty, template + will apply for all companies set on the environment. + +#. Add new lines. + + - Related model: When adding new line, the section where to locate the data; "header" + which, as its name indicates, refers to the header of the document and "lines" refers + to the structure of lines or table of the document. + - Field: Map the field to be completed. Example: product + - Pattern: Optional field to complete. Define pattern of the document so that it + recognizes the place to get the field selected on PDF template. Need to use regular + expression. Example: ([0-9]{7}) [0-7]{1} + - Value type: + - Fixed: Select this value, if the field mapped will always have an specific + value and not extract the information from template. In this case Pattern field + must be empty. + - Variable: Select variable to get the information from template. In this case, + Pattern field must be completed. + - For Value type "Variable" will appear extra fields to complete: + - Search value: Indicates the field by which the value obtained in the PDF will + be searched on the system. + - Default value: If the search result is empty for the search value option, you + can set default value to create a record and not getting error message. + - Log distint value?: This option is useful when getting prices in order to + compare prices inside system and prices obtained from PDF. This will create lines + with prices obtained from the system but create log on chatter to see the + differences obtained from PDF. + +Check demo data to further information. diff --git a/base_import_pdf_simple/readme/CONTRIBUTORS.rst b/base_import_pdf_simple/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..5fb71305308 --- /dev/null +++ b/base_import_pdf_simple/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Tecnativa `_: + + * Víctor Martínez + * Pedro M. Baeza diff --git a/base_import_pdf_simple/readme/DESCRIPTION.rst b/base_import_pdf_simple/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..11f6cd98d07 --- /dev/null +++ b/base_import_pdf_simple/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module allows you to import PDF files and generate records based on the data +contained in those PDF files. +It also allows you to define a pattern that indicates how to recognize and extract +the data from the PDF to generate a record. diff --git a/base_import_pdf_simple/readme/ROADMAP.rst b/base_import_pdf_simple/readme/ROADMAP.rst new file mode 100644 index 00000000000..6b540867ff1 --- /dev/null +++ b/base_import_pdf_simple/readme/ROADMAP.rst @@ -0,0 +1,36 @@ +- Add operator in template lines (= or ilike) +- Add support for selection fields as default value. +- Simplify auto-detection (defining a text only to search the system should search the + corresponding regular expression). +- Allow compatibility with registration process created from email alias (for purchase + order for example). +- Remove error if some file is not auto-detected template, options: boolean (default + option according to system parameter) to omit error for not found files or change + process to 2 steps, auto-detect and show lines (each one with respect to a file) with + template applied (similar to dms_auto_classification). +- Create test_base_import_pdf_simple module with sale, purchase and account dependencies + to leave templates created in runboat and tests more useful for testers. +- Display a more readable error if there is an error in Preview process, example: wrong + pattern. Message: "Please check template defined, some items are not correctly set". +- Add a progress bar (widget=“gauge”) in the import wizard process, useful if we import + for example sales orders with 20 lines and thus know the progress. + +Compatibility with csv, xls, etc: + +- Separate much of the logic to new module base_import_simple that would contain the logic + of templates, type of files (csv, excel, etc) in the templates and wizard and this module + would depend on the other adding only what relates to PDF. +- The base module should take into account for each template whether each line is a new + record or not, and start line (in case you want to omit any), only page 1 would be imported. +- The preview smart-btton would serve exactly the same purpose. +- In the case of csv and Excel that each record is a line, the document will NOT be attached + to the record. +- If you indicate that each record is a line the column will be the key, otherwise you must + specify to which line each line of the template refers. +- In the case of csv it will try to auto-detect the lines and columns (no need to complicate + delimiters configuration). +- The menu "Import PDF" of the favorite menu would become "Import file", and the allowed file + extensions would be those obtained from a method (it would be extended by other modules that + add other formats such as PDF). +- Add queue_job_base_import_simple module to process everything by queues (example: Excel + with hundreds of lines, each one a record). diff --git a/base_import_pdf_simple/readme/USAGE.rst b/base_import_pdf_simple/readme/USAGE.rst new file mode 100644 index 00000000000..c616a1a61fc --- /dev/null +++ b/base_import_pdf_simple/readme/USAGE.rst @@ -0,0 +1,6 @@ +This module allows to upload PDF files in any Odoo model. It processes each of the files +and converts it into a new record. +Technically, the pdf is transformed into text and that text is processed to create the +record. +The module incorporates an option in Favorites Import PDF and Template configuration in +order to recognized any document structure. diff --git a/base_import_pdf_simple/security/ir.model.access.csv b/base_import_pdf_simple/security/ir.model.access.csv new file mode 100644 index 00000000000..9a2ed5f8a07 --- /dev/null +++ b/base_import_pdf_simple/security/ir.model.access.csv @@ -0,0 +1,10 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_base_import_pdf_template_user,base.import.pdf.template,model_base_import_pdf_template,base.group_user,1,0,0,0 +access_base_import_pdf_template_admin,base.import.pdf.template,model_base_import_pdf_template,base.group_no_one,1,1,1,1 +access_base_import_pdf_template_line_user,base.import.pdf.template.line,model_base_import_pdf_template_line,base.group_user,1,0,0,0 +access_base_import_pdf_template_line_admin,base.import.pdf.template.line,model_base_import_pdf_template_line,base.group_no_one,1,1,1,1 +access_base_import_pdf_template_line_mapped_user,base.import.pdf.template.line.mapped,model_base_import_pdf_template_line_mapped,base.group_user,1,0,0,0 +access_base_import_pdf_template_line_mapped_admin,base.import.pdf.template.line.mapped,model_base_import_pdf_template_line_mapped,base.group_no_one,1,1,1,1 +access_wizard_base_import_pdf_preview,wizard.base.import.pdf.preview,model_wizard_base_import_pdf_preview,base.group_no_one,1,1,1,1 +access_wizard_base_import_pdf_upload,wizard.base.import.pdf.upload,model_wizard_base_import_pdf_upload,base.group_user,1,1,1,1 +access_wizard_base_import_pdf_upload_line,wizard.base.import.pdf.upload.line,model_wizard_base_import_pdf_upload_line,base.group_user,1,1,1,1 diff --git a/base_import_pdf_simple/security/security.xml b/base_import_pdf_simple/security/security.xml new file mode 100644 index 00000000000..f151c334ac4 --- /dev/null +++ b/base_import_pdf_simple/security/security.xml @@ -0,0 +1,17 @@ + + + + Base Import Pdf Template usage multi-company + + + [('company_id', 'in', [False] + company_ids)] + + + Base Import Pdf Template Line usage multi-company + + + [('template_id.company_id', 'in', [False] + company_ids)] + + diff --git a/base_import_pdf_simple/static/description/icon.png b/base_import_pdf_simple/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/base_import_pdf_simple/static/description/index.html b/base_import_pdf_simple/static/description/index.html new file mode 100644 index 00000000000..68650a7fe94 --- /dev/null +++ b/base_import_pdf_simple/static/description/index.html @@ -0,0 +1,546 @@ + + + + + + +Base Import Pdf Simple + + + +
+

Base Import Pdf Simple

+ + +

Beta License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runboat

+

This module allows you to import PDF files and generate records based on the data +contained in those PDF files. +It also allows you to define a pattern that indicates how to recognize and extract +the data from the PDF to generate a record.

+

Table of contents

+ +
+

Configuration

+

To configure a PDF document template for import, the first thing to do is to have the +document defined with a specific structure.

+
    +
  1. Go to Settings > Technical > Base Import PDF Simple > Templates
  2. +
  3. Create a new template by entering a characteristic name and the model on which +the record will be generated.
  4. +
+
+

Fields to consider completing on template:

+
+
    +
  • Main Model: model on which the record will be generated. Example: purchase.order
  • +
  • Child field: One2many field that will create records from selected template. +Example: Order Lines (purchase.order)
  • +
  • Auto detect pattern: Define a characteristic pattern of the document so that +it recognizes that it corresponds to the template we are creating. Need to use +regular expression. Example: (?<=ESA79935607)[Ss]*
  • +
  • Header Items: Complete this field if the template has a header table to extract +information lines. Example: Reference,Quantity,Price
  • +
  • Company: Set the company that will use the template. If it is empty, template +will apply for all companies set on the environment.
  • +
+
+
+
    +
  1. Add new lines.
  2. +
+
+
    +
  • Related model: When adding new line, the section where to locate the data; “header” +which, as its name indicates, refers to the header of the document and “lines” refers +to the structure of lines or table of the document.
  • +
  • Field: Map the field to be completed. Example: product
  • +
  • Pattern: Optional field to complete. Define pattern of the document so that it +recognizes the place to get the field selected on PDF template. Need to use regular +expression. Example: ([0-9]{7}) [0-7]{1}
  • +
  • +
    Value type:
    +
      +
    • Fixed: Select this value, if the field mapped will always have an specific +value and not extract the information from template. In this case Pattern field +must be empty.
    • +
    • Variable: Select variable to get the information from template. In this case, +Pattern field must be completed.
    • +
    +
    +
    +
  • +
  • For Value type “Variable” will appear extra fields to complete:
  • +
  • Search value: Indicates the field by which the value obtained in the PDF will +be searched on the system.
  • +
  • Default value: If the search result is empty for the search value option, you +can set default value to create a record and not getting error message.
  • +
  • Log distint value?: This option is useful when getting prices in order to +compare prices inside system and prices obtained from PDF. This will create lines +with prices obtained from the system but create log on chatter to see the +differences obtained from PDF.
  • +
+
+

Check demo data to further information.

+
+
+

Usage

+

This module allows to upload PDF files in any Odoo model. It processes each of the files +and converts it into a new record. +Technically, the pdf is transformed into text and that text is processed to create the +record. +The module incorporates an option in Favorites Import PDF and Template configuration in +order to recognized any document structure.

+
+
+

Known issues / Roadmap

+
    +
  • Add operator in template lines (= or ilike)
  • +
  • Add support for selection fields as default value.
  • +
  • Simplify auto-detection (defining a text only to search the system should search the +corresponding regular expression).
  • +
  • Allow compatibility with registration process created from email alias (for purchase +order for example).
  • +
  • Remove error if some file is not auto-detected template, options: boolean (default +option according to system parameter) to omit error for not found files or change +process to 2 steps, auto-detect and show lines (each one with respect to a file) with +template applied (similar to dms_auto_classification).
  • +
  • Create test_base_import_pdf_simple module with sale, purchase and account dependencies +to leave templates created in runboat and tests more useful for testers.
  • +
  • Display a more readable error if there is an error in Preview process, example: wrong +pattern. Message: “Please check template defined, some items are not correctly set”.
  • +
  • Add a progress bar (widget=“gauge”) in the import wizard process, useful if we import +for example sales orders with 20 lines and thus know the progress.
  • +
+

Compatibility with csv, xls, etc:

+
    +
  • Separate much of the logic to new module base_import_simple that would contain the logic +of templates, type of files (csv, excel, etc) in the templates and wizard and this module +would depend on the other adding only what relates to PDF.
  • +
  • The base module should take into account for each template whether each line is a new +record or not, and start line (in case you want to omit any), only page 1 would be imported.
  • +
  • The preview smart-btton would serve exactly the same purpose.
  • +
  • In the case of csv and Excel that each record is a line, the document will NOT be attached +to the record.
  • +
  • If you indicate that each record is a line the column will be the key, otherwise you must +specify to which line each line of the template refers.
  • +
  • In the case of csv it will try to auto-detect the lines and columns (no need to complicate +delimiters configuration).
  • +
  • The menu “Import PDF” of the favorite menu would become “Import file”, and the allowed file +extensions would be those obtained from a method (it would be extended by other modules that +add other formats such as PDF).
  • +
  • Add queue_job_base_import_simple module to process everything by queues (example: Excel +with hundreds of lines, each one a record).
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Víctor Martínez
    • +
    • Pedro M. Baeza
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

victoralmau

+

This module is part of the OCA/edi project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/base_import_pdf_simple/static/src/js/import_menu.js b/base_import_pdf_simple/static/src/js/import_menu.js new file mode 100644 index 00000000000..963a1e5d4a4 --- /dev/null +++ b/base_import_pdf_simple/static/src/js/import_menu.js @@ -0,0 +1,35 @@ +odoo.define("base_import_pdf_simple.BaseImportPdfSimpleMenu", function (require) { + "use strict"; + + const FavoriteMenu = require("web.FavoriteMenu"); + const {useModel} = require("web.Model"); + const {Component} = owl; + + class BaseImportPdfSimpleMenu extends Component { + constructor() { + super(...arguments); + this.model = useModel("searchModel"); + } + openWizardBaseImportPdfUpload() { + const ctx = this.model.config.context; + ctx.default_model = this.model.config.modelName; + this.trigger("do-action", { + action: "base_import_pdf_simple.action_wizard_base_import_pdf_upload", + options: { + additional_context: ctx, + }, + }); + } + static shouldBeDisplayed() { + return true; + } + } + BaseImportPdfSimpleMenu.props = {}; + BaseImportPdfSimpleMenu.template = "BaseImportPdfSimple.ImportRecords"; + FavoriteMenu.registry.add( + "base-import-pdf-simple-menu", + BaseImportPdfSimpleMenu, + 1 + ); + return BaseImportPdfSimpleMenu; +}); diff --git a/base_import_pdf_simple/static/src/xml/import_menu.xml b/base_import_pdf_simple/static/src/xml/import_menu.xml new file mode 100644 index 00000000000..3b42245de12 --- /dev/null +++ b/base_import_pdf_simple/static/src/xml/import_menu.xml @@ -0,0 +1,11 @@ + + + + + Import PDF + + + diff --git a/base_import_pdf_simple/tests/__init__.py b/base_import_pdf_simple/tests/__init__.py new file mode 100644 index 00000000000..958416ba4dc --- /dev/null +++ b/base_import_pdf_simple/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from . import test_sale_import_pdf_simple diff --git a/base_import_pdf_simple/tests/data/res-partner.pdf b/base_import_pdf_simple/tests/data/res-partner.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3df9f6ea8dd6613daa57c435fc481c4b24654eb GIT binary patch literal 13556 zcmeI3Wn7fq*6?W%5K&S<7`kJI35M6#0hLBXQ3(l2Q9=+9L_m-b z=~g=5Yd~(jZ_jhic|JVf-uW@J=UUfVd+om1|Fwr*OJ0#5EC3}2gM>hC)=tFY;>3b_ zj@~XP5ZB=mH?g3cC(6p(%@cEl<%i{r<$&dl<%;D3e7a$|W7%TaVS%uOumrHcSPFC9$Bc49MHl2lGV(BqSgNAr`c;0)YjDfFjGBq%tbH zAUi7;uVWo6FK3XTrW+ddD|_hA>`eu=th~KZo@kJuJj&P62Bo7UYkpb=ky9lhkf4#V z2?zoe5D_|lfk4Cvh=_nN67vFqg#I!?{yP)65JCX<#ey!|*Tz*>rmFuxXpd&DMHQa20v8eBbI=xGLTrDrqaX0?n zGqAr}4wzNPZZP(kT#ozVrxQ6hv^UT-fIUVZaH50qa`W-D0g3@eoJeHQXg7?)&*7kf zvURkQb@KAHh34FGt;eEl`* zkFV`d1xlV)0hla*&AMZAC0#2JX22cS6R63_)B!Tas44Y!iTr-|fV_!aZ(<}WYDW&}SMWZEX-jpPfWR};;? zpuQ)rq&1n~Fh(Zma*@?$A&$Q?mAARfO?+~Unf)O+Ay#s2k&~6x#ads7 zz`|Is+#&mxFDLa`{(^Fm^VfzGlc`N#IU4cFk7qGinuPYeOQMSgsYg@pHLX3#!e=#- z`{?L6WQ%-T(tM`$2F2!mip%E
umo+#)28gZ_aD&$#?!4$;|m5Qh*?c+sJtvepq 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 + + + base.import.pdf.template.line.tree + base.import.pdf.template.line + + + + + + + + + + + base.import.pdf.template.line.form + base.import.pdf.template.line + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/base_import_pdf_simple/views/base_import_pdf_template_views.xml b/base_import_pdf_simple/views/base_import_pdf_template_views.xml new file mode 100644 index 00000000000..70735bbb273 --- /dev/null +++ b/base_import_pdf_simple/views/base_import_pdf_template_views.xml @@ -0,0 +1,110 @@ + + + + base.import.pdf.template.search + base.import.pdf.template + + + + + + + + + base.import.pdf.template.tree + base.import.pdf.template + + + + + + + + + + + base.import.pdf.template.form + base.import.pdf.template + +
+ +
+
+ +
+ Name +

+ +

+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + Templates + ir.actions.act_window + base.import.pdf.template + tree,form + + + +
diff --git a/base_import_pdf_simple/wizards/__init__.py b/base_import_pdf_simple/wizards/__init__.py new file mode 100644 index 00000000000..263ade838e8 --- /dev/null +++ b/base_import_pdf_simple/wizards/__init__.py @@ -0,0 +1,3 @@ +from . import wizard_base_import_pdf_mixin +from . import wizard_base_import_pdf_preview +from . import wizard_base_import_pdf_upload diff --git a/base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py b/base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py new file mode 100644 index 00000000000..4c954df2e4d --- /dev/null +++ b/base_import_pdf_simple/wizards/wizard_base_import_pdf_mixin.py @@ -0,0 +1,65 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import base64 +import logging +from tempfile import NamedTemporaryFile + +import pypdf + +from odoo import _, fields, models +from odoo.exceptions import UserError + +logger = logging.getLogger(__name__) + + +class WizardBaseImportPdfMixin(models.AbstractModel): + _name = "wizard.base.import.pdf.mixin" + _description = "Wizard Base Import Pdf Mixin" + + extraction_mode = fields.Char() + attachment_id = fields.Many2one(comodel_name="ir.attachment") + + def _pdf_text_extraction_pypdf(self, fileobj): + res = False + try: + res = [] + reader = pypdf.PdfReader(fileobj.name) + for pdf_page in reader.pages: + res.append(pdf_page.extract_text()) + logger.info("Text extraction made with pypdf") + except Exception as e: + logger.warning("Text extraction with pypdf failed. Error: %s", e) + return res + + def simple_pdf_text_extraction(self, file_data): + fileobj = NamedTemporaryFile("wb", prefix="odoo-simple-pdf-", suffix=".pdf") + fileobj.write(file_data) + method = "_pdf_text_extraction_%s" % self.extraction_mode + res = False + if hasattr(self, method): + res = getattr(self, method)(fileobj) + fileobj.close() + if not res: + raise UserError(_("Odoo could not extract the text from the PDF.")) + return res + + def _fallback_parse_pdf(self, file_data): + return self.simple_pdf_text_extraction(file_data) + + def _parse_pdf(self, data=False): + file_data = base64.b64decode(data or self.attachment_id.datas) + parsed_item = self._fallback_parse_pdf(file_data) + return parsed_item or {} + + def _parse_pdf_grouped(self): + """In some cases we need to get the text in all possible extraction modes + (e.g. when we do not know which template to apply).""" + res = {} + extraction_modes = dict( + self._fields["extraction_mode"]._description_selection(self.env) + ) + file_data = base64.b64decode(self.attachment_id.datas) + for extraction_mode in list(extraction_modes.keys()): + self.extraction_mode = extraction_mode + res[extraction_mode] = self._fallback_parse_pdf(file_data) + return res diff --git a/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview.py b/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview.py new file mode 100644 index 00000000000..97ba7a24d6f --- /dev/null +++ b/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview.py @@ -0,0 +1,54 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class WizardBaseImportPdfPreview(models.TransientModel): + _name = "wizard.base.import.pdf.preview" + _description = "Wizard Base Import Pdf Preview" + _inherit = "wizard.base.import.pdf.mixin" + + data_file = fields.Binary(string="File", attachment=True) + data = fields.Text(string="RAW data", readonly=True) + total_pages = fields.Integer(string="Total pages", readonly=True) + template_id = fields.Many2one( + comodel_name="base.import.pdf.template", readonly=True + ) + extraction_mode = fields.Selection( + selection=[("pypdf", "Pypdf")], + default="pypdf", + string="Extraction mode", + ) + header_values = fields.Text(string="Header values", readonly=True) + table_info = fields.Text(string="Table info", readonly=True) + lines_values = fields.Text(string="Lines values", readonly=True) + + @api.model + def default_get(self, fields): + res = super().default_get(fields) + if self.env.context.get("active_model") == "base.import.pdf.template": + res["template_id"] = self.env.context.get("active_id") + return res + + @api.onchange("data_file", "extraction_mode") + def _onchange_attachment_ids(self): + text = False + total_pages = 0 + if self.data_file: + data = self._parse_pdf(self.data_file) + total_pages = len(data) + text = "".join(data) + self.data = text + self.total_pages = total_pages + # Set header_values, table_info and lines_values + header_values = False + table_info = False + lines_values = False + if text and self.template_id: + header_values = self.template_id._get_field_header_values(text) + table_info = self.template_id._get_table_info(text) + lines_values = self.template_id._get_field_child_values(table_info) + self.header_values = header_values + self.table_info = table_info + self.lines_values = lines_values diff --git a/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview_views.xml b/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview_views.xml new file mode 100644 index 00000000000..b52071f76d1 --- /dev/null +++ b/base_import_pdf_simple/wizards/wizard_base_import_pdf_preview_views.xml @@ -0,0 +1,46 @@ + + + + + wizard.base.import.pdf.preview + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + Preview PDF + wizard.base.import.pdf.preview + form + new + +
diff --git a/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py b/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py new file mode 100644 index 00000000000..aba9b48275b --- /dev/null +++ b/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload.py @@ -0,0 +1,210 @@ +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tests import Form + +logger = logging.getLogger(__name__) + + +class WizardBaseImportPdfUpload(models.TransientModel): + _name = "wizard.base.import.pdf.upload" + _description = "Wizard Base Import Pdf Upload" + + model = fields.Char() + attachment_ids = fields.Many2many(comodel_name="ir.attachment", string="Files") + allowed_template_ids = fields.Many2many( + comodel_name="base.import.pdf.template", compute="_compute_allowed_template_ids" + ) + data = fields.Text(compute="_compute_data", store=True) + line_ids = fields.One2many( + comodel_name="wizard.base.import.pdf.upload.line", + inverse_name="parent_id", + ) + + @api.depends("model") + def _compute_allowed_template_ids(self): + template_model = self.env["base.import.pdf.template"] + for item in self: + item.allowed_template_ids = template_model._get_items_from_model(item.model) + + @api.depends("line_ids") + def _compute_data(self): + for item in self: + data = "" + for line in item.line_ids: + data += line.data if line.data else "" + item.data = data + + def action_process(self): + """Creamos las lineas, auto-detección + procesar cada línea.""" + if not self.allowed_template_ids: + raise UserError(_("There is no template that can be applied")) + lines = [] + for attachment in self.attachment_ids: + lines.append((0, 0, {"attachment_id": attachment.id})) + self.line_ids = lines + # Error si corresponde + lines_without_template = self.line_ids.filtered(lambda x: not x.template_id) + if lines_without_template: + raise UserError( + _( + "No template has been auto-detected from %s, it may be " + "necessary to create a new one." + ) + % fields.first(lines_without_template).attachment_id.name + ) + # Process + return records + records = self.env[self.model] + for line in self.line_ids: + records += line.action_process() + action = { + "type": "ir.actions.act_window", + "res_model": records._name, + "context": self.env.context, + } + if len(records) == 1: + action.update( + { + "views": [(False, "form")], + "view_mode": "form", + "res_id": records.id, + } + ) + else: + action.update( + { + "name": _("Generated Documents"), + "views": [(False, "tree"), (False, "form")], + "view_mode": "tree,form", + "domain": [("id", "in", records.ids)], + } + ) + return action + + +class WizardBaseImportPdfUploadLine(models.TransientModel): + _name = "wizard.base.import.pdf.upload.line" + _description = "Wizard Base Import Pdf upload Line" + _inherit = "wizard.base.import.pdf.mixin" + + parent_id = fields.Many2one(comodel_name="wizard.base.import.pdf.upload") + data = fields.Text() + template_id = fields.Many2one( + comodel_name="base.import.pdf.template", + string="Template", + compute="_compute_template_id", + store=True, + ) + extraction_mode = fields.Selection(related="template_id.extraction_mode") + log_text = fields.Text() + + @api.depends("attachment_id") + def _compute_template_id(self): + self.template_id = False + for item in self.filtered("attachment_id"): + data = item._parse_pdf_grouped() + text = "" + for key in list(data.keys()): + text += "".join(data[key]) + item.template_id = ( + item.parent_id.allowed_template_ids._auto_detect_from_text(text) + ) + + def action_process(self): + """Parse the file again, this time with the corresponding extraction mode.""" + self.extraction_mode = self.template_id.extraction_mode + data = self._parse_pdf() + self.data = "".join(data) + record = self._process_form() + self.attachment_id.write({"res_model": record._name, "res_id": record.id}) + return record + + def _process_set_value_form(self, _form, field_name, value): + old_value = getattr(_form, field_name) + model_name = self.env.context.get("model_name") + related_model = self.env.context.get("related_model") + template_line = self.template_id.line_ids.filtered( + lambda x: x.model == model_name + and x.field_name == field_name + and x.related_model == related_model + ) + if not template_line: + return + if template_line.log_distinct_value: + if old_value and old_value != value: + old_value_data = ( + old_value.display_name + if isinstance(old_value, models.Model) + else old_value + ) + new_value_data = ( + value.display_name if isinstance(value, models.Model) else value + ) + if not self.log_text: + self.log_text = "" + self.log_text += ( + _( + """

%(item_name)s has been set with %(new_value)s instead of + %(old_value)s

""" + ) + % { + "item_name": getattr(_form, "name"), # noqa: B009 + "old_value": old_value_data, + "new_value": new_value_data, + } + ) + else: + try: + setattr(_form, field_name, value) + except Exception: + if not self.log_text: + self.log_text = "" + self.log_text += _( + "Error to set %(field_name)s with value %(value)s" + ) % { + "field_name": field_name, + "value": value, + } + + def _process_form(self): + """Create record with Form() according to text.""" + # text = "".join(data) + text = self.data + template = self.template_id + model = self.env[template.model] + ctx = template._prepare_ctx_from_model(template.model) + model_form = Form(model.with_context(**ctx)) + # Set the values of the header in Form + header_values = template._get_field_header_values(text) + for field_name in list(header_values.keys()): + field_data = header_values[field_name] + self.with_context( + model_name=template.model, related_model="header" + )._process_set_value_form(model_form, field_name, field_data) + # Repeat the process for lines + table_info = template._get_table_info(text) + lines_values = template._get_field_child_values(table_info) + for line in lines_values: + child_line = getattr(model_form, template.child_field_name) + with child_line.new() as line_form: + # Fixed values (it is not possible to set context to lines) + child_fixed_values = template._get_fixed_fields_from_model( + template.child_model + ) + for field_name in list(child_fixed_values.keys()): + setattr(line_form, field_name, child_fixed_values[field_name]) + # et the values of any line + for field_name in list(line.keys()): + self.with_context( + model_name=template.child_model, related_model="lines" + )._process_set_value_form(line_form, field_name, line[field_name]) + try: + record = model_form.save() + except (AssertionError) as err: + raise UserError(err) from err + if self.log_text: + record._message_log(body=self.log_text) + return record diff --git a/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload_views.xml b/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload_views.xml new file mode 100644 index 00000000000..1f3c2edb9c7 --- /dev/null +++ b/base_import_pdf_simple/wizards/wizard_base_import_pdf_upload_views.xml @@ -0,0 +1,38 @@ + + + + + wizard.base.import.pdf.upload + +
+ + + + +
+
+
+
+
+ + Import PDFs + wizard.base.import.pdf.upload + form + new + +
diff --git a/requirements.txt b/requirements.txt index cc77741e469..dfbdc0276ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # generated from manifests external_dependencies factur-x +pypdf PyPDF2 PyYAML diff --git a/setup/base_import_pdf_simple/odoo/addons/base_import_pdf_simple b/setup/base_import_pdf_simple/odoo/addons/base_import_pdf_simple new file mode 120000 index 00000000000..18328181dab --- /dev/null +++ b/setup/base_import_pdf_simple/odoo/addons/base_import_pdf_simple @@ -0,0 +1 @@ +../../../../base_import_pdf_simple \ No newline at end of file diff --git a/setup/base_import_pdf_simple/setup.py b/setup/base_import_pdf_simple/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/base_import_pdf_simple/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)