Skip to content

Commit

Permalink
Fix bug OCA#16 : add support for adjustment lines (per line and global)
Browse files Browse the repository at this point in the history
Add support for Tax Due Date on invoice import
  • Loading branch information
alexis-via authored and kevinxdev committed Dec 26, 2022
1 parent 6b71be0 commit 38c6953
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 17 deletions.
1 change: 1 addition & 0 deletions account_invoice_import/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'security/ir.model.access.csv',
'security/rule.xml',
'views/account_invoice_import_config.xml',
'views/account_config_settings.xml',
'wizard/account_invoice_import_view.xml',
'views/account_invoice.xml',
'views/partner.xml',
Expand Down
2 changes: 2 additions & 0 deletions account_invoice_import/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-

from . import partner
from . import company
from . import account_config_settings
from . import account_invoice_import_config
from . import account_invoice
14 changes: 14 additions & 0 deletions account_invoice_import/models/account_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- 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 import models, fields


class AccountConfigSettings(models.TransientModel):
_inherit = 'account.config.settings'

adjustment_credit_account_id = fields.Many2one(
related='company_id.adjustment_credit_account_id')
adjustment_debit_account_id = fields.Many2one(
related='company_id.adjustment_debit_account_id')
16 changes: 16 additions & 0 deletions account_invoice_import/models/company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- 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 import models, fields


class ResCompany(models.Model):
_inherit = 'res.company'

adjustment_credit_account_id = fields.Many2one(
'account.account', string='Adjustment Credit Account',
domain=[('deprecated', '=', False)])
adjustment_debit_account_id = fields.Many2one(
'account.account', string='Adjustment Debit Account',
domain=[('deprecated', '=', False)])
33 changes: 33 additions & 0 deletions account_invoice_import/views/account_config_settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->

<odoo>

<record id="view_account_config_settings" model="ir.ui.view">
<field name="name">invoice_import.account.config.settings.form</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings" />
<field name="arch" type="xml">
<xpath expr="//div[@name='invoice_taxes']/.." position="after">
<group name="invoice_import" attrs="{'invisible': [('has_chart_of_accounts','=',False)]}">
<label for="id" string="Invoice Import"/>
<div name="invoice_import">
<div>
<label for="adjustment_debit_account_id"/>
<field name="adjustment_debit_account_id" class="oe_inline"/>
</div>
<div>
<label for="adjustment_credit_account_id"/>
<field name="adjustment_credit_account_id" class="oe_inline"/>
</div>
</div>
</group>
</xpath>
</field>
</record>


</odoo>
123 changes: 106 additions & 17 deletions account_invoice_import/wizard/account_invoice_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def fallback_parse_pdf_invoice(self, file_data):
# 'name': 'Gelierzucker Extra 250g',
# 'price_unit': 1.45, # price_unit without taxes always positive
# 'qty': -2.0, # < 0 when it's a refund
# 'price_subtotal': 2.90,
# 'uom': {'unece_code': 'C62'},
# 'taxes': [list of tax_dict],
# 'date_start': '2015-10-01',
Expand Down Expand Up @@ -249,7 +250,7 @@ def _prepare_create_invoice_vals(self, parsed_inv, import_config=False):
if aacount_id:
for line in vals['invoice_line_ids']:
line[2]['account_analytic_id'] = aacount_id
return vals
return (vals, config)

