Skip to content

[18.0][19.0] account: round_globally with price-included taxes puts the rounding cent of one tax on a base line of another tax #290493

Description

@mmircoli-nexapp

Odoo Version

  • 17.0
  • 18.0
  • 19.0

Steps to Reproduce

Fresh database, only account installed, no demo data, generic chart of accounts (reproduced on 18.0 community @ df8a3fb0ccf; the same code is in 19.0).

  1. Set the company tax_calculation_rounding_method to round_globally.

  2. Create two sale taxes, both price-included: 10% incl and 22% incl.

  3. Create a customer invoice with these four lines (quantity 1):

    Line Price Tax
    A 28.00 10% incl
    A discount -28.00 10% incl
    B 16.00 22% incl
    C 24.00 22% incl

    This is a typical e-commerce order: a gift product fully discounted, plus two paid products at a different VAT rate. No third-party module is involved: the example uses only account on a fresh database.

  4. Confirm the invoice and look at the journal items.

Current Behavior

Line Tax price_subtotal balance tax_base_amount
A 10% incl 25.45 -25.46
A discount 10% incl -25.45 25.45
B 22% incl 13.11 -13.11
C 22% incl 19.67 -19.67
Tax 22% incl -7.21 32.79
  • The 22% tax is computed globally: 40.00 / 1.22 × 0.22 = 7.21, so its base is 32.79, and the tax line says so (tax_base_amount = 32.79).
  • The 22% base lines only sum to 13.11 + 19.67 = 32.78. The missing cent is put on line A, which carries the 10% tax.
  • As a result the journal items show a 10% base of 0.01 with no 10% tax line, and a 22% base of 32.78 instead of 32.79.

Why: account.tax._round_tax_details_base_lines (in _round_tax_details_base_lines, grouping_function) groups base lines only by currency and is_refund (19.0 adds computation_key), not by tax. The delta of the whole document is then given, through _distribute_delta_amount_smoothly, to the line with the largest absolute amount. Here that is line A (|28.00|), which is fully discounted and at another rate.

Consequences:

  • The core contradicts itself on the same move: the tax line says the 22% base is 32.79, the base lines say 32.78.
  • Tax reports built on tax lines and analyses of journal items grouped by tax (tax_ids) differ by one cent per affected document. In our production database this happened on 10 documents over 9 months, in both directions (10% → 22% and 22% → 10%), so it becomes very hard to reconcile.
  • A tax gets a taxable base (0.01) with no tax amount, and a line that is 100% discounted carries a non-zero base.

Expected Behavior

A rounding delta generated by a tax should stay on base lines that carry that same tax. In the example, the cent should go to line C (24.00, 22% incl), giving:

Line Tax balance
A 10% incl -25.45
A discount 10% incl 25.45
B 22% incl -13.11
C 22% incl -19.68

The document total (40.00) and the tax amounts stay unchanged. Only the base line that absorbs the rounding changes.

A possible approach: first distribute the delta inside groups of lines with the same taxes. Then distribute any residual delta on the whole document, as the current code does, so the total still matches the sum of the lines with taxes included.

Related

Reproduction script (odoo-bin shell, rolls back)
company = env.company
company.tax_calculation_rounding_method = 'round_globally'
Tax = env['account.tax']
def mk(name, amt):
    return Tax.create({'name': name, 'amount': amt, 'amount_type': 'percent', 'type_tax_use': 'sale', 'price_include_override': 'tax_included', 'company_id': company.id})
t10 = mk('REPRO 10% incl', 10); t22 = mk('REPRO 22% incl', 22)
partner = env['res.partner'].create({'name': 'Repro'})
L = lambda n, p, t: (0, 0, {'name': n, 'quantity': 1, 'price_unit': p, 'tax_ids': [(6, 0, t.ids)]})
mv = env['account.move'].create({'move_type': 'out_invoice', 'partner_id': partner.id, 'currency_id': company.currency_id.id, 'invoice_line_ids': [
    L('A', 28, t10), L('A discount', -28, t10), L('B', 16, t22), L('C', 24, t22)]})
mv.action_post()
for l in mv.line_ids.sorted('id'):
    print(f"{l.name:12} {l.display_type:12} subtotal={l.price_subtotal:8.2f} balance={l.balance:8.2f} tax_base_amount={l.tax_base_amount:8.2f} taxes={l.tax_ids.mapped('name') or l.tax_line_id.name}")
print('amount_total', mv.amount_total, 'currency', mv.currency_id.name)
env.cr.rollback()

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions