From 52642ffd59a9380f8c39058fc93908bf85af3b47 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 5 Aug 2016 15:19:46 +0200 Subject: [PATCH 001/104] Extract regular PDF invoice import from account_invoice_import to a dedicated module account_invoice_import_invoice2data Update README.rst and headers to latest OCA conventions. --- .../README.rst | 117 ++++++++++++++++++ .../__init__.py | 3 + .../__openerp__.py | 19 +++ .../demo/demo_data.xml | 37 ++++++ .../tests/__init__.py | 6 + .../tests/pdf/invoice_free_fiber_201507.pdf | Bin 0 -> 120150 bytes .../tests/test_invoice_import.py | 106 ++++++++++++++++ .../wizard/__init__.py | 3 + .../wizard/account_invoice_import.py | 65 ++++++++++ 9 files changed, 356 insertions(+) create mode 100644 account_invoice_import_invoice2data/README.rst create mode 100644 account_invoice_import_invoice2data/__init__.py create mode 100644 account_invoice_import_invoice2data/__openerp__.py create mode 100644 account_invoice_import_invoice2data/demo/demo_data.xml create mode 100644 account_invoice_import_invoice2data/tests/__init__.py create mode 100644 account_invoice_import_invoice2data/tests/pdf/invoice_free_fiber_201507.pdf create mode 100644 account_invoice_import_invoice2data/tests/test_invoice_import.py create mode 100644 account_invoice_import_invoice2data/wizard/__init__.py create mode 100644 account_invoice_import_invoice2data/wizard/account_invoice_import.py diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst new file mode 100644 index 0000000000..154527443a --- /dev/null +++ b/account_invoice_import_invoice2data/README.rst @@ -0,0 +1,117 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=================================== +Account Invoice Import Invoice2data +=================================== + +This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. + +To know the full story behind the development of this module, read this `blog post `_. + +Installation +============ + +This module requires the Python library *invoice2data* available on `Github `_. + +To install the right version of the library, run: + +.. code:: + + sudo pip install git+https://github.com/m3nu/invoice2data.git + +In order to use a recent version of invoice2data on Odoo v8, you need an Odoo v8 dated after January 29th 2016, so that it contains `this fix `_ which fixes `this bug `_. + +The invoice2data library requires the latest version of the *pdftotext* utility, which is not yet packaged in Debian/Ubuntu. So you should download it from `the FTP server of Foolabs `_ then uncompress it and copy the file *bin64/pdftotext* to */usr/local/bin*. + +If you want the invoice2data library to fallback on OCR if the PDF doesn't contain text (only a very small minority of PDF invoices are image-based and require OCR), you should also install `Imagemagick `_ (to get the *convert* utility to convert PDF to TIFF) and `Tesseract OCR `_ : + +.. code:: + + sudo apt-get install imagemagick tesseract-ocr + +If you want to use custom invoice templates for the invoice2data lib (instead of the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as: + +.. code:: + + invoice2data_templates_dir = /opt/invoice2data_local_templates + +and store your invoice templates in YAML format (*.yml* extension) in the directory that you have configured above. If you add invoice tempates in this directory, you don't have to restart Odoo, they will be used automatically on the next invoice import. + +French users should also install the module *l10n_fr_invoice_import* available in the `French localization `_, cf `this PR `_. + +Configuration +============= + +Go to the form view of the supplier and configure it with the following parameters: + +* *is a Company ?* is True +* *Supplier* is True +* the *TIN* (i.e. VAT number) is set (the VAT number is used by default when searching the supplier in the Odoo partner database) +* in the *Accounting* tab, create an *Invoice Import Configuration*. + +For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. + +Here are some hints to help you add a template for your supplier: + +* Take `Free SAS template file `_ as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf + +* Try to run the invoice2data library manually on the sample invoice of Free: + +.. code:: + + % python -m invoice2data.main --debug invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf + +On the output, you will get first the text of the PDF, then some debug info on the parsing of the invoice and the regexps, and, on the last line, you will have the dict that contain the result of the parsing. + +* if the VAT number of the supplier is present in the text of the PDF invoice, I think it's a good idea to use it as the keyword. It is a good practice to add 2 other keywoards: one for the language (for example, match on the word *Invoice* in the language of the invoice) and one for the currency, so as to match only the invoices of that supplier in this particular language and currency. + +* the list of *fields* should contain the following entries: + + * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_invoice_pdf_import* is installed, add a 'siren' key) + * 'amount' ('amount' is the total amount with taxes) + * 'amount_untaxed' or 'amount_tax' (one or the other, no need for both) + * 'date': the date of the invoice + * 'invoice_number' + * 'date_due', if this information is available in the text of the PDF file. + +Usage +===== + +Refer to the usage section of the module *account_invoice_import*. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/95/8.0 + +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 smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Alexis de Lattre + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/account_invoice_import_invoice2data/__init__.py b/account_invoice_import_invoice2data/__init__.py new file mode 100644 index 0000000000..3b4c3edf09 --- /dev/null +++ b/account_invoice_import_invoice2data/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import wizard diff --git a/account_invoice_import_invoice2data/__openerp__.py b/account_invoice_import_invoice2data/__openerp__.py new file mode 100644 index 0000000000..72e70ce2ef --- /dev/null +++ b/account_invoice_import_invoice2data/__openerp__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# © 2015-2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Account Invoice Import Invoice2data', + 'version': '8.0.1.0.0', + 'category': 'Accounting & Finance', + 'license': 'AGPL-3', + 'summary': 'Import supplier invoices using the invoice2data lib', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'http://www.akretion.com', + 'depends': ['account_invoice_import'], + 'external_dependencies': {'python': ['invoice2data']}, + 'data': [], + 'demo': ['demo/demo_data.xml'], + 'images': ['images/sshot-wizard1.png'], + 'installable': True, +} diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml new file mode 100644 index 0000000000..b203509a45 --- /dev/null +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -0,0 +1,37 @@ + + + + + + + Internet Access + + + service + + + + + + Free + 1line_static_product + Fiber optic access at the main office + + + + + Free SAS + + + + 8 rue de la Ville l’Évèque + 75008 + Paris + + http://www.free.fr + FR 604 219 388 61 + + + + + diff --git a/account_invoice_import_invoice2data/tests/__init__.py b/account_invoice_import_invoice2data/tests/__init__.py new file mode 100644 index 0000000000..53a91fdbc0 --- /dev/null +++ b/account_invoice_import_invoice2data/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# The test below works well, but the full installation of invoice2data +# with the special version of the pdftotext binary is difficult +# and I don't know how to put it in the configuration of Travis +from . import test_invoice_import diff --git a/account_invoice_import_invoice2data/tests/pdf/invoice_free_fiber_201507.pdf b/account_invoice_import_invoice2data/tests/pdf/invoice_free_fiber_201507.pdf new file mode 100644 index 0000000000000000000000000000000000000000..adc46f510f651a589cf0a6127db103374ffa3d0f GIT binary patch literal 120150 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�X7mx+C$K0%D3_wa0P?o=9|z;;&v%)LR@V%- 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

