[IMP] account: Allow tax to not be affected by a previous one

In India, we need to configure taxes like this:
- 6% applied on base amount
- 6% applied on base amount
- 1% applied on 1.12 * base amount

If both 6% taxes are affected the base of subsequent taxes, it gives the following result:
- 6% applied on base amount
- 6% applied on 1.06 * base amount
- 1% applied on 1.06 * 1.06 *  base amount

Thanks to the new 'is_base_affected' field set on the second tax, it gives the expected results.

closes odoo/odoo#66134

Task: 2343958
Related: odoo/upgrade#2254
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
Laurent Smet
2021-03-08 15:37:08 +00:00
parent ba1d2b754f
commit 25dcf48225
8 changed files with 63 additions and 11 deletions
+15 -5
View File
@@ -73,7 +73,11 @@ class AccountTax(models.Model):
price_include = fields.Boolean(string='Included in Price', default=False,
help="Check this if the price you use on the product and invoices includes this tax.")
include_base_amount = fields.Boolean(string='Affect Base of Subsequent Taxes', default=False,
help="If set, taxes which are computed after this one will be computed based on the price tax included.")
help="If set, taxes with a higher sequence than this one will be affected by it, provided they accept it.")
is_base_affected = fields.Boolean(
string="Base Affected by Previous Taxes",
default=True,
help="If set, taxes with a lower sequence might affect this one, provided they try to do it.")
analytic = fields.Boolean(string="Include in Analytic Cost", help="If set, the amount computed by this tax will be assigned to the same analytic account as the invoice line (if any)")
tax_group_id = fields.Many2one('account.tax.group', string="Tax Group", default=_default_tax_group, required=True)
# Technical field to make the 'tax_exigibility' field invisible if the same named field is set to false in 'res.company' model
@@ -373,6 +377,7 @@ class AccountTax(models.Model):
# 3) Deal with the rounding methods
if not currency:
currency = company.currency_id
# By default, for each tax, tax amount will first be computed
# and rounded at the 'Account' decimal precision for each
# PO/SO/invoice line and then these rounded amounts will be
@@ -510,11 +515,16 @@ class AccountTax(models.Model):
i = 0
cumulated_tax_included_amount = 0
for tax in taxes:
price_include = self._context.get('force_price_include', tax.price_include)
if price_include or tax.is_base_affected:
tax_base_amount = base
else:
tax_base_amount = total_excluded
tax_repartition_lines = (is_refund and tax.refund_repartition_line_ids or tax.invoice_repartition_line_ids).filtered(lambda x: x.repartition_type == 'tax')
sum_repartition_factor = sum(tax_repartition_lines.mapped('factor'))
price_include = self._context.get('force_price_include', tax.price_include)
#compute the tax_amount
if price_include and total_included_checkpoints.get(i):
# We know the total to reach for that tax, so we make a substraction to avoid any rounding issues
@@ -522,7 +532,7 @@ class AccountTax(models.Model):
cumulated_tax_included_amount = 0
else:
tax_amount = tax.with_context(force_price_include=False)._compute_amount(
base, sign * price_unit, quantity, product, partner)
tax_base_amount, sign * price_unit, quantity, product, partner)
# Round the tax_amount multiplied by the computed repartition lines factor.
tax_amount = round(tax_amount, precision_rounding=prec)
@@ -563,7 +573,7 @@ class AccountTax(models.Model):
'id': tax.id,
'name': partner and tax.with_context(lang=partner.lang).name or tax.name,
'amount': sign * line_amount,
'base': round(sign * base, precision_rounding=prec),
'base': round(sign * tax_base_amount, precision_rounding=prec),
'sequence': tax.sequence,
'account_id': tax.cash_basis_transition_account_id.id if tax.tax_exigibility == 'on_payment' else repartition_line.account_id.id,
'analytic': tax.analytic,
+6 -1
View File
@@ -914,7 +914,11 @@ class AccountTaxTemplate(models.Model):
price_include = fields.Boolean(string='Included in Price', default=False,
help="Check this if the price you use on the product and invoices includes this tax.")
include_base_amount = fields.Boolean(string='Affect Subsequent Taxes', default=False,
help="If set, taxes which are computed after this one will be computed based on the price tax included.")
help="If set, taxes with a higher sequence than this one will be affected by it, provided they accept it.")
is_base_affected = fields.Boolean(
string="Base Affected by Previous Taxes",
default=True,
help="If set, taxes with a lower sequence might affect this one, provided they try to do it.")
analytic = fields.Boolean(string="Analytic Cost", help="If set, the amount computed by this tax will be assigned to the same analytic account as the invoice line (if any)")
invoice_repartition_line_ids = fields.One2many(string="Repartition for Invoices", comodel_name="account.tax.repartition.line.template", inverse_name="invoice_tax_id", copy=True, help="Repartition when the tax is used on an invoice")
refund_repartition_line_ids = fields.One2many(string="Repartition for Refund Invoices", comodel_name="account.tax.repartition.line.template", inverse_name="refund_tax_id", copy=True, help="Repartition when the tax is used on a refund")
@@ -964,6 +968,7 @@ class AccountTaxTemplate(models.Model):
'description': self.description,
'price_include': self.price_include,
'include_base_amount': self.include_base_amount,
'is_base_affected': self.is_base_affected,
'analytic': self.analytic,
'children_tax_ids': [(6, 0, children_ids)],
'tax_exigibility': self.tax_exigibility,
+28
View File
@@ -1045,3 +1045,31 @@ class TestTax(TestTaxCommon):
],
res3
)
def test_is_base_affected(self):
taxes = self.env['account.tax'].create([{
'name': 'test_is_base_affected%s' % i,
'amount_type': 'percent',
'amount': amount,
'include_base_amount': include_base_amount,
'is_base_affected': is_base_affected,
'sequence': i,
} for i, amount, include_base_amount, is_base_affected in [
(0, 6, True, True),
(1, 6, True, False),
(2, 10, False, True),
]])
self._check_compute_all_results(
123.2, # 'total_included'
100.0, # 'total_excluded'
[
# base, amount
# -------------------------
(100.0, 6.0),
(100.0, 6.0),
(112.0, 11.2),
# -------------------------
],
taxes.compute_all(100.0),
)
@@ -172,6 +172,8 @@
<group name="price_definitions">
<field name="price_include" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="include_base_amount" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="is_base_affected"
attrs="{'invisible': ['|', ('amount_type','=', 'group'), ('price_include', '=', True)]}"/>
</group>
<group name="tax_configuration">
<field name="active" groups="base.group_no_one"/>
@@ -164,6 +164,8 @@
<group name="advanced_booleans">
<field name="price_include" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="include_base_amount" attrs="{'invisible':[('amount_type','=', 'group')]}" />
<field name="is_base_affected"
attrs="{'invisible': ['|', ('amount_type','=', 'group'), ('price_include', '=', True)]}"/>
<field name="hide_tax_exigibility" invisible="1"/>
<field name="tax_exigibility" widget="radio" attrs="{'invisible':['|', ('amount_type','=', 'group'), ('hide_tax_exigibility', '=', False)]}" groups="account.group_account_readonly"/>
<field name="cash_basis_transition_account_id" options="{'no_create': True}" attrs="{'invisible': [('tax_exigibility', '=', 'on_invoice')], 'required': [('tax_exigibility', '=', 'on_payment')]}" groups="account.group_account_readonly"/>
+1 -1
View File
@@ -5,7 +5,7 @@
<field name="model">account.tax</field>
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="include_base_amount" position="after">
<field name="is_base_affected" position="after">
<field name="l10n_in_reverse_charge" attrs="{'invisible':[('amount_type','=', 'group')]}"/>
</field>
</field>
+1 -1
View File
@@ -10,7 +10,7 @@ class AccountTax(models.Model):
def write(self, vals):
forbidden_fields = set([
'amount_type', 'amount', 'type_tax_use', 'tax_group_id', 'price_include',
'include_base_amount'
'include_base_amount', 'is_base_affected',
])
if forbidden_fields & set(vals.keys()):
tax_ids = self.env['pos.order.line'].sudo().search([
+8 -3
View File
@@ -245,7 +245,7 @@ exports.PosModel = Backbone.Model.extend({
},
},{
model: 'account.tax',
fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids'],
fields: ['name','amount', 'price_include', 'include_base_amount', 'is_base_affected', 'amount_type', 'children_tax_ids'],
domain: function(self) {return [['company_id', '=', self.company && self.company.id || false]]},
loaded: function(self, taxes){
self.taxes = taxes;
@@ -2218,11 +2218,16 @@ exports.Orderline = Backbone.Model.extend({
i = 0;
var cumulated_tax_included_amount = 0;
_(taxes.reverse()).each(function(tax){
if(tax.price_include || tax.is_base_affected)
var tax_base_amount = base;
else
var tax_base_amount = total_excluded;
if(tax.price_include && total_included_checkpoints[i] !== undefined){
var tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount);
cumulated_tax_included_amount = 0;
}else
var tax_amount = self._compute_all(tax, base, quantity, true);
var tax_amount = self._compute_all(tax, tax_base_amount, quantity, true);
tax_amount = round_pr(tax_amount, currency_rounding);
@@ -2233,7 +2238,7 @@ exports.Orderline = Backbone.Model.extend({
'id': tax.id,
'name': tax.name,
'amount': sign * tax_amount,
'base': sign * round_pr(base, currency_rounding),
'base': sign * round_pr(tax_base_amount, currency_rounding),
});
if(tax.include_base_amount)