← Back to team overview

openerp-community team mailing list archive

lp:~openerp-community/openobject-addons/fix-account-trunk-tax-computation-method into lp:openobject-addons

 

Lorenzo Battistini - Agile BG - Domsense has proposed merging lp:~openerp-community/openobject-addons/fix-account-trunk-tax-computation-method into lp:openobject-addons.

Requested reviews:
  OpenERP Core Team (openerp)
Related bugs:
  Bug #707923 in OpenERP Addons: "[5 - 6.0 - 6.1] invoice tax - rounding issues and computation method"
  https://bugs.launchpad.net/openobject-addons/+bug/707923

For more details, see:
https://code.launchpad.net/~openerp-community/openobject-addons/fix-account-trunk-tax-computation-method/+merge/104945
-- 
https://code.launchpad.net/~openerp-community/openobject-addons/fix-account-trunk-tax-computation-method/+merge/104945
Your team OpenERP Community is subscribed to branch lp:~openerp-community/openobject-addons/fix-account-trunk-tax-computation-method.
=== modified file 'account/__openerp__.py'
--- account/__openerp__.py	2012-04-03 08:27:56 +0000
+++ account/__openerp__.py	2012-05-07 17:18:21 +0000
@@ -135,6 +135,7 @@
         'demo/account_minimal.xml',
         'demo/account_invoice_demo.xml',
 #        'account_unit_test.xml',
+        'demo/account_tax.xml',
     ],
     'test': [
         'test/account_customer_invoice.yml',
@@ -150,6 +151,7 @@
         'test/test_edi_invoice.yml',
         'test/account_report.yml',
         'test/account_fiscalyear_close_state.yml', #last test, as it will definitively close the demo fiscalyear
+        'test/tax_computation.yml',
     ],
     'installable': True,
     'auto_install': False,

=== modified file 'account/account.py'
--- account/account.py	2012-04-03 08:29:06 +0000
+++ account/account.py	2012-05-07 17:18:21 +0000
@@ -3,6 +3,8 @@
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>)
+#    Copyright (C) 2012 Domsense srl (<http://www.domsense.com>)
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -1980,11 +1982,26 @@
         'company_id': _default_company,
     }
     _order = 'sequence'
+    
+    def _get_product_for_python_compute(self, cr, uid, tax, products):
+        if ('product' in tax.python_applicable or
+            'product' in tax.python_compute or
+            'product' in tax.python_compute_inv) and len(products) > 1:
+            raise osv.except_osv(_('Error'), _("Python computation for tax %s is configured for using 'product' variable, but the current invoice uses computation by column and the same tax covers several products") % tax.name)
+        elif ('product' in tax.python_applicable or
+            'product' in tax.python_compute or
+            'product' in tax.python_compute_inv) and len(products) == 1:
+            product = products[0]
+        else:
+            product = None
+        return product
 
     def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
         res = []
         for tax in taxes:
             if tax.applicable_type=='code':
+                if isinstance(product, list):
+                    product = self._get_product_for_python_compute(cr, uid, tax, product)
                 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
                 exec tax.python_applicable in localdict
                 if localdict.get('result', False):
@@ -2024,6 +2041,8 @@
                 data['tax_amount']=quantity
                # data['amount'] = quantity
             elif tax.type=='code':
+                if isinstance(product, list):
+                    product = self._get_product_for_python_compute(cr, uid, tax, product)
                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
                 exec tax.python_compute in localdict
                 amount = localdict['result']
@@ -2146,6 +2165,8 @@
                 amount = tax.amount
 
             elif tax.type=='code':
+                if isinstance(product, list):
+                    product = self._get_product_for_python_compute(cr, uid, tax, product)
                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
                 exec tax.python_compute_inv in localdict
                 amount = localdict['result']

=== modified file 'account/account_invoice.py'
--- account/account_invoice.py	2012-04-03 10:58:01 +0000
+++ account/account_invoice.py	2012-05-07 17:18:21 +0000
@@ -3,6 +3,8 @@
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>)
+#    Copyright (C) 2012 Domsense srl (<http://www.domsense.com>)
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -274,7 +276,8 @@
         'payment_ids': fields.function(_compute_lines, relation='account.move.line', type="many2many", string='Payments'),
         'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
         'user_id': fields.many2one('res.users', 'Salesman', readonly=True, states={'draft':[('readonly',False)]}),
