[MERGE] forward port of branch 8.0 up to 591e329
[odoo/odoo.git] / addons / account / account_invoice.py
index 0d6353d..641c890 100644 (file)
@@ -24,6 +24,7 @@ from lxml import etree
 
 from openerp import models, fields, api, _
 from openerp.exceptions import except_orm, Warning, RedirectWarning
+from openerp.tools import float_compare
 import openerp.addons.decimal_precision as dp
 
 # mapping invoice type to journal type
@@ -86,7 +87,8 @@ class account_invoice(models.Model):
     @api.returns('account.analytic.journal', lambda r: r.id)
     def _get_journal_analytic(self, inv_type):
         """ Return the analytic journal corresponding to the given invoice type. """
-        journal_type = TYPE2JOURNAL.get(inv_type, 'sale')
+        type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
+        journal_type = type2journal.get(inv_type, 'sale')
         journal = self.env['account.analytic.journal'].search([('type', '=', journal_type)], limit=1)
         if not journal:
             raise except_orm(_('No Analytic Journal!'),
@@ -107,42 +109,41 @@ class account_invoice(models.Model):
         'state', 'currency_id', 'invoice_line.price_subtotal',
         'move_id.line_id.account_id.type',
         'move_id.line_id.amount_residual',
+        # Fixes the fact that move_id.line_id.amount_residual, being not stored and old API, doesn't trigger recomputation
+        'move_id.line_id.reconcile_id',
         'move_id.line_id.amount_residual_currency',
         'move_id.line_id.currency_id',
         'move_id.line_id.reconcile_partial_id.line_partial_ids.invoice.type',
     )
+    # An invoice's residual amount is the sum of its unreconciled move lines and,
+    # for partially reconciled move lines, their residual amount divided by the
+    # number of times this reconciliation is used in an invoice (so we split
+    # the residual amount between all invoice)
     def _compute_residual(self):
-        nb_inv_in_partial_rec = max_invoice_id = 0
         self.residual = 0.0
+        # Each partial reconciliation is considered only once for each invoice it appears into,
+        # and its residual amount is divided by this number of invoices
+        partial_reconciliations_done = []
         for line in self.sudo().move_id.line_id:
-            if line.account_id.type in ('receivable', 'payable'):
-                if line.currency_id == self.currency_id:
-                    self.residual += line.amount_residual_currency
-                else:
-                    # ahem, shouldn't we use line.currency_id here?
-                    from_currency = line.company_id.currency_id.with_context(date=line.date)
-                    self.residual += from_currency.compute(line.amount_residual, self.currency_id)
-                # we check if the invoice is partially reconciled and if there
-                # are other invoices involved in this partial reconciliation
+            if line.account_id.type not in ('receivable', 'payable'):
+                continue
+            if line.reconcile_partial_id and line.reconcile_partial_id.id in partial_reconciliations_done:
+                continue
+            # Get the correct line residual amount
+            if line.currency_id == self.currency_id:
+                line_amount = line.currency_id and line.amount_residual_currency or line.amount_residual
+            else:
+                from_currency = line.company_id.currency_id.with_context(date=line.date)
+                line_amount = from_currency.compute(line.amount_residual, self.currency_id)
+            # For partially reconciled lines, split the residual amount
+            if line.reconcile_partial_id:
+                partial_reconciliation_invoices = set()
                 for pline in line.reconcile_partial_id.line_partial_ids:
                     if pline.invoice and self.type == pline.invoice.type:
-                        nb_inv_in_partial_rec += 1
-                        # store the max invoice id as for this invoice we will
-                        # make a balance instead of a simple division
-                        max_invoice_id = max(max_invoice_id, pline.invoice.id)
-        if nb_inv_in_partial_rec:
-            # if there are several invoices in a partial reconciliation, we
-            # split the residual by the number of invoices to have a sum of
-            # residual amounts that matches the partner balance
-            new_value = self.currency_id.round(self.residual / nb_inv_in_partial_rec)
-            if self.id == max_invoice_id:
-                # if it's the last the invoice of the bunch of invoices
-                # partially reconciled together, we make a balance to avoid
-                # rounding errors
-                self.residual = self.residual - ((nb_inv_in_partial_rec - 1) * new_value)
-            else:
-                self.residual = new_value
-        # prevent the residual amount on the invoice to be less than 0
+                        partial_reconciliation_invoices.update([pline.invoice.id])
+                line_amount = self.currency_id.round(line_amount / len(partial_reconciliation_invoices))
+                partial_reconciliations_done.append(line.reconcile_partial_id.id)
+            self.residual += line_amount
         self.residual = max(self.residual, 0.0)
 
     @api.one
@@ -317,16 +318,26 @@ class account_invoice(models.Model):
     @api.model
     def fields_view_get(self, view_id=None, view_type=False, toolbar=False, submenu=False):
         context = self._context
+
+        def get_view_id(xid, name):
+            try:
+                return self.env['ir.model.data'].xmlid_to_res_id('account.' + xid, raise_if_not_found=True)
+            except ValueError:
+                try:
+                    return self.env['ir.ui.view'].search([('name', '=', name)], limit=1).id
+                except Exception:
+                    return False    # view not found
+
         if context.get('active_model') == 'res.partner' and context.get('active_ids'):
             partner = self.env['res.partner'].browse(context['active_ids'])[0]
             if not view_type:
-                view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.tree')]).id
+                view_id = get_view_id('invoice_tree', 'account.invoice.tree')
                 view_type = 'tree'
             elif view_type == 'form':
                 if partner.supplier and not partner.customer:
-                    view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.supplier.form')]).id
+                    view_id = get_view_id('invoice_supplier_form', 'account.invoice.supplier.form')
                 elif partner.customer and not partner.supplier:
-                    view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.form')]).id
+                    view_id = get_view_id('invoice_form', 'account.invoice.form')
 
         res = super(account_invoice, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
 
@@ -705,6 +716,7 @@ class account_invoice(models.Model):
                 account_invoice_tax.create(tax)
         else:
             tax_key = []
+            precision = self.env['decimal.precision'].precision_get('Account')
             for tax in self.tax_line:
                 if tax.manual:
                     continue
@@ -713,7 +725,7 @@ class account_invoice(models.Model):
                 if key not in compute_taxes:
                     raise except_orm(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
                 base = compute_taxes[key]['base']
-                if abs(base - tax.base) > company_currency.rounding:
+                if float_compare(abs(base - tax.base), company_currency.rounding, precision_digits=precision) == 1:
                     raise except_orm(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
             for key in compute_taxes:
                 if key not in tax_key:
@@ -812,7 +824,7 @@ class account_invoice(models.Model):
                     if line.value == 'fixed':
                         total_fixed += line.value_amount
                     if line.value == 'procent':
-                        total_percent += line.value_amount
+                        total_percent += (line.value_amount/100.0)
                 total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
                 if (total_fixed + total_percent) > 100:
                     raise except_orm(_('Error!'), _("Cannot create the invoice.\nThe related payment term is probably misconfigured as it gives a computed amount greater than the total invoiced amount. In order to avoid rounding issues, the latest line of your payment term must be of type 'balance'."))
@@ -1608,7 +1620,7 @@ class res_partner(models.Model):
     _inherit = 'res.partner'
 
     invoice_ids = fields.One2many('account.invoice', 'partner_id', string='Invoices',
-        readonly=True)
+        readonly=True, copy=False)
 
     def _find_accounting_partner(self, partner):
         '''