The Accounting upgrade / Community edition

Odoo 18 to 19 Accounting migration: follow the invoice, test the result

Upgrade custom Accounting addons from Odoo 18 to 19: account-field changes, reconciliation links, report behavior, original code, and practical tests.

By VersionBridge · Updated

18 19

Choose a familiar invoice.
Follow the dependencies.
Check the outcome.

What changes in Odoo 19 Accounting? For Community custom addons, review the account.account.deprecatedactive rename, removed account–journal restriction fields, the removed account.full.reconcile.exchange_move_id relation, and removed model-specific read_group overrides. The sections below connect these changes to account selection, invoice lines, reconciliation, and report tests.

Take one invoice your addon handles differently from standard Odoo. Follow it from account selection through reconciliation to the report your customer reads. That gives you a useful route through this upgrade: which choices must remain possible, which relationships your code follows, and which numbers must still mean the same thing.

The Odoo 18 → 19 changes below touch all three. An account-status field changes name and default; journal restriction fields disappear; several reporting overrides are removed. These deserve different kinds of review. A missing field gives you a reference to fix. A removed reporting override gives you a behavior to investigate.

This guide covers selected Community edition Accounting changes. Start with the part of the workflow your addon extends, then follow its dependencies. The scenarios are suggested tests, not claims about failures observed in Odoo 19.

Before the invoice: can the right account still be chosen?

There is a connection worth noticing before you edit anything: the Odoo 18 journal field account_control_ids contains a domain on deprecated. Both are affected by the upgrade.

On account.account, deprecated becomes active, with a different default:

Odoo 18

deprecated = fields.Boolean(default=False, tracking=True)

Odoo 19

active = fields.Boolean(default=True, tracking=True)

Source: Odoo's account definitions in 18.0 and 19.0.

Read each existing condition as a sentence before rewriting it. Does it mean “offer accounts that can be used,” “exclude accounts we have retired,” or something specific to your addon? The declarations show the rename and default change; they do not settle the migration of every custom domain or stored value.

Now look at the other half of account selection. Odoo 19 removes account.account.allowed_journal_ids and account.journal.account_control_ids. If your addon uses these fields to constrain posting, review those rules alongside the account-status change.

Read these changes togetherOne selector, two dependencies to revisit
Odoo 18 · journal restrictionaccount.journal
account_control_ids
Field removed in 19
its domain references
Odoo 18 · account statusaccount.account
deprecated
Renamed to active in 19

Review the permitted accounts and the availability condition in the same test. This diagram shows the Odoo 18 dependency, not a replacement design.

Inspect the Odoo 18 restriction fields

On account.account:

allowed_journal_ids = fields.Many2many(
        'account.journal',
        string="Allowed Journals",
        help="Define in which journals this account can be used. If empty, can be used in all journals.",
        check_company=True,
    )

On account.journal — notice the deprecated condition:

account_control_ids = fields.Many2many('account.account', 'journal_account_control_rel', 'journal_id', 'account_id', string='Allowed accounts',
        check_company=True,
        domain="[('deprecated', '=', False), ('account_type', '!=', 'off_balance')]")

These are the Odoo 18 definitions of fields removed in Odoo 19, not replacement code.

Source: Odoo 18 account restrictions and journal restrictions.

The useful test is a pair of attempts. Try an account–journal combination your customization should allow, then one it should reject. Repeat with an account that should be unavailable. For a multi-company addon, include an account from another company. Record both what the selector offers and what happens when the transaction is submitted.

You are ready to move on when those outcomes match the intended rules—not merely when the old field names have disappeared from your code.

On the invoice line: does the selector still express your rule?

An invoice-line customization may depend on a shortcut that no longer exists: account.move.line.product_uom_category_id is removed.

Inspect the removed unit-of-measure shortcut

Odoo 18 definition

product_uom_category_id = fields.Many2one(
        comodel_name='uom.category',
        related='product_id.uom_id.category_id',
    )

Source: Odoo 18 invoice-line fields.

The old related path explains what the field exposed. It is not evidence that following the same path directly is a valid Odoo 19 replacement. Check the target fields before making that substitution.

Put the form through the interaction your users perform: choose a product, inspect the permitted units, then change the product. If your addon supplies a domain or validation rule here, check both the choices displayed and the value accepted when the line is saved.

After payment: can your code still follow the reconciliation?

Move from the invoice to the journal entry your customization expects to find after reconciliation. In Odoo 18, account.full.reconcile.exchange_move_id provides a relation to account.move. That field is removed in Odoo 19.

Odoo 18 — removed relation

exchange_move_id = fields.Many2one('account.move', index="btree_not_null")

Source: Odoo 18 full reconciliation.