-        'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
+        'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]}),
+        'vertical_comp' : fields.boolean('Tax Computation By Column'),
     }
     _defaults = {
         'type': _get_type,
@@ -1459,6 +1462,7 @@
                 continue
             res.append(mres)
             tax_code_found= False
+            # computation by column needed?
             for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
                     line.quantity, line.product_id,
@@ -1587,6 +1591,54 @@
         'base_amount': 0.0,
         'tax_amount': 0.0,
     }
+    
+    def build_grouped_lines(self, cr, uid, document_lines,
+        tax_field_name='invoice_line_tax_id', price_unit_field_name='price_unit', 
+        discount_field_name='discount', quantity_field_name='quantity', product_id_field_name='product_id', account_id_field_name='account_id'):
+        lines = []
+        lines_grouped_by_tax = {}
+        for line in document_lines:
+            key = tuple(tax.id for tax in line[tax_field_name])
+            if not lines_grouped_by_tax.get(key, False):
+                lines_grouped_by_tax[key] = []
+            lines_grouped_by_tax[key].append(line)
+        for key in lines_grouped_by_tax:
+            price_unit = 0.0
+            products = []
+            account_ids = []
+            for line in lines_grouped_by_tax[key]:
+                price_unit += (line[price_unit_field_name]* (1-(discount_field_name and line[discount_field_name] or 0.0)/100.0)) * line[quantity_field_name]
+                if product_id_field_name and line[product_id_field_name] not in products:
+                    products.append(line.product_id)
+                if account_id_field_name and line[account_id_field_name]['id'] not in account_ids:
+                    account_ids.append(line[account_id_field_name]['id'])
+            lines.append({
+                # using the first line, as being grouped by taxes, invoice_line_tax_id is always the same for every group
+                'taxes': lines_grouped_by_tax[key][0][tax_field_name],
+                'price_unit': price_unit,
+                'quantity': 1,
+                'products': products,
+                'account_ids': account_ids,
+                })
+        return lines
+    
+    def build_invoice_line_list(self, cr, uid, invoice_id, context=None):
+        lines = []
+        inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
+        if inv.vertical_comp:
+            lines = self.build_grouped_lines(cr, uid, inv.invoice_line)
+        else:
+            for line in inv.invoice_line:
+                price_unit = (line.price_unit* (1-(line.discount or 0.0)/100.0))
+                lines.append({
+                    'taxes': line.invoice_line_tax_id,
+                    'price_unit': price_unit,
+                    'quantity': line.quantity,
+                    'products': line.product_id,
+                    'account_ids': [line.account_id.id],
+                    })
+        return lines
+        
     def compute(self, cr, uid, invoice_id, context=None):
         tax_grouped = {}
         tax_obj = self.pool.get('account.tax')
@@ -1595,8 +1647,9 @@
         cur = inv.currency_id
         company_currency = inv.company_id.currency_id.id
 
-        for line in inv.invoice_line:
-            for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, line.product_id, inv.partner_id)['taxes']:
+        lines = self.build_invoice_line_list(cr, uid, invoice_id, context=None)
+        for line in lines:
+            for tax in tax_obj.compute_all(cr, uid, line['taxes'], line['price_unit'], line['quantity'], line['products'], inv.partner_id)['taxes']:
                 tax['price_unit'] = cur_obj.round(cr, uid, cur, tax['price_unit'])
                 val={}
                 val['invoice_id'] = inv.id
@@ -1611,13 +1664,17 @@
                     val['tax_code_id'] = tax['tax_code_id']
                     val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
                     val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
-                    val['account_id'] = tax['account_collected_id'] or line.account_id.id
+                    if not tax['account_collected_id'] and len(line['account_ids']) > 1:
+                        raise osv.except_osv(_('Error'), _("The invoice %s is configured for using computation by column but tax %s does not have an 'account_collected_id' value and its lines cover several accounts") % (inv.name, tax['name']))
+                    val['account_id'] = tax['account_collected_id'] or line['account_ids'][0]
                 else:
                     val['base_code_id'] = tax['ref_base_code_id']
                     val['tax_code_id'] = tax['ref_tax_code_id']
                     val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
                     val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
