diff --git a/account_invoice_import/__openerp__.py b/account_invoice_import/__openerp__.py index f95987c980..b7755a8ccc 100644 --- a/account_invoice_import/__openerp__.py +++ b/account_invoice_import/__openerp__.py @@ -10,7 +10,7 @@ 'summary': 'Import supplier invoices/refunds as PDF or XML files', 'author': 'Akretion,Odoo Community Association (OCA)', 'website': 'http://www.akretion.com', - 'depends': ['account', 'base_iban', 'base_vat_sanitized'], + 'depends': ['account', 'base_iban', 'base_business_document_import'], 'external_dependencies': {'python': ['lxml']}, 'data': [ 'security/ir.model.access.csv', diff --git a/account_invoice_import/wizard/account_invoice_import.py b/account_invoice_import/wizard/account_invoice_import.py index 291a47dd0f..67790b8df2 100644 --- a/account_invoice_import/wizard/account_invoice_import.py +++ b/account_invoice_import/wizard/account_invoice_import.py @@ -15,6 +15,7 @@ class AccountInvoiceImport(models.TransientModel): _name = 'account.invoice.import' + _inherit = ['business.document.import'] _description = 'Wizard to import supplier invoices/refunds' invoice_file = fields.Binary( @@ -101,53 +102,14 @@ def fallback_parse_pdf_invoice(self, file_data): # }], # } - @api.model - def _select_partner(self, parsed_inv, partner_type='supplier'): - if parsed_inv.get('partner'): - return parsed_inv['partner'] - if parsed_inv.get('partner_vat'): - vat = parsed_inv['partner_vat'].replace(' ', '').upper() - # use base_vat_sanitized - partners = self.env['res.partner'].search([ - (partner_type, '=', True), - ('parent_id', '=', False), - ('sanitized_vat', '=', vat)]) - if partners: - return partners[0] - else: - # TODO: update error msg - raise UserError(_( - "The analysis of the invoice returned '%s' as " - "partner VAT number. But there are no supplier " - "with this VAT number in Odoo.") % vat) - if parsed_inv.get('partner_email'): - partners = self.env['res.partner'].search([ - ('email', '=ilike', parsed_inv['partner_email']), - (partner_type, '=', True)]) - if partners: - return partners[0].commercial_partner_id - if parsed_inv.get('partner_name'): - partners = self.env['res.partner'].search([ - ('name', '=ilike', parsed_inv['partner_name']), - ('is_company', '=', True), - (partner_type, '=', True)]) - if partners: - return partners[0] - raise UserError(_( - "Invoice parsing didn't return the VAT number of the " - "supplier. In this case, invoice parsing should return the " - "email or the name of the partner, but it was not returned " - "or it was returned but it didn't match any " - "existing supplier.")) - @api.model def _prepare_create_invoice_vals(self, parsed_inv): aio = self.env['account.invoice'] ailo = self.env['account.invoice.line'] company = self.env.user.company_id assert parsed_inv.get('amount_total'), 'Missing amount_total' - partner = self._select_partner(parsed_inv) - currency = self._get_currency(parsed_inv) + partner = self._match_partner(parsed_inv) + currency = self._match_currency(parsed_inv) vals = { 'partner_id': partner.id, 'currency_id': currency.id, @@ -274,48 +236,6 @@ def _prepare_create_invoice_vals(self, parsed_inv): line_dict['account_analytic_id'] = aacount_id return vals - @api.model - def _match_product(self, parsed_line, partner=False): - """This method is designed to be inherited""" - ppo = self.env['product.product'] - if parsed_line.get('product'): - return parsed_line['product'] - if parsed_line.get('product_ean13'): - # Don't filter on purchase_ok = 1 because we don't depend - # on the purchase module - products = ppo.search([ - ('ean13', '=', parsed_line['product_ean13'])]) - if products: - return products[0] - if parsed_line.get('product_code'): - # Should probably be modified to match via the supplier code - products = ppo.search( - [('default_code', '=', parsed_line['product_code'])]) - if products: - return products[0] - # WARNING: Won't work for multi-variant products - # because product.supplierinfo is attached to product template - if partner: - sinfo = self.env['product.supplierinfo'].search([ - ('name', '=', partner.id), - ('product_code', '=', parsed_line['product_code']), - ]) - if ( - sinfo and - sinfo[0].product_tmpl_id.product_variant_ids and - len( - sinfo[0].product_tmpl_id.product_variant_ids) == 1 - ): - return sinfo[0].product_tmpl_id.product_variant_ids[0] - raise UserError(_( - "Could not find any corresponding product in the Odoo database " - "with EAN13 '%s' or Default Code '%s' or " - "Supplier Product Code '%s' with supplier '%s'.") % ( - parsed_line.get('product_ean13'), - parsed_line.get('product_code'), - parsed_line.get('product_code'), - partner and partner.name or 'None')) - @api.model def set_1line_price_unit_and_quantity(self, il_vals, parsed_inv): """For the moment, we only take into account the 'price_include' @@ -341,34 +261,6 @@ def set_1line_start_end_dates(self, il_vals, parsed_inv): il_vals['start_date'] = parsed_inv.get('date_start') il_vals['end_date'] = parsed_inv.get('date_end') - @api.model - def _get_currency(self, parsed_inv): - if parsed_inv.get('currency'): - return parsed_inv['currency'] - if parsed_inv.get('currency_iso'): - currency_iso = parsed_inv['currency_iso'].upper() - currencies = self.env['res.currency'].search( - [('name', '=', currency_iso)]) - if currencies: - return currencies[0] - else: - raise UserError(_( - "The analysis of the invoice returned '%s' as " - "the currency ISO code. But there are no currency " - "with that name in Odoo.") % currency_iso) - if parsed_inv.get('currency_symbol'): - cur_symbol = parsed_inv['currency_symbol'] - currencies = self.env['res.currency'].search( - [('symbol', '=', cur_symbol)]) - if currencies: - return currencies[0] - else: - raise UserError(_( - "The analysis of the invoice returned '%s' as " - "the currency symbol. But there are no currency " - "with that symbol in Odoo.") % cur_symbol) - return self.env.user.company_id.currency_id - @api.multi def parse_invoice(self): self.ensure_one() @@ -441,8 +333,8 @@ def import_invoice(self): aio = self.env['account.invoice'] iaao = self.env['ir.actions.act_window'] parsed_inv = self.parse_invoice() - partner = self._select_partner(parsed_inv) - currency = self._get_currency(parsed_inv) + partner = self._match_partner(parsed_inv) + currency = self._match_currency(parsed_inv) parsed_inv['partner'] = partner parsed_inv['currency'] = currency self.write({ diff --git a/base_business_document_import/README.rst b/base_business_document_import/README.rst new file mode 100644 index 0000000000..170cc01ea7 --- /dev/null +++ b/base_business_document_import/README.rst @@ -0,0 +1,56 @@ +.. 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 + +============================= +Base Business Document Import +============================= + +This is a technical module ; it doesn't bring any useful feature by itself. This module is the base modules for 2 other modules : + +* *account_invoice_import* which imports supplier invoices as PDF or XML files (this module also requires some additionnal modules such as *account_invoice_import_invoice2data*, *account_invoice_import_ubl*, etc... to support specific invoice formats), + +* *sale_invoice_import* which imports sale orders as CSV, XML or PDF files (this module also requires some additionnal modules such as *sale_invoice_import_csv* or *sale_invoice_import_ubl* to support specific order formats) + +Configuration +============= + +No configuration needed. + +Usage +===== + +.. 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 +<https://github.com/OCA/account-invoicing/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 <alexis.delattre@akretion.com> + +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/base_business_document_import/__init__.py b/base_business_document_import/__init__.py new file mode 100644 index 0000000000..cde864bae2 --- /dev/null +++ b/base_business_document_import/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/base_business_document_import/__openerp__.py b/base_business_document_import/__openerp__.py new file mode 100644 index 0000000000..80e1fff5b2 --- /dev/null +++ b/base_business_document_import/__openerp__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Base Business Document Import', + 'version': '8.0.1.0.0', + 'category': 'Hidden', + 'license': 'AGPL-3', + 'summary': 'Provides technical tools to import sale orders or supplier ' + 'invoices', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'http://www.akretion.com', + 'depends': ['product', 'base_vat_sanitized'], + 'external_dependencies': {'python': ['PyPDF2']}, + 'installable': True, +} diff --git a/base_business_document_import/models/__init__.py b/base_business_document_import/models/__init__.py new file mode 100644 index 0000000000..4a3c2e6a58 --- /dev/null +++ b/base_business_document_import/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import business_document_import diff --git a/base_business_document_import/models/business_document_import.py b/base_business_document_import/models/business_document_import.py new file mode 100644 index 0000000000..40fe999c67 --- /dev/null +++ b/base_business_document_import/models/business_document_import.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, api, _ +from openerp.exceptions import Warning as UserError +import logging + +logger = logging.getLogger(__name__) + + +class BusinessDocumentImport(models.AbstractModel): + _name = 'business.document.import' + _description = 'Common methods to import business documents' + + @api.model + def _match_partner(self, parsed_inv, partner_type='supplier'): + if parsed_inv.get('partner'): + return parsed_inv['partner'] + if parsed_inv.get('partner_vat'): + vat = parsed_inv['partner_vat'].replace(' ', '').upper() + # use base_vat_sanitized + partners = self.env['res.partner'].search([ + (partner_type, '=', True), + ('parent_id', '=', False), + ('sanitized_vat', '=', vat)]) + if partners: + return partners[0] + else: + # TODO: update error msg + raise UserError(_( + "The analysis of the invoice returned '%s' as " + "partner VAT number. But there are no supplier " + "with this VAT number in Odoo.") % vat) + if parsed_inv.get('partner_email'): + partners = self.env['res.partner'].search([ + ('email', '=ilike', parsed_inv['partner_email']), + (partner_type, '=', True)]) + if partners: + return partners[0].commercial_partner_id + if parsed_inv.get('partner_name'): + partners = self.env['res.partner'].search([ + ('name', '=ilike', parsed_inv['partner_name']), + ('is_company', '=', True), + (partner_type, '=', True)]) + if partners: + return partners[0] + raise UserError(_( + "Invoice parsing didn't return the VAT number of the " + "partner. In this case, invoice parsing should return the " + "email or the name of the partner, but it was not returned " + "or it was returned but it didn't match any " + "existing partner.")) + # TODO : now that we use it for sale order, we may not want to + # always return a parent partner + + @api.model + def _match_product(self, parsed_line, partner=False): + """This method is designed to be inherited""" + ppo = self.env['product.product'] + if parsed_line.get('product'): + return parsed_line['product'] + if parsed_line.get('product_ean13'): + # Don't filter on purchase_ok = 1 because we don't depend + # on the purchase module + products = ppo.search([ + ('ean13', '=', parsed_line['product_ean13'])]) + if products: + return products[0] + if parsed_line.get('product_code'): + # Should probably be modified to match via the supplier code + products = ppo.search( + [('default_code', '=', parsed_line['product_code'])]) + if products: + return products[0] + # WARNING: Won't work for multi-variant products + # because product.supplierinfo is attached to product template + if partner: + sinfo = self.env['product.supplierinfo'].search([ + ('name', '=', partner.id), + ('product_code', '=', parsed_line['product_code']), + ]) + if ( + sinfo and + sinfo[0].product_tmpl_id.product_variant_ids and + len( + sinfo[0].product_tmpl_id.product_variant_ids) == 1 + ): + return sinfo[0].product_tmpl_id.product_variant_ids[0] + raise UserError(_( + "Could not find any corresponding product in the Odoo database " + "with EAN13 '%s' or Default Code '%s' or " + "Supplier Product Code '%s' with supplier '%s'.") % ( + parsed_line.get('product_ean13'), + parsed_line.get('product_code'), + parsed_line.get('product_code'), + partner and partner.name or 'None')) + + @api.model + def _match_currency(self, parsed_inv): + if parsed_inv.get('currency'): + return parsed_inv['currency'] + if parsed_inv.get('currency_iso'): + currency_iso = parsed_inv['currency_iso'].upper() + currencies = self.env['res.currency'].search( + [('name', '=', currency_iso)]) + if currencies: + return currencies[0] + else: + raise UserError(_( + "The analysis of the invoice returned '%s' as " + "the currency ISO code. But there are no currency " + "with that name in Odoo.") % currency_iso) + if parsed_inv.get('currency_symbol'): + cur_symbol = parsed_inv['currency_symbol'] + currencies = self.env['res.currency'].search( + [('symbol', '=', cur_symbol)]) + if currencies: + return currencies[0] + else: + raise UserError(_( + "The analysis of the invoice returned '%s' as " + "the currency symbol. But there are no currency " + "with that symbol in Odoo.") % cur_symbol) + return self.env.user.company_id.currency_id