wiB*!r&vlT|Kp#d!|N5JC&La&ft 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 Tj{}P*5BXx>P4Fo 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%0A9ratVpLO!?-4@8G7zEnPY0v7>7z>klqe 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 literal 0 HcmV?d00001 diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py new file mode 100644 index 0000000000..773b50fb0a --- /dev/null +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# © 2015-2016 Akretion France (www.akretion.com) +# @author: Alexis de Lattre +# The licence is in the file __openerp__.py + +from openerp.tests.common import TransactionCase +import base64 +from openerp.tools import file_open, float_compare + + +class TestInvoiceImport(TransactionCase): + + def setUp(self): + super(TestInvoiceImport, self).setUp() + frtax = self.env['account.tax'].create({ + 'name': 'French VAT purchase 20.0%', + 'description': 'FR-VAT-buy-20.0', + 'amount': 0.2, + 'type': 'percent', + 'account_collected_id': self.env.ref('account.a_expense').id, + 'account_paid_id': self.env.ref('account.a_expense').id, + 'base_sign': -1, + 'tax_sign': -1, + 'type_tax_use': 'purchase', + }) + # Set this tax on Internet access product + internet_product = self.env.ref( + 'account_invoice_import_invoice2data.internet_access') + internet_product.supplier_taxes_id = [(6, 0, [frtax.id])] + + def test_import_free_invoice(self): + f = file_open( + 'account_invoice_import_invoice2data/tests/pdf/' + 'invoice_free_fiber_201507.pdf', + 'rb') + pdf_file = f.read() + wiz = self.env['account.invoice.import'].create({ + 'invoice_file': base64.b64encode(pdf_file), + 'invoice_filename': 'invoice_free_fiber_201507.pdf', + }) + f.close() + wiz.import_invoice() + # Check result of invoice creation + invoices = self.env['account.invoice'].search([ + ('state', '=', 'draft'), + ('type', '=', 'in_invoice'), + ('supplier_invoice_number', '=', '562044387') + ]) + self.assertEquals(len(invoices), 1) + inv = invoices[0] + self.assertEquals(inv.type, 'in_invoice') + self.assertEquals(inv.date_invoice, '2015-07-02') + self.assertEquals( + inv.partner_id, + self.env.ref('account_invoice_import_invoice2data.free')) + self.assertEquals(inv.journal_id.type, 'purchase') + self.assertEquals( + float_compare(inv.check_total, 29.99, precision_digits=2), 0) + self.assertEquals( + float_compare(inv.amount_total, 29.99, precision_digits=2), 0) + self.assertEquals( + float_compare(inv.amount_untaxed, 24.99, precision_digits=2), 0) + self.assertEquals( + len(inv.invoice_line), 1) + iline = inv.invoice_line[0] + self.assertEquals(iline.name, 'Fiber optic access at the main office') + self.assertEquals( + iline.product_id, + self.env.ref('account_invoice_import_invoice2data.internet_access')) + self.assertEquals( + float_compare(iline.quantity, 1.0, precision_digits=0), 0) + self.assertEquals( + float_compare(iline.price_unit, 24.99, precision_digits=2), 0) + + # Prepare data for next test i.e. invoice update + # (we re-use the invoice created by the first import !) + inv.write({ + 'date_invoice': False, + 'supplier_invoice_number': False, + 'check_total': False, + }) + + # New import with update of an existing draft invoice + f = file_open( + 'account_invoice_import_invoice2data/tests/pdf/' + 'invoice_free_fiber_201507.pdf', + 'rb') + pdf_file = f.read() + wiz2 = self.env['account.invoice.import'].create({ + 'invoice_file': base64.b64encode(pdf_file), + 'invoice_filename': 'invoice_free_fiber_201507.pdf', + }) + f.close() + action = wiz2.import_invoice() + # Choose to update the existing invoice + wiz2.with_context(action['context']).update_invoice() + invoices = self.env['account.invoice'].search([ + ('state', '=', 'draft'), + ('type', '=', 'in_invoice'), + ('supplier_invoice_number', '=', '562044387') + ]) + self.assertEquals(len(invoices), 1) + inv = invoices[0] + self.assertEquals(inv.date_invoice, '2015-07-02') + self.assertEquals( + float_compare(inv.check_total, 29.99, precision_digits=2), 0) diff --git a/account_invoice_import_invoice2data/wizard/__init__.py b/account_invoice_import_invoice2data/wizard/__init__.py new file mode 100644 index 0000000000..8164a0ae51 --- /dev/null +++ b/account_invoice_import_invoice2data/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import account_invoice_import diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py new file mode 100644 index 0000000000..2184d7b51c --- /dev/null +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# © 2015-2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api, tools, _ +from openerp.exceptions import Warning as UserError +from datetime import datetime +import os +from tempfile import mkstemp +import logging +from invoice2data.main import extract_data +from invoice2data.template import read_templates + +logger = logging.getLogger(__name__) + + +class AccountInvoiceImport(models.TransientModel): + _inherit = 'account.invoice.import' + + @api.model + def fallback_parse_pdf_invoice(self, file_data): + '''This method must be inherited by additionnal modules with + the same kind of logic as the account_bank_statement_import_* + modules''' + logger.info('Trying to analyze PDF invoice with invoice2data lib') + fd, file_name = mkstemp() + try: + os.write(fd, file_data) + finally: + os.close(fd) + local_templates_dir = tools.config.get( + 'invoice2data_templates_dir', False) + logger.debug( + 'invoice2data local_templates_dir=%s', local_templates_dir) + templates = None + if local_templates_dir and os.path.isdir(local_templates_dir): + templates = read_templates(local_templates_dir) + logger.debug( + 'Calling invoice2data.extract_data with templates=%s', + templates) + try: + res = extract_data(file_name, templates=templates) + except Exception, e: + raise UserError(_( + "PDF Invoice parsing failed. Error message: %s") % e) + if not res: + raise UserError(_( + "This PDF invoice doesn't match a known template of " + "the invoice2data lib.")) + logger.info('Result of invoice2data PDF extraction: %s', res) + # rewrite a few keys + res['amount_total'] = res.pop('amount') + # If you crash here, you should just update invoice2data to the + # latest version from github + res['currency_iso'] = res.pop('currency') + if 'amount_tax' in res and 'amount_untaxed' not in res: + res['amount_untaxed'] = res['amount_total'] - res['amount_tax'] + elif 'amount_untaxed' not in res and 'amount_tax' not in res: + # For invoices that never have taxes + res['amount_untaxed'] = res['amount_total'] + # convert datetime to string, to make it json serializable + for key, value in res.iteritems(): + if value and isinstance(value, datetime): + res[key] = fields.Date.to_string(value) + return res From cef91f041c1502f56eb251ff21b196702b5f3187 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 12 Aug 2016 09:32:37 +0200 Subject: [PATCH 002/104] Adapt code to plug the module 'ovh_supplier_invoice' Better key names in the parsed_inv dict parsed_inv doesn't need to be JSON serializable anymore (small drawback: the invoice is parsed a second time on the second step... but the second step is rarely used) --- .../tests/test_invoice_import.py | 3 ++- .../wizard/account_invoice_import.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 773b50fb0a..ab8b3f67d2 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -66,7 +66,8 @@ def test_import_free_invoice(self): self.assertEquals(iline.name, 'Fiber optic access at the main office') self.assertEquals( iline.product_id, - self.env.ref('account_invoice_import_invoice2data.internet_access')) + self.env.ref( + 'account_invoice_import_invoice2data.internet_access')) self.assertEquals( float_compare(iline.quantity, 1.0, precision_digits=0), 0) self.assertEquals( diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 2184d7b51c..4805a568ac 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -53,6 +53,8 @@ def fallback_parse_pdf_invoice(self, file_data): # If you crash here, you should just update invoice2data to the # latest version from github res['currency_iso'] = res.pop('currency') + if 'vat' in res: + res['partner_vat'] = res.pop('vat') if 'amount_tax' in res and 'amount_untaxed' not in res: res['amount_untaxed'] = res['amount_total'] - res['amount_tax'] elif 'amount_untaxed' not in res and 'amount_tax' not in res: From 6e25b2480d07bdc7700e528c353cdd8fdd24d40a Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 13 Aug 2016 01:03:19 +0200 Subject: [PATCH 003/104] Fix product matching Move code from account_invoice_import_invoice2data to account_invoice_import --- .../wizard/account_invoice_import.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 4805a568ac..910a05f93f 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -55,13 +55,4 @@ def fallback_parse_pdf_invoice(self, file_data): res['currency_iso'] = res.pop('currency') if 'vat' in res: res['partner_vat'] = res.pop('vat') - if 'amount_tax' in res and 'amount_untaxed' not in res: - res['amount_untaxed'] = res['amount_total'] - res['amount_tax'] - elif 'amount_untaxed' not in res and 'amount_tax' not in res: - # For invoices that never have taxes - res['amount_untaxed'] = res['amount_total'] - # convert datetime to string, to make it json serializable - for key, value in res.iteritems(): - if value and isinstance(value, datetime): - res[key] = fields.Date.to_string(value) return res From c904d22327daac972b35e0631dbb14bff9af8315 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 16 Aug 2016 00:23:09 +0200 Subject: [PATCH 004/104] FIX PEP8 and account_invoice_import_invoice2data unittests Update REAME and some interface strings about UBL being an ISO standard Small code changes --- .../tests/test_invoice_import.py | 9 ++++----- .../wizard/account_invoice_import.py | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index ab8b3f67d2..a3ef313423 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -29,14 +29,13 @@ def setUp(self): internet_product.supplier_taxes_id = [(6, 0, [frtax.id])] def test_import_free_invoice(self): + filename = 'invoice_free_fiber_201507.pdf' f = file_open( - 'account_invoice_import_invoice2data/tests/pdf/' - 'invoice_free_fiber_201507.pdf', - 'rb') + 'account_invoice_import_invoice2data/tests/pdf/' + filename, 'rb') pdf_file = f.read() wiz = self.env['account.invoice.import'].create({ 'invoice_file': base64.b64encode(pdf_file), - 'invoice_filename': 'invoice_free_fiber_201507.pdf', + 'invoice_filename': filename, }) f.close() wiz.import_invoice() @@ -94,7 +93,7 @@ def test_import_free_invoice(self): f.close() action = wiz2.import_invoice() # Choose to update the existing invoice - wiz2.with_context(action['context']).update_invoice() + wiz2.update_invoice() invoices = self.env['account.invoice'].search([ ('state', '=', 'draft'), ('type', '=', 'in_invoice'), diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 910a05f93f..4414bab326 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -2,9 +2,8 @@ # © 2015-2016 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api, tools, _ +from openerp import models, api, tools, _ from openerp.exceptions import Warning as UserError -from datetime import datetime import os from tempfile import mkstemp import logging From ac32fcaf59f3249d094a738351623335e191f588 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 16 Aug 2016 00:51:17 +0200 Subject: [PATCH 005/104] PEP8 fix --- .../tests/test_invoice_import.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index a3ef313423..1b42f9a908 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -92,6 +92,8 @@ def test_import_free_invoice(self): }) f.close() action = wiz2.import_invoice() + self.assertEquals( + action['res_model'], 'account.invoice.import') # Choose to update the existing invoice wiz2.update_invoice() invoices = self.env['account.invoice'].search([ From ba4a4e8169add852e4ceea01d19388d5daa9f177 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 19 Aug 2016 00:22:39 +0200 Subject: [PATCH 006/104] Better/cleaner pivot invoice dict format (no more IDs in the pivot invoice dict, cleaner organisation) Code refactoring: move code in base_business_document_import, factorise code for tax matching (it was duplicated in UBL and ZUGFeRD) Now support PDF with embedded UBL XML file Enable unittests on account_invoice_import_ubl More absolute xpath in account_invoice_import_ubl instead of relative xpath WARNING: these are big changes, I may have broken a few details --- .../wizard/account_invoice_import.py | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 4414bab326..4c691d1439 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -21,6 +21,10 @@ def fallback_parse_pdf_invoice(self, file_data): '''This method must be inherited by additionnal modules with the same kind of logic as the account_bank_statement_import_* modules''' + return self.invoice2data_parse_invoice(file_data) + + @api.model + def invoice2data_parse_invoice(self, file_data): logger.info('Trying to analyze PDF invoice with invoice2data lib') fd, file_name = mkstemp() try: @@ -38,20 +42,40 @@ def fallback_parse_pdf_invoice(self, file_data): 'Calling invoice2data.extract_data with templates=%s', templates) try: - res = extract_data(file_name, templates=templates) + invoice2data_res = extract_data(file_name, templates=templates) except Exception, e: raise UserError(_( "PDF Invoice parsing failed. Error message: %s") % e) - if not res: + if not invoice2data_res: raise UserError(_( "This PDF invoice doesn't match a known template of " "the invoice2data lib.")) - logger.info('Result of invoice2data PDF extraction: %s', res) - # rewrite a few keys - res['amount_total'] = res.pop('amount') - # If you crash here, you should just update invoice2data to the - # latest version from github - res['currency_iso'] = res.pop('currency') - if 'vat' in res: - res['partner_vat'] = res.pop('vat') - return res + logger.info( + 'Result of invoice2data PDF extraction: %s', invoice2data_res) + return self.invoice2data_to_parsed_inv(invoice2data_res) + + @api.model + def invoice2data_to_parsed_inv(self, invoice2data_res): + parsed_inv = { + 'partner': { + 'vat': invoice2data_res.get('vat'), + 'name': invoice2data_res.get('partner_name'), + 'email': invoice2data_res.get('partner_email'), + 'siren': invoice2data_res.get('siren'), + }, + 'currency': { + 'iso': invoice2data_res.get('currency'), + }, + 'amount_total': invoice2data_res.get('amount'), + 'invoice_number': invoice2data_res.get('invoice_number'), + 'date': invoice2data_res.get('date'), + 'date_due': invoice2data_res.get('date_due'), + 'date_start': invoice2data_res.get('date_start'), + 'date_end': invoice2data_res.get('date_end'), + 'description': invoice2data_res.get('description'), + } + if 'amount_untaxed' in invoice2data_res: + parsed_inv['amount_untaxed'] = invoice2data_res['amount_untaxed'] + if 'amount_tax' in invoice2data_res: + parsed_inv['amount_tax'] = invoice2data_res['amount_tax'] + return parsed_inv From 9705bcd92fdd8fd64f0fa27ff98fd38e6fbdb511 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 28 Sep 2016 17:03:29 +0200 Subject: [PATCH 007/104] account_invoice_import_invoice2data: allow to use both a local template dir and the built-in templates Also allow to use only a local template dir README updated to explain how to configure all this --- account_invoice_import_invoice2data/README.rst | 9 ++++++++- .../wizard/account_invoice_import.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index 154527443a..a977f4a59a 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -31,7 +31,7 @@ If you want the invoice2data library to fallback on OCR if the PDF doesn't conta sudo apt-get install imagemagick tesseract-ocr -If you want to use custom invoice templates for the invoice2data lib (instead of the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as: +If you want to use custom invoice templates for the invoice2data lib (in addition to the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as: .. code:: @@ -39,6 +39,13 @@ If you want to use custom invoice templates for the invoice2data lib (instead of and store your invoice templates in YAML format (*.yml* extension) in the directory that you have configured above. If you add invoice tempates in this directory, you don't have to restart Odoo, they will be used automatically on the next invoice import. +If you want to use only your custom invoice templates and ignore the templates provided by the invoice2data lib, you should have in your Odoo server configuration file: + +.. code:: + + invoice2data_templates_dir = /opt/invoice2data_local_templates + invoice2data_exclude_built_in_templates = True + French users should also install the module *l10n_fr_invoice_import* available in the `French localization `_, cf `this PR `_. Configuration diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 4c691d1439..41180f28b3 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -9,6 +9,7 @@ import logging from invoice2data.main import extract_data from invoice2data.template import read_templates +import pkg_resources logger = logging.getLogger(__name__) @@ -38,6 +39,11 @@ def invoice2data_parse_invoice(self, file_data): templates = None if local_templates_dir and os.path.isdir(local_templates_dir): templates = read_templates(local_templates_dir) + exclude_built_in_templates = tools.config.get( + 'invoice2data_exclude_built_in_templates', False) + if not exclude_built_in_templates: + templates += read_templates( + pkg_resources.resource_filename('invoice2data', 'templates')) logger.debug( 'Calling invoice2data.extract_data with templates=%s', templates) From a5f032f124b9ff590f94f51454303ef7fca98e8e Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 28 Sep 2016 19:48:39 +0200 Subject: [PATCH 008/104] Transfer log level of Odoo to invoice2data lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Special thanks to Sébastien Beau for his help to achieve this --- .../wizard/account_invoice_import.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 41180f28b3..dbc799a889 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -9,6 +9,7 @@ import logging from invoice2data.main import extract_data from invoice2data.template import read_templates +from invoice2data.main import logger as loggeri2data import pkg_resources logger = logging.getLogger(__name__) @@ -32,6 +33,8 @@ def invoice2data_parse_invoice(self, file_data): os.write(fd, file_data) finally: os.close(fd) + # Transfer log level of Odoo to invoice2data + loggeri2data.setLevel(logger.getEffectiveLevel()) local_templates_dir = tools.config.get( 'invoice2data_templates_dir', False) logger.debug( From 187f4944304901798230dfb46ad20454c8080f0e Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Tue, 4 Oct 2016 15:05:50 +0200 Subject: [PATCH 009/104] variable initialization --- .../wizard/account_invoice_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index dbc799a889..8cd0826094 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -39,7 +39,7 @@ def invoice2data_parse_invoice(self, file_data): 'invoice2data_templates_dir', False) logger.debug( 'invoice2data local_templates_dir=%s', local_templates_dir) - templates = None + templates = [] if local_templates_dir and os.path.isdir(local_templates_dir): templates = read_templates(local_templates_dir) exclude_built_in_templates = tools.config.get( From e75c4b8ebb1422c8a6874a56f77afa8d457a1b55 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 4 Oct 2016 19:48:09 +0200 Subject: [PATCH 010/104] Add support for extraction + matching on website PEP8 fix --- .../wizard/account_invoice_import.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 8cd0826094..6e23904332 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -41,7 +41,7 @@ def invoice2data_parse_invoice(self, file_data): 'invoice2data local_templates_dir=%s', local_templates_dir) templates = [] if local_templates_dir and os.path.isdir(local_templates_dir): - templates = read_templates(local_templates_dir) + templates += read_templates(local_templates_dir) exclude_built_in_templates = tools.config.get( 'invoice2data_exclude_built_in_templates', False) if not exclude_built_in_templates: @@ -70,6 +70,7 @@ def invoice2data_to_parsed_inv(self, invoice2data_res): 'vat': invoice2data_res.get('vat'), 'name': invoice2data_res.get('partner_name'), 'email': invoice2data_res.get('partner_email'), + 'website': invoice2data_res.get('partner_website'), 'siren': invoice2data_res.get('siren'), }, 'currency': { From 97f46ace8aef465b19a213825aa9e05fddc35414 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 18 Oct 2016 23:02:55 +0200 Subject: [PATCH 011/104] 8.0 Add support for partner bank matching on invoice update (#6) Add support for partner bank matching on invoice update (before, it was only supported on invoice creation) --- account_invoice_import_invoice2data/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index a977f4a59a..cdad6462fd 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -90,13 +90,13 @@ Refer to the usage section of the module *account_invoice_import*. .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/95/8.0 + :target: https://runbot.odoo-community.org/runbot/226/8.0 Bug Tracker =========== Bugs are tracked on `GitHub Issues -`_. In case of trouble, please +`_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. From 849e0458b68b143c4652e9c7e496788ca6ade9d2 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 19 Oct 2016 08:58:57 +0200 Subject: [PATCH 012/104] 8.0 Use try/except when importing external libs (#8) [FIX] LINT Use try/except when importing external libs Remove self.ensure_one() that has nothing to do in an api.model method --- .../wizard/account_invoice_import.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 6e23904332..73de4b6220 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -6,14 +6,17 @@ from openerp.exceptions import Warning as UserError import os from tempfile import mkstemp -import logging -from invoice2data.main import extract_data -from invoice2data.template import read_templates -from invoice2data.main import logger as loggeri2data import pkg_resources - +import logging logger = logging.getLogger(__name__) +try: + from invoice2data.main import extract_data + from invoice2data.template import read_templates + from invoice2data.main import logger as loggeri2data +except ImportError: + logger.debug('Cannot import invoice2data') + class AccountInvoiceImport(models.TransientModel): _inherit = 'account.invoice.import' From 46bbabe3b2812752d8845b793dfaeedaa2d77a46 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Sat, 12 Nov 2016 14:17:15 +0100 Subject: [PATCH 013/104] Make modules uninstallable --- account_invoice_import_invoice2data/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__openerp__.py b/account_invoice_import_invoice2data/__openerp__.py index 72e70ce2ef..e963a33756 100644 --- a/account_invoice_import_invoice2data/__openerp__.py +++ b/account_invoice_import_invoice2data/__openerp__.py @@ -15,5 +15,5 @@ 'data': [], 'demo': ['demo/demo_data.xml'], 'images': ['images/sshot-wizard1.png'], - 'installable': True, + 'installable': False, } From 23436a614884b282bcdef9b3ad03e5670dd83eac Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Mon, 28 Nov 2016 22:27:08 -0500 Subject: [PATCH 014/104] OCA Transbot updated translations from Transifex --- .../i18n/sl.po | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/sl.po diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po new file mode 100644 index 0000000000..a736082d82 --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +# Translators: +# Matjaž Mozetič , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-12 13:37+0000\n" +"PO-Revision-Date: 2016-11-12 13:37+0000\n" +"Last-Translator: Matjaž Mozetič , 2016\n" +"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: account_invoice_import_invoice2data +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "Internetni dostop" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:59 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "Neuspešna obdelava PDF računa. Napaka: %s" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:62 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" +"Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice invoice2data." + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "Čarovnik za uvoz prejetih računov/dobropisov" From b57ca5e48b7b339c8a4d8b694062abd4d67d420d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 29 Dec 2016 17:09:40 +0100 Subject: [PATCH 015/104] migration of account_invoice_import_invoice2data and dependencies --- account_invoice_import_invoice2data/__openerp__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/__openerp__.py b/account_invoice_import_invoice2data/__openerp__.py index e963a33756..acf9a1f7d8 100644 --- a/account_invoice_import_invoice2data/__openerp__.py +++ b/account_invoice_import_invoice2data/__openerp__.py @@ -4,7 +4,7 @@ { 'name': 'Account Invoice Import Invoice2data', - 'version': '8.0.1.0.0', + 'version': '9.0.1.0.0', 'category': 'Accounting & Finance', 'license': 'AGPL-3', 'summary': 'Import supplier invoices using the invoice2data lib', @@ -15,5 +15,5 @@ 'data': [], 'demo': ['demo/demo_data.xml'], 'images': ['images/sshot-wizard1.png'], - 'installable': False, + 'installable': True, } From c246143bacacbf209fc426f4cbe41a2af7b7ec55 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 7 Jan 2017 22:41:00 +0100 Subject: [PATCH 016/104] Disable tests for invoice2data because it's too difficult to install a recent version of pdftotext on travis's ubuntu 12.04 images --- account_invoice_import_invoice2data/tests/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/tests/__init__.py b/account_invoice_import_invoice2data/tests/__init__.py index 53a91fdbc0..67f6ba9334 100644 --- a/account_invoice_import_invoice2data/tests/__init__.py +++ b/account_invoice_import_invoice2data/tests/__init__.py @@ -2,5 +2,9 @@ # The test below works well, but the full installation of invoice2data # with the special version of the pdftotext binary is difficult -# and I don't know how to put it in the configuration of Travis -from . import test_invoice_import +# Invoice2data new requires a recent version of poppler-utils +# from https://poppler.freedesktop.org/releases.html +# But Travis build environnement are based on Ubuntu 12.04 and I didn't +# find any PPA for poppler-utils for 12.04 and it doesn't seem easy to have a +# static build +#from . import test_invoice_import From 673fc38bbf38308f004861057d74a2cc533a0d49 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 9 Jan 2017 09:08:14 +0100 Subject: [PATCH 017/104] tests --- .../tests/test_invoice_import.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 1b42f9a908..57f76542a8 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -17,8 +17,14 @@ def setUp(self): 'description': 'FR-VAT-buy-20.0', 'amount': 0.2, 'type': 'percent', - 'account_collected_id': self.env.ref('account.a_expense').id, - 'account_paid_id': self.env.ref('account.a_expense').id, + 'account_collected_id': self.env['account.account'].search([ + ('user_type_id', '=', + self.env.ref('account.data_account_type_expenses').id) + ], limit=1).id, + 'account_paid_id': self.env['account.account'].search([ + ('user_type_id', '=', + self.env.ref('account.data_account_type_expenses').id) + ], limit=1).id, 'base_sign': -1, 'tax_sign': -1, 'type_tax_use': 'purchase', @@ -43,7 +49,7 @@ def test_import_free_invoice(self): invoices = self.env['account.invoice'].search([ ('state', '=', 'draft'), ('type', '=', 'in_invoice'), - ('supplier_invoice_number', '=', '562044387') + ('reference', '=', '562044387') ]) self.assertEquals(len(invoices), 1) inv = invoices[0] @@ -76,7 +82,7 @@ def test_import_free_invoice(self): # (we re-use the invoice created by the first import !) inv.write({ 'date_invoice': False, - 'supplier_invoice_number': False, + 'reference': False, 'check_total': False, }) @@ -99,7 +105,7 @@ def test_import_free_invoice(self): invoices = self.env['account.invoice'].search([ ('state', '=', 'draft'), ('type', '=', 'in_invoice'), - ('supplier_invoice_number', '=', '562044387') + ('reference', '=', '562044387') ]) self.assertEquals(len(invoices), 1) inv = invoices[0] From 57c5b3b6a7e93430f533bacaa9c450a720eb3ac6 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 15 Feb 2017 15:11:22 +0100 Subject: [PATCH 018/104] Prepare v10 branch Rename __openerp__.py to __manifest__.py and set installable to False --- .../{__openerp__.py => __manifest__.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename account_invoice_import_invoice2data/{__openerp__.py => __manifest__.py} (96%) diff --git a/account_invoice_import_invoice2data/__openerp__.py b/account_invoice_import_invoice2data/__manifest__.py similarity index 96% rename from account_invoice_import_invoice2data/__openerp__.py rename to account_invoice_import_invoice2data/__manifest__.py index acf9a1f7d8..1fdfd72478 100644 --- a/account_invoice_import_invoice2data/__openerp__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -15,5 +15,5 @@ 'data': [], 'demo': ['demo/demo_data.xml'], 'images': ['images/sshot-wizard1.png'], - 'installable': True, + 'installable': False, } From 49a0436c88cd7f21d35369fb226e9e38f38b7248 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 20 Feb 2017 00:12:07 +0100 Subject: [PATCH 019/104] Port base_business_document_import* to v10 Also port all the modules that generate the XML documents: account_invoice_ubl, account_invoice_zugferd, purchase_order_ubl and sale_order_ubl --- account_invoice_import_invoice2data/README.rst | 2 +- account_invoice_import_invoice2data/__manifest__.py | 6 +++--- account_invoice_import_invoice2data/demo/demo_data.xml | 6 ++---- .../tests/test_invoice_import.py | 8 ++++---- .../wizard/account_invoice_import.py | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index cdad6462fd..fcea4918b8 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -90,7 +90,7 @@ Refer to the usage section of the module *account_invoice_import*. .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/226/8.0 + :target: https://runbot.odoo-community.org/runbot/226/10.0 Bug Tracker =========== diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 1fdfd72478..8c0c4f5450 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Akretion (Alexis de Lattre ) +# © 2015-2017 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Account Invoice Import Invoice2data', - 'version': '9.0.1.0.0', + 'version': '10.0.1.0.0', 'category': 'Accounting & Finance', 'license': 'AGPL-3', 'summary': 'Import supplier invoices using the invoice2data lib', @@ -15,5 +15,5 @@ 'data': [], 'demo': ['demo/demo_data.xml'], 'images': ['images/sshot-wizard1.png'], - 'installable': False, + 'installable': True, } diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml index b203509a45..cc9f73712a 100644 --- a/account_invoice_import_invoice2data/demo/demo_data.xml +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -1,7 +1,6 @@ - - + Internet Access @@ -33,5 +32,4 @@ - - + diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 57f76542a8..9f1748607f 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Akretion France (www.akretion.com) +# © 2015-2017 Akretion France (www.akretion.com) # @author: Alexis de Lattre -# The licence is in the file __openerp__.py +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.tests.common import TransactionCase +from odoo.tests.common import TransactionCase import base64 -from openerp.tools import file_open, float_compare +from odoo.tools import file_open, float_compare class TestInvoiceImport(TransactionCase): diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 73de4b6220..8bc00534b2 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -2,8 +2,8 @@ # © 2015-2016 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, api, tools, _ -from openerp.exceptions import Warning as UserError +from odoo import models, api, tools, _ +from odoo.exceptions import UserError import os from tempfile import mkstemp import pkg_resources From 65d150667c17efb2f9abc5aabf2e5ac207856c01 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 27 Feb 2017 23:23:59 +0100 Subject: [PATCH 020/104] Continue port of modules for v10.0, in particular sale_order_import_* module Fix spelling mistake and other remarks on README by Tarteo --- account_invoice_import_invoice2data/README.rst | 2 +- .../wizard/account_invoice_import.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index fcea4918b8..d96e1ce8a0 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -46,7 +46,7 @@ If you want to use only your custom invoice templates and ignore the templates p invoice2data_templates_dir = /opt/invoice2data_local_templates invoice2data_exclude_built_in_templates = True -French users should also install the module *l10n_fr_invoice_import* available in the `French localization `_, cf `this PR `_. +French users should also install the module *l10n_fr_business_document_import* available in the `French localization `_. Configuration ============= diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 8bc00534b2..ace7b57c85 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -23,7 +23,7 @@ class AccountInvoiceImport(models.TransientModel): @api.model def fallback_parse_pdf_invoice(self, file_data): - '''This method must be inherited by additionnal modules with + '''This method must be inherited by additional modules with the same kind of logic as the account_bank_statement_import_* modules''' return self.invoice2data_parse_invoice(file_data) From 533d1188d8eb56b6be500cc076fcdd168a0bed1f Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 1 Jun 2017 02:09:23 +0200 Subject: [PATCH 021/104] Update README of invoice2data --- account_invoice_import_invoice2data/README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index d96e1ce8a0..6d8cc2fd1e 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -15,15 +15,17 @@ Installation This module requires the Python library *invoice2data* available on `Github `_. -To install the right version of the library, run: +To install the library, run: .. code:: - sudo pip install git+https://github.com/m3nu/invoice2data.git + sudo pip install invoice2data -In order to use a recent version of invoice2data on Odoo v8, you need an Odoo v8 dated after January 29th 2016, so that it contains `this fix `_ which fixes `this bug `_. +If you use Ubuntu 16.04 LTS, you can use the pdftotext version 0.41.0 that is packaged in the distribution: -The invoice2data library requires the latest version of the *pdftotext* utility, which is not yet packaged in Debian/Ubuntu. So you should download it from `the FTP server of Foolabs `_ then uncompress it and copy the file *bin64/pdftotext* to */usr/local/bin*. +.. code:: + + sudo apt-get install poppler-utils If you want the invoice2data library to fallback on OCR if the PDF doesn't contain text (only a very small minority of PDF invoices are image-based and require OCR), you should also install `Imagemagick `_ (to get the *convert* utility to convert PDF to TIFF) and `Tesseract OCR `_ : From 6ec55103e81de9e61144e3ec95cbad078e4027ac Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sun, 11 Mar 2018 21:49:49 +0100 Subject: [PATCH 022/104] Update to work with latest version of invoice2data (#51) * Update to work with latest version of invoice2data * Add requirements.txt file --- account_invoice_import_invoice2data/README.rst | 6 +++--- account_invoice_import_invoice2data/__manifest__.py | 2 +- .../wizard/account_invoice_import.py | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index 6d8cc2fd1e..290ae8f9b2 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -13,13 +13,13 @@ To know the full story behind the development of this module, read this `blog po Installation ============ -This module requires the Python library *invoice2data* available on `Github `_. +This module requires the Python library *invoice2data* available on `Github `_ with a version >= 0.2.74 (February 2018). -To install the library, run: +To install the latest version of this library, run: .. code:: - sudo pip install invoice2data + sudo pip install --upgrade invoice2data If you use Ubuntu 16.04 LTS, you can use the pdftotext version 0.41.0 that is packaged in the distribution: diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 8c0c4f5450..f924121694 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Account Invoice Import Invoice2data', - 'version': '10.0.1.0.0', + 'version': '10.0.1.0.1', 'category': 'Accounting & Finance', 'license': 'AGPL-3', 'summary': 'Import supplier invoices using the invoice2data lib', diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index ace7b57c85..c4879a668b 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -6,13 +6,12 @@ from odoo.exceptions import UserError import os from tempfile import mkstemp -import pkg_resources import logging logger = logging.getLogger(__name__) try: from invoice2data.main import extract_data - from invoice2data.template import read_templates + from invoice2data.extract.loader import read_templates from invoice2data.main import logger as loggeri2data except ImportError: logger.debug('Cannot import invoice2data') @@ -48,8 +47,7 @@ def invoice2data_parse_invoice(self, file_data): exclude_built_in_templates = tools.config.get( 'invoice2data_exclude_built_in_templates', False) if not exclude_built_in_templates: - templates += read_templates( - pkg_resources.resource_filename('invoice2data', 'templates')) + templates += read_templates() logger.debug( 'Calling invoice2data.extract_data with templates=%s', templates) From 80637ea22c87f740c46b0298bd5ce3eb0a83955d Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 31 Mar 2018 05:31:32 +0200 Subject: [PATCH 023/104] OCA Transbot updated translations from Transifex --- .../i18n/es.po | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/es.po diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po new file mode 100644 index 0000000000..d91474dc11 --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +# Translators: +# enjolras , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-12 01:43+0000\n" +"PO-Revision-Date: 2018-03-12 01:43+0000\n" +"Last-Translator: enjolras , 2018\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "Acceso a internet" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" From cc59c20302cc1664f8b1d232bb5f45451174e7ba Mon Sep 17 00:00:00 2001 From: oca-travis Date: Tue, 31 Jul 2018 14:04:13 +0000 Subject: [PATCH 024/104] Update account_invoice_import_invoice2data.pot --- .../account_invoice_import_invoice2data.pot | 38 +++++++++++++++++++ .../i18n/es.po | 4 +- .../i18n/sl.po | 12 +++--- 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot new file mode 100644 index 0000000000..2b169b28ce --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -0,0 +1,38 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \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: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#, python-format +msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" + diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po index d91474dc11..716dfa1c75 100644 --- a/account_invoice_import_invoice2data/i18n/es.po +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * account_invoice_import_invoice2data -# +# # Translators: # enjolras , 2018 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2018-03-12 01:43+0000\n" "Last-Translator: enjolras , 2018\n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: account_invoice_import_invoice2data diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po index a736082d82..d0225e5762 100644 --- a/account_invoice_import_invoice2data/i18n/sl.po +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * account_invoice_import_invoice2data -# +# # Translators: # Matjaž Mozetič , 2016 msgid "" @@ -12,25 +12,27 @@ msgstr "" "PO-Revision-Date: 2016-11-12 13:37+0000\n" "Last-Translator: Matjaž Mozetič , 2016\n" "Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: sl\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" +"%100==4 ? 2 : 3);\n" #. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" msgstr "Internetni dostop" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:59 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" msgstr "Neuspešna obdelava PDF računa. Napaka: %s" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:62 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format msgid "" "This PDF invoice doesn't match a known template of the invoice2data lib." From 2c9ecabbafe7574804579bf77302480a444b155a Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 19 Jan 2018 00:12:17 +0100 Subject: [PATCH 025/104] Partner <-> Invoice import config : switch M2O <-> O2M --- .../demo/demo_data.xml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml index cc9f73712a..76fa0539b6 100644 --- a/account_invoice_import_invoice2data/demo/demo_data.xml +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -11,13 +11,6 @@ - - Free - 1line_static_product - Fiber optic access at the main office - - - Free SAS @@ -29,7 +22,15 @@ http://www.free.fr FR 604 219 388 61 - + + Free + + 1line_static_product + Fiber optic access at the main office + + + + From d6f60811063a7dd7c57d3457c5d4ad291dd61a46 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sun, 19 Aug 2018 00:59:00 +0200 Subject: [PATCH 026/104] Finalize the development account_invoice_download Update the account_invoice_download_weboob following the changes in account_invoice_download Add README.rst for the 2 modules --- .../wizard/account_invoice_import.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index c4879a668b..0d32151730 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -2,7 +2,7 @@ # © 2015-2016 Akretion (Alexis de Lattre ) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, api, tools, _ +from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError import os from tempfile import mkstemp @@ -89,4 +89,7 @@ def invoice2data_to_parsed_inv(self, invoice2data_res): parsed_inv['amount_untaxed'] = invoice2data_res['amount_untaxed'] if 'amount_tax' in invoice2data_res: parsed_inv['amount_tax'] = invoice2data_res['amount_tax'] + for key, value in parsed_inv.items(): + if key.startswith('date') and parsed_inv[key]: + parsed_inv[key] = fields.Date.to_string(parsed_inv[key]) return parsed_inv From f591a6bd730c80b3d81a5fe8e5242d57890924e6 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 22 Aug 2018 22:49:09 +0200 Subject: [PATCH 027/104] account_invoice_import: add a mail gateway Update text displayed in the invoice import wizard and make list of supported formats modular (like in bank statement import) --- .../__manifest__.py | 2 +- .../wizard/account_invoice_import_view.xml | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index f924121694..3afc1d1e6d 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -12,7 +12,7 @@ 'website': 'http://www.akretion.com', 'depends': ['account_invoice_import'], 'external_dependencies': {'python': ['invoice2data']}, - 'data': [], + 'data': ['wizard/account_invoice_import_view.xml'], 'demo': ['demo/demo_data.xml'], 'images': ['images/sshot-wizard1.png'], 'installable': True, diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml new file mode 100644 index 0000000000..32590954f6 --- /dev/null +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml @@ -0,0 +1,23 @@ + + + + + + + + invoice2data.account.invoice.import.form + account.invoice.import + + +

    +
  • regular PDF file without any embedded XML file via the invoice2data library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier.
  • +