-                    val['account_id'] = tax['account_paid_id'] or line.account_id.id
+                    if not tax['account_paid_id'] and len(line['account_ids']) > 1:
+                        raise osv.except_osv(_('Error'), _("The invoice %s is configured for using computation by column but tax %s does not have an 'account_paid_id' value and its lines cover several accounts") % (inv.name, tax['name']))
+                    val['account_id'] = tax['account_paid_id'] or line['account_ids'][0]
 
                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
                 if not key in tax_grouped:

=== modified file 'account/account_invoice_view.xml'
--- account/account_invoice_view.xml	2012-04-25 13:40:18 +0000
+++ account/account_invoice_view.xml	2012-05-07 17:18:21 +0000
@@ -224,6 +224,7 @@
                             <field name="origin"/>
                             <field name="user_id"/>
                             <field name="move_id" groups="account.group_account_user"/>
+                            <field name="vertical_comp"/>
                             <separator colspan="4" string="Additional Information"/>
                             <field colspan="4" name="comment" nolabel="1"/>
                         </page>
@@ -319,6 +320,7 @@
                             <field domain="[('partner_id.ref_companies', 'in', [company_id])]" name="partner_bank_id"/>
                             <field name="origin"/>
                             <field name="move_id" groups="account.group_account_user"/>
+                            <field name="vertical_comp"/>
                             <separator colspan="4" string="Additional Information"/>
                             <field colspan="4" name="comment" nolabel="1"/>
                         </page>

=== added file 'account/demo/account_tax.xml'
--- account/demo/account_tax.xml	1970-01-01 00:00:00 +0000
+++ account/demo/account_tax.xml	2012-05-07 17:18:21 +0000
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data noupdate="1">
+
+        <!-- Tax codes -->
+
+        <record id="account_tax_code_10" model="account.tax.code">
+            <field name="name">10 %</field>
+        </record>
+        <record id="account_tax_code_10_imp" model="account.tax.code">
+            <field name="name">10 % IMP</field>
+        </record>
+
+        <record id="account_tax_code_21" model="account.tax.code">
+            <field name="name">21 %</field>
+        </record>
+        <record id="account_tax_code_21_imp" model="account.tax.code">
+            <field name="name">21 % IMP</field>
+        </record>
+
+        <record id="account_tax_code_21_inc" model="account.tax.code">
+            <field name="name">21 % INC</field>
+        </record>
+        <record id="account_tax_code_21_inc_imp" model="account.tax.code">
+            <field name="name">21 % INC IMP</field>
+        </record>
+
+        <record id="account_tax_code_20_inc" model="account.tax.code">
+            <field name="name">20 % INC</field>
+        </record>
+        <record id="account_tax_code_20_inc_imp" model="account.tax.code">
+            <field name="name">20 % INC IMP</field>
+        </record>
+
+        <!-- taxes -->
+
+        <record id="account_tax_10" model="account.tax">
+            <field name="name">10 %</field>
+            <field name="amount">0.1</field>
+            <field name="tax_code_id" ref="account_tax_code_10"></field>
+            <field name="base_code_id" ref="account_tax_code_10_imp"></field>
+        </record>
+
+        <record id="account_tax_21" model="account.tax">
+            <field name="name">21 %</field>
+            <field name="amount">0.21</field>
+            <field name="tax_code_id" ref="account_tax_code_21"></field>
+            <field name="base_code_id" ref="account_tax_code_21_imp"></field>
+        </record>
+
+        <record id="account_tax_21_inc" model="account.tax">
+            <field name="name">21 % INC</field>
+            <field name="amount">0.21</field>
+            <field name="price_include">1</field>
+            <field name="tax_code_id" ref="account_tax_code_21_inc"></field>
+            <field name="base_code_id" ref="account_tax_code_21_inc_imp"></field>
+        </record>
+
+        <record id="account_tax_20_inc" model="account.tax">
+            <field name="name">20 % INC</field>
+            <field name="amount">0.2</field>
+            <field name="price_include">1</field>
+            <field name="tax_code_id" ref="account_tax_code_20_inc"></field>
+            <field name="base_code_id" ref="account_tax_code_20_inc_imp"></field>
+        </record>
+        
+    </data>
+</openerp>