If your addon follows that relation to display a link, export an entry, or continue processing, use a reconciliation with an exchange difference as your test case. Write down the entry or information the feature needs to reach, then establish the appropriate Odoo 19 route to it.

The field's removal does not identify its replacement. Keep that as a specific investigation in your migration work, rather than assigning an unverified field name and moving on.

At the report: an open screen is only the first check

Three removed read_group overrides deserve attention if you customize grouped Accounting reports. Their Odoo 18 implementations do different jobs:

Report surface What its Odoo 18 override does A useful comparison on Odoo 19
Invoice analysis — account.invoice.report Calculates price_average as price_subtotal / quantity, or zero when quantity is zero. Use unequal quantities and compare the average; include a zero-quantity case.
Journal items — account.move.line Suppresses the summed amount_currency when results are not grouped by currency_id. Compare groupings with and without currency, using entries in different currencies.
Bank statement lines — account.bank.statement.line Supplies a latest running balance for certain groupings when show_running_balance_latest is set. Exercise the grouping and context used by your dashboard; compare the displayed running balance.

Source: the Odoo 18 overrides for invoice analysis, journal items, and bank statement lines.

These are removals of model-specific implementations. They do not establish that read_group is unavailable through inheritance, or that all three behaviors vanished from Odoo 19. Trace the target implementation and test what your extension receives.

A small fixture that makes an average worth checking

Use two invoice lines with unequal quantities: one unit at 100 and nine units at 10. For this illustrative test fixture, assume subtotals of 100 and 90 in the same currency and comparable units, with no discounts.

The Odoo 18 override's subtotal-over-quantity calculation gives 190 ÷ 10 = 19. Simply averaging the two unit prices gives (100 + 10) ÷ 2 = 55. This is not an observed Odoo 19 discrepancy; it shows why a test with equal quantities would be less revealing.

Illustrative test data · not an observed Odoo 19 resultTen units. Two prices. A revealing test.
1 × 100 = 100 subtotal9 × 10 = 90 subtotal
Subtotal ÷ quantity19 / unit

190 ÷ 10
The calculation in the Odoo 18 override.

Mean of the two unit prices55 / unit

(100 + 10) ÷ 2
A different calculation to distinguish in testing.

Same currency, comparable units, no discounts. Compare both returned report values and the displayed figure.

If your addon extends this report, preserve the fixture and compare the returned values and displayed result. A screenshot of a report opening cannot answer which average it contains.

Follow the sign as well as the amount

For tag-based custom calculations, account.account.tag.tax_negate is also removed. Its Odoo 18 help text describes negating the absolute balance associated with the tag during tax-report computation.

Inspect the removed tax-tag flag
tax_negate = fields.Boolean(string="Negate Tax Balance", help="Check this box to negate the absolute value of the balance of the lines associated with this tag in tax report computation.")

Source: Odoo 18 account tags.

If your code reads or sets the flag, include a transaction that exercises that branch of your calculation. Compare the resulting sign and amount against your intended behavior. Confirm the target reporting logic before choosing a replacement.

Two side trips, only if your addon uses them

Customer portal details. PortalAccount.details_form_validate and PortalAccount.extra_details_form_validate are removed from the Accounting controller. The former's Odoo 18 implementation checks VAT, name, and company-name edits using partner.can_edit_vat(), with an exception for partner creation; the latter checks additional required fields. Test an existing customer's edit, a new partner, and an empty custom required field. Follow the Odoo 19 request path before deciding where your validation belongs.

Peppol invoice navigation. account.journal.action_peppol_ready_moves is removed. Its Odoo 18 action opens invoices with a ready-state search filter. If your custom button calls it, verify the invoice list the button is meant to open as well as the action that replaces it.

Source: Odoo 18 Accounting portal and Peppol journal action.

Turn the review into an acceptance record

For each part your addon changes, keep one small record: scenario → intended result → Odoo 19 result → evidence. Link the evidence to a test, an output comparison, or a reproducible manual run. Use the scenarios below as prompts; completing the worksheet records your review, not automated validation.

Your review worksheet

Mark a row after recording its outcome and evidence, or recording why it does not apply.

0 of 7 recorded
Marks reset on reload. Keep your evidence separately. No tests run here.

Once the addon loads and these outcomes have evidence, your review tells the next developer something useful: which behavior was checked, with which data, and what remains unresolved.

Continue your upgrade planning

Use the Odoo upgrade guides hub to find other published version paths. If you are considering a later target, the Odoo 18 to 20 migration guide covers that larger jump and is explicitly a pre-release preview, not guidance for a stable Odoo 19 migration.

Related guides

Bring the review back to your own addon.
Start with a free Odoo 18 → 19 module scan.
Scan your module →