+ + + + + From cdaf232d1391bc4f5f4d1b0d15d5a084bffb56fb Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sun, 16 Sep 2018 21:19:11 +0000 Subject: [PATCH 028/104] Update account_invoice_import_invoice2data.pot --- .../i18n/account_invoice_import_invoice2data.pot | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot index 2b169b28ce..292db9c907 100644 --- a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -13,6 +13,11 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -36,3 +41,13 @@ msgstr "" msgid "Wizard to import supplier invoices/refunds" msgstr "" +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgstr "" + From 9d8a059d94d0ae488f9ab02e43ebaea33171049a Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 24 Nov 2018 20:33:10 +0000 Subject: [PATCH 029/104] Update translation files Updated by Actualizar ficheiros PO com o novo POT (msgmerge) hook in Weblate. --- account_invoice_import_invoice2data/i18n/es.po | 18 ++++++++++++++++++ account_invoice_import_invoice2data/i18n/sl.po | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po index 716dfa1c75..b2d4faab18 100644 --- a/account_invoice_import_invoice2data/i18n/es.po +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -18,6 +18,11 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -41,3 +46,16 @@ msgstr "" #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." +msgstr "" diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po index d0225e5762..fe9256f5e9 100644 --- a/account_invoice_import_invoice2data/i18n/sl.po +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -19,6 +19,11 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" "%100==4 ? 2 : 3);\n" +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -43,3 +48,16 @@ msgstr "" #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "Čarovnik za uvoz prejetih računov/dobropisov" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." +msgstr "" From 26995509f9bb2adfd08abdffa5cd2f609d7eb05c Mon Sep 17 00:00:00 2001 From: alvarorib Date: Sat, 15 Dec 2018 12:54:37 +0000 Subject: [PATCH 030/104] Added translation using Weblate (Portuguese) --- .../i18n/pt.po | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/pt.po diff --git a/account_invoice_import_invoice2data/i18n/pt.po b/account_invoice_import_invoice2data/i18n/pt.po new file mode 100644 index 0000000000..31d0771077 --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/pt.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#, python-format +msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgstr "" From 3a0fded3061667f13f8a263ee6db94063033c52e Mon Sep 17 00:00:00 2001 From: Pedro Castro Silva Date: Sun, 16 Dec 2018 23:28:02 +0000 Subject: [PATCH 031/104] Translated using Weblate (Portuguese) Currently translated at 85.7% (6 of 7 strings) Translation: edi-10.0/edi-10.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-10-0/edi-10-0-account_invoice_import_invoice2data/pt/ --- account_invoice_import_invoice2data/i18n/pt.po | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/pt.po b/account_invoice_import_invoice2data/i18n/pt.po index 31d0771077..322bf1c850 100644 --- a/account_invoice_import_invoice2data/i18n/pt.po +++ b/account_invoice_import_invoice2data/i18n/pt.po @@ -6,41 +6,44 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2018-12-17 16:58+0000\n" +"Last-Translator: Pedro Castro Silva \n" "Language-Team: none\n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.3\n" #. module: account_invoice_import_invoice2data #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" -msgstr "" +msgstr "arquivo PDF regular sem qualquer arquivo XML incorporado via" #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" -msgstr "" +msgstr "Acesso à Internet" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "" +msgstr "Análise de fatura PDF falhou. Mensagem de erro: %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format msgid "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +"Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" -msgstr "" +msgstr "Assistente para importar as faturas/créditos do fornecedor" #. module: account_invoice_import_invoice2data #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form @@ -51,3 +54,5 @@ msgstr "" #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." msgstr "" +"biblioteca (lê o texto do PDF e obtém a informação). Apenas funcionará se a " +"biblioteca de invoice2data tiver um modelo para as faturas deste fornecedor." From 145e2037acffa847044f795211a3ea9ca5690ac7 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 3 Apr 2019 02:46:08 +0000 Subject: [PATCH 032/104] icon.png --- .../static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 account_invoice_import_invoice2data/static/description/icon.png diff --git a/account_invoice_import_invoice2data/static/description/icon.png b/account_invoice_import_invoice2data/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 From 15856601266fe0a2279d3ae7ba9703423631ad3e Mon Sep 17 00:00:00 2001 From: Benoit Date: Thu, 7 Mar 2019 09:30:33 +0100 Subject: [PATCH 033/104] join invoice_number and description if invoice2data returns a list --- .../wizard/account_invoice_import.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 0d32151730..a511f3820d 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -78,13 +78,16 @@ def invoice2data_to_parsed_inv(self, invoice2data_res): 'iso': invoice2data_res.get('currency'), }, 'amount_total': invoice2data_res.get('amount'), - 'invoice_number': invoice2data_res.get('invoice_number'), 'date': invoice2data_res.get('date'), 'date_due': invoice2data_res.get('date_due'), 'date_start': invoice2data_res.get('date_start'), 'date_end': invoice2data_res.get('date_end'), - 'description': invoice2data_res.get('description'), } + for field in ['invoice_number', 'description']: + if isinstance(invoice2data_res.get(field), list): + parsed_inv[field] = ' '.join(invoice2data_res[field]) + else: + parsed_inv[field] = invoice2data_res.get(field) if 'amount_untaxed' in invoice2data_res: parsed_inv['amount_untaxed'] = invoice2data_res['amount_untaxed'] if 'amount_tax' in invoice2data_res: From 745aaed3df340c6388a5aa1723d72e4e5a900daf Mon Sep 17 00:00:00 2001 From: Matteo Trubini Date: Tue, 8 Oct 2019 15:20:41 +0200 Subject: [PATCH 034/104] Update invoice2data library reference link From https://github.com/m3nu/invoice2data to https://github.com/invoice-x/invoice2data --- account_invoice_import_invoice2data/README.rst | 8 ++++---- .../wizard/account_invoice_import_view.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index 290ae8f9b2..2f75eb09d5 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -6,14 +6,14 @@ Account Invoice Import Invoice2data =================================== -This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. +This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. To know the full story behind the development of this module, read this `blog post `_. Installation ============ -This module requires the Python library *invoice2data* available on `Github `_ with a version >= 0.2.74 (February 2018). +This module requires the Python library *invoice2data* available on `Github `_ with a version >= 0.2.74 (February 2018). To install the latest version of this library, run: @@ -60,11 +60,11 @@ Go to the form view of the supplier and configure it with the following paramete * the *TIN* (i.e. VAT number) is set (the VAT number is used by default when searching the supplier in the Odoo partner database) * in the *Accounting* tab, create an *Invoice Import Configuration*. -For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. +For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. Here are some hints to help you add a template for your supplier: -* Take `Free SAS template file `_ as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf +* Take `Free SAS template file `_ as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf * Try to run the invoice2data library manually on the sample invoice of Free: diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml index 32590954f6..2eb900b937 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml @@ -14,7 +14,7 @@
    -
  • regular PDF file without any embedded XML file via the invoice2data library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier.
  • +
  • regular PDF file without any embedded XML file via the invoice2data library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier.