=== added file 'account/test/tax_computation.yml'
--- account/test/tax_computation.yml	1970-01-01 00:00:00 +0000
+++ account/test/tax_computation.yml	2012-05-07 17:18:21 +0000
@@ -0,0 +1,471 @@
+#-
+  #First invoice. 2 lines
+    #price_unit = 23.83, tax = 0.21
+    #price_unit = 7.44, tax = 0.21
+#-
+-
+  Computation by line
+-
+
+  !record {model: account.invoice, id: invoice_1_computation_by_line}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 0
+    invoice_line:
+      - account_id: a_sale
+        name: '23.83'
+        price_unit: 23.83
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+      - account_id: a_sale
+        name: '7.44'
+        price_unit: 7.44
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_1_computation_by_line")])  
+-  
+    Then I verify the amount (computation by line).
+    23.83 × 0.21 = 5.0043 = 5
+    7.44 × 0.21 = 1.5624 = 1.56
+    5 + 1.56 = 6.56
+-  
+    !assert {model: account.invoice, id: invoice_1_computation_by_line}:
+        - amount_tax == 6.56
+
+-
+  Computation by column
+-
+  !record {model: account.invoice, id: invoice_1_computation_by_column}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 1
+    invoice_line:
+      - account_id: a_sale
+        name: '23.83'
+        price_unit: 23.83
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+      - account_id: a_sale
+        name: '7.44'
+        price_unit: 7.44
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_1_computation_by_column")])  
+-  
+    Then I verify the amount (computation by column).
+    23.83 + 7.44 = 31.27
+    31.27 × 0.21 = 6.5667 = 6.57
+-  
+    !assert {model: account.invoice, id: invoice_1_computation_by_column}:
+        - amount_tax == 6.57
+
+
+#-
+  #Second invoice. 2 lines
+    #price_unit = 24.92, tax = 0.21
+    #price_unit = 7.44, tax = 0.21
+#-
+-
+  Computation by line
+-
+  !record {model: account.invoice, id: invoice_2_computation_by_line}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 0
+    invoice_line:
+      - account_id: a_sale
+        name: '24.92'
+        price_unit: 24.92
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+      - account_id: a_sale
+        name: '7.44'
+        price_unit: 7.44
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_2_computation_by_line")])  
+-  
+    Then I verify the amount (computation by line).
+    24.92 × 0.21 = 5.2332 = 5.23
+    7.44 × 0.21 = 1.5624 = 1.56
+    5.23 + 1.56 = 6.79
+-  
+    !assert {model: account.invoice, id: invoice_2_computation_by_line}:
+        - amount_tax == 6.79
+-
+  Computation by column
+-
+  !record {model: account.invoice, id: invoice_2_computation_by_column}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 1
+    invoice_line:
+      - account_id: a_sale
+        name: '24.92'
+        price_unit: 24.92
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+      - account_id: a_sale
+        name: '7.44'
+        price_unit: 7.44
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_2_computation_by_column")])  
+-  
+    Then I verify the amount (computation by column).
+    24.92 + 7.44 = 32.36
+    32.36 × 0.21 = 6.7956 = 6.8
+-  
+    !assert {model: account.invoice, id: invoice_2_computation_by_column}:
+        - amount_tax == 6.8
+
+
+#-
+  #Third invoice. 6 lines
+    #price_unit = 1.99, tax = 0.1
+    #price_unit = 0.38, tax = 0.1
+    #price_unit = 5.68, tax = 0.1
+    #price_unit = 0.45, tax = 0.1
+    #price_unit = 1.05, tax = 0.1
+    #price_unit = 2.87, tax = 0.1
+#-
+-
+  Computation by line
+-
+  !record {model: account.invoice, id: invoice_3_computation_by_line}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 0
+    invoice_line:
+      - account_id: a_sale
+        name: '1.99'
+        price_unit: 1.99
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '0.38'
+        price_unit: 0.38
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '5.68'
+        price_unit: 5.68
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '0.45'
+        price_unit: 0.45
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '1.05'
+        price_unit: 1.05
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '2.87'
+        price_unit: 2.87
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_3_computation_by_line")])  
+-  
+    Then I verify the amount (computation by line).
+    1.99 × 0.1 = 0.199 = 0.2
+    0.38 × 0.1 = 0.038 = 0.04
+    5.68 × 0.1 = 0.568 = 0.57
+    0.45 × 0.1 = 0.045 = 0.05
+    1.05 × 0.1 = 0.105 = 0.11
+    2.87 × 0.1 = 0.287 = 0.29
+    0.2 + 0.04 + 0.57 + 0.05 + 0.11 + 0.29 = 1.26
+-  
+    !assert {model: account.invoice, id: invoice_3_computation_by_line}:
+        - amount_tax == 1.26
+-
+  Computation by column
+-
+  !record {model: account.invoice, id: invoice_3_computation_by_column}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 1
+    invoice_line:
+      - account_id: a_sale
+        name: '1.99'
+        price_unit: 1.99
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '0.38'
+        price_unit: 0.38
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '5.68'
+        price_unit: 5.68
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '0.45'
+        price_unit: 0.45
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '1.05'
+        price_unit: 1.05
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+      - account_id: a_sale
+        name: '2.87'
+        price_unit: 2.87
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_10
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_3_computation_by_column")])  
+-  
+    Then I verify the amount (computation by column).
+    1.99 + 0.38 + 5.68 + 0.45 + 1.05 + 2.87 = 12.42
+    12.42 * 0.1 = 1.242 = 1.24
+-  
+    !assert {model: account.invoice, id: invoice_3_computation_by_column}:
+        - amount_tax == 1.24
+
+
+#-
+  #Fourth invoice. 2 lines
+    #price_unit = 465, tax = 0.21 included in price
+    #price_unit = 9, tax = 0.21 included in price
+#-
+-
+  Computation by line
+-
+  !record {model: account.invoice, id: invoice_4_computation_by_line}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 0
+    invoice_line:
+      - account_id: a_sale
+        name: '465'
+        price_unit: 465
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21_inc
+      - account_id: a_sale
+        name: '9'
+        price_unit: 9
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21_inc
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_4_computation_by_line")])  
+-  
+    Then I verify the amount (computation by line).
+    465 ÷ 1.21 = 384.297520661 = 384.3
+    9 ÷ 1.21 = 7.438016529 = 7.44
+    465 + 9 = 474
+    384.3 + 7.44 = 391.74
+    474 − 391.74 = 82.26
+-  
+    !assert {model: account.invoice, id: invoice_4_computation_by_line}:
+        - amount_tax == 82.26
+-
+  Computation by column
+-
+  !record {model: account.invoice, id: invoice_4_computation_by_column}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 1
+    invoice_line:
+      - account_id: a_sale
+        name: '465'
+        price_unit: 465
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21_inc
+      - account_id: a_sale
+        name: '9'
+        price_unit: 9
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_21_inc
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_4_computation_by_column")])  
+-  
+    Then I verify the amount (computation by column).
+    465 + 9 = 474
+    474 ÷ 1.21 = 391.73553719 = 391.74
+    474 − 391.74 = 82.26
+-  
+    !assert {model: account.invoice, id: invoice_4_computation_by_column}:
+        - amount_tax == 82.26
+
+
+#-
+  #Fifth invoice. 2 lines
+    #price_unit = 148.28, tax = 0.2 included in price
+    #price_unit = 148.28, tax = 0.2 included in price
+#-
+-
+  Computation by line
+-
+  !record {model: account.invoice, id: invoice_5_computation_by_line}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 0
+    invoice_line:
+      - account_id: a_sale
+        name: '148.28'
+        price_unit: 148.28
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_20_inc
+      - account_id: a_sale
+        name: '148.28'
+        price_unit: 148.28
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_20_inc
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_5_computation_by_line")])  
+-  
+    Then I verify the amount (computation by line).
+    148.28 + 148.28 = 296.56
+    148.28 ÷ 1.2 = 123.566666667 = 123.57
+    123.57 × 2 = 247.14
+    296.56 - 247.14 = 49.42
+-  
+    !assert {model: account.invoice, id: invoice_5_computation_by_line}:
+        - amount_tax == 49.42
+-
+  Computation by column
+-
+  !record {model: account.invoice, id: invoice_5_computation_by_column}:
+    account_id: a_recv
+    company_id: base.main_company
+    currency_id: base.EUR
+    date_invoice: !eval time.strftime('%Y-%m-%d')
+    vertical_comp: 1
+    invoice_line:
+      - account_id: a_sale
+        name: '148.28'
+        price_unit: 148.28
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_20_inc
+      - account_id: a_sale
+        name: '148.28'
+        price_unit: 148.28
+        quantity: 1.0
+        invoice_line_tax_id:
+            - account_tax_20_inc
+    journal_id: sales_journal
+    partner_id: base.res_partner_3
+    
+-  
+    Compute the total tax.
+-  
+    !python {model: account.invoice}: |
+        self.button_compute(cr, uid, [ref("invoice_5_computation_by_column")])  
+-  
+    Then I verify the amount (computation by column).
+    148.28 + 148.28 = 296.56
+    296.56 ÷ 1.2 = 247.133333333 = 247.13
+    296.56 - 247.13 = 49.43
+-  
+    !assert {model: account.invoice, id: invoice_5_computation_by_column}:
+        - amount_tax == 49.43

