diff --git a/account_invoice_import/README.rst b/account_invoice_import/README.rst
index 0718607fa6..4d10bd8c9d 100644
--- a/account_invoice_import/README.rst
+++ b/account_invoice_import/README.rst
@@ -17,13 +17,13 @@ Account Invoice Import
: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/14.0/account_invoice_import
+ :target: https://github.com/OCA/edi/tree/16.0/account_invoice_import
:alt: OCA/edi
.. |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
+ :target: https://translation.odoo-community.org/projects/edi-16-0/edi-16-0-account_invoice_import
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
- :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=14.0
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -99,7 +99,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 to smash it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -142,6 +142,6 @@ Current `maintainer `__:
|maintainer-alexis-via|
-This module is part of the `OCA/edi `_ project on GitHub.
+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/__manifest__.py b/account_invoice_import/__manifest__.py
index 9cd6af1ecf..7fb0232266 100644
--- a/account_invoice_import/__manifest__.py
+++ b/account_invoice_import/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "Account Invoice Import",
- "version": "14.0.3.4.0",
+ "version": "16.0.1.0.0",
"category": "Accounting & Finance",
"license": "AGPL-3",
"summary": "Import supplier invoices/refunds as PDF or XML files",
diff --git a/account_invoice_import/static/description/index.html b/account_invoice_import/static/description/index.html
index e8e2e62e3e..7fac16a6f7 100644
--- a/account_invoice_import/static/description/index.html
+++ b/account_invoice_import/static/description/index.html
@@ -369,7 +369,7 @@ Account Invoice Import
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:26f0853d4122605d020e48687fa420f66fdad1a6c7e41efe74059e0a845f5348
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
This module has been started by lazy accounting users who hate enter they vendor bills manually in Odoo. Almost all companies have several vendor bills to enter regularly in the system from the same vendors: phone bill, electricity bill, Internet access, train tickets, etc. Most of these invoices are available as PDF. If we are able to automatically extract from the PDF the required information to enter the invoice as vendor bill in Odoo, then this module will create it automatically. To know the full story behind the development of this module, read this blog post .
In order to reliably extract the required information from the invoice, two international standards exists to describe an Invoice in XML:
@@ -441,7 +441,7 @@
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 .
+feedback .
Do not contact contributors directly about support or help with technical issues.
diff --git a/account_invoice_import/tests/test_invoice_import.py b/account_invoice_import/tests/test_invoice_import.py
index 56087a7981..5de2822d6e 100644
--- a/account_invoice_import/tests/test_invoice_import.py
+++ b/account_invoice_import/tests/test_invoice_import.py
@@ -8,42 +8,50 @@
from unittest import mock
from odoo import fields
-from odoo.tests.common import SavepointCase
+from odoo.tests.common import TransactionCase
from odoo.tools import file_open, float_is_zero
logger = logging.getLogger(__name__)
-class TestInvoiceImport(SavepointCase):
+class TestInvoiceImport(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env.ref("base.main_company")
cls.company.invoice_import_email = "alexis.delattre@testme.com"
cls.expense_account = cls.env["account.account"].create(
- {
- "code": "612AII",
- "name": "expense account invoice import",
- "user_type_id": cls.env.ref("account.data_account_type_expenses").id,
- "company_id": cls.company.id,
- }
+ [
+ {
+ "code": "612AII",
+ "name": "expense account invoice import",
+ "user_type_id": cls.env.ref(
+ "account.data_account_type_expenses"
+ ).id,
+ "company_id": cls.company.id,
+ }
+ ]
)
cls.income_account = cls.env["account.account"].create(
- {
- "code": "707AII",
- "name": "revenue account invoice import",
- "user_type_id": cls.env.ref("account.data_account_type_revenue").id,
- "company_id": cls.company.id,
- }
+ [
+ {
+ "code": "707AII",
+ "name": "revenue account invoice import",
+ "user_type_id": cls.env.ref("account.data_account_type_revenue").id,
+ "company_id": cls.company.id,
+ }
+ ]
)
cls.adjustment_account = cls.env["account.account"].create(
- {
- "code": "Adjustment",
- "name": "adjustment from invoice import",
- "user_type_id": cls.env.ref(
- "account.data_account_type_current_assets"
- ).id,
- }
+ [
+ {
+ "code": "Adjustment",
+ "name": "adjustment from invoice import",
+ "user_type_id": cls.env.ref(
+ "account.data_account_type_current_assets"
+ ).id,
+ }
+ ]
)
purchase_tax_vals = {
"name": "Test 1% VAT Purchase",
@@ -58,7 +66,7 @@ def setUpClass(cls):
# "account_id": cls.expense_account.id,
# "refund_account_id": cls.expense_account.id,
}
- cls.purchase_tax = cls.env["account.tax"].create(purchase_tax_vals)
+ cls.purchase_tax = cls.env["account.tax"].create([purchase_tax_vals])
sale_tax_vals = purchase_tax_vals.copy()
sale_tax_vals.update(
{
@@ -67,16 +75,18 @@ def setUpClass(cls):
"type_tax_use": "sale",
}
)
- cls.sale_tax = cls.env["account.tax"].create(sale_tax_vals)
+ cls.sale_tax = cls.env["account.tax"].create([sale_tax_vals])
cls.product = cls.env["product.product"].create(
- {
- "name": "Expense product",
- "default_code": "AII-TEST-PRODUCT",
- "taxes_id": [(6, 0, [cls.sale_tax.id])],
- "supplier_taxes_id": [(6, 0, [cls.purchase_tax.id])],
- "property_account_income_id": cls.income_account.id,
- "property_account_expense_id": cls.expense_account.id,
- }
+ [
+ {
+ "name": "Expense product",
+ "default_code": "AII-TEST-PRODUCT",
+ "taxes_id": [(6, 0, [cls.sale_tax.id])],
+ "supplier_taxes_id": [(6, 0, [cls.purchase_tax.id])],
+ "property_account_income_id": cls.income_account.id,
+ "property_account_expense_id": cls.expense_account.id,
+ }
+ ]
)
cls.all_import_config = [
{
@@ -117,33 +127,37 @@ def setUpClass(cls):
}
)
cls.partner_with_email = cls.env["res.partner"].create(
- {
- "is_company": True,
- "name": "AgroMilk",
- "email": "invoicing@agromilk.com",
- "country_id": cls.env.ref("base.fr").id,
- }
+ [
+ {
+ "is_company": True,
+ "name": "AgroMilk",
+ "email": "invoicing@agromilk.com",
+ "country_id": cls.env.ref("base.fr").id,
+ }
+ ]
)
cls.partner_with_email_with_inv_config = cls.env["res.partner"].create(
- {
- "is_company": True,
- "name": "Anevia",
- "email": "invoicing@anevia.com",
- "country_id": cls.env.ref("base.fr").id,
- "invoice_import_ids": [
- (
- 0,
- 0,
- {
- "name": "Import config for Anevia",
- "company_id": cls.company.id,
- "invoice_line_method": "1line_static_product",
- "static_product_id": cls.product.id,
- "label": "Flamingo 220S",
- },
- )
- ],
- }
+ [
+ {
+ "is_company": True,
+ "name": "Anevia",
+ "email": "invoicing@anevia.com",
+ "country_id": cls.env.ref("base.fr").id,
+ "invoice_import_ids": [
+ (
+ 0,
+ 0,
+ {
+ "name": "Import config for Anevia",
+ "company_id": cls.company.id,
+ "invoice_line_method": "1line_static_product",
+ "static_product_id": cls.product.id,
+ "label": "Flamingo 220S",
+ },
+ )
+ ],
+ }
+ ]
)
company = cls.env.ref("base.main_company")
company.update(
@@ -366,10 +380,12 @@ def test_email_gateway(self):
def test_email_gateway_multi_comp_1_matching(self):
comp = self.env["res.company"].create(
- {
- "name": "Let it fail INC",
- "invoice_import_email": "project-discussion@example.com",
- }
+ [
+ {
+ "name": "Let it fail INC",
+ "invoice_import_email": "project-discussion@example.com",
+ }
+ ]
)
logger_name = "odoo.addons.account_invoice_import.wizard.account_invoice_import"
@@ -408,7 +424,7 @@ def test_email_gateway_multi_comp_1_matching(self):
self.assertIn(msg, "\n".join(watcher.output))
def test_email_gateway_multi_comp_none_matching(self):
- self.env["res.company"].create({"name": "Let it fail INC"})
+ self.env["res.company"].create([{"name": "Let it fail INC"}])
logger_name = "odoo.addons.account_invoice_import.wizard.account_invoice_import"
with self.assertLogs(logger_name, "ERROR") as watcher:
self.env["mail.thread"].with_context(
diff --git a/account_invoice_import/views/account_invoice_import_config.xml b/account_invoice_import/views/account_invoice_import_config.xml
index d44b412e3e..7415608b14 100644
--- a/account_invoice_import/views/account_invoice_import_config.xml
+++ b/account_invoice_import/views/account_invoice_import_config.xml
@@ -21,6 +21,7 @@
+
diff --git a/account_invoice_import/views/account_journal_dashboard.xml b/account_invoice_import/views/account_journal_dashboard.xml
index 9a695d8fd0..92263ddef3 100644
--- a/account_invoice_import/views/account_journal_dashboard.xml
+++ b/account_invoice_import/views/account_journal_dashboard.xml
@@ -9,15 +9,28 @@
account.journal
-
- action
- %(account_invoice_import_action)d
- btn btn-primary
-
+ 1
+
+
+
+ Upload
+
+
diff --git a/account_invoice_import/views/res_partner.xml b/account_invoice_import/views/res_partner.xml
index 8b103ab394..be4110594f 100644
--- a/account_invoice_import/views/res_partner.xml
+++ b/account_invoice_import/views/res_partner.xml
@@ -8,16 +8,16 @@
res.partner
-
-
+
%s "
- "to set a Single Line method."
+ "You have selected a Multi Line method for this import but "
+ "Odoo could not extract/read information about the lines "
+ "of the invoice. You should update the Invoice Import "
+ "Configuration of %(name)s to set a Single Line "
+ "method."
)
- % (partner.id, partner.display_name)
+ % {"id": partner.id, "name": partner.display_name}
)
# Write analytic account + fix syntax for taxes
@@ -397,10 +397,13 @@ def _prepare_line_vals_nline(self, partner, vals, parsed_inv, import_config):
product = self.env["product.product"].browse(il_vals["product_id"])
raise UserError(
_(
- "Account missing on product '%s' or on it's related "
- "category '%s'."
+ "Account missing on product '%(product_name)s' or on it's "
+ "related category '%(product_categ_name)s'."
)
- % (product.display_name, product.categ_id.display_name)
+ % {
+ "product_name": product.display_name,
+ "product_categ_name": product.categ_id.display_name,
+ }
)
if line.get("name"):
il_vals["name"] = line["name"]
@@ -496,7 +499,9 @@ def parse_invoice(self, invoice_file_b64, invoice_filename, email_from=None):
try:
xml_root = etree.fromstring(file_data)
except Exception as e:
- raise UserError(_("This XML file is not XML-compliant. Error: %s") % e)
+ raise UserError(
+ _("This XML file is not XML-compliant. Error: %s") % e
+ ) from None
pretty_xml_bytes = etree.tostring(
xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True
)
@@ -686,15 +691,15 @@ def _prepare_partner_update(self):
if self.partner_id.vat != self.partner_vat:
raise UserError(
_(
- "The vendor to update '%s' already has a VAT number (%s) "
- "which is different from the vendor VAT number "
- "of the invoice (%s)."
- )
- % (
- self.partner_id.display_name,
- self.partner_id.vat,
- self.partner_vat,
+ "The vendor to update '%(vendor_name)s' already has a VAT "
+ "number (%(vendor_vat)s) which is different from the vendor "
+ "VAT number of the invoice (%(inv_vendor_vat)s)."
)
+ % {
+ "vendor_name": self.partner_id.display_name,
+ "vendor_vat": self.partner_id.vat,
+ "inv_vendor_vat": self.partner_vat,
+ }
)
else:
@@ -704,15 +709,16 @@ def _prepare_partner_update(self):
if self.partner_id.country_id != self.partner_country_id:
raise UserError(
_(
- "The vendor to update '%s' already has a country (%s) "
- "which is different from the country of the vendor "
- "of the invoice (%s)."
- )
- % (
- self.partner_id.display_name,
- self.partner_id.country_id.display_name,
- self.partner_country_id.display_name,
+ "The vendor to update '%(vendor_name)s' already has a "
+ "country (%(vendor_country)s) which is different from the "
+ "country of the vendor of the invoice "
+ "(%(inv_vendor_country)s)."
)
+ % {
+ "vendor_name": self.partner_id.display_name,
+ "vendor_country": self.partner_id.country_id.display_name,
+ "inv_vendor_country": self.partner_country_id.display_name,
+ }
)
else:
vals["country_id"] = self.partner_country_id.id
@@ -836,10 +842,13 @@ def import_invoice(self):
existing_inv = self.invoice_already_exists(partner, parsed_inv)
if existing_inv:
self.message = _(
- "This invoice already exists in Odoo. It's "
- "Supplier Invoice Number is '%s' and it's Odoo number "
- "is '%s'"
- ) % (parsed_inv.get("invoice_number"), existing_inv.name)
+ "This invoice already exists in Odoo. It's Supplier Invoice Number "
+ "is '%(supplier_invoice_no)s' and it's Odoo number is "
+ "'%(odoo_invoice_no)s'"
+ ) % {
+ "supplier_invoice_no": parsed_inv.get("invoice_number"),
+ "odoo_invoice_no": existing_inv.name,
+ }
self.state = "config"
if self.import_config_id: # button called from 'config' step
@@ -1068,13 +1077,14 @@ def post_process_invoice(self, parsed_inv, invoice, import_config): # noqa: C90
invoice.message_post(
body=_(
"Missing Invoice Import Configuration on partner "
- "%s : "
- "the imported invoice is incomplete."
- )
- % (
- invoice.commercial_partner_id.id,
- invoice.commercial_partner_id.display_name,
+ "%(name)s : the imported invoice is "
+ "incomplete."
)
+ % {
+ "id": invoice.commercial_partner_id.id,
+ "name": invoice.commercial_partner_id.display_name,
+ }
)
return
inv_cur = invoice.currency_id
@@ -1188,18 +1198,19 @@ def post_process_invoice(self, parsed_inv, invoice, import_config): # noqa: C90
)
invoice.message_post(
body=_(
- "The tax amount for tax %s has been forced "
- "to %s (amount computed by Odoo was: %s)."
+ "The tax amount for tax %(tax)s has been "
+ "forced to %(forced_amount)s (amount computed by "
+ "Odoo was: %(computed_amount)s)."
)
- % (
- mline.tax_line_id.display_name,
- format_amount(
+ % {
+ "tax": mline.tax_line_id.display_name,
+ "forced_amount": format_amount(
self.env, new_amount_currency, invoice.currency_id
),
- format_amount(
+ "computed_amount": format_amount(
self.env, mline.amount_currency, invoice.currency_id
),
- )
+ }
)
new_balance = invoice.currency_id._convert(
new_amount_currency,
@@ -1268,29 +1279,30 @@ def update_invoice_lines(self, parsed_inv, invoice, seller):
if cdict.get("qty"):
chatter.append(
_(
- "The quantity has been updated on the invoice line "
- "with product '%s' from %s to %s %s"
- )
- % (
- eline.product_id.display_name,
- cdict["qty"][0],
- cdict["qty"][1],
- eline.product_uom_id.name,
+ "The quantity has been updated on the invoice line with "
+ "product '%(product)s' from %(old_qty)s to %(new_qty)s %(uom)s"
)
+ % {
+ "product": eline.product_id.display_name,
+ "old_qty": cdict["qty"][0],
+ "new_qty": cdict["qty"][1],
+ "uom": eline.product_uom_id.name,
+ }
)
write_vals["quantity"] = cdict["qty"][1]
if cdict.get("price_unit"):
chatter.append(
_(
- "The unit price has been updated on the invoice "
- "line with product '%s' from %s to %s %s"
- )
- % (
- eline.product_id.display_name,
- eline.price_unit,
- cdict["price_unit"][1], # TODO fix
- invoice.currency_id.name,
+ "The unit price has been updated on the invoice line with "
+ "product '%(product)s' from %(old_price)s to %(new_price)s "
+ "%(currency)s"
)
+ % {
+ "product": eline.product_id.display_name,
+ "old_price": eline.price_unit,
+ "new_price": cdict["price_unit"][1], # TODO fix
+ "currency": invoice.currency_id.name,
+ }
)
write_vals["price_unit"] = cdict["price_unit"][1]
if write_vals:
@@ -1303,8 +1315,11 @@ def update_invoice_lines(self, parsed_inv, invoice, seller):
for line in compare_res["to_remove"]
]
chatter.append(
- _("%d invoice line(s) deleted: %s")
- % (len(compare_res["to_remove"]), ", ".join(to_remove_label))
+ _("%(nb_lines)d invoice line(s) deleted: %(labels)s")
+ % {
+ "nb_lines": len(compare_res["to_remove"]),
+ "labels": ", ".join(to_remove_label),
+ }
)
compare_res["to_remove"].unlink()
if compare_res["to_add"]:
@@ -1319,8 +1334,11 @@ def update_invoice_lines(self, parsed_inv, invoice, seller):
% (new_line.quantity, new_line.product_uom_id.name, new_line.name)
)
chatter.append(
- _("%d new invoice line(s) created: %s")
- % (len(compare_res["to_add"]), ", ".join(to_create_label))
+ _("%(nb_lines)d new invoice line(s) created: %(labels)s")
+ % {
+ "nb_lines": len(compare_res["to_add"]),
+ "labels": ", ".join(to_create_label),
+ }
)
invoice.compute_taxes()
return True
@@ -1383,10 +1401,14 @@ def update_invoice(self):
if partner != invoice.commercial_partner_id:
raise UserError(
_(
- "The supplier of the imported invoice (%s) is different from "
- "the supplier of the invoice to update (%s)."
+ "The supplier of the imported invoice (%(imp_invoice)s) is "
+ "different from the supplier of the invoice to update "
+ "(%(up_invoice)s)."
)
- % (partner.name, invoice.commercial_partner_id.name)
+ % {
+ "imp_invoice": partner.name,
+ "up_invoice": invoice.commercial_partner_id.name,
+ }
)
if not self.import_config_id:
raise UserError(_("You must select an Invoice Import Configuration."))
@@ -1397,10 +1419,14 @@ def update_invoice(self):
if currency != invoice.currency_id:
raise UserError(
_(
- "The currency of the imported invoice (%s) is different from "
- "the currency of the existing invoice (%s)"
+ "The currency of the imported invoice (%(imp_currency)s) is "
+ "different from the currency of the existing invoice "
+ "(%(exp_currency)s)"
)
- % (currency.name, invoice.currency_id.name)
+ % {
+ "imp_currency": currency.name,
+ "exp_currency": invoice.currency_id.name,
+ }
)
vals = self._prepare_update_invoice_vals(parsed_inv, invoice)
logger.debug("Updating supplier invoice with vals=%s", vals)
@@ -1584,11 +1610,16 @@ def message_new(self, msg_dict, custom_values=None):
attach_bytes = attach.content.encode("utf-8")
else:
attach_bytes = attach.content
- origin = _("email sent by %s on %s with subject %s ") % (
- msg_dict.get("email_from") and html.escape(msg_dict["email_from"]),
- msg_dict.get("date"),
- msg_dict.get("subject") and html.escape(msg_dict["subject"]),
- )
+ origin = _(
+ "email sent by %(sender)s on %(date)s with subject "
+ "%(subject)s "
+ ) % {
+ "sender": msg_dict.get("email_from")
+ and html.escape(msg_dict["email_from"]),
+ "date": msg_dict.get("date"),
+ "subject": msg_dict.get("subject")
+ and html.escape(msg_dict["subject"]),
+ }
try:
invoice_id = self.create_invoice_webservice(
base64.b64encode(attach_bytes),
diff --git a/setup/account_invoice_import/odoo/addons/account_invoice_import b/setup/account_invoice_import/odoo/addons/account_invoice_import
new file mode 120000
index 0000000000..bb1a141f21
--- /dev/null
+++ b/setup/account_invoice_import/odoo/addons/account_invoice_import
@@ -0,0 +1 @@
+../../../../account_invoice_import
\ No newline at end of file
diff --git a/setup/account_invoice_import/setup.py b/setup/account_invoice_import/setup.py
new file mode 100644
index 0000000000..28c57bb640
--- /dev/null
+++ b/setup/account_invoice_import/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)