Odoo 19 → 20

Odoo 19 to 20 Sales migration: a guide for custom modules

A release that reshapes the invoicing and re-invoicing plumbing around Sales, retires several sales-team KPI and template shortcuts, and reworks a number of order-level actions, so most custom-module breakage will cluster around billing logic and stored computed fields rather than the UI.

1,252 verified changesSalesverified on Odoo 20
1,252
verified changes
83
models affected
25
modules affected

Change breakdown

Changed method377
Added method196
Changed view / record174
Added field94
Added view / record85
Removed method73
Changed field66
Removed view / record50

Removed and renamed items are what a custom module feels first — they break references at load time. Added and internally-changed items rarely touch existing code.

Where the changes land

sale564
sale_timesheet136
sale_management103
sale_project98
sale_stock80
sale_loyalty50
sale_pdf_quote_builder40
sale_gelato31

Changes reach 25 modules in all — but they cluster. sale carries the most (564 changes), so it's the first place to look if you extend it.

A release that reshapes the invoicing and re-invoicing plumbing around Sales, retires several sales-team KPI and template shortcuts, and reworks a number of order-level actions, so most custom-module breakage will cluster around billing logic and stored computed fields rather than the UI.

Odoo 20 was released on 24 September 2026. This guide covers Odoo 19 → 20 Sales, Community Edition, built from the released version; Enterprise modules are not included in these counts. It counts 1,252 verified changes across 25 core modules.

Start by mapping where your module touches the money side of Sales: anything that reads timesheet-to-invoice links, sales-team revenue targets, re-invoicing policy on products, or the order-level 'update prices/taxes' buttons is the most likely to break. Once you know those touchpoints, work outward to the smaller field renames and helper removals, which are usually a quick find-and-replace.

A fair number of the changes are in invoicing and computed-value territory, so give your compute methods and any stored fields that depended on the removed ones a close read before trusting a silent upgrade.

What needs the closest look

Timesheet lines no longer carry their invoice link

Removed field — account.analytic.line.timesheet_invoice_id

The direct pointer from an analytic/timesheet line back to the invoice it generated has been dropped. If your module reads this to trace which invoice billed a timesheet, or filters/groups on it, you'll need to derive that relationship another way through the sale order line or move it depended on.

Present in Odoo 19, removed in 20:

timesheet_invoice_id = fields.Many2one('account.move', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet", index='btree_not_null')

Billable-type classification on timesheets is gone

Removed field — account.analytic.line.timesheet_invoice_type

The stored selection that labelled a timesheet line by its billing nature has been removed along with its compute. Any reports, filters or automations keying off this 'billable type' will need to be reworked, since the value is no longer maintained on the line.

Present in Odoo 19, removed in 20:

timesheet_invoice_type = fields.Selection(TIMESHEET_INVOICE_TYPES, string="Billable Type",
            compute='_compute_timesheet_invoice_type', compute_sudo=True, store=True, readonly=True)

Sales-team 'invoiced this month' figure removed

Removed field — crm.team.invoiced

The computed monthly invoiced revenue used on the sales team kanban is gone. If your module surfaced this KPI or built dashboards on it, recompute it yourself from the invoices rather than reading the field.

Present in Odoo 19, removed in 20:

invoiced = fields.Float(
        compute='_compute_invoiced',
        string='Invoiced This Month', readonly=True,
        help="Invoice revenue for the current month. This is the amount the sales "
                "channel has invoiced this month. It is used to compute the progression ratio "
                "of the current and target revenue on the kanban view.")

Sales-team invoicing target removed

Removed field — crm.team.invoiced_target

The manual monthly revenue target that paired with the invoiced figure has also been dropped. Custom views or progression-ratio logic that referenced the target will need to store and read that goal from your own field.

Present in Odoo 19, removed in 20:

invoiced_target = fields.Float(
        string='Invoicing Target',
        help="Revenue Target for the current month (untaxed total of paid invoices).")

The 'product is in this sale order' catalog flag is gone

Removed field — product.product.product_catalog_product_is_in_sale_order

The helper boolean (with its search method) that told the product catalog whether a product already appeared on the current order has been removed. If you reused it to drive catalog UI or domains, replace it with your own check against the order's lines.

Present in Odoo 19, removed in 20:

product_catalog_product_is_in_sale_order = fields.Boolean(
        compute='_compute_product_is_in_sale_order',
        search='_search_product_is_in_sale_order',
    )

Re-invoice cost policy moved off this field

Removed field — product.template.expense_policy

The 'Re-Invoice Costs' selection on the product template has been removed as part of reshaping how expense/cost re-invoicing is configured. Anywhere your module reads or writes this policy needs revisiting against the new re-invoicing setup rather than this field.

Present in Odoo 19, removed in 20:

expense_policy = fields.Selection(
        selection=[
            ('no', "No"),
            ('cost', "At cost"),
            ('sales_price', "Sales price"),
        ],
        string="Re-Invoice Costs", default='no',
        compute='_compute_expense_policy', store=True, readonly=False,
        help="Validated expenses, vendor bills, or stock pickings (set up to track costs) can be invoiced to the customer at either cost or sales price.")

Auto-purchase-on-sale service flag removed

Removed field — product.template.service_to_purchase

The company-dependent 'Subcontract Service' boolean that triggered an automatic RfQ when the product was sold is gone. If your logic branched on this to create purchase requests, you'll need to hook into the current subcontracting/purchase flow instead of this flag.

Present in Odoo 19, removed in 20:

service_to_purchase = fields.Boolean(
        "Subcontract Service", company_dependent=True, copy=False,
        help="If ticked, each time you sell this product through a SO, a RfQ is automatically created to buy the product. Tip: don't forget to set a vendor on the product.")

Also worth checking

Invoicing and re-invoicing configuration. Several settings and shortcuts around billing defaults have been retired. The company-level default sale order template and its settings counterpart, the automatic-invoice toggle, and the default invoice policy have all been removed from the configuration models, so any module that read these to seed new orders or invoices needs to source those defaults elsewhere. In the same spirit, the product template's re-invoicing visibility helper has been renamed from the old expense-policy name to a re-invoice-policy name — update any view attribute or override that referenced the old one. Sales order templates also lost their custom create override, so if you relied on template-creation side effects, verify your own create logic still fires.

Sales-team revenue tracking. Alongside the removed invoiced and target fields, the method that updated the invoicing target on the sales team is gone; drop any calls to it and manage targets through your own field if you still need them.

Order and line actions. The order-level actions to recompute prices and to recompute taxes have both been removed, so buttons or server actions in your module that called them will error — rebuild those recalculations against the current order recompute flow. On the line side, the undiscounted amount field, the translated product name field, and the helper that walked up to a line's parent section have all disappeared; anywhere you displayed the pre-discount total, showed a translated name, or grouped lines under their section header will need a new approach.

Project and partner touchpoints. The project method that assembled sale-item data for the project view has been removed, so custom project-to-sales widgets should be re-examined. The partner-side helper that decided whether the VAT number could still be edited is also gone; if you gated VAT editing on it, replace that guard with your own check.

Beyond these, 480 smaller internal changes exist — only relevant if you override Odoo's private helpers or compute methods.

Your migration checklist

  1. Grep your codebase for the removed billing fields — the timesheet invoice link and billable type, the sales-team invoiced and target fields, the product re-invoice and subcontract-service flags — and list every read, write, view and domain that touches them.
  2. Fix the configuration defaults first: replace uses of the removed company sale order template, automatic-invoice toggle and default invoice policy with the new re-invoicing setup, and rename the product template's re-invoice-policy visibility reference.
  3. Rework any code calling the removed order actions (update prices, update taxes) and the removed sales-team target update method against the current recompute flow.
  4. Repair order-line usages of the undiscounted amount, translated product name and parent-section helper, providing your own computations where needed.
  5. Revisit project and partner integrations that used the removed sale-items data method and the VAT-edit guard.
  6. Run your full test suite on Odoo 20 and exercise every affected workflow end to end before go-live.

Related guides

Is your module ready for Odoo 20?
Scan it free — every breaking change above, found in your actual code, with AI fixes verified on a real Odoo server.
Scan your module →