=== modified file 'purchase/__openerp__.py'
--- purchase/__openerp__.py	2012-04-03 13:26:18 +0000
+++ purchase/__openerp__.py	2012-05-07 17:18:21 +0000
@@ -72,6 +72,7 @@
         'test/ui/print_report.yml',
         'test/ui/duplicate_order.yml',
         'test/ui/delete_order.yml',
+        'test/tax_computation.yml',
     ],
     'demo': [
         'purchase_order_demo.yml',

=== modified file 'purchase/purchase.py'
--- purchase/purchase.py	2012-05-03 10:29:10 +0000
+++ purchase/purchase.py	2012-05-07 17:18:21 +0000
@@ -3,6 +3,8 @@
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>)
+#    Copyright (C) 2012 Domsense srl (<http://www.domsense.com>)
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -39,6 +41,8 @@
     def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
         res = {}
         cur_obj=self.pool.get('res.currency')
+        ait_pool = self.pool.get('account.invoice.tax')
+        tax_pool = self.pool.get('account.tax')
         for order in self.browse(cr, uid, ids, context=context):
             res[order.id] = {
                 'amount_untaxed': 0.0,
@@ -48,11 +52,20 @@
             val = val1 = 0.0
             cur = order.pricelist_id.currency_id
             for line in order.order_line:
-               val1 += line.price_subtotal
-               for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
-                    val += c.get('amount', 0.0)
+                val1 += line.price_subtotal
+            res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
+            if order.vertical_comp:
+                lines = ait_pool.build_grouped_lines(cr, uid, order.order_line,
+                    tax_field_name='taxes_id', quantity_field_name='product_qty',  account_id_field_name=None, discount_field_name=None)
+                for line in lines:
+                    for c in tax_pool.compute_all(cr, uid, line['taxes'], line['price_unit'], line['quantity'], line['products'], order.partner_id)['taxes']:
+                        val += c.get('amount', 0.0)
+                res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
+            else:
+                for line in order.order_line:
+                   for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
+                        val += c.get('amount', 0.0)
             res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
-            res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
             res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
         return res
 
@@ -191,20 +204,24 @@
         ),
         'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Untaxed Amount',
             store={
+                'purchase.order': (lambda self, cr, uid, ids, c={}: ids, ['vertical_comp'], 10),
                 'purchase.order.line': (_get_order, None, 10),
             }, multi="sums", help="The amount without tax"),
         'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Taxes',
             store={
+                'purchase.order': (lambda self, cr, uid, ids, c={}: ids, ['vertical_comp'], 10),
                 'purchase.order.line': (_get_order, None, 10),
             }, multi="sums", help="The tax amount"),
         'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Total',
             store={
+                'purchase.order': (lambda self, cr, uid, ids, c={}: ids, ['vertical_comp'], 10),
                 'purchase.order.line': (_get_order, None, 10),
             }, multi="sums",help="The total amount"),
         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
         'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
         'create_uid':  fields.many2one('res.users', 'Responsible'),
         'company_id': fields.many2one('res.company','Company',required=True,select=1),