From ff5f0e596bb908151c804a2f5cfdd74033482287 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 13 Feb 2020 12:50:48 +0100 Subject: [PATCH 035/104] [MIG] account_invoice_import_invoice2data to v12 (from v10) --- .../__init__.py | 2 -- .../__manifest__.py | 11 +++--- .../demo/demo_data.xml | 2 -- .../readme/CONFIGURE.rst | 30 ++++++++++++++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 3 ++ .../readme/INSTALL.rst | 36 +++++++++++++++++++ .../tests/__init__.py | 11 +----- .../tests/test_invoice_import.py | 29 +++++++-------- .../wizard/__init__.py | 2 -- .../wizard/account_invoice_import.py | 6 ++-- 11 files changed, 91 insertions(+), 42 deletions(-) create mode 100644 account_invoice_import_invoice2data/readme/CONFIGURE.rst create mode 100644 account_invoice_import_invoice2data/readme/CONTRIBUTORS.rst create mode 100644 account_invoice_import_invoice2data/readme/DESCRIPTION.rst create mode 100644 account_invoice_import_invoice2data/readme/INSTALL.rst diff --git a/account_invoice_import_invoice2data/__init__.py b/account_invoice_import_invoice2data/__init__.py index 3b4c3edf09..40272379f7 100644 --- a/account_invoice_import_invoice2data/__init__.py +++ b/account_invoice_import_invoice2data/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import wizard diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 3afc1d1e6d..2076d782b3 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -1,19 +1,18 @@ -# -*- coding: utf-8 -*- -# © 2015-2017 Akretion (Alexis de Lattre ) +# Copyright 2015-2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Account Invoice Import Invoice2data', - 'version': '10.0.1.0.1', - 'category': 'Accounting & Finance', + 'version': '12.0.1.0.0', + 'category': 'Invoicing Management', 'license': 'AGPL-3', 'summary': 'Import supplier invoices using the invoice2data lib', 'author': 'Akretion,Odoo Community Association (OCA)', - 'website': 'http://www.akretion.com', + 'website': 'https://github.com/OCA/edi', 'depends': ['account_invoice_import'], 'external_dependencies': {'python': ['invoice2data']}, 'data': ['wizard/account_invoice_import_view.xml'], 'demo': ['demo/demo_data.xml'], - 'images': ['images/sshot-wizard1.png'], 'installable': True, } diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml index 76fa0539b6..6174ce22bd 100644 --- a/account_invoice_import_invoice2data/demo/demo_data.xml +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -7,8 +7,6 @@ service - - diff --git a/account_invoice_import_invoice2data/readme/CONFIGURE.rst b/account_invoice_import_invoice2data/readme/CONFIGURE.rst new file mode 100644 index 0000000000..315d80c669 --- /dev/null +++ b/account_invoice_import_invoice2data/readme/CONFIGURE.rst @@ -0,0 +1,30 @@ +Go to the form view of the supplier and configure it with the following parameters: + +* *Is a Vendor* is True +* the *VAT* is set (the VAT number is used by default when searching the supplier in the Odoo partner database) +* in the *Invoicing* tab, create an *Invoice Import Configuration*. + +For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. + +Here are some hints to help you add a template for your supplier: + +* Take `Free SAS template file `_ as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf + +* Try to run the invoice2data library manually on the sample invoice of Free: + +.. code:: + + % python -m invoice2data.main --debug invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf + +On the output, you will get first the text of the PDF, then some debug info on the parsing of the invoice and the regexps, and, on the last line, you will have the dict that contain the result of the parsing. + +* if the VAT number of the supplier is present in the text of the PDF invoice, I think it's a good idea to use it as the keyword. It is a good practice to add 2 other keywoards: one for the language (for example, match on the word *Invoice* in the language of the invoice) and one for the currency, so as to match only the invoices of that supplier in this particular language and currency. + +* the list of *fields* should contain the following entries: + + * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_business_document_import* is installed, add a 'siren' key) + * 'amount' ('amount' is the total amount with taxes) + * 'amount_untaxed' or 'amount_tax' (one or the other, no need for both) + * 'date': the date of the invoice + * 'invoice_number' + * 'date_due', if this information is available in the text of the PDF file. diff --git a/account_invoice_import_invoice2data/readme/CONTRIBUTORS.rst b/account_invoice_import_invoice2data/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..ff65d68ce6 --- /dev/null +++ b/account_invoice_import_invoice2data/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexis de Lattre diff --git a/account_invoice_import_invoice2data/readme/DESCRIPTION.rst b/account_invoice_import_invoice2data/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..05b1825ad6 --- /dev/null +++ b/account_invoice_import_invoice2data/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. + +To know the full story behind the development of this module, read this `blog post `_. diff --git a/account_invoice_import_invoice2data/readme/INSTALL.rst b/account_invoice_import_invoice2data/readme/INSTALL.rst new file mode 100644 index 0000000000..229226bfeb --- /dev/null +++ b/account_invoice_import_invoice2data/readme/INSTALL.rst @@ -0,0 +1,36 @@ +This module requires the Python library *invoice2data* available on `Github `_ with a version >= 0.2.74 (February 2018). + +To install the latest version of this library, run: + +.. code:: + + sudo pip3 install --upgrade invoice2data + +If you use Ubuntu 16.04 LTS or higher, you can use the pdftotext version 0.41.0 that is packaged in the distribution: + +.. code:: + + sudo apt install poppler-utils + +If you want the invoice2data library to fallback on OCR if the PDF doesn't contain text (only a very small minority of PDF invoices are image-based and require OCR), you should also install `Imagemagick `_ (to get the *convert* utility to convert PDF to TIFF) and `Tesseract OCR `_ : + +.. code:: + + sudo apt install imagemagick tesseract-ocr + +If you want to use custom invoice templates for the invoice2data lib (in addition to the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as: + +.. code:: + + invoice2data_templates_dir = /opt/invoice2data_local_templates + +and store your invoice templates in YAML format (*.yml* extension) in the directory that you have configured above. If you add invoice tempates in this directory, you don't have to restart Odoo, they will be used automatically on the next invoice import. + +If you want to use only your custom invoice templates and ignore the templates provided by the invoice2data lib, you should have in your Odoo server configuration file: + +.. code:: + + invoice2data_templates_dir = /opt/invoice2data_local_templates + invoice2data_exclude_built_in_templates = True + +French users should also install the module *l10n_fr_business_document_import* available in the `French localization `_. diff --git a/account_invoice_import_invoice2data/tests/__init__.py b/account_invoice_import_invoice2data/tests/__init__.py index 67f6ba9334..d0a138175d 100644 --- a/account_invoice_import_invoice2data/tests/__init__.py +++ b/account_invoice_import_invoice2data/tests/__init__.py @@ -1,10 +1 @@ -# -*- coding: utf-8 -*- - -# The test below works well, but the full installation of invoice2data -# with the special version of the pdftotext binary is difficult -# Invoice2data new requires a recent version of poppler-utils -# from https://poppler.freedesktop.org/releases.html -# But Travis build environnement are based on Ubuntu 12.04 and I didn't -# find any PPA for poppler-utils for 12.04 and it doesn't seem easy to have a -# static build -#from . import test_invoice_import +from . import test_invoice_import diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 9f1748607f..093054fdd3 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- -# © 2015-2017 Akretion France (www.akretion.com) +# Copyright 2015-2020 Akretion France (www.akretion.com) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo.tests.common import TransactionCase -import base64 from odoo.tools import file_open, float_compare +from odoo import fields +import base64 class TestInvoiceImport(TransactionCase): @@ -15,18 +15,16 @@ def setUp(self): frtax = self.env['account.tax'].create({ 'name': 'French VAT purchase 20.0%', 'description': 'FR-VAT-buy-20.0', - 'amount': 0.2, - 'type': 'percent', - 'account_collected_id': self.env['account.account'].search([ + 'amount': 20, + 'amount_type': 'percent', + 'account_id': self.env['account.account'].search([ ('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id) ], limit=1).id, - 'account_paid_id': self.env['account.account'].search([ + 'refund_account_id': self.env['account.account'].search([ ('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id) ], limit=1).id, - 'base_sign': -1, - 'tax_sign': -1, 'type_tax_use': 'purchase', }) # Set this tax on Internet access product @@ -54,20 +52,19 @@ def test_import_free_invoice(self): self.assertEquals(len(invoices), 1) inv = invoices[0] self.assertEquals(inv.type, 'in_invoice') - self.assertEquals(inv.date_invoice, '2015-07-02') + self.assertEquals( + fields.Date.to_string(inv.date_invoice), '2015-07-02') self.assertEquals( inv.partner_id, self.env.ref('account_invoice_import_invoice2data.free')) self.assertEquals(inv.journal_id.type, 'purchase') - self.assertEquals( - float_compare(inv.check_total, 29.99, precision_digits=2), 0) self.assertEquals( float_compare(inv.amount_total, 29.99, precision_digits=2), 0) self.assertEquals( float_compare(inv.amount_untaxed, 24.99, precision_digits=2), 0) self.assertEquals( - len(inv.invoice_line), 1) - iline = inv.invoice_line[0] + len(inv.invoice_line_ids), 1) + iline = inv.invoice_line_ids[0] self.assertEquals(iline.name, 'Fiber optic access at the main office') self.assertEquals( iline.product_id, @@ -83,7 +80,6 @@ def test_import_free_invoice(self): inv.write({ 'date_invoice': False, 'reference': False, - 'check_total': False, }) # New import with update of an existing draft invoice @@ -109,6 +105,5 @@ def test_import_free_invoice(self): ]) self.assertEquals(len(invoices), 1) inv = invoices[0] - self.assertEquals(inv.date_invoice, '2015-07-02') self.assertEquals( - float_compare(inv.check_total, 29.99, precision_digits=2), 0) + fields.Date.to_string(inv.date_invoice), '2015-07-02') diff --git a/account_invoice_import_invoice2data/wizard/__init__.py b/account_invoice_import_invoice2data/wizard/__init__.py index 8164a0ae51..9817a01c22 100644 --- a/account_invoice_import_invoice2data/wizard/__init__.py +++ b/account_invoice_import_invoice2data/wizard/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import account_invoice_import diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index a511f3820d..99fd42e13d 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -1,5 +1,5 @@ -# -*- coding: utf-8 -*- -# © 2015-2016 Akretion (Alexis de Lattre ) +# Copyright 2015-2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models, tools, _ @@ -53,7 +53,7 @@ def invoice2data_parse_invoice(self, file_data): templates) try: invoice2data_res = extract_data(file_name, templates=templates) - except Exception, e: + except Exception as e: raise UserError(_( "PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: From 30ee8a904437ed911e1072b6e5555498aca68a9c Mon Sep 17 00:00:00 2001 From: Thorsten Vocks Date: Mon, 16 Mar 2020 10:12:02 +0000 Subject: [PATCH 036/104] Added translation using Weblate (German) --- .../i18n/de.po | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/de.po diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po new file mode 100644 index 0000000000..8406513a69 --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#, python-format +msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgstr "" From 14c4e8ee4e1893004c5b0a32823ef0de37421aaf Mon Sep 17 00:00:00 2001 From: Thorsten Vocks Date: Mon, 16 Mar 2020 10:13:05 +0000 Subject: [PATCH 037/104] Translated using Weblate (German) Currently translated at 85.7% (6 of 7 strings) Translation: edi-12.0/edi-12.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data/de/ --- account_invoice_import_invoice2data/i18n/de.po | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po index 8406513a69..c2050e3642 100644 --- a/account_invoice_import_invoice2data/i18n/de.po +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -6,41 +6,44 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2020-03-16 13:13+0000\n" +"Last-Translator: Thorsten Vocks \n" "Language-Team: none\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 3.10\n" #. module: account_invoice_import_invoice2data #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" -msgstr "" +msgstr "reguläre PDF Datei ohne eingebettete XML Datei per" #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" -msgstr "" +msgstr "Internet Zugang" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "" +msgstr "PDF Rechnungsverarbeitung ist gescheitert. Fehler Meldung: %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format msgid "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +"Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" -msgstr "" +msgstr "Assistent für den Lieferantenrechnung-/gutschrift Import" #. module: account_invoice_import_invoice2data #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form @@ -51,3 +54,6 @@ msgstr "" #: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." msgstr "" +"Bibliothek (liest den Text der PDF und erhält die Informationen). Dies " +"funktioniert nur, wenn die Bibliothek invoice2data eine Vorlage für die " +"Rechnungen dieses Lieferanten enthält." From 8c9ecb287e554d6abf66096cb7c824e8f8810a3f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 20 Apr 2020 16:46:58 +0000 Subject: [PATCH 038/104] [UPD] README.rst --- .../README.rst | 87 ++-- .../static/description/index.html | 480 ++++++++++++++++++ 2 files changed, 535 insertions(+), 32 deletions(-) create mode 100644 account_invoice_import_invoice2data/static/description/index.html diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index 2f75eb09d5..b38dd5804d 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -1,15 +1,39 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 - =================================== Account Invoice Import Invoice2data =================================== +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/12.0/account_invoice_import_invoice2data + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/226/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. To know the full story behind the development of this module, read this `blog post `_. +**Table of contents** + +.. contents:: + :local: + Installation ============ @@ -19,19 +43,19 @@ To install the latest version of this library, run: .. code:: - sudo pip install --upgrade invoice2data + sudo pip3 install --upgrade invoice2data -If you use Ubuntu 16.04 LTS, you can use the pdftotext version 0.41.0 that is packaged in the distribution: +If you use Ubuntu 16.04 LTS or higher, you can use the pdftotext version 0.41.0 that is packaged in the distribution: .. code:: - sudo apt-get install poppler-utils + sudo apt install poppler-utils If you want the invoice2data library to fallback on OCR if the PDF doesn't contain text (only a very small minority of PDF invoices are image-based and require OCR), you should also install `Imagemagick `_ (to get the *convert* utility to convert PDF to TIFF) and `Tesseract OCR `_ : .. code:: - sudo apt-get install imagemagick tesseract-ocr + sudo apt install imagemagick tesseract-ocr If you want to use custom invoice templates for the invoice2data lib (in addition to the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as: @@ -55,10 +79,9 @@ Configuration Go to the form view of the supplier and configure it with the following parameters: -* *is a Company ?* is True -* *Supplier* is True -* the *TIN* (i.e. VAT number) is set (the VAT number is used by default when searching the supplier in the Odoo partner database) -* in the *Accounting* tab, create an *Invoice Import Configuration*. +* *Is a Vendor* is True +* the *VAT* is set (the VAT number is used by default when searching the supplier in the Odoo partner database) +* in the *Invoicing* tab, create an *Invoice Import Configuration*. For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. @@ -78,49 +101,49 @@ On the output, you will get first the text of the PDF, then some debug info on t * the list of *fields* should contain the following entries: - * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_invoice_pdf_import* is installed, add a 'siren' key) + * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_business_document_import* is installed, add a 'siren' key) * 'amount' ('amount' is the total amount with taxes) * 'amount_untaxed' or 'amount_tax' (one or the other, no need for both) * 'date': the date of the invoice * 'invoice_number' * 'date_due', if this information is available in the text of the PDF file. -Usage -===== - -Refer to the usage section of the module *account_invoice_import*. - -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/226/10.0 - 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 smashing it by providing a detailed and welcomed feedback. +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= +Authors +~~~~~~~ + +* Akretion + Contributors ------------- +~~~~~~~~~~~~ * Alexis de Lattre -Maintainer ----------- +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - 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. -To contribute to this module, please visit https://odoo-community.org. +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/account_invoice_import_invoice2data/static/description/index.html b/account_invoice_import_invoice2data/static/description/index.html new file mode 100644 index 0000000000..8aaad2dc32 --- /dev/null +++ b/account_invoice_import_invoice2data/static/description/index.html @@ -0,0 +1,480 @@ + + + + + + +Account Invoice Import Invoice2data + + + +
+

Account Invoice Import Invoice2data

+ + +

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

+

This module is an extension of the module account_invoice_import: it adds support for regular PDF invoices i.e. PDF invoice that don’t have an embedded XML file. It uses the invoice2data library which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice.

+

To know the full story behind the development of this module, read this blog post.

+

Table of contents

+ +
+

Installation

+

This module requires the Python library invoice2data available on Github with a version >= 0.2.74 (February 2018).

+

To install the latest version of this library, run:

+
+sudo pip3 install --upgrade invoice2data
+
+

If you use Ubuntu 16.04 LTS or higher, you can use the pdftotext version 0.41.0 that is packaged in the distribution:

+
+sudo apt install poppler-utils
+
+

If you want the invoice2data library to fallback on OCR if the PDF doesn’t contain text (only a very small minority of PDF invoices are image-based and require OCR), you should also install Imagemagick (to get the convert utility to convert PDF to TIFF) and Tesseract OCR :

+
+sudo apt install imagemagick tesseract-ocr
+
+

If you want to use custom invoice templates for the invoice2data lib (in addition to the templates provided by the invoice2data lib), you should add a line in your Odoo server configuration file such as:

+
+invoice2data_templates_dir = /opt/invoice2data_local_templates
+
+

and store your invoice templates in YAML format (.yml extension) in the directory that you have configured above. If you add invoice tempates in this directory, you don’t have to restart Odoo, they will be used automatically on the next invoice import.

+

If you want to use only your custom invoice templates and ignore the templates provided by the invoice2data lib, you should have in your Odoo server configuration file:

+
+invoice2data_templates_dir = /opt/invoice2data_local_templates
+invoice2data_exclude_built_in_templates = True
+
+

French users should also install the module l10n_fr_business_document_import available in the French localization.

+
+
+

Configuration

+

Go to the form view of the supplier and configure it with the following parameters:

+
    +
  • Is a Vendor is True
  • +
  • the VAT is set (the VAT number is used by default when searching the supplier in the Odoo partner database)
  • +
  • in the Invoicing tab, create an Invoice Import Configuration.
  • +
+

For the PDF invoice of your supplier that don’t have an embedded XML file, you will have to create a template file in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with regexp, it should not take more than 10 minutes for each supplier.

+

Here are some hints to help you add a template for your supplier:

+
    +
  • Take Free SAS template file as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf
  • +
  • Try to run the invoice2data library manually on the sample invoice of Free:
  • +
+
+% python -m invoice2data.main --debug invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf
+
+

On the output, you will get first the text of the PDF, then some debug info on the parsing of the invoice and the regexps, and, on the last line, you will have the dict that contain the result of the parsing.

+
    +
  • if the VAT number of the supplier is present in the text of the PDF invoice, I think it’s a good idea to use it as the keyword. It is a good practice to add 2 other keywoards: one for the language (for example, match on the word Invoice in the language of the invoice) and one for the currency, so as to match only the invoices of that supplier in this particular language and currency.
  • +
  • the list of fields should contain the following entries:
      +
    • ‘vat’ with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a ‘partner_name’ key, or, if the supplier is French and the module l10n_fr_business_document_import is installed, add a ‘siren’ key)
    • +
    • ‘amount’ (‘amount’ is the total amount with taxes)
    • +
    • ‘amount_untaxed’ or ‘amount_tax’ (one or the other, no need for both)
    • +
    • ‘date’: the date of the invoice
    • +
    • ‘invoice_number’
    • +
    • ‘date_due’, if this information is available in the text of the PDF file.
    • +
    +
  • +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

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.

+

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.

