diff --git a/l10n_ch_qr_bill_scan/README.rst b/l10n_ch_qr_bill_scan/README.rst new file mode 100644 index 0000000000..e69de29bb2 diff --git a/l10n_ch_qr_bill_scan/__manifest__.py b/l10n_ch_qr_bill_scan/__manifest__.py index b4eb89bf9b..9a8f2c5b2e 100644 --- a/l10n_ch_qr_bill_scan/__manifest__.py +++ b/l10n_ch_qr_bill_scan/__manifest__.py @@ -2,17 +2,15 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Switzerland - QR-bill scan', - 'summary': 'Scan QR-bill to create vendor bills', - 'version': '13.0.1.0.0', - 'author': "Camptocamp,Odoo Community Association (OCA)", - 'category': 'Localization', - 'website': 'https://github.com/OCA/l10n-switzerland', - 'license': 'AGPL-3', - 'depends': ['l10n_ch', 'account_invoice_import'], - 'data': [ - "wizard/account_invoice_import_view.xml" - ], - 'auto_install': False, - 'installable': True, + "name": "Switzerland - QR-bill scan", + "summary": "Scan QR-bill to create vendor bills", + "version": "13.0.1.0.0", + "author": "Camptocamp,Odoo Community Association (OCA)", + "category": "Localization", + "website": "https://github.com/OCA/l10n-switzerland", + "license": "AGPL-3", + "depends": ["l10n_ch", "account_invoice_import"], + "data": ["wizard/account_invoice_import_view.xml"], + "auto_install": False, + "installable": True, } diff --git a/l10n_ch_qr_bill_scan/readme/DESCRIPTION.rst b/l10n_ch_qr_bill_scan/readme/DESCRIPTION.rst index 0430eb2517..4bfbd093f3 100644 --- a/l10n_ch_qr_bill_scan/readme/DESCRIPTION.rst +++ b/l10n_ch_qr_bill_scan/readme/DESCRIPTION.rst @@ -10,7 +10,7 @@ Optionally you can also import Vendor bills directly from a PDF. To enable this feature those additional dependencies must be installed on your Odoo server: - pip install pyzbar,pdf2image,cv2,numpy + pip install pyzbar,numpy,pdf2image,opencv-python If you are looking to generate QR-bill, this feature is already available diff --git a/l10n_ch_qr_bill_scan/readme/USAGE.rst b/l10n_ch_qr_bill_scan/readme/USAGE.rst index f80141bb2a..911e8087db 100644 --- a/l10n_ch_qr_bill_scan/readme/USAGE.rst +++ b/l10n_ch_qr_bill_scan/readme/USAGE.rst @@ -2,7 +2,7 @@ Launch wizard `Invoicing` -> `Vendors` -> `Import Vendor Bill`. In there you can either directly the QR code of the QR-bill or you can import a PDF/image file. -If it's the first time you import a Bill from a Vendor the wizard will ask you to create the partner and to set a import configuration for that partner +If it's the first time you import a Bill from a Vendor the wizard will ask you to create the partner and to set a import configuration for that partner A configuration let you set which expense account you want the invoice to be imported with. diff --git a/l10n_ch_qr_bill_scan/tests/test_scan_qrbill.py b/l10n_ch_qr_bill_scan/tests/test_scan_qrbill.py index 1be6aff885..3dbd3a0a9e 100644 --- a/l10n_ch_qr_bill_scan/tests/test_scan_qrbill.py +++ b/l10n_ch_qr_bill_scan/tests/test_scan_qrbill.py @@ -1,16 +1,13 @@ # Copyright 2020 Camptocamp (http://www.camptocamp.com/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import base64 -import time -from odoo.addons.account.tests.account_test_classes import AccountingTestCase - -from odoo.modules.module import get_resource_path from odoo import tools from odoo.exceptions import UserError -from odoo.addons.l10n_ch_qr_bill_scan.tools import QR +from odoo.modules.module import get_resource_path +from odoo.tests import common -from mock import MagicMock +from odoo.addons.l10n_ch_qr_bill_scan.tools import QR # QR Reference QRR = "210000000003139471430009017" @@ -59,15 +56,13 @@ "", ] -from odoo.tests import common class TestScanQRBill(common.SavepointCase): - @classmethod def setUpClass(cls): super().setUpClass() cls.env.user.company_id.invoice_import_create_bank_account = True - cls.supplier = cls.env['res.partner'].create( + cls.supplier = cls.env["res.partner"].create( { "name": "Camptocamp", "street": "EPFL Innovation Park", @@ -86,16 +81,18 @@ def setUpClass(cls): "user_type_id": cls.env.ref("account.data_account_type_expenses").id, } ) - cls.env["account.invoice.import.config"].create({ - "name": "Camptocamp - one line no product", - "partner_id": cls.supplier.id, - "invoice_line_method": "1line_no_product", - "account_id": cls.expense_account.id, - }) + cls.env["account.invoice.import.config"].create( + { + "name": "Camptocamp - one line no product", + "partner_id": cls.supplier.id, + "invoice_line_method": "1line_no_product", + "account_id": cls.expense_account.id, + } + ) def wiz_import_invoice_file(self, file_path, file_name): """ Import a file of a vendor bill """ - with tools.file_open(file_path, 'rb') as f: + with tools.file_open(file_path, "rb") as f: invoice_file = base64.b64encode(f.read()) wiz = self.env["account.invoice.import"].create({}) wiz.invoice_file = invoice_file @@ -109,8 +106,8 @@ def import_invoice_file(self, file_path, file_name): """ wiz = self.wiz_import_invoice_file(file_path, file_name) res = wiz.import_invoice() - if res.get('res_model') == 'account.move': - invoice = self.env['account.move'].browse(res['res_id']) + if res.get("res_model") == "account.move": + invoice = self.env["account.move"].browse(res["res_id"]) return invoice return None @@ -127,8 +124,8 @@ def import_invoice_scan(self, invoice_scan): """ wiz = self.wiz_import_invoice_scan(invoice_scan) res = wiz.import_invoice() - if res.get('res_model') == 'account.move': - invoice = self.env['account.move'].browse(res['res_id']) + if res.get("res_model") == "account.move": + invoice = self.env["account.move"].browse(res["res_id"]) return invoice return None @@ -176,12 +173,12 @@ def test_scan_QR_new_partner(self): wiz = self.wiz_import_invoice_scan(scan_data) wiz.import_invoice() - self.assertEqual(wiz.state, 'select-partner') - self.assertEqual(wiz.partner_name, 'New Vendor') - self.assertEqual(wiz.partner_street, 'EPFL Innovation Park Bldg A') - self.assertEqual(wiz.partner_zip, '1015') - self.assertEqual(wiz.partner_city, 'Lausanne') - self.assertEqual(wiz.partner_country_id, self.env.ref('base.ch')) + self.assertEqual(wiz.state, "select-partner") + self.assertEqual(wiz.partner_name, "New Vendor") + self.assertEqual(wiz.partner_street, "EPFL Innovation Park Bldg A") + self.assertEqual(wiz.partner_zip, "1015") + self.assertEqual(wiz.partner_city, "Lausanne") + self.assertEqual(wiz.partner_country_id, self.env.ref("base.ch")) def test_scan_QR_swico(self): self.assertTrue(False) @@ -230,7 +227,7 @@ def test_scan_QR_bad_amount(self): self.import_invoice_scan(scan_data) def test_import_QR_pdf(self): - partner = self.env['res.partner'].create( + partner = self.env["res.partner"].create( { "name": "My Company", # this was generated from Odoo "street": "addr 1", @@ -241,16 +238,19 @@ def test_import_QR_pdf(self): } ) partner.supplier_rank = 1 - self.env["account.invoice.import.config"].create({ - "name": "Camptocamp - one line no product", - "partner_id": partner.id, - "invoice_line_method": "1line_no_product", - "account_id": self.expense_account.id, - }) - - invoice_fp = get_resource_path('l10n_ch_qr_bill_scan', 'tests', 'data', 'qr-bill.pdf') - self.env['account.invoice.import'] + self.env["account.invoice.import.config"].create( + { + "name": "Camptocamp - one line no product", + "partner_id": partner.id, + "invoice_line_method": "1line_no_product", + "account_id": self.expense_account.id, + } + ) + + invoice_fp = get_resource_path( + "l10n_ch_qr_bill_scan", "tests", "data", "qr-bill.pdf" + ) invoice = self.import_invoice_file(invoice_fp, "qr-bill.pdf") self.assertTrue(invoice) self.assertEqual(invoice.invoice_payment_ref, "000000000000000000202000058") - self.assertEqual(invoice.amount_total, 1.) + self.assertEqual(invoice.amount_total, 1.0) diff --git a/l10n_ch_qr_bill_scan/wizard/account_invoice_import.py b/l10n_ch_qr_bill_scan/wizard/account_invoice_import.py index 6086a07563..50207d511e 100644 --- a/l10n_ch_qr_bill_scan/wizard/account_invoice_import.py +++ b/l10n_ch_qr_bill_scan/wizard/account_invoice_import.py @@ -1,11 +1,7 @@ # Copyright 2020 Camptocamp # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -import base64 import logging -import mimetypes - -import json from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -13,7 +9,6 @@ from ..tools import QR - logger = logging.getLogger(__name__) try: @@ -38,17 +33,16 @@ class AccountInvoiceImport(models.TransientModel): invoice_file = fields.Binary(string="PDF, PNG or XML Invoice", required=False) state = fields.Selection( - selection_add=[ - ("select-partner", "Select partner"), - ], - default="import", + selection_add=[("select-partner", "Select partner")], default="import", ) partner_name = fields.Char("Name", readonly=True) partner_street = fields.Char("Street", readonly=True) partner_zip = fields.Char("ZIP", readonly=True) partner_city = fields.Char("City", readonly=True) - partner_country_id = fields.Many2one(string="Country", comodel_name="res.country", readonly=True) + partner_country_id = fields.Many2one( + string="Country", comodel_name="res.country", readonly=True + ) def get_parsed_invoice(self): if self.invoice_scan: @@ -82,11 +76,10 @@ def _parse_billing_info(self, bill_info): def parse_qrbill(self, qr_data): qr_match = QR.valid_re.search(qr_data) if not qr_match: - raise UserError( - _("The data doesn't match with a QR-Code from a QR-bill")) + raise UserError(_("The data doesn't match with a QR-Code from a QR-bill")) # Ignore everything before "SPC" if qr_match.start() > 0: - qr_data = qr_data[qr_match.start():] + qr_data = qr_data[qr_match.start() :] qr_data = qr_data.split("\n") @@ -96,11 +89,11 @@ def parse_qrbill(self, qr_data): raise UserError(_("The {} ")) parsed_inv = { "iban": qr_data[QR.IBAN], - "partner": self._get_qr_address(qr_data[QR.CREDITOR:QR.CREDITOR + QR.ADR_LEN]), + "partner": self._get_qr_address( + qr_data[QR.CREDITOR : QR.CREDITOR + QR.ADR_LEN] + ), "amount_total": amount, - "currency": { - "iso": qr_data[QR.CURRENCY], - }, + "currency": {"iso": qr_data[QR.CURRENCY]}, "invoice_number": qr_data[QR.REF], "description": qr_data[QR.MSG], } @@ -117,7 +110,7 @@ def _patch_swiss_qr_code(self, bill_img): # find an replace Swiss Cross as it prevent ZBar from detecting the # QR Code ch_cross_img = cv2.imread( - get_resource_path('l10n_ch_qr_bill_scan', 'data', 'CH-Cross_7mm.png') + get_resource_path("l10n_ch_qr_bill_scan", "data", "CH-Cross_7mm.png") ) ch_cross_img = cv2.resize(ch_cross_img, (55, 55)) res = cv2.matchTemplate(ch_cross_img, bill_img, cv2.TM_CCOEFF_NORMED) @@ -127,16 +120,16 @@ def _patch_swiss_qr_code(self, bill_img): y = locations[0][0] x = locations[1][0] patch_img = cv2.imread( - get_resource_path('l10n_ch_qr_bill_scan', 'data', 'QR-patch.png') + get_resource_path("l10n_ch_qr_bill_scan", "data", "QR-patch.png") ) - bill_img[y:y + patch_img.shape[0], x:x + patch_img.shape[1]] = patch_img + bill_img[y : y + patch_img.shape[0], x : x + patch_img.shape[1]] = patch_img return bill_img def parse_pdf_invoice(self, file_data): if decode: logger.debug("Search for Swiss QR-Code in PDF file") # TODO support multiple pages - pdf_img = pdf2image.convert_from_bytes(file_data)[0].convert('RGB') + pdf_img = pdf2image.convert_from_bytes(file_data)[0].convert("RGB") pdf_img = np.array(pdf_img) # Convert RGB to BGR pdf_img = pdf_img[:, :, ::-1].copy() @@ -154,7 +147,8 @@ def _hook_no_partner_found(self, partner_dict): """Switch wizard to partner creation. """ country = self.env["res.country"].search( - [("code", "=", partner_dict["country_code"])], limit=1) + [("code", "=", partner_dict["country_code"])], limit=1 + ) wiz_vals = { "state": "select-partner", "partner_name": partner_dict["name"], diff --git a/l10n_ch_qr_bill_scan/wizard/account_invoice_import_view.xml b/l10n_ch_qr_bill_scan/wizard/account_invoice_import_view.xml index cc01a3cf50..48cb13a22f 100644 --- a/l10n_ch_qr_bill_scan/wizard/account_invoice_import_view.xml +++ b/l10n_ch_qr_bill_scan/wizard/account_invoice_import_view.xml @@ -12,52 +12,72 @@ [] form new - {'res_partner_search_mode': 'supplier', 'default_is_company': True} - + {'res_partner_search_mode': 'supplier', 'default_is_company': True} +