+        'vertical_comp' : fields.boolean('Tax Computation By Column'),
     }
     _defaults = {
         'date_order': fields.date.context_today,

=== modified file 'purchase/purchase_view.xml'
--- purchase/purchase_view.xml	2012-04-25 13:22:02 +0000
+++ purchase/purchase_view.xml	2012-05-07 17:18:21 +0000
@@ -140,6 +140,7 @@
                         <field name="partner_ref"/>
                         <field name="shipped"/>
                         <field name="company_id" groups="base.group_multi_company" widget="selection"/>
+                        <field name="vertical_comp"/>
                     </group>
                     <notebook colspan="4">
                         <page string="Purchase Order">

=== added file 'purchase/test/tax_computation.yml'
--- purchase/test/tax_computation.yml	1970-01-01 00:00:00 +0000
+++ purchase/test/tax_computation.yml	2012-05-07 17:18:21 +0000
@@ -0,0 +1,67 @@
+#-
+  #First order. 2 lines
+    #price_unit = 23.83, tax = 0.21
+    #price_unit = 7.44, tax = 0.21
+#-
+-
+  Computation by line
+-
+  !record {model: purchase.order, id: order_1_computation_by_line}:
+    partner_id: base.res_partner_agrolait
+    location_id: stock.stock_location_3
+    pricelist_id: 1
+    vertical_comp: 0
+    order_line:
+      - product_qty: 1.0
+        product_uom: 1
+        price_unit: 23.83
+        name: '23.83'
+        taxes_id:
+            - account.account_tax_21
+        date_planned: '2012-05-06'
+      - product_qty: 1.0
+        product_uom: 1
+        price_unit: 7.44
+        name: '7.44'
+        taxes_id:
+            - account.account_tax_21
+        date_planned: '2012-05-06'
+-  
+    Then I verify the amount (computation by line).
+    23.83 × 0.21 = 5.0043 = 5
+    7.44 × 0.21 = 1.5624 = 1.56
+    5 + 1.56 = 6.56
+-  
+    !assert {model: purchase.order, id: order_1_computation_by_line}:
+        - amount_tax == 6.56
+
+-
+  Computation by column
+-
+  !record {model: purchase.order, id: order_1_computation_by_column}:
+    partner_id: base.res_partner_agrolait
+    location_id: stock.stock_location_3
+    pricelist_id: 1
+    vertical_comp: 1
+    order_line:
+      - product_qty: 1.0
+        product_uom: 1
+        price_unit: 23.83
+        name: '23.83'
+        taxes_id:
+            - account.account_tax_21
+        date_planned: '2012-05-06'
+      - product_qty: 1.0
+        product_uom: 1
+        price_unit: 7.44
+        name: '7.44'
+        taxes_id:
+            - account.account_tax_21
+        date_planned: '2012-05-06'
+-  
+    Then I verify the amount (computation by column).
+    23.83 + 7.44 = 31.27
+    31.27 × 0.21 = 6.5667 = 6.57
+-  
+    !assert {model: purchase.order, id: order_1_computation_by_column}:
+        - amount_tax == 6.57

=== modified file 'sale/__openerp__.py'
--- sale/__openerp__.py	2012-04-27 13:12:41 +0000
+++ sale/__openerp__.py	2012-05-07 17:18:21 +0000
@@ -96,6 +96,7 @@
         'test/cancel_order.yml',
         'test/delete_order.yml',
         'test/edi_sale_order.yml',
+        'test/tax_computation.yml',
     ],
     'installable': True,
     'auto_install': False,