+
+
+
+ + From 43066a22665788171fb4463be71a43a64085ee08 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Wed, 29 Apr 2020 15:37:22 +0000 Subject: [PATCH 039/104] [UPD] Update account_invoice_import_invoice2data.pot --- .../account_invoice_import_invoice2data.pot | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot index 292db9c907..43a84abe40 100644 --- a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: <>\n" "Language-Team: \n" @@ -14,7 +14,7 @@ msgstr "" "Plural-Forms: \n" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" msgstr "" @@ -36,18 +36,30 @@ msgstr "" msgid "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." msgstr "" From 7b3d6460e3f88d942f904514180596a9b85298d0 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Wed, 29 Apr 2020 15:37:34 +0000 Subject: [PATCH 040/104] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: edi-12.0/edi-12.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data/ --- .../i18n/de.po | 28 +++++++++++++++---- .../i18n/es.po | 18 ++++++++++-- .../i18n/pt.po | 28 +++++++++++++++---- .../i18n/sl.po | 18 ++++++++++-- 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po index c2050e3642..328529fed1 100644 --- a/account_invoice_import_invoice2data/i18n/de.po +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * account_invoice_import_invoice2data +# * account_invoice_import_invoice2data # msgid "" msgstr "" @@ -17,7 +17,7 @@ msgstr "" "X-Generator: Weblate 3.10\n" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" msgstr "reguläre PDF Datei ohne eingebettete XML Datei per" @@ -36,23 +36,39 @@ msgstr "PDF Rechnungsverarbeitung ist gescheitert. Fehler Meldung: %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format -msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" "Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "Assistent für den Lieferantenrechnung-/gutschrift Import" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form -msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." msgstr "" "Bibliothek (liest den Text der PDF und erhält die Informationen). Dies " "funktioniert nur, wenn die Bibliothek invoice2data eine Vorlage für die " diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po index b2d4faab18..1999d6c979 100644 --- a/account_invoice_import_invoice2data/i18n/es.po +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -19,7 +19,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" msgstr "" @@ -42,18 +42,30 @@ msgid "" "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" "library (reads the text of the PDF and get the information). It will work " "only if the invoice2data library has a template for the invoices of that " diff --git a/account_invoice_import_invoice2data/i18n/pt.po b/account_invoice_import_invoice2data/i18n/pt.po index 322bf1c850..777789f5b7 100644 --- a/account_invoice_import_invoice2data/i18n/pt.po +++ b/account_invoice_import_invoice2data/i18n/pt.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * account_invoice_import_invoice2data +# * account_invoice_import_invoice2data # msgid "" msgstr "" @@ -17,7 +17,7 @@ msgstr "" "X-Generator: Weblate 3.3\n" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" msgstr "arquivo PDF regular sem qualquer arquivo XML incorporado via" @@ -36,23 +36,39 @@ msgstr "Análise de fatura PDF falhou. Mensagem de erro: %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format -msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" "Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "Assistente para importar as faturas/créditos do fornecedor" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form -msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." msgstr "" "biblioteca (lê o texto do PDF e obtém a informação). Apenas funcionará se a " "biblioteca de invoice2data tiver um modelo para as faturas deste fornecedor." diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po index fe9256f5e9..48e2a9647a 100644 --- a/account_invoice_import_invoice2data/i18n/sl.po +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -20,7 +20,7 @@ msgstr "" "%100==4 ? 2 : 3);\n" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" msgstr "" @@ -44,18 +44,30 @@ msgid "" msgstr "" "Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice invoice2data." +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" msgstr "Čarovnik za uvoz prejetih računov/dobropisov" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" msgstr "" #. module: account_invoice_import_invoice2data -#: model:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" "library (reads the text of the PDF and get the information). It will work " "only if the invoice2data library has a template for the invoices of that " From 35287fa44030b2ccbd9c2b1ec5813cb6caba84ae Mon Sep 17 00:00:00 2001 From: Maria Sparenberg Date: Wed, 27 May 2020 07:04:48 +0000 Subject: [PATCH 041/104] Translated using Weblate (German) Currently translated at 100.0% (9 of 9 strings) Translation: edi-12.0/edi-12.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data/de/ --- account_invoice_import_invoice2data/i18n/de.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po index 328529fed1..30b284df4a 100644 --- a/account_invoice_import_invoice2data/i18n/de.po +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2020-03-16 13:13+0000\n" -"Last-Translator: Thorsten Vocks \n" +"PO-Revision-Date: 2020-05-27 09:19+0000\n" +"Last-Translator: Maria Sparenberg \n" "Language-Team: none\n" "Language: de\n" "MIME-Version: 1.0\n" @@ -45,23 +45,23 @@ msgstr "" #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template msgid "Unit(s)" -msgstr "" +msgstr "Einheit(en)" #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" -msgstr "Assistent für den Lieferantenrechnung-/gutschrift Import" +msgstr "Assistent für den Import von Lieferantenrechnungen / -gutschriften" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" -msgstr "" +msgstr "invoice2data" #. module: account_invoice_import_invoice2data #: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template msgid "kg" -msgstr "" +msgstr "kg" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form From 39d89bdd9a2117bdf310255720a86ba05cea6d09 Mon Sep 17 00:00:00 2001 From: Bosd Date: Tue, 15 Dec 2020 16:46:32 +0000 Subject: [PATCH 042/104] Added translation using Weblate (Dutch) --- .../i18n/nl.po | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/nl.po diff --git a/account_invoice_import_invoice2data/i18n/nl.po b/account_invoice_import_invoice2data/i18n/nl.po new file mode 100644 index 0000000000..d3c63ea80c --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/nl.po @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#, python-format +msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Unit(s)" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "kg" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgstr "" From 8a8ddd586e2b60734298984227ba1f99aa6c246a Mon Sep 17 00:00:00 2001 From: Bosd Date: Tue, 15 Dec 2020 18:28:24 +0000 Subject: [PATCH 043/104] Translated using Weblate (Dutch) Currently translated at 100.0% (9 of 9 strings) Translation: edi-12.0/edi-12.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data/nl/ --- .../i18n/nl.po | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/nl.po b/account_invoice_import_invoice2data/i18n/nl.po index d3c63ea80c..4d064ce95b 100644 --- a/account_invoice_import_invoice2data/i18n/nl.po +++ b/account_invoice_import_invoice2data/i18n/nl.po @@ -6,60 +6,67 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2020-12-15 19:19+0000\n" +"Last-Translator: Bosd \n" "Language-Team: none\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" -msgstr "" +msgstr "gewoon PDF bestand zonder een geintegreerd XML bestand via de" #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" -msgstr "" +msgstr "Internet Access" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "" +msgstr "Het is niet gelukt om de PDF factuur te verwerken. Fout melding: %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 #, python-format msgid "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +"Deze PDF factuur heeft geen overeenkomstig herkennings sjabloon van de " +"invoice2data library." #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template msgid "Unit(s)" -msgstr "" +msgstr "Stuk(s)" #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" -msgstr "" +msgstr "Hulp voor het importeren van inkoop / credit facturen" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" -msgstr "" +msgstr "invoice2data" #. module: account_invoice_import_invoice2data #: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template msgid "kg" -msgstr "" +msgstr "kg" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." msgstr "" +"bibliotheek (leest de teksten in de PDF voor het verkrijgen van de " +"informatie). Het werkt enkel wanneer er een sjabloon beschikbaar is voor de " +"betreffende leverancier." From b6c11d196506c77c254e67418c793259747d6a11 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 5 Mar 2021 17:43:25 +0100 Subject: [PATCH 044/104] [MIG] account_invoice_import_invoice2data to v14.0 --- .../__manifest__.py | 7 +- .../demo/demo_data.xml | 5 +- .../readme/CONFIGURE.rst | 5 +- .../tests/test_invoice_import.py | 73 ++++++++----------- .../wizard/account_invoice_import.py | 2 +- .../wizard/account_invoice_import_view.xml | 2 +- 6 files changed, 41 insertions(+), 53 deletions(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 2076d782b3..e740bee618 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -1,14 +1,15 @@ -# Copyright 2015-2020 Akretion France (http://www.akretion.com/) +# Copyright 2015-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'Account Invoice Import Invoice2data', - 'version': '12.0.1.0.0', - 'category': 'Invoicing Management', + 'version': '14.0.1.0.0', + 'category': 'Accounting/Accounting', 'license': 'AGPL-3', 'summary': 'Import supplier invoices using the invoice2data lib', 'author': 'Akretion,Odoo Community Association (OCA)', + 'maintainers': ['alexis-via'], 'website': 'https://github.com/OCA/edi', 'depends': ['account_invoice_import'], 'external_dependencies': {'python': ['invoice2data']}, diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml index 6174ce22bd..6dd1a61564 100644 --- a/account_invoice_import_invoice2data/demo/demo_data.xml +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -6,14 +6,15 @@ Internet Access + service
Free SAS - - + 1 + 0 8 rue de la Ville l’Évèque 75008 Paris diff --git a/account_invoice_import_invoice2data/readme/CONFIGURE.rst b/account_invoice_import_invoice2data/readme/CONFIGURE.rst index 315d80c669..9dd2e4c4fb 100644 --- a/account_invoice_import_invoice2data/readme/CONFIGURE.rst +++ b/account_invoice_import_invoice2data/readme/CONFIGURE.rst @@ -1,10 +1,9 @@ Go to the form view of the supplier and configure it with the following parameters: -* *Is a Vendor* is True * the *VAT* is set (the VAT number is used by default when searching the supplier in the Odoo partner database) * in the *Invoicing* tab, create an *Invoice Import Configuration*. -For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. +For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. Here are some hints to help you add a template for your supplier: @@ -22,7 +21,7 @@ On the output, you will get first the text of the PDF, then some debug info on t * the list of *fields* should contain the following entries: - * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_business_document_import* is installed, add a 'siren' key) + * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key) * 'amount' ('amount' is the total amount with taxes) * 'amount_untaxed' or 'amount_tax' (one or the other, no need for both) * 'date': the date of the invoice diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 093054fdd3..25b30cc58e 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -1,4 +1,4 @@ -# Copyright 2015-2020 Akretion France (www.akretion.com) +# Copyright 2015-2021 Akretion France (www.akretion.com) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -11,20 +11,12 @@ class TestInvoiceImport(TransactionCase): def setUp(self): - super(TestInvoiceImport, self).setUp() + super().setUp() frtax = self.env['account.tax'].create({ 'name': 'French VAT purchase 20.0%', 'description': 'FR-VAT-buy-20.0', 'amount': 20, 'amount_type': 'percent', - 'account_id': self.env['account.account'].search([ - ('user_type_id', '=', - self.env.ref('account.data_account_type_expenses').id) - ], limit=1).id, - 'refund_account_id': self.env['account.account'].search([ - ('user_type_id', '=', - self.env.ref('account.data_account_type_expenses').id) - ], limit=1).id, 'type_tax_use': 'purchase', }) # Set this tax on Internet access product @@ -37,73 +29,68 @@ def test_import_free_invoice(self): f = file_open( 'account_invoice_import_invoice2data/tests/pdf/' + filename, 'rb') pdf_file = f.read() + pdf_file_b64 = base64.b64encode(pdf_file) wiz = self.env['account.invoice.import'].create({ - 'invoice_file': base64.b64encode(pdf_file), + 'invoice_file': pdf_file_b64, 'invoice_filename': filename, }) f.close() wiz.import_invoice() # Check result of invoice creation - invoices = self.env['account.invoice'].search([ + invoices = self.env['account.move'].search([ ('state', '=', 'draft'), - ('type', '=', 'in_invoice'), - ('reference', '=', '562044387') + ('move_type', '=', 'in_invoice'), + ('ref', '=', '562044387') ]) - self.assertEquals(len(invoices), 1) + self.assertEqual(len(invoices), 1) inv = invoices[0] - self.assertEquals(inv.type, 'in_invoice') - self.assertEquals( - fields.Date.to_string(inv.date_invoice), '2015-07-02') - self.assertEquals( + self.assertEqual(inv.move_type, 'in_invoice') + self.assertEqual( + fields.Date.to_string(inv.invoice_date), '2015-07-02') + self.assertEqual( inv.partner_id, self.env.ref('account_invoice_import_invoice2data.free')) - self.assertEquals(inv.journal_id.type, 'purchase') - self.assertEquals( + self.assertEqual(inv.journal_id.type, 'purchase') + self.assertEqual( float_compare(inv.amount_total, 29.99, precision_digits=2), 0) - self.assertEquals( + self.assertEqual( float_compare(inv.amount_untaxed, 24.99, precision_digits=2), 0) - self.assertEquals( + self.assertEqual( len(inv.invoice_line_ids), 1) iline = inv.invoice_line_ids[0] - self.assertEquals(iline.name, 'Fiber optic access at the main office') - self.assertEquals( + self.assertEqual(iline.name, 'Fiber optic access at the main office') + self.assertEqual( iline.product_id, self.env.ref( 'account_invoice_import_invoice2data.internet_access')) - self.assertEquals( + self.assertEqual( float_compare(iline.quantity, 1.0, precision_digits=0), 0) - self.assertEquals( + self.assertEqual( float_compare(iline.price_unit, 24.99, precision_digits=2), 0) # Prepare data for next test i.e. invoice update # (we re-use the invoice created by the first import !) inv.write({ - 'date_invoice': False, - 'reference': False, + 'invoice_date': False, + 'ref': False, }) # New import with update of an existing draft invoice - f = file_open( - 'account_invoice_import_invoice2data/tests/pdf/' - 'invoice_free_fiber_201507.pdf', - 'rb') - pdf_file = f.read() wiz2 = self.env['account.invoice.import'].create({ - 'invoice_file': base64.b64encode(pdf_file), + 'invoice_file': pdf_file_b64, 'invoice_filename': 'invoice_free_fiber_201507.pdf', }) - f.close() action = wiz2.import_invoice() - self.assertEquals( + self.assertEqual( action['res_model'], 'account.invoice.import') # Choose to update the existing invoice wiz2.update_invoice() - invoices = self.env['account.invoice'].search([ + invoices = self.env['account.move'].search([ ('state', '=', 'draft'), - ('type', '=', 'in_invoice'), - ('reference', '=', '562044387') + ('move_type', '=', 'in_invoice'), + ('ref', '=', '562044387') ]) - self.assertEquals(len(invoices), 1) + self.assertEqual(len(invoices), 1) inv = invoices[0] - self.assertEquals( - fields.Date.to_string(inv.date_invoice), '2015-07-02') + self.assertEqual( + fields.Date.to_string(inv.invoice_date), '2015-07-02') diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 99fd42e13d..dd9ba15b18 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -1,4 +1,4 @@ -# Copyright 2015-2020 Akretion France (http://www.akretion.com/) +# Copyright 2015-2021 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml index 2eb900b937..37b2fc9202 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml @@ -1,6 +1,6 @@ From 74f4e0628867eb7c26ec1eb389c43347e5a1fc99 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 5 Mar 2021 17:46:31 +0100 Subject: [PATCH 045/104] account_invoice_import_invoice2data: black, isort and other pre-commit stuff --- .../__manifest__.py | 26 ++-- .../demo/demo_data.xml | 17 ++- .../tests/test_invoice_import.py | 122 +++++++++--------- .../wizard/account_invoice_import.py | 97 +++++++------- .../wizard/account_invoice_import_view.xml | 12 +- 5 files changed, 140 insertions(+), 134 deletions(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index e740bee618..b62e03bb5f 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -3,17 +3,17 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'Account Invoice Import Invoice2data', - 'version': '14.0.1.0.0', - 'category': 'Accounting/Accounting', - 'license': 'AGPL-3', - 'summary': 'Import supplier invoices using the invoice2data lib', - 'author': 'Akretion,Odoo Community Association (OCA)', - 'maintainers': ['alexis-via'], - 'website': 'https://github.com/OCA/edi', - 'depends': ['account_invoice_import'], - 'external_dependencies': {'python': ['invoice2data']}, - 'data': ['wizard/account_invoice_import_view.xml'], - 'demo': ['demo/demo_data.xml'], - 'installable': True, + "name": "Account Invoice Import Invoice2data", + "version": "14.0.1.0.0", + "category": "Accounting/Accounting", + "license": "AGPL-3", + "summary": "Import supplier invoices using the invoice2data lib", + "author": "Akretion,Odoo Community Association (OCA)", + "maintainers": ["alexis-via"], + "website": "https://github.com/OCA/edi", + "depends": ["account_invoice_import"], + "external_dependencies": {"python": ["invoice2data"]}, + "data": ["wizard/account_invoice_import_view.xml"], + "demo": ["demo/demo_data.xml"], + "installable": True, } diff --git a/account_invoice_import_invoice2data/demo/demo_data.xml b/account_invoice_import_invoice2data/demo/demo_data.xml index 6dd1a61564..d32cd646ed 100644 --- a/account_invoice_import_invoice2data/demo/demo_data.xml +++ b/account_invoice_import_invoice2data/demo/demo_data.xml @@ -1,34 +1,33 @@ - - + Internet Access - - - + + + service Free SAS - + 1 0 8 rue de la Ville l’Évèque 75008 Paris - + http://www.free.fr FR 604 219 388 61 Free - + 1line_static_product Fiber optic access at the main office - + diff --git a/account_invoice_import_invoice2data/tests/test_invoice_import.py b/account_invoice_import_invoice2data/tests/test_invoice_import.py index 25b30cc58e..8c5c30ee4d 100644 --- a/account_invoice_import_invoice2data/tests/test_invoice_import.py +++ b/account_invoice_import_invoice2data/tests/test_invoice_import.py @@ -2,95 +2,101 @@ # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import base64 + +from odoo import fields from odoo.tests.common import TransactionCase from odoo.tools import file_open, float_compare -from odoo import fields -import base64 class TestInvoiceImport(TransactionCase): - def setUp(self): super().setUp() - frtax = self.env['account.tax'].create({ - 'name': 'French VAT purchase 20.0%', - 'description': 'FR-VAT-buy-20.0', - 'amount': 20, - 'amount_type': 'percent', - 'type_tax_use': 'purchase', - }) + frtax = self.env["account.tax"].create( + { + "name": "French VAT purchase 20.0%", + "description": "FR-VAT-buy-20.0", + "amount": 20, + "amount_type": "percent", + "type_tax_use": "purchase", + } + ) # Set this tax on Internet access product internet_product = self.env.ref( - 'account_invoice_import_invoice2data.internet_access') + "account_invoice_import_invoice2data.internet_access" + ) internet_product.supplier_taxes_id = [(6, 0, [frtax.id])] def test_import_free_invoice(self): - filename = 'invoice_free_fiber_201507.pdf' - f = file_open( - 'account_invoice_import_invoice2data/tests/pdf/' + filename, 'rb') + filename = "invoice_free_fiber_201507.pdf" + f = file_open("account_invoice_import_invoice2data/tests/pdf/" + filename, "rb") pdf_file = f.read() pdf_file_b64 = base64.b64encode(pdf_file) - wiz = self.env['account.invoice.import'].create({ - 'invoice_file': pdf_file_b64, - 'invoice_filename': filename, - }) + wiz = self.env["account.invoice.import"].create( + { + "invoice_file": pdf_file_b64, + "invoice_filename": filename, + } + ) f.close() wiz.import_invoice() # Check result of invoice creation - invoices = self.env['account.move'].search([ - ('state', '=', 'draft'), - ('move_type', '=', 'in_invoice'), - ('ref', '=', '562044387') - ]) + invoices = self.env["account.move"].search( + [ + ("state", "=", "draft"), + ("move_type", "=", "in_invoice"), + ("ref", "=", "562044387"), + ] + ) self.assertEqual(len(invoices), 1) inv = invoices[0] - self.assertEqual(inv.move_type, 'in_invoice') - self.assertEqual( - fields.Date.to_string(inv.invoice_date), '2015-07-02') + self.assertEqual(inv.move_type, "in_invoice") + self.assertEqual(fields.Date.to_string(inv.invoice_date), "2015-07-02") self.assertEqual( - inv.partner_id, - self.env.ref('account_invoice_import_invoice2data.free')) - self.assertEqual(inv.journal_id.type, 'purchase') + inv.partner_id, self.env.ref("account_invoice_import_invoice2data.free") + ) + self.assertEqual(inv.journal_id.type, "purchase") + self.assertEqual(float_compare(inv.amount_total, 29.99, precision_digits=2), 0) self.assertEqual( - float_compare(inv.amount_total, 29.99, precision_digits=2), 0) - self.assertEqual( - float_compare(inv.amount_untaxed, 24.99, precision_digits=2), 0) - self.assertEqual( - len(inv.invoice_line_ids), 1) + float_compare(inv.amount_untaxed, 24.99, precision_digits=2), 0 + ) + self.assertEqual(len(inv.invoice_line_ids), 1) iline = inv.invoice_line_ids[0] - self.assertEqual(iline.name, 'Fiber optic access at the main office') + self.assertEqual(iline.name, "Fiber optic access at the main office") self.assertEqual( iline.product_id, - self.env.ref( - 'account_invoice_import_invoice2data.internet_access')) - self.assertEqual( - float_compare(iline.quantity, 1.0, precision_digits=0), 0) - self.assertEqual( - float_compare(iline.price_unit, 24.99, precision_digits=2), 0) + self.env.ref("account_invoice_import_invoice2data.internet_access"), + ) + self.assertEqual(float_compare(iline.quantity, 1.0, precision_digits=0), 0) + self.assertEqual(float_compare(iline.price_unit, 24.99, precision_digits=2), 0) # Prepare data for next test i.e. invoice update # (we re-use the invoice created by the first import !) - inv.write({ - 'invoice_date': False, - 'ref': False, - }) + inv.write( + { + "invoice_date": False, + "ref": False, + } + ) # New import with update of an existing draft invoice - wiz2 = self.env['account.invoice.import'].create({ - 'invoice_file': pdf_file_b64, - 'invoice_filename': 'invoice_free_fiber_201507.pdf', - }) + wiz2 = self.env["account.invoice.import"].create( + { + "invoice_file": pdf_file_b64, + "invoice_filename": "invoice_free_fiber_201507.pdf", + } + ) action = wiz2.import_invoice() - self.assertEqual( - action['res_model'], 'account.invoice.import') + self.assertEqual(action["res_model"], "account.invoice.import") # Choose to update the existing invoice wiz2.update_invoice() - invoices = self.env['account.move'].search([ - ('state', '=', 'draft'), - ('move_type', '=', 'in_invoice'), - ('ref', '=', '562044387') - ]) + invoices = self.env["account.move"].search( + [ + ("state", "=", "draft"), + ("move_type", "=", "in_invoice"), + ("ref", "=", "562044387"), + ] + ) self.assertEqual(len(invoices), 1) inv = invoices[0] - self.assertEqual( - fields.Date.to_string(inv.invoice_date), '2015-07-02') + self.assertEqual(fields.Date.to_string(inv.invoice_date), "2015-07-02") diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index dd9ba15b18..08996a6a39 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -2,34 +2,35 @@ # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, tools, _ -from odoo.exceptions import UserError +import logging import os from tempfile import mkstemp -import logging + +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError + logger = logging.getLogger(__name__) try: - from invoice2data.main import extract_data from invoice2data.extract.loader import read_templates - from invoice2data.main import logger as loggeri2data + from invoice2data.main import extract_data, logger as loggeri2data except ImportError: - logger.debug('Cannot import invoice2data') + logger.debug("Cannot import invoice2data") class AccountInvoiceImport(models.TransientModel): - _inherit = 'account.invoice.import' + _inherit = "account.invoice.import" @api.model def fallback_parse_pdf_invoice(self, file_data): - '''This method must be inherited by additional modules with + """This method must be inherited by additional modules with the same kind of logic as the account_bank_statement_import_* - modules''' + modules""" return self.invoice2data_parse_invoice(file_data) @api.model def invoice2data_parse_invoice(self, file_data): - logger.info('Trying to analyze PDF invoice with invoice2data lib') + logger.info("Trying to analyze PDF invoice with invoice2data lib") fd, file_name = mkstemp() try: os.write(fd, file_data) @@ -37,62 +38,60 @@ def invoice2data_parse_invoice(self, file_data): os.close(fd) # Transfer log level of Odoo to invoice2data loggeri2data.setLevel(logger.getEffectiveLevel()) - local_templates_dir = tools.config.get( - 'invoice2data_templates_dir', False) - logger.debug( - 'invoice2data local_templates_dir=%s', local_templates_dir) + local_templates_dir = tools.config.get("invoice2data_templates_dir", False) + logger.debug("invoice2data local_templates_dir=%s", local_templates_dir) templates = [] if local_templates_dir and os.path.isdir(local_templates_dir): templates += read_templates(local_templates_dir) exclude_built_in_templates = tools.config.get( - 'invoice2data_exclude_built_in_templates', False) + "invoice2data_exclude_built_in_templates", False + ) if not exclude_built_in_templates: templates += read_templates() - logger.debug( - 'Calling invoice2data.extract_data with templates=%s', - templates) + logger.debug("Calling invoice2data.extract_data with templates=%s", templates) try: invoice2data_res = extract_data(file_name, templates=templates) except Exception as e: - raise UserError(_( - "PDF Invoice parsing failed. Error message: %s") % e) + raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: - raise UserError(_( - "This PDF invoice doesn't match a known template of " - "the invoice2data lib.")) - logger.info( - 'Result of invoice2data PDF extraction: %s', invoice2data_res) + raise UserError( + _( + "This PDF invoice doesn't match a known template of " + "the invoice2data lib." + ) + ) + logger.info("Result of invoice2data PDF extraction: %s", invoice2data_res) return self.invoice2data_to_parsed_inv(invoice2data_res) @api.model def invoice2data_to_parsed_inv(self, invoice2data_res): parsed_inv = { - 'partner': { - 'vat': invoice2data_res.get('vat'), - 'name': invoice2data_res.get('partner_name'), - 'email': invoice2data_res.get('partner_email'), - 'website': invoice2data_res.get('partner_website'), - 'siren': invoice2data_res.get('siren'), - }, - 'currency': { - 'iso': invoice2data_res.get('currency'), - }, - 'amount_total': invoice2data_res.get('amount'), - 'date': invoice2data_res.get('date'), - 'date_due': invoice2data_res.get('date_due'), - 'date_start': invoice2data_res.get('date_start'), - 'date_end': invoice2data_res.get('date_end'), - } - for field in ['invoice_number', 'description']: + "partner": { + "vat": invoice2data_res.get("vat"), + "name": invoice2data_res.get("partner_name"), + "email": invoice2data_res.get("partner_email"), + "website": invoice2data_res.get("partner_website"), + "siren": invoice2data_res.get("siren"), + }, + "currency": { + "iso": invoice2data_res.get("currency"), + }, + "amount_total": invoice2data_res.get("amount"), + "date": invoice2data_res.get("date"), + "date_due": invoice2data_res.get("date_due"), + "date_start": invoice2data_res.get("date_start"), + "date_end": invoice2data_res.get("date_end"), + } + for field in ["invoice_number", "description"]: if isinstance(invoice2data_res.get(field), list): - parsed_inv[field] = ' '.join(invoice2data_res[field]) + parsed_inv[field] = " ".join(invoice2data_res[field]) else: parsed_inv[field] = invoice2data_res.get(field) - if 'amount_untaxed' in invoice2data_res: - parsed_inv['amount_untaxed'] = invoice2data_res['amount_untaxed'] - if 'amount_tax' in invoice2data_res: - parsed_inv['amount_tax'] = invoice2data_res['amount_tax'] + if "amount_untaxed" in invoice2data_res: + parsed_inv["amount_untaxed"] = invoice2data_res["amount_untaxed"] + if "amount_tax" in invoice2data_res: + parsed_inv["amount_tax"] = invoice2data_res["amount_tax"] for key, value in parsed_inv.items(): - if key.startswith('date') and parsed_inv[key]: - parsed_inv[key] = fields.Date.to_string(parsed_inv[key]) + if key.startswith("date") and value: + parsed_inv[key] = fields.Date.to_string(value) return parsed_inv diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml index 37b2fc9202..6c11356c93 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import_view.xml @@ -1,20 +1,22 @@ - + - invoice2data.account.invoice.import.form account.invoice.import - - + +
    -
  • regular PDF file without any embedded XML file via the invoice2data library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier.
  • +
  • regular PDF file without any embedded XML file via the invoice2data library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier.
