Skip to content

Commit

Permalink
Add support for importing customer invoices (bug OCA#24)
Browse files Browse the repository at this point in the history
Add basic unit-tests in account_invoice_import
  • Loading branch information
alexis-via authored and yvaucher committed Aug 12, 2020
1 parent 76d7f29 commit e077dd1
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 10 deletions.
3 changes: 3 additions & 0 deletions account_invoice_import/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

from . import test_invoice_import
136 changes: 136 additions & 0 deletions account_invoice_import/tests/test_invoice_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo.tests.common import TransactionCase
from odoo.tools import float_compare


class TestInvoiceImport(TransactionCase):

def setUp(self):
super(TestInvoiceImport, self).setUp()
self.expense_account = self.env['account.account'].create({
'code': '612AII',
'name': 'expense account invoice import',
'user_type_id':
self.env.ref('account.data_account_type_expenses').id,
})
self.income_account = self.env['account.account'].create({
'code': '707AII',
'name': 'revenue account invoice import',
'user_type_id':
self.env.ref('account.data_account_type_revenue').id,
})
purchase_tax_vals = {
'name': 'Test 1% VAT',
'description': 'ZZ-VAT-buy-1.0',
'type_tax_use': 'purchase',
'amount': 1,
'amount_type': 'percent',
'unece_type_id': self.env.ref('account_tax_unece.tax_type_vat').id,
'unece_categ_id': self.env.ref('account_tax_unece.tax_categ_s').id,
'account_id': self.expense_account.id,
'refund_account_id': self.expense_account.id,
}
self.purchase_tax = self.env['account.tax'].create(purchase_tax_vals)
sale_tax_vals = purchase_tax_vals.copy()
sale_tax_vals.update({
'description': 'ZZ-VAT-sale-1.0',
'type_tax_use': 'sale',
})
self.sale_tax = self.env['account.tax'].create(sale_tax_vals)
self.product = self.env['product.product'].create({
'name': 'Expense product',
'default_code': 'AII-TEST-PRODUCT',
'taxes_id': [(6, 0, [self.sale_tax.id])],
'supplier_taxes_id': [(6, 0, [self.purchase_tax.id])],
'property_account_income_id': self.income_account.id,
'property_account_expense_id': self.expense_account.id,
})
self.all_import_config = [
{
'invoice_line_method': '1line_no_product',
'account': self.expense_account,
'taxes': self.purchase_tax,
},
{
'invoice_line_method': '1line_static_product',
'product': self.product,
},
{
'invoice_line_method': 'nline_no_product',
'account': self.expense_account,
},
{
'invoice_line_method': 'nline_static_product',
'product': self.product,
},
{
'invoice_line_method': 'nline_auto_product',
}
]

def test_import_in_invoice(self):
parsed_inv = {
'type': 'in_invoice',
'amount_untaxed': 100.0,
'amount_total': 101.0,
'invoice_number': 'INV-2017-9876',
'date_invoice': '2017-08-16',
'date_due': '2017-08-31',
'date_start': '2017-08-01',
'date_end': '2017-08-31',
'partner': {
'name': 'Agrolait',
},
'description': 'Daily milk for the kids',
'lines': [{
'product': {'code': 'AII-TEST-PRODUCT'},
'name': 'Super test product',
'qty': 2,
'price_unit': 50,
'taxes': [{
'amount_type': 'percent',
'amount': 1.0,
'unece_type_code': 'VAT',
'unece_categ_code': 'S',
}],
}]
}
for import_config in self.all_import_config:
self.env['account.invoice.import'].create_invoice(
parsed_inv, import_config)

def test_import_out_invoice(self):
parsed_inv = {
'type': 'out_invoice',
'date_invoice': '2017-08-16',
'partner': {
'name': 'Agrolait',
},
'lines': [{
'product': {'code': 'AII-TEST-PRODUCT'},
'name': 'Super product',
'qty': 3,
'price_unit': 10.22,
'date_start': '2017-08-01',
'date_end': '2017-08-31',
'taxes': [{ # only needed for method 'nline_no_product'
'amount_type': 'percent',
'amount': 1.0,
'unece_type_code': 'VAT',
'unece_categ_code': 'S',
}],
}]
}
for import_config in self.all_import_config:
if not import_config['invoice_line_method'].startswith('nline'):
continue
inv = self.env['account.invoice.import'].create_invoice(
parsed_inv, import_config)
prec = inv.currency_id.rounding
self.assertFalse(float_compare(
inv.amount_untaxed, 30.66, precision_rounding=prec))
self.assertFalse(float_compare(
inv.amount_total, 30.97, precision_rounding=prec))
24 changes: 15 additions & 9 deletions account_invoice_import/wizard/account_invoice_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,20 @@ def fallback_parse_pdf_invoice(self, file_data):
# 'label': 'Force invoice line description',
# 'product': product recordset,
# }
#
# Note: we also support importing customer invoices via
# create_invoice() but only with 'nline_*' invoice import methods.

@api.model
def _prepare_create_invoice_vals(self, parsed_inv, import_config=False):
assert parsed_inv.get('pre-processed'), 'pre-processing not done'
aio = self.env['account.invoice']
ailo = self.env['account.invoice.line']
bdio = self.env['business.document.import']
rpo = self.env['res.partner']
company = self.env.user.company_id
start_end_dates_installed = hasattr(ailo, 'start_date') and\
hasattr(ailo, 'end_date')
assert parsed_inv.get('amount_total'), 'Missing amount_total'
partner = bdio._match_partner(
parsed_inv['partner'], parsed_inv['chatter_msg'])
partner = partner.commercial_partner_id
Expand Down Expand Up @@ -339,6 +342,11 @@ def parse_invoice(self):
def pre_process_parsed_inv(self, parsed_inv):
if parsed_inv.get('pre-processed'):
return parsed_inv
parsed_inv['pre-processed'] = True
if 'chatter_msg' not in parsed_inv:
parsed_inv['chatter_msg'] = []
if parsed_inv.get('type') in ('out_invoice', 'out_refund'):
return parsed_inv
prec_ac = self.env['decimal.precision'].precision_get('Account')
prec_pp = self.env['decimal.precision'].precision_get('Product Price')
prec_uom = self.env['decimal.precision'].precision_get(
Expand Down Expand Up @@ -374,9 +382,6 @@ def pre_process_parsed_inv(self, parsed_inv):
line['qty'] = float_round(line['qty'], precision_digits=prec_uom)
line['price_unit'] = float_round(
line['price_unit'], precision_digits=prec_pp)
if 'chatter_msg' not in parsed_inv:
parsed_inv['chatter_msg'] = []
parsed_inv['pre-processed'] = True
logger.debug('Result of invoice parsing parsed_inv=%s', parsed_inv)
return parsed_inv

Expand Down Expand Up @@ -436,17 +441,17 @@ def import_invoice(self):
action['res_id'] = self.id
return action
else:
action = self.create_invoice(parsed_inv)
action = self.create_invoice_action(parsed_inv)
return action

@api.multi
def create_invoice(self, parsed_inv=None):
def create_invoice_action(self, parsed_inv=None):
'''parsed_inv is not a required argument'''
self.ensure_one()
iaao = self.env['ir.actions.act_window']
if parsed_inv is None:
parsed_inv = self.parse_invoice()
invoice = self._create_invoice(parsed_inv)
invoice = self.create_invoice(parsed_inv)
invoice.message_post(_(
"This invoice has been created automatically via file import"))
action = iaao.for_xml_id('account', 'action_invoice_tree2')
Expand All @@ -457,9 +462,8 @@ def create_invoice(self, parsed_inv=None):
})
return action

# TODO: this method should be callable from webservices
@api.model
def _create_invoice(self, parsed_inv, import_config=False):
def create_invoice(self, parsed_inv, import_config=False):
aio = self.env['account.invoice']
bdio = self.env['business.document.import']
parsed_inv = self.pre_process_parsed_inv(parsed_inv)
Expand Down Expand Up @@ -510,6 +514,8 @@ def _prepare_global_adjustment_line(

@api.model
def post_process_invoice(self, parsed_inv, invoice, import_config):
if parsed_inv.get('type') in ('out_invoice', 'out_refund'):
return
prec = invoice.currency_id.rounding
# If untaxed amount is wrong, create adjustment lines
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
class="oe_highlight" string="Import" states="import"/>
<button name="update_invoice" type="object"
class="oe_highlight" string="Update Existing" states="update"/>
<button name="create_invoice" type="object"
<button name="create_invoice_action" type="object"
class="oe_highlight" string="Create New" states="update"/>
<button name="update_invoice" type="object"
class="oe_highlight" string="Update Invoice"
Expand Down

0 comments on commit e077dd1

Please sign in to comment.