Create a new vendor in your address book -

+

+

Odoo helps you easily track all activities related to a vendor.

account.invoice.import - +
-

Scan a QR-bill code from a supplier invoice

+

+ + + + Scan a QR-bill code from a supplier invoice

- +
-

OR

+

+ OR +

-

Partner was not found or is not set as a supplier. Please create or select the partner before continuing

+

Partner was not found or is not set as a supplier. Please create or select the partner before continuing

- {'default_name': partner_name, 'default_street': partner_street, 'default_zip': partner_zip, 'default_city': partner_city, 'default_country_id': partner_country_id} - config,update,update-from-invoice,select-partner - {'readonly': [('state', '!=', 'select-partner')], 'required': [('state', '=', 'select-partner')]} + {'default_name': partner_name, 'default_street': partner_street, 'default_zip': partner_zip, 'default_city': partner_city, 'default_country_id': partner_country_id} + config,update,update-from-invoice,select-partner + {'readonly': [('state', '!=', 'select-partner')], 'required': [('state', '=', 'select-partner')]} - - - - - + + + + + - config,update-from-invoice,update,select-partner - {'required': [('state', 'in', ('config', 'update-from-invoice', 'update', 'select-partner'))]} + config,update-from-invoice,update,select-partner + {'required': [('state', 'in', ('config', 'update-from-invoice', 'update', 'select-partner'))]} {'default_partner_id': partner_id}