From da99c91043573ba743b93e29adbd492031f33e33 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Thu, 17 Jun 2021 16:05:46 +0000 Subject: [PATCH 046/104] [UPD] Update account_invoice_import_invoice2data.pot --- .../account_invoice_import_invoice2data.pot | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot index 43a84abe40..c3679fa724 100644 --- a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -1,12 +1,12 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * account_invoice_import_invoice2data +# * account_invoice_import_invoice2data # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,6 +18,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -25,21 +35,27 @@ msgid "Internet Access" msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "This PDF invoice doesn't match a known template of the invoice2data lib." +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" +msgid "Units" msgstr "" #. module: account_invoice_import_invoice2data @@ -52,14 +68,10 @@ msgstr "" msgid "invoice2data" msgstr "" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form -msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." msgstr "" - From 3216f1d7a0f444d518fb37e575c73136d8f644ae Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 17 Jun 2021 16:11:32 +0000 Subject: [PATCH 047/104] [UPD] README.rst --- .../README.rst | 23 ++++++++++++------- .../static/description/index.html | 13 ++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index b38dd5804d..362f0ba1d8 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -14,13 +14,13 @@ Account Invoice Import Invoice2data :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/12.0/account_invoice_import_invoice2data + :target: https://github.com/OCA/edi/tree/14.0/account_invoice_import_invoice2data :alt: OCA/edi .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/edi-12-0/edi-12-0-account_invoice_import_invoice2data + :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/226/12.0 + :target: https://runbot.odoo-community.org/runbot/226/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -79,11 +79,10 @@ Configuration Go to the form view of the supplier and configure it with the following parameters: -* *Is a Vendor* is True * the *VAT* is set (the VAT number is used by default when searching the supplier in the Odoo partner database) * in the *Invoicing* tab, create an *Invoice Import Configuration*. -For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. +For the PDF invoice of your supplier that don't have an embedded XML file, you will have to create a `template file `_ in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with `regexp `_, it should not take more than 10 minutes for each supplier. Here are some hints to help you add a template for your supplier: @@ -101,7 +100,7 @@ On the output, you will get first the text of the PDF, then some debug info on t * the list of *fields* should contain the following entries: - * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key, or, if the supplier is French and the module *l10n_fr_business_document_import* is installed, add a 'siren' key) + * 'vat' with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a 'partner_name' key) * 'amount' ('amount' is the total amount with taxes) * 'amount_untaxed' or 'amount_tax' (one or the other, no need for both) * 'date': the date of the invoice @@ -114,7 +113,7 @@ 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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -144,6 +143,14 @@ 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. -This module is part of the `OCA/edi `_ project on GitHub. +.. |maintainer-alexis-via| image:: https://github.com/alexis-via.png?size=40px + :target: https://github.com/alexis-via + :alt: alexis-via + +Current `maintainer `__: + +|maintainer-alexis-via| + +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/account_invoice_import_invoice2data/static/description/index.html b/account_invoice_import_invoice2data/static/description/index.html index 8aaad2dc32..2a9f55ffe7 100644 --- a/account_invoice_import_invoice2data/static/description/index.html +++ b/account_invoice_import_invoice2data/static/description/index.html @@ -367,7 +367,7 @@

Account Invoice Import Invoice2data

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

This module is an extension of the module account_invoice_import: it adds support for regular PDF invoices i.e. PDF invoice that don’t have an embedded XML file. It uses the invoice2data library which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice.

To know the full story behind the development of this module, read this blog post.

Table of contents

@@ -415,11 +415,10 @@

Installation

Configuration

Go to the form view of the supplier and configure it with the following parameters:

    -
  • Is a Vendor is True
  • the VAT is set (the VAT number is used by default when searching the supplier in the Odoo partner database)
  • in the Invoicing tab, create an Invoice Import Configuration.
-

For the PDF invoice of your supplier that don’t have an embedded XML file, you will have to create a template file in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with regexp, it should not take more than 10 minutes for each supplier.

+

For the PDF invoice of your supplier that don’t have an embedded XML file, you will have to create a template file in YAML format in the invoice2data Python library. It is quite easy to do ; if you are familiar with regexp, it should not take more than 10 minutes for each supplier.