@api.model
def set_1line_price_unit_and_quantity(self, il_vals, parsed_inv):
Expand Down Expand Up @@ -389,9 +390,7 @@ def import_invoice(self):
('type', '=', parsed_inv['type'])]
existing_invs = aio.search(
domain +
[(
'reference', '=ilike', parsed_inv.get('invoice_number')
)])
[('reference', '=ilike', parsed_inv.get('invoice_number'))])
if existing_invs:
raise UserError(_(
"This invoice already exists in Odoo. It's "
Expand All @@ -415,14 +414,16 @@ def import_invoice(self):
action['res_id'] = self.id
return action
else:
action = self.create_invoice()
action = self.create_invoice(parsed_inv)
return action

@api.multi
def create_invoice(self):
def create_invoice(self, parsed_inv=None):
'''parsed_inv is not a required argument'''
self.ensure_one()
iaao = self.env['ir.actions.act_window']
parsed_inv = self.parse_invoice()
if parsed_inv is None:
parsed_inv = self.parse_invoice()
invoice = self._create_invoice(parsed_inv)
invoice.message_post(_(
"This invoice has been created automatically via file import"))
Expand All @@ -439,26 +440,114 @@ def create_invoice(self):
def _create_invoice(self, parsed_inv, import_config=False):
aio = self.env['account.invoice']
bdio = self.env['business.document.import']
vals = self._prepare_create_invoice_vals(
(vals, import_config) = self._prepare_create_invoice_vals(
parsed_inv, import_config=import_config)
logger.debug('Invoice vals for creation: %s', vals)
invoice = aio.create(vals)
self.post_process_invoice(parsed_inv, invoice)
self.post_process_invoice(parsed_inv, invoice, import_config)
logger.info('Invoice ID %d created', invoice.id)
bdio.post_create_or_update(parsed_inv, invoice)
return invoice

@api.model
def post_process_invoice(self, parsed_inv, invoice):
# Force tax amount if necessary
prec = self.env['decimal.precision'].precision_get('Account')
def _prepare_global_adjustment_line(
self, diff_amount, invoice, import_config):
ailo = self.env['account.invoice.line']
prec = invoice.currency_id.rounding
il_vals = {
'name': _('Adjustment'),
'quantity': 1,
'price_unit': diff_amount,
}
# no taxes nor product on such a global adjustment line
if import_config['invoice_line_method'] == 'nline_no_product':
il_vals['account_id'] = import_config['account'].id
elif import_config['invoice_line_method'] == 'nline_static_product':
account = ailo.get_invoice_line_account(
invoice.type, import_config['product'],
invoice.fiscal_position_id, invoice.company_id)
il_vals['account_id'] = account.id
elif import_config['invoice_line_method'] == 'nline_auto_product':
res_cmp = float_compare(diff_amount, 0, precision_rounding=prec)
company = invoice.company_id
if res_cmp > 0:
if not company.adjustment_debit_account_id:
raise UserError(_(
"You must configure the 'Adjustment Debit Account' "
"on the Accounting Configuration page."))
il_vals['account_id'] = company.adjustment_debit_account_id.id
else:
if not company.adjustment_credit_account_id:
raise UserError(_(
"You must configure the 'Adjustment Credit Account' "
"on the Accounting Configuration page."))
il_vals['account_id'] = company.adjustment_credit_account_id.id
logger.debug("Prepared global ajustment invoice line %s", il_vals)
return il_vals

@api.model
def post_process_invoice(self, parsed_inv, invoice, import_config):
prec = invoice.currency_id.rounding
# If untaxed amount is wrong, create adjustment lines
if (
parsed_inv.get('amount_total') and
parsed_inv.get('amount_untaxed') and
import_config['invoice_line_method'].startswith('nline') and
float_compare(
invoice.amount_total,
parsed_inv['amount_total'],
precision_digits=prec)):
parsed_inv['amount_untaxed'], invoice.amount_untaxed,
precision_rounding=prec)):
# Try to find the line that has a problem
# TODO : on invoice creation, the lines are in the same
# order, but not on invoice update...
for i in range(len(parsed_inv['lines'])):
iline = invoice.invoice_line_ids[i]
odoo_subtotal = iline.price_subtotal
parsed_subtotal = parsed_inv['lines'][i]['price_subtotal']
if float_compare(
odoo_subtotal, parsed_subtotal,
precision_rounding=prec):
diff_amount = float_round(
parsed_subtotal - odoo_subtotal,
precision_rounding=prec)
logger.info(
'Price subtotal difference found on invoice line %d '
'(source:%s, odoo:%s, diff:%s).',
i + 1, parsed_subtotal, odoo_subtotal, diff_amount)
copy_dict = {
'name': _('Adjustment on %s') % iline.name,
'quantity': 1,
'price_unit': diff_amount,
}
if import_config['invoice_line_method'] ==\
'nline_auto_product':
copy_dict['product_id'] = False
# Add the adjustment line
iline.copy(copy_dict)
logger.info('Adjustment invoice line created')
if float_compare(
parsed_inv['amount_untaxed'], invoice.amount_untaxed,
precision_rounding=prec):
# create global ajustment line
diff_amount = float_round(
parsed_inv['amount_untaxed'] - invoice.amount_untaxed,
precision_rounding=prec)
logger.info(
'Amount untaxed difference found '
'(source: %s, odoo:%s, diff:%s)',
parsed_inv['amount_untaxed'], invoice.amount_untaxed,
diff_amount)
il_vals = self._prepare_global_adjustment_line(
diff_amount, invoice, import_config)
il_vals['invoice_id'] = invoice.id
self.env['account.invoice.line'].create(il_vals)
logger.info('Global adjustment invoice line created')
# Invalidate cache
invoice = self.env['account.invoice'].browse(invoice.id)
assert not float_compare(
parsed_inv['amount_untaxed'], invoice.amount_untaxed,
precision_rounding=prec)
# Force tax amount if necessary
if float_compare(
invoice.amount_total, parsed_inv['amount_total'],
precision_rounding=prec):
if not invoice.tax_line_ids:
raise UserError(_(
"The total amount is different from the untaxed amount, "
Expand Down

0 comments on commit 38c6953

Please sign in to comment.