=== modified file 'sale/sale.py'
--- sale/sale.py	2012-04-27 13:33:26 +0000
+++ sale/sale.py	2012-05-07 17:18:21 +0000
@@ -3,6 +3,8 @@
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>)
+#    Copyright (C) 2012 Domsense srl (<http://www.domsense.com>)
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -73,6 +75,8 @@
 
     def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
         cur_obj = self.pool.get('res.currency')
+        ait_pool = self.pool.get('account.invoice.tax')
+        tax_pool = self.pool.get('account.tax')
         res = {}
         for order in self.browse(cr, uid, ids, context=context):
             res[order.id] = {
@@ -84,8 +88,17 @@
             cur = order.pricelist_id.currency_id
             for line in order.order_line:
                 val1 += line.price_subtotal
-                val += self._amount_line_tax(cr, uid, line, context=context)
-            res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
+            if order.vertical_comp:
+                lines = ait_pool.build_grouped_lines(cr, uid, order.order_line,
+                    tax_field_name='tax_id', quantity_field_name='product_uom_qty',  account_id_field_name=None)
+                for line in lines:
+                    for c in tax_pool.compute_all(cr, uid, line['taxes'], line['price_unit'], line['quantity'], line['products'], order.partner_id)['taxes']:
+                        val += c.get('amount', 0.0)
+                res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
+            else:
+                for line in order.order_line:
+                    val += self._amount_line_tax(cr, uid, line, context=context)
+                res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
             res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
             res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
         return res
@@ -248,19 +261,19 @@
 
         'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
             store = {
-                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line', 'vertical_comp'], 10),
                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
             },
             multi='sums', help="The amount without tax."),
         'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
             store = {
-                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line', 'vertical_comp'], 10),
                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
             },
             multi='sums', help="The tax amount."),
         'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Sale Price'), string='Total',
             store = {
-                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
+                'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line', 'vertical_comp'], 10),
                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
             },
             multi='sums', help="The total amount."),