Here are some hints to help you add a template for your supplier:

  • Take Free SAS template file as an example. You will find a sample PDF invoice for this supplier under invoice2data/test/pdfs/2015-07-02-invoice_free_fiber.pdf
  • @@ -432,7 +431,7 @@

    Configuration

    • if the VAT number of the supplier is present in the text of the PDF invoice, I think it’s a good idea to use it as the keyword. It is a good practice to add 2 other keywoards: one for the language (for example, match on the word Invoice in the language of the invoice) and one for the currency, so as to match only the invoices of that supplier in this particular language and currency.
    • the list of fields should contain the following entries:
        -
      • ‘vat’ with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a ‘partner_name’ key, or, if the supplier is French and the module l10n_fr_business_document_import is installed, add a ‘siren’ key)
      • +
      • ‘vat’ with the VAT number of the supplier (if the VAT number of the supplier is not in the text of PDF file, add a ‘partner_name’ key)
      • ‘amount’ (‘amount’ is the total amount with taxes)
      • ‘amount_untaxed’ or ‘amount_tax’ (one or the other, no need for both)
      • ‘date’: the date of the invoice
      • @@ -447,7 +446,7 @@

        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 smashing it by providing a detailed and welcomed -feedback.

        +feedback.

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

        @@ -471,7 +470,9 @@

        Maintainers

        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.

        -

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

        +

        Current maintainer:

        +

        alexis-via

        +

        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.

        From 758ebb3906353835abeafdc423aca1702b76defc Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Thu, 11 Jun 2020 17:56:36 +0200 Subject: [PATCH 048/104] [IMP] account_invoice_import_invoice2data: Allow a field for company vat --- .../wizard/account_invoice_import.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 08996a6a39..48903124e3 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -91,6 +91,8 @@ def invoice2data_to_parsed_inv(self, invoice2data_res): parsed_inv["amount_untaxed"] = invoice2data_res["amount_untaxed"] if "amount_tax" in invoice2data_res: parsed_inv["amount_tax"] = invoice2data_res["amount_tax"] + if "company_vat" in invoice2data_res: + parsed_inv["company"] = {"vat": invoice2data_res["company_vat"]} for key, value in parsed_inv.items(): if key.startswith("date") and value: parsed_inv[key] = fields.Date.to_string(value) From 2eaa3bd2a9367860e2ba280c4e12a6a23ba5a89c Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 2 Sep 2021 15:28:06 +0200 Subject: [PATCH 049/104] Add excludes key in account_invoice_import_invoice2data and update its README Exclude account_invoice_import_invoice2data from travis tests --- account_invoice_import_invoice2data/__manifest__.py | 1 + account_invoice_import_invoice2data/readme/DESCRIPTION.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index b62e03bb5f..2f139671d5 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -12,6 +12,7 @@ "maintainers": ["alexis-via"], "website": "https://github.com/OCA/edi", "depends": ["account_invoice_import"], + "excludes": ["account_invoice_import_simple_pdf"], "external_dependencies": {"python": ["invoice2data"]}, "data": ["wizard/account_invoice_import_view.xml"], "demo": ["demo/demo_data.xml"], diff --git a/account_invoice_import_invoice2data/readme/DESCRIPTION.rst b/account_invoice_import_invoice2data/readme/DESCRIPTION.rst index 05b1825ad6..f002e3a2f6 100644 --- a/account_invoice_import_invoice2data/readme/DESCRIPTION.rst +++ b/account_invoice_import_invoice2data/readme/DESCRIPTION.rst @@ -1,3 +1,5 @@ This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. To know the full story behind the development of this module, read this `blog post `_. + +**WARNING**: an alternative module **account_invoice_import_simple_pdf** developped in July 2021 provides the same feature but has one big advantage: the accountant can add support for a new vendor by himself, no more invoice templates which require technical skill. This new module *account_invoice_import_simple_pdf* will probably replace the module *account_invoice_import_invoice2data* in future Odoo versions. From d20be5b22c96773f7c031088976e86ed1110210e Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 26 Oct 2021 22:12:50 +0000 Subject: [PATCH 050/104] [UPD] README.rst --- account_invoice_import_invoice2data/README.rst | 2 ++ .../static/description/index.html | 1 + 2 files changed, 3 insertions(+) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index 362f0ba1d8..e24f0b4c0b 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -29,6 +29,8 @@ This module is an extension of the module *account_invoice_import*: it adds supp To know the full story behind the development of this module, read this `blog post `_. +**WARNING**: an alternative module **account_invoice_import_simple_pdf** developped in July 2021 provides the same feature but has one big advantage: the accountant can add support for a new vendor by himself, no more invoice templates which require technical skill. This new module *account_invoice_import_simple_pdf* will probably replace the module *account_invoice_import_invoice2data* in future Odoo versions. + **Table of contents** .. contents:: diff --git a/account_invoice_import_invoice2data/static/description/index.html b/account_invoice_import_invoice2data/static/description/index.html index 2a9f55ffe7..8d64a8aa49 100644 --- a/account_invoice_import_invoice2data/static/description/index.html +++ b/account_invoice_import_invoice2data/static/description/index.html @@ -370,6 +370,7 @@

        Account Invoice Import Invoice2data

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

        This module is an extension of the module account_invoice_import: it adds support for regular PDF invoices i.e. PDF invoice that don’t have an embedded XML file. It uses the invoice2data library which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice.

        To know the full story behind the development of this module, read this blog post.

        +

        WARNING: an alternative module account_invoice_import_simple_pdf developped in July 2021 provides the same feature but has one big advantage: the accountant can add support for a new vendor by himself, no more invoice templates which require technical skill. This new module account_invoice_import_simple_pdf will probably replace the module account_invoice_import_invoice2data in future Odoo versions.

        Table of contents

          From 7bd111e044acd381df8cd8c04a2e38c12941451f Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Mon, 29 Nov 2021 13:43:07 +0100 Subject: [PATCH 051/104] [FIX] account_invoice_import_simple_pdf: Remove exclude with invoice2data --- account_invoice_import_invoice2data/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 2f139671d5..f933586d06 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -12,7 +12,7 @@ "maintainers": ["alexis-via"], "website": "https://github.com/OCA/edi", "depends": ["account_invoice_import"], - "excludes": ["account_invoice_import_simple_pdf"], + # "excludes": ["account_invoice_import_simple_pdf"], "external_dependencies": {"python": ["invoice2data"]}, "data": ["wizard/account_invoice_import_view.xml"], "demo": ["demo/demo_data.xml"], From 241b595a739bb397e0c4f7b32df77d3564e5d7ac Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Mon, 29 Nov 2021 13:07:48 +0100 Subject: [PATCH 052/104] [IMP] From travis to Github actions --- account_invoice_import_invoice2data/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index f933586d06..c779b74695 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -13,7 +13,7 @@ "website": "https://github.com/OCA/edi", "depends": ["account_invoice_import"], # "excludes": ["account_invoice_import_simple_pdf"], - "external_dependencies": {"python": ["invoice2data"]}, + "external_dependencies": {"python": ["invoice2data"], "deb": ["poppler-utils"]}, "data": ["wizard/account_invoice_import_view.xml"], "demo": ["demo/demo_data.xml"], "installable": True, From befb123a0533b55784306db9146426ae4714b149 Mon Sep 17 00:00:00 2001 From: Bosd Date: Thu, 27 Jan 2022 21:55:55 +0000 Subject: [PATCH 053/104] Translated using Weblate (Dutch) Currently translated at 100.0% (9 of 9 strings) Translation: edi-14.0/edi-14.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data/nl/ --- account_invoice_import_invoice2data/i18n/nl.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/nl.po b/account_invoice_import_invoice2data/i18n/nl.po index 4d064ce95b..330c01127f 100644 --- a/account_invoice_import_invoice2data/i18n/nl.po +++ b/account_invoice_import_invoice2data/i18n/nl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2020-12-15 19:19+0000\n" +"PO-Revision-Date: 2022-01-28 00:48+0000\n" "Last-Translator: Bosd \n" "Language-Team: none\n" "Language: nl\n" @@ -19,13 +19,13 @@ msgstr "" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" -msgstr "gewoon PDF bestand zonder een geintegreerd XML bestand via de" +msgstr "normaal PDF bestand zonder een geintegreerd XML bestand via de" #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" -msgstr "Internet Access" +msgstr "Internet Toegang" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 @@ -67,6 +67,6 @@ msgstr "kg" #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." msgstr "" -"bibliotheek (leest de teksten in de PDF voor het verkrijgen van de " +"bibliotheek (extraheert de teksten in de PDF voor het verkrijgen van de " "informatie). Het werkt enkel wanneer er een sjabloon beschikbaar is voor de " "betreffende leverancier." From c426f2c098554a14ffe738a741abd936216e2991 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 18 Mar 2022 13:21:09 +0100 Subject: [PATCH 054/104] account_invoice_import_invoice2data: pin dateparser=1.1.1 Fixes https://github.com/OCA/edi/issues/544 --- account_invoice_import_invoice2data/__manifest__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index c779b74695..5b3e3b3ff2 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -13,7 +13,14 @@ "website": "https://github.com/OCA/edi", "depends": ["account_invoice_import"], # "excludes": ["account_invoice_import_simple_pdf"], - "external_dependencies": {"python": ["invoice2data"], "deb": ["poppler-utils"]}, + "external_dependencies": { + "python": [ + "invoice2data", + # https://github.com/OCA/edi/issues/544 + "dateparser==1.1.1", + ], + "deb": ["poppler-utils"], + }, "data": ["wizard/account_invoice_import_view.xml"], "demo": ["demo/demo_data.xml"], "installable": True, From 276428ffd1cd87cb5f388ce6dcd3f5716abb7f71 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 19 Mar 2022 11:28:54 +0000 Subject: [PATCH 055/104] account_invoice_import_invoice2data 14.0.1.0.1 --- account_invoice_import_invoice2data/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 5b3e3b3ff2..e318562bf0 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Account Invoice Import Invoice2data", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "category": "Accounting/Accounting", "license": "AGPL-3", "summary": "Import supplier invoices using the invoice2data lib", From cec7567381de148b8ff4c4a12f200e6f25e57b78 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 15 Jul 2022 00:38:02 +0200 Subject: [PATCH 056/104] account_invoice_import: improve handling of simple PDF invoices Implement what is described in issue #610: - account_invoice_import_simple_pdf and account_invoice_import_invoice2data can now be use d together - when importing an invoice for an unknown partner, it creates an empty invoice with PDF a s attachment - when importing an invoice via mail gateway and the from email is set on a partner that doesn't have any import config, create a supplier invoice with PDF as attachment and with that partner - when importing an invoice via mail gateway and the from email is set on a partner that has a single-line import config, create a supplier invoice with PDF as attachment, that partner and 1 invoice line with price_unit=0. --- .../wizard/account_invoice_import.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 48903124e3..11fd04724f 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -26,7 +26,10 @@ def fallback_parse_pdf_invoice(self, file_data): """This method must be inherited by additional modules with the same kind of logic as the account_bank_statement_import_* modules""" - return self.invoice2data_parse_invoice(file_data) + res = super().fallback_parse_pdf_invoice(file_data) + if not res: + res = self.invoice2data_parse_invoice(file_data) + return res @api.model def invoice2data_parse_invoice(self, file_data): @@ -54,12 +57,7 @@ def invoice2data_parse_invoice(self, file_data): except Exception as e: raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: - raise UserError( - _( - "This PDF invoice doesn't match a known template of " - "the invoice2data lib." - ) - ) + return False logger.info("Result of invoice2data PDF extraction: %s", invoice2data_res) return self.invoice2data_to_parsed_inv(invoice2data_res) From 83810efec7c3d8c4399771ce05ffe1c10ef49472 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 27 Sep 2022 21:09:43 +0000 Subject: [PATCH 057/104] [UPD] Update account_invoice_import_invoice2data.pot --- .../i18n/account_invoice_import_invoice2data.pot | 7 ------- 1 file changed, 7 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot index c3679fa724..1d9114feae 100644 --- a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -45,13 +45,6 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "" -#. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 -#, python-format -msgid "" -"This PDF invoice doesn't match a known template of the invoice2data lib." -msgstr "" - #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template From f3db904c72fbccf9e0a4877ff2a42f519b7713c8 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 27 Sep 2022 21:17:24 +0000 Subject: [PATCH 058/104] account_invoice_import_invoice2data 14.0.2.0.0 --- account_invoice_import_invoice2data/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index e318562bf0..91ee43f0ce 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Account Invoice Import Invoice2data", - "version": "14.0.1.0.1", + "version": "14.0.2.0.0", "category": "Accounting/Accounting", "license": "AGPL-3", "summary": "Import supplier invoices using the invoice2data lib", From ace1aae8bde79aa64e0f3f0dc72a30c59133b5e4 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Tue, 27 Sep 2022 21:17:51 +0000 Subject: [PATCH 059/104] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: edi-14.0/edi-14.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data/ --- .../i18n/de.po | 46 ++++++++++------ .../i18n/es.po | 28 +++++----- .../i18n/nl.po | 54 ++++++++++++------- .../i18n/pt.po | 38 +++++++------ .../i18n/sl.po | 39 ++++++++------ 5 files changed, 124 insertions(+), 81 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po index 30b284df4a..6ddbbdded2 100644 --- a/account_invoice_import_invoice2data/i18n/de.po +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -21,6 +21,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "reguläre PDF Datei ohne eingebettete XML Datei per" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -28,24 +38,21 @@ msgid "Internet Access" msgstr "Internet Zugang" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 -#, python-format -msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "PDF Rechnungsverarbeitung ist gescheitert. Fehler Meldung: %s" +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "" -"This PDF invoice doesn't match a known template of the invoice2data lib." -msgstr "" -"Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "PDF Rechnungsverarbeitung ist gescheitert. Fehler Meldung: %s" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" -msgstr "Einheit(en)" +msgid "Units" +msgstr "" #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import @@ -57,12 +64,6 @@ msgstr "Assistent für den Import von Lieferantenrechnungen / -gutschriften" msgid "invoice2data" msgstr "invoice2data" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "kg" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" @@ -73,3 +74,14 @@ msgstr "" "Bibliothek (liest den Text der PDF und erhält die Informationen). Dies " "funktioniert nur, wenn die Bibliothek invoice2data eine Vorlage für die " "Rechnungen dieses Lieferanten enthält." + +#~ msgid "" +#~ "This PDF invoice doesn't match a known template of the invoice2data lib." +#~ msgstr "" +#~ "Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." + +#~ msgid "Unit(s)" +#~ msgstr "Einheit(en)" + +#~ msgid "kg" +#~ msgstr "kg" diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po index 1999d6c979..29fbf1256c 100644 --- a/account_invoice_import_invoice2data/i18n/es.po +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -23,6 +23,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -30,22 +40,20 @@ msgid "Internet Access" msgstr "Acceso a internet" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 -#, python-format -msgid "PDF Invoice parsing failed. Error message: %s" +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "" -"This PDF invoice doesn't match a known template of the invoice2data lib." +msgid "PDF Invoice parsing failed. Error message: %s" msgstr "" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" +msgid "Units" msgstr "" #. module: account_invoice_import_invoice2data @@ -58,12 +66,6 @@ msgstr "" msgid "invoice2data" msgstr "" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" diff --git a/account_invoice_import_invoice2data/i18n/nl.po b/account_invoice_import_invoice2data/i18n/nl.po index 330c01127f..7db201f9db 100644 --- a/account_invoice_import_invoice2data/i18n/nl.po +++ b/account_invoice_import_invoice2data/i18n/nl.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * account_invoice_import_invoice2data +# * account_invoice_import_invoice2data # msgid "" msgstr "" @@ -21,6 +21,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "normaal PDF bestand zonder een geintegreerd XML bestand via de" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -28,24 +38,21 @@ msgid "Internet Access" msgstr "Internet Toegang" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 -#, python-format -msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "Het is niet gelukt om de PDF factuur te verwerken. Fout melding: %s" +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "This PDF invoice doesn't match a known template of the invoice2data lib." -msgstr "" -"Deze PDF factuur heeft geen overeenkomstig herkennings sjabloon van de " -"invoice2data library." +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "Het is niet gelukt om de PDF factuur te verwerken. Fout melding: %s" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" -msgstr "Stuk(s)" +msgid "Units" +msgstr "" #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import @@ -57,16 +64,25 @@ msgstr "Hulp voor het importeren van inkoop / credit facturen" msgid "invoice2data" msgstr "invoice2data" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "kg" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form -msgid "library (reads the text of the PDF and get the information). It will work only if the invoice2data library has a template for the invoices of that supplier." +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." msgstr "" "bibliotheek (extraheert de teksten in de PDF voor het verkrijgen van de " "informatie). Het werkt enkel wanneer er een sjabloon beschikbaar is voor de " "betreffende leverancier." + +#~ msgid "" +#~ "This PDF invoice doesn't match a known template of the invoice2data lib." +#~ msgstr "" +#~ "Deze PDF factuur heeft geen overeenkomstig herkennings sjabloon van de " +#~ "invoice2data library." + +#~ msgid "Unit(s)" +#~ msgstr "Stuk(s)" + +#~ msgid "kg" +#~ msgstr "kg" diff --git a/account_invoice_import_invoice2data/i18n/pt.po b/account_invoice_import_invoice2data/i18n/pt.po index 777789f5b7..f4b3bb9397 100644 --- a/account_invoice_import_invoice2data/i18n/pt.po +++ b/account_invoice_import_invoice2data/i18n/pt.po @@ -21,6 +21,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "arquivo PDF regular sem qualquer arquivo XML incorporado via" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -28,23 +38,20 @@ msgid "Internet Access" msgstr "Acesso à Internet" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 -#, python-format -msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "Análise de fatura PDF falhou. Mensagem de erro: %s" +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "" -"This PDF invoice doesn't match a known template of the invoice2data lib." -msgstr "" -"Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "Análise de fatura PDF falhou. Mensagem de erro: %s" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" +msgid "Units" msgstr "" #. module: account_invoice_import_invoice2data @@ -57,12 +64,6 @@ msgstr "Assistente para importar as faturas/créditos do fornecedor" msgid "invoice2data" msgstr "" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" @@ -72,3 +73,8 @@ msgid "" msgstr "" "biblioteca (lê o texto do PDF e obtém a informação). Apenas funcionará se a " "biblioteca de invoice2data tiver um modelo para as faturas deste fornecedor." + +#~ msgid "" +#~ "This PDF invoice doesn't match a known template of the invoice2data lib." +#~ msgstr "" +#~ "Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po index 48e2a9647a..5dd67f38d4 100644 --- a/account_invoice_import_invoice2data/i18n/sl.po +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -24,6 +24,16 @@ msgstr "" msgid "regular PDF file without any embedded XML file via the" msgstr "" +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template @@ -31,23 +41,20 @@ msgid "Internet Access" msgstr "Internetni dostop" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:57 -#, python-format -msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "Neuspešna obdelava PDF računa. Napaka: %s" +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" #. module: account_invoice_import_invoice2data -#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:60 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format -msgid "" -"This PDF invoice doesn't match a known template of the invoice2data lib." -msgstr "" -"Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice invoice2data." +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "Neuspešna obdelava PDF računa. Napaka: %s" #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "Unit(s)" +msgid "Units" msgstr "" #. module: account_invoice_import_invoice2data @@ -60,12 +67,6 @@ msgstr "Čarovnik za uvoz prejetih računov/dobropisov" msgid "invoice2data" msgstr "" -#. module: account_invoice_import_invoice2data -#: model:product.product,weight_uom_name:account_invoice_import_invoice2data.internet_access -#: model:product.template,weight_uom_name:account_invoice_import_invoice2data.internet_access_product_template -msgid "kg" -msgstr "" - #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "" @@ -73,3 +74,9 @@ msgid "" "only if the invoice2data library has a template for the invoices of that " "supplier." msgstr "" + +#~ msgid "" +#~ "This PDF invoice doesn't match a known template of the invoice2data lib." +#~ msgstr "" +#~ "Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice " +#~ "invoice2data." From e68eb0c253412035c8dad9b80f797e58741f0bb8 Mon Sep 17 00:00:00 2001 From: bosd Date: Sat, 23 Apr 2022 12:17:20 +0200 Subject: [PATCH 060/104] [FIX] account_invoice_import_invoice2data: Fallback to tesseract --- .../wizard/account_invoice_import.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 11fd04724f..62df017655 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -4,6 +4,7 @@ import logging import os +import shutil from tempfile import mkstemp from odoo import _, api, fields, models, tools @@ -16,6 +17,10 @@ from invoice2data.main import extract_data, logger as loggeri2data except ImportError: logger.debug("Cannot import invoice2data") +try: + from invoice2data.input import tesseract +except ImportError: + logger.debug("Cannot import tesseract") class AccountInvoiceImport(models.TransientModel): @@ -57,7 +62,28 @@ def invoice2data_parse_invoice(self, file_data): except Exception as e: raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: - return False + if not shutil.which("tesseract"): + logger.warning( + "Fallback on tesseract impossible, Could not find the utility. " + "Hint: sudo apt install tesseract-ocr" + ) + return False + # Fallback on tesseract + logger.info("PDF Invoice parsing failed: Falling back on Tesseract ocr") + try: + # from invoice2data.input import tesseract + invoice2data_res = extract_data( + file_name, templates=templates, input_module=tesseract + ) + except Exception as e: + raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) + if not invoice2data_res: + raise UserError( + _( + "This PDF invoice doesn't match a known template of " + "the invoice2data lib." + ) + ) logger.info("Result of invoice2data PDF extraction: %s", invoice2data_res) return self.invoice2data_to_parsed_inv(invoice2data_res) From d087bafb95a2e06f89e10b38d01bca207b2eac11 Mon Sep 17 00:00:00 2001 From: bosd Date: Tue, 14 Feb 2023 21:54:50 +0100 Subject: [PATCH 061/104] [FIX] account_invoice_import_invoice2data: Tempfile Handling --- .../wizard/account_invoice_import.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/account_invoice_import_invoice2data/wizard/account_invoice_import.py b/account_invoice_import_invoice2data/wizard/account_invoice_import.py index 62df017655..c06c17988e 100644 --- a/account_invoice_import_invoice2data/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data/wizard/account_invoice_import.py @@ -5,7 +5,7 @@ import logging import os import shutil -from tempfile import mkstemp +from tempfile import NamedTemporaryFile from odoo import _, api, fields, models, tools from odoo.exceptions import UserError @@ -39,12 +39,10 @@ def fallback_parse_pdf_invoice(self, file_data): @api.model def invoice2data_parse_invoice(self, file_data): logger.info("Trying to analyze PDF invoice with invoice2data lib") - fd, file_name = mkstemp() - try: - os.write(fd, file_data) - finally: - os.close(fd) - # Transfer log level of Odoo to invoice2data + fileobj = NamedTemporaryFile( + "wb", prefix="odoo-aii-inv2data-pdf-", suffix=".pdf" + ) + fileobj.write(file_data) loggeri2data.setLevel(logger.getEffectiveLevel()) local_templates_dir = tools.config.get("invoice2data_templates_dir", False) logger.debug("invoice2data local_templates_dir=%s", local_templates_dir) @@ -58,8 +56,9 @@ def invoice2data_parse_invoice(self, file_data): templates += read_templates() logger.debug("Calling invoice2data.extract_data with templates=%s", templates) try: - invoice2data_res = extract_data(file_name, templates=templates) + invoice2data_res = extract_data(fileobj.name, templates=templates) except Exception as e: + fileobj.close() raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: if not shutil.which("tesseract"): @@ -67,17 +66,20 @@ def invoice2data_parse_invoice(self, file_data): "Fallback on tesseract impossible, Could not find the utility. " "Hint: sudo apt install tesseract-ocr" ) + fileobj.close() return False # Fallback on tesseract logger.info("PDF Invoice parsing failed: Falling back on Tesseract ocr") try: # from invoice2data.input import tesseract invoice2data_res = extract_data( - file_name, templates=templates, input_module=tesseract + fileobj.name, templates=templates, input_module=tesseract ) except Exception as e: + fileobj.close() raise UserError(_("PDF Invoice parsing failed. Error message: %s") % e) if not invoice2data_res: + fileobj.close() raise UserError( _( "This PDF invoice doesn't match a known template of " @@ -85,6 +87,7 @@ def invoice2data_parse_invoice(self, file_data): ) ) logger.info("Result of invoice2data PDF extraction: %s", invoice2data_res) + fileobj.close() return self.invoice2data_to_parsed_inv(invoice2data_res) @api.model From ead1ba1764d1b6c01193d52e619095307a770ced Mon Sep 17 00:00:00 2001 From: oca-ci Date: Fri, 24 Feb 2023 15:31:20 +0000 Subject: [PATCH 062/104] [UPD] Update account_invoice_import_invoice2data.pot --- .../i18n/account_invoice_import_invoice2data.pot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot index 1d9114feae..b234df98d7 100644 --- a/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot +++ b/account_invoice_import_invoice2data/i18n/account_invoice_import_invoice2data.pot @@ -41,10 +41,18 @@ msgstr "" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" msgstr "" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template From 4ca70b414c2c8d4f1cc5551a8b826e56988bcdba Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 24 Feb 2023 15:38:12 +0000 Subject: [PATCH 063/104] account_invoice_import_invoice2data 14.0.2.1.0 --- account_invoice_import_invoice2data/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index 91ee43f0ce..d3085f4664 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Account Invoice Import Invoice2data", - "version": "14.0.2.0.0", + "version": "14.0.2.1.0", "category": "Accounting/Accounting", "license": "AGPL-3", "summary": "Import supplier invoices using the invoice2data lib", From 9b78df268ad663780bce1f6073cb58352be26520 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 24 Feb 2023 15:38:24 +0000 Subject: [PATCH 064/104] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: edi-14.0/edi-14.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data/ --- account_invoice_import_invoice2data/i18n/de.po | 13 ++++++++----- account_invoice_import_invoice2data/i18n/es.po | 7 +++++++ account_invoice_import_invoice2data/i18n/nl.po | 15 +++++++++------ account_invoice_import_invoice2data/i18n/pt.po | 13 ++++++++----- account_invoice_import_invoice2data/i18n/sl.po | 14 ++++++++------ 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/de.po b/account_invoice_import_invoice2data/i18n/de.po index 6ddbbdded2..d49acdd0c6 100644 --- a/account_invoice_import_invoice2data/i18n/de.po +++ b/account_invoice_import_invoice2data/i18n/de.po @@ -48,6 +48,14 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "PDF Rechnungsverarbeitung ist gescheitert. Fehler Meldung: %s" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" +"Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template @@ -75,11 +83,6 @@ msgstr "" "funktioniert nur, wenn die Bibliothek invoice2data eine Vorlage für die " "Rechnungen dieses Lieferanten enthält." -#~ msgid "" -#~ "This PDF invoice doesn't match a known template of the invoice2data lib." -#~ msgstr "" -#~ "Diese PDF Rechnung entspricht keinem Template der invoice2data Bibliothek." - #~ msgid "Unit(s)" #~ msgstr "Einheit(en)" diff --git a/account_invoice_import_invoice2data/i18n/es.po b/account_invoice_import_invoice2data/i18n/es.po index 29fbf1256c..09232456fb 100644 --- a/account_invoice_import_invoice2data/i18n/es.po +++ b/account_invoice_import_invoice2data/i18n/es.po @@ -50,6 +50,13 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template diff --git a/account_invoice_import_invoice2data/i18n/nl.po b/account_invoice_import_invoice2data/i18n/nl.po index 7db201f9db..11d50775d7 100644 --- a/account_invoice_import_invoice2data/i18n/nl.po +++ b/account_invoice_import_invoice2data/i18n/nl.po @@ -48,6 +48,15 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "Het is niet gelukt om de PDF factuur te verwerken. Fout melding: %s" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" +"Deze PDF factuur heeft geen overeenkomstig herkennings sjabloon van de " +"invoice2data library." + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template @@ -75,12 +84,6 @@ msgstr "" "informatie). Het werkt enkel wanneer er een sjabloon beschikbaar is voor de " "betreffende leverancier." -#~ msgid "" -#~ "This PDF invoice doesn't match a known template of the invoice2data lib." -#~ msgstr "" -#~ "Deze PDF factuur heeft geen overeenkomstig herkennings sjabloon van de " -#~ "invoice2data library." - #~ msgid "Unit(s)" #~ msgstr "Stuk(s)" diff --git a/account_invoice_import_invoice2data/i18n/pt.po b/account_invoice_import_invoice2data/i18n/pt.po index f4b3bb9397..8a8b837198 100644 --- a/account_invoice_import_invoice2data/i18n/pt.po +++ b/account_invoice_import_invoice2data/i18n/pt.po @@ -48,6 +48,14 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "Análise de fatura PDF falhou. Mensagem de erro: %s" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" +"Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template @@ -73,8 +81,3 @@ msgid "" msgstr "" "biblioteca (lê o texto do PDF e obtém a informação). Apenas funcionará se a " "biblioteca de invoice2data tiver um modelo para as faturas deste fornecedor." - -#~ msgid "" -#~ "This PDF invoice doesn't match a known template of the invoice2data lib." -#~ msgstr "" -#~ "Esta fatura PDF não combina com um modelo conhecido do invoice2data lib." diff --git a/account_invoice_import_invoice2data/i18n/sl.po b/account_invoice_import_invoice2data/i18n/sl.po index 5dd67f38d4..2d66646e85 100644 --- a/account_invoice_import_invoice2data/i18n/sl.po +++ b/account_invoice_import_invoice2data/i18n/sl.po @@ -51,6 +51,14 @@ msgstr "" msgid "PDF Invoice parsing failed. Error message: %s" msgstr "Neuspešna obdelava PDF računa. Napaka: %s" +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" +"Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice invoice2data." + #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template @@ -74,9 +82,3 @@ msgid "" "only if the invoice2data library has a template for the invoices of that " "supplier." msgstr "" - -#~ msgid "" -#~ "This PDF invoice doesn't match a known template of the invoice2data lib." -#~ msgstr "" -#~ "Ta PDF račun se ne ujema z nobeno znano predlogo iz knjižnice " -#~ "invoice2data." From 38fa4c9e55207f029f324eba0dddebc37197cc4d Mon Sep 17 00:00:00 2001 From: Claude R Perrin Date: Mon, 29 May 2023 09:15:05 +0000 Subject: [PATCH 065/104] Added translation using Weblate (French) --- .../i18n/fr.po | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 account_invoice_import_invoice2data/i18n/fr.po diff --git a/account_invoice_import_invoice2data/i18n/fr.po b/account_invoice_import_invoice2data/i18n/fr.po new file mode 100644 index 0000000000..a639ce6bd1 --- /dev/null +++ b/account_invoice_import_invoice2data/i18n/fr.po @@ -0,0 +1,79 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_import_invoice2data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "regular PDF file without any embedded XML file via the" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id +msgid "ID" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,name:account_invoice_import_invoice2data.internet_access +#: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Internet Access" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "PDF Invoice parsing failed. Error message: %s" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 +#, python-format +msgid "" +"This PDF invoice doesn't match a known template of the invoice2data lib." +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access +#: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template +msgid "Units" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import +msgid "Wizard to import supplier invoices/refunds" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "invoice2data" +msgstr "" + +#. module: account_invoice_import_invoice2data +#: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form +msgid "" +"library (reads the text of the PDF and get the information). It will work " +"only if the invoice2data library has a template for the invoices of that " +"supplier." +msgstr "" From 67abc3efd2644f65d4a23a3e10ace238c0a4c883 Mon Sep 17 00:00:00 2001 From: Claude R Perrin Date: Mon, 29 May 2023 09:16:54 +0000 Subject: [PATCH 066/104] Translated using Weblate (French) Currently translated at 100.0% (11 of 11 strings) Translation: edi-14.0/edi-14.0-account_invoice_import_invoice2data Translate-URL: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data/fr/ --- .../i18n/fr.po | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/account_invoice_import_invoice2data/i18n/fr.po b/account_invoice_import_invoice2data/i18n/fr.po index a639ce6bd1..f8d8a489e4 100644 --- a/account_invoice_import_invoice2data/i18n/fr.po +++ b/account_invoice_import_invoice2data/i18n/fr.po @@ -6,46 +6,48 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2023-05-29 11:08+0000\n" +"Last-Translator: Claude R Perrin \n" "Language-Team: none\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.17\n" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "regular PDF file without any embedded XML file via the" -msgstr "" +msgstr "fichier PDF standard sans aucun fichier XML intégré à l’aide de" #. module: account_invoice_import_invoice2data #: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__display_name msgid "Display Name" -msgstr "" +msgstr "Nom affiché" #. module: account_invoice_import_invoice2data #: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import__id msgid "ID" -msgstr "" +msgstr "ID" #. module: account_invoice_import_invoice2data #: model:product.product,name:account_invoice_import_invoice2data.internet_access #: model:product.template,name:account_invoice_import_invoice2data.internet_access_product_template msgid "Internet Access" -msgstr "" +msgstr "Accès internet" #. module: account_invoice_import_invoice2data #: model:ir.model.fields,field_description:account_invoice_import_invoice2data.field_account_invoice_import____last_update msgid "Last Modified on" -msgstr "" +msgstr "Dernière modification le" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 #, python-format msgid "PDF Invoice parsing failed. Error message: %s" -msgstr "" +msgstr "L'analyse de la facture PDF a échoué. Message d'erreur : %s" #. module: account_invoice_import_invoice2data #: code:addons/account_invoice_import_invoice2data/wizard/account_invoice_import.py:0 @@ -53,22 +55,24 @@ msgstr "" msgid "" "This PDF invoice doesn't match a known template of the invoice2data lib." msgstr "" +"Cette facture PDF ne correspond pas à un modèle connu de la bibliothèque " +"invoice2data." #. module: account_invoice_import_invoice2data #: model:product.product,uom_name:account_invoice_import_invoice2data.internet_access #: model:product.template,uom_name:account_invoice_import_invoice2data.internet_access_product_template msgid "Units" -msgstr "" +msgstr "Unités" #. module: account_invoice_import_invoice2data #: model:ir.model,name:account_invoice_import_invoice2data.model_account_invoice_import msgid "Wizard to import supplier invoices/refunds" -msgstr "" +msgstr "Assistant pour importer les factures/avoirs des fournisseurs" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form msgid "invoice2data" -msgstr "" +msgstr "invoice2data" #. module: account_invoice_import_invoice2data #: model_terms:ir.ui.view,arch_db:account_invoice_import_invoice2data.account_invoice_import_form @@ -77,3 +81,6 @@ msgid "" "only if the invoice2data library has a template for the invoices of that " "supplier." msgstr "" +"bibliothèque (qui lit le texte du PDF et trouve l'information). Il ne " +"fonctionnera que si la bibliothèque invoice2data dispose d'un modèle pour " +"les factures de ce fournisseur." From 218a21a74c9be7671b2df9c01e99c929aee9bbf0 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 24 Jan 2023 12:58:58 +0100 Subject: [PATCH 067/104] [FIX] remove pin version of dateparser Issue reported here https://github.com/OCA/edi/issues/544 is fixed in recent version of dateparser --- account_invoice_import_invoice2data/__manifest__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/account_invoice_import_invoice2data/__manifest__.py b/account_invoice_import_invoice2data/__manifest__.py index d3085f4664..c6ebf1fd5d 100644 --- a/account_invoice_import_invoice2data/__manifest__.py +++ b/account_invoice_import_invoice2data/__manifest__.py @@ -16,8 +16,7 @@ "external_dependencies": { "python": [ "invoice2data", - # https://github.com/OCA/edi/issues/544 - "dateparser==1.1.1", + "dateparser", ], "deb": ["poppler-utils"], }, From 3a0f439fc271cca3eec1622e265eebcafd374c4d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 3 Sep 2023 12:42:17 +0000 Subject: [PATCH 068/104] [UPD] README.rst --- .../README.rst | 15 ++++--- .../static/description/index.html | 44 ++++++++++--------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/account_invoice_import_invoice2data/README.rst b/account_invoice_import_invoice2data/README.rst index e24f0b4c0b..ff8bf7ccec 100644 --- a/account_invoice_import_invoice2data/README.rst +++ b/account_invoice_import_invoice2data/README.rst @@ -2,10 +2,13 @@ Account Invoice Import Invoice2data =================================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:55f449cbbddf2c1ecf6d30366d846285cf23eab60b5ea8439bf0648788f83b38 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Account Invoice Import Invoice2data .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-account_invoice_import_invoice2data :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/226/14.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=14.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module is an extension of the module *account_invoice_import*: it adds support for regular PDF invoices i.e. PDF invoice that don't have an embedded XML file. It uses the `invoice2data library `_ which takes care of extracting the text of the PDF invoice, find an existing invoice template and execute the invoice template to extract the useful information from the invoice. @@ -114,7 +117,7 @@ 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 smashing it by providing a detailed and welcomed +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. diff --git a/account_invoice_import_invoice2data/static/description/index.html b/account_invoice_import_invoice2data/static/description/index.html index 8d64a8aa49..5d5c754299 100644 --- a/account_invoice_import_invoice2data/static/description/index.html +++ b/account_invoice_import_invoice2data/static/description/index.html @@ -1,20 +1,20 @@ - + - + Account Invoice Import Invoice2data + + +
          +

          Account Invoice Import Invoice2data DB Templates

          + + +

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

          +

          This module extends Account Invoice Import Invoice2data with DB-stored +templates and a GUI editor for authoring them.

          +

          It implements the long-standing roadmap item the parent module references +as “An graphical template builder”: instead of (or alongside) the +disk-loaded invoice2data_templates_dir set, accountants can author +templates directly in Odoo. Each invoice2data.template record is +merged into the lib’s template list at import time, so the cascade +behaviour is unchanged.

          +

          Two authoring modes live side by side:

          +
            +
          • Guided – fill in Name, Keywords and a list of Fields +(canonical invoice2data field names, regex or static value, optional +replace pair, opt-in extract_number flag for text-mixed +numerics). The JSON is composed from those on save.
          • +
          • Power user – paste a full invoice2data JSON template into the +JSON tab; the Fields tab is then ignored.
          • +
          +

          A Suggest fields button uses the lib’s authoring helpers +(invoice2data.extract.template_builder.suggested_template and the +label-detection helpers) to pre-fill the field grid from the latest +attached PDF – the “guessing framework” the parent module’s 2017 TODO +list asked for.

          +

          Table of contents

          + +
          +

          Known issues / Roadmap

          +
            +
          • Visual PDF region picker for area:-style templates – click-to-define +rectangles on the PDF preview; ties into invoice2data’s +camelot / Excalibur path.
          • +
          • AI authoring (--new-template --ai from the lib) wired as an action, +with the provider configured via invoice2data.ai settings.
          • +
          • Per-record audit of which DB template matched on a given import (write +the match back to account.move for traceability).
          • +
          +
          +
          +

          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

          +
            +
          • Therp BV
          • +
          • bosd
          • +
          +
          +
          +

          Contributors

          +
            +
          • Original DB-storage design (2017): Therp BV
          • +
          • 16.0 module + GUI builder: bosd
          • +
          +
          +
          +

          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:

          +

          bosd

          +

          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/account_invoice_import_invoice2data_db_templates/tests/test_db_templates.py b/account_invoice_import_invoice2data_db_templates/tests/test_db_templates.py index 1a39702bff..c510885858 100644 --- a/account_invoice_import_invoice2data_db_templates/tests/test_db_templates.py +++ b/account_invoice_import_invoice2data_db_templates/tests/test_db_templates.py @@ -217,7 +217,5 @@ def test_wizard_collect_includes_db_templates(self): return_value=[], ): collected = Wizard._invoice2data_collect_templates() - names = [ - tpl["template_name"] for tpl in collected if "template_name" in tpl - ] + names = [tpl["template_name"] for tpl in collected if "template_name" in tpl] self.assertIn("acme.purchase.wizardtest", names) diff --git a/account_invoice_import_invoice2data_db_templates/wizard/account_invoice_import.py b/account_invoice_import_invoice2data_db_templates/wizard/account_invoice_import.py index 38d096cfe3..c7fd67c15d 100644 --- a/account_invoice_import_invoice2data_db_templates/wizard/account_invoice_import.py +++ b/account_invoice_import_invoice2data_db_templates/wizard/account_invoice_import.py @@ -80,9 +80,7 @@ def _invoice2data_collect_templates(self): ) if not exclude_built_in: templates += read_templates() - templates += self.env["invoice2data.template"].get_templates( - "purchase_invoice" - ) + templates += self.env["invoice2data.template"].get_templates("purchase_invoice") return templates def _invoice2data_try_tesseract(self, path, templates): From 6f8012db46382502d7f51dd60abc4700bd1ccf02 Mon Sep 17 00:00:00 2001 From: bosd <5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me> Date: Mon, 20 Jul 2026 00:03:44 +0200 Subject: [PATCH 102/104] [FIX] db_templates: named placeholders in _() (pylint W8120) --- .../models/invoice2data_template_field.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py b/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py index 6f23161b80..5061d850b6 100644 --- a/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py +++ b/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py @@ -93,13 +93,19 @@ def _check_parser_args(self): for line in self: if line.parser in {"regex", "lines"} and not line.regex: raise ValidationError( - _("Field '%s' uses parser '%s' but has no regex set.") - % (line.name, line.parser) + _( + "Field '%(field)s' uses parser '%(parser)s' but has " + "no regex set." + ) + % {"field": line.name, "parser": line.parser} ) if line.parser == "static" and not line.static_value: raise ValidationError( - _("Field '%s' uses parser 'static' but has no value set.") - % line.name + _( + "Field '%(field)s' uses parser 'static' but has no " + "value set." + ) + % {"field": line.name} ) def _to_field_dict(self): From d3f5717184dabba87bd87a8c8ec407f6c5bc8235 Mon Sep 17 00:00:00 2001 From: bosd <5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me> Date: Mon, 20 Jul 2026 00:09:18 +0200 Subject: [PATCH 103/104] [FIX] db_templates: black reformat --- .../models/invoice2data_template_field.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py b/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py index 5061d850b6..d9fc428d67 100644 --- a/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py +++ b/account_invoice_import_invoice2data_db_templates/models/invoice2data_template_field.py @@ -101,10 +101,7 @@ def _check_parser_args(self): ) if line.parser == "static" and not line.static_value: raise ValidationError( - _( - "Field '%(field)s' uses parser 'static' but has no " - "value set." - ) + _("Field '%(field)s' uses parser 'static' but has no " "value set.") % {"field": line.name} ) From 8cb94d47f53870124cde747c112dc8bf8fbb3b7d Mon Sep 17 00:00:00 2001 From: bosd <5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me> Date: Fri, 24 Jul 2026 00:32:35 +0200 Subject: [PATCH 104/104] [FIX] db_templates (16.0): setup/ scaffolding Pre-commit's 'unchecked-in-files' guard was failing because the OCA setup//{setup.py, odoo/addons/} scaffolding wasn't committed alongside the module. Add it (matches what setuptools-odoo-make-default generates). --- .../addons/account_invoice_import_invoice2data_db_templates | 1 + .../setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/account_invoice_import_invoice2data_db_templates/odoo/addons/account_invoice_import_invoice2data_db_templates create mode 100644 setup/account_invoice_import_invoice2data_db_templates/setup.py diff --git a/setup/account_invoice_import_invoice2data_db_templates/odoo/addons/account_invoice_import_invoice2data_db_templates b/setup/account_invoice_import_invoice2data_db_templates/odoo/addons/account_invoice_import_invoice2data_db_templates new file mode 120000 index 0000000000..9edd3dab7d --- /dev/null +++ b/setup/account_invoice_import_invoice2data_db_templates/odoo/addons/account_invoice_import_invoice2data_db_templates @@ -0,0 +1 @@ +../../../../account_invoice_import_invoice2data_db_templates \ No newline at end of file diff --git a/setup/account_invoice_import_invoice2data_db_templates/setup.py b/setup/account_invoice_import_invoice2data_db_templates/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/account_invoice_import_invoice2data_db_templates/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)