@@ -268,7 +281,8 @@
         'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you want your invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.", required=True, readonly=True, states={'draft': [('readonly', False)]}),
         'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
-        'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True,readonly=True)
+        'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True,readonly=True),
+        'vertical_comp' : fields.boolean('Tax Computation By Column'),
     }
     _defaults = {
         'picking_policy': 'direct',

=== modified file 'sale/sale_view.xml'
--- sale/sale_view.xml	2012-05-03 10:29:10 +0000
+++ sale/sale_view.xml	2012-05-07 17:18:21 +0000
@@ -239,6 +239,7 @@
                                 <field name="payment_term" widget="selection"/>
                                 <field name="fiscal_position" widget="selection"/>
                                 <field name="company_id" widget="selection" groups="base.group_multi_company"/>
+                                <field name="vertical_comp"/>
                             </group>
                             <separator colspan="4" string="Notes" groups="base.group_sale_notes_subtotal"/>
                             <field colspan="4" name="note" nolabel="1" groups="base.group_sale_notes_subtotal"/>

=== added file 'sale/test/tax_computation.yml'
--- sale/test/tax_computation.yml	1970-01-01 00:00:00 +0000
+++ sale/test/tax_computation.yml	2012-05-07 17:18:21 +0000
@@ -0,0 +1,65 @@
+#-
+  #First order. 2 lines
+    #price_unit = 23.83, tax = 0.21
+    #price_unit = 7.44, tax = 0.21
+#-
+-
+  Computation by line
+-
+  !record {model: sale.order, id: order_1_computation_by_line}:
+    partner_id: base.res_partner_agrolait
+    partner_invoice_id: base.res_partner_address_8invoice
+    partner_shipping_id: base.res_partner_address_8invoice
+    pricelist_id: 1
+    vertical_comp: 0
+    order_line:
+      - product_uom_qty: 1.0
+        product_uom: 1
+        price_unit: 23.83
+        name: '23.83'
+        tax_id:
+            - account.account_tax_21
+      - product_uom_qty: 1.0
+        product_uom: 1
+        price_unit: 7.44
+        name: '7.44'
+        tax_id:
+            - account.account_tax_21
+-  
+    Then I verify the amount (computation by line).
+    23.83 × 0.21 = 5.0043 = 5
+    7.44 × 0.21 = 1.5624 = 1.56
+    5 + 1.56 = 6.56
+-  
+    !assert {model: sale.order, id: order_1_computation_by_line}:
+        - amount_tax == 6.56
+
+-
+  Computation by column
+-
+  !record {model: sale.order, id: order_1_computation_by_column}:
+    partner_id: base.res_partner_agrolait
+    partner_invoice_id: base.res_partner_address_8invoice
+    partner_shipping_id: base.res_partner_address_8invoice
+    pricelist_id: 1
+    vertical_comp: 1
+    order_line:
+      - product_uom_qty: 1.0
+        product_uom: 1
+        price_unit: 23.83
+        name: '23.83'
+        tax_id:
+            - account.account_tax_21
+      - product_uom_qty: 1.0
+        product_uom: 1
+        price_unit: 7.44
+        name: '7.44'
+        tax_id:
+            - account.account_tax_21
+-  
+    Then I verify the amount (computation by column).
+    23.83 + 7.44 = 31.27
+    31.27 × 0.21 = 6.5667 = 6.57
+-  
+    !assert {model: sale.order, id: order_1_computation_by_column}:
+        - amount_tax == 6.57


Follow ups