# Odoo 19 to 20 Inventory migration: a guide for custom modules

> A broad but mostly mechanical Inventory release for custom modules, dominated by the removal of copy behaviour overrides, a handful of pricelist and product fields disappearing, and a batch of unit-of-measure fields being renamed to a consistent name.

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

One module disappears outright: `stock_picking_batch` is removed in Odoo 20 and batch and wave picking now live in `stock`. A manifest that depends on `stock_picking_batch` fails at install, so fix `depends` before anything else.

Start by treating this as two separate jobs. The first is field-level plumbing: several unit-of-measure fields have been renamed to a shorter, consistent `uom_id`, and a few fields on pricelists, products and settings are simply gone. Grep your module for those old names first, because any read, write, domain or view reference to a renamed or removed field will break loudly and is quick to fix. The second job is behavioural: Odoo has stripped out a number of `copy_data` overrides and a couple of routing/pricing helpers, so if your logic depended on the '(copy)' naming or on those helpers you'll need to adjust.

The areas most likely to bite are duplication/copy flows, pricelist item configuration, and anything touching product routes or supplier/lot units of measure.

## What needs the closest look

### Product document creation hook is gone

*Removed method — `ProductDocumentController.get_additional_create_params`*

This controller method existed purely as an extension point so other modules could inject extra values when a product document was created via the frontend. It has been removed, so if your module overrode it to add create parameters, that hook no longer fires — you'll need to move that logic into whatever create path now remains, or reintroduce the extension yourself.

Removed in Odoo 20 — it was defined as:

```python
def get_additional_create_params(self, **kwargs):
```

### Delivery carriers no longer auto-rename on duplicate

*Removed method — `delivery.carrier.copy_data`*

The carrier's copy override used to append '(copy)' to the name when you duplicated a record. It's been removed, so duplicated carriers keep the original name. If your module relied on that suffix to distinguish copies, add your own naming logic.

Removed in Odoo 20 — it was defined as:

```python
def copy_data(self, default=None):
```

### Product categories no longer auto-rename on duplicate

*Removed method — `product.category.copy_data`*

Same story as carriers: the category duplication no longer forces a '(copy)' suffix on the name. Anywhere you counted on duplicated categories being uniquely named, handle that explicitly.

Removed in Odoo 20 — it was defined as:

```python
def copy_data(self, default=None):
```

### Pricelists no longer auto-rename on duplicate

*Removed method — `product.pricelist.copy_data`*

The pricelist copy override that added '(copy)' to the name has been dropped. Duplicating a pricelist now preserves the name as-is, so revisit any code or test that assumed the renamed copy.

Removed in Odoo 20 — it was defined as:

```python
def copy_data(self, default=None):
```

### Pricelist rule 'applied on' selector removed

*Removed field — `product.pricelist.item.display_applied_on`*

This was the field driving whether a pricelist item applied to a product or a category. It's been removed, which means the underlying way rules are scoped has changed. If your module read or set `display_applied_on` on pricelist items, you must rework it against the current pricelist item structure.

Present in Odoo 19, **removed in 20**:

```python
display_applied_on = fields.Selection(
        selection=[
            ('1_product', "Product"),
            ('2_product_category', "Category"),
        ],
        default='1_product',
        required=True,
        help="Pricelist Item applicable on selected option")
```

### Percentage-based pricelist price removed

*Removed field — `product.pricelist.item.percent_price`*

The dedicated percentage price field on pricelist items is gone. Any custom rule creation or reporting that wrote to or read `percent_price` needs to be redirected to the remaining price computation fields on the item.

Present in Odoo 19, **removed in 20**:

```python
percent_price = fields.Float(
        string="Percentage Price",
        help="You can apply a mark-up by setting a negative discount.")
```

### The product 'total routes' extension point is gone

*Removed method — `product.product.get_total_routes`*

This method returned the full set of routes for a product and was designed for other modules to extend. With it removed, if your module overrode `get_total_routes` to add routes, that contribution point no longer exists — find where routes are now aggregated and hook in there instead.

Removed in Odoo 20 — it was defined as:

```python
def get_total_routes(self):
```

## Also worth checking

**Duplication behaviour across the board.** Beyond the carriers, categories and pricelists called out above, the `copy_data` overrides have also been removed from product tags, product templates and stock locations. The common thread is that Odoo has stopped forcing '(copy)' suffixes on duplicated records in Inventory. If any of your flows or tests depend on a duplicated record's name being changed automatically, add that behaviour yourself.

**Unit-of-measure field renames.** Several models have had their `product_uom_id` field renamed to the shorter `uom_id`: this affects product replenishment, supplier info, and stock lots. These are purely name changes, but they touch commonly-referenced records, so scan your domains, views and Python for the old `product_uom_id` on those models and update them.

**Stock move destination rename.** The move field that pointed at the final/forecasted location has been renamed from `location_final_id` to `forecasted_location_id`. If your module reasons about where a move ultimately lands, point at the new name.

**Fields that simply disappeared.** A few product and partner fields are gone entirely: the 'in selected section of order' flag and the 'exclude for' attribute-value field on products, the pickup-location flag on partners, and the reception-report setting group plus the bpost and DHL delivery module toggles in settings. If you referenced any of these in logic or views, remove or replace those references. Also note the `write` override on pricelist items has been removed, so any customisation that relied on chaining through it should be re-checked.

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

## Your migration checklist

1. Search your module for the renamed fields (`product_uom_id` on replenish, supplierinfo and stock.lot, and `location_final_id` on stock.move) and update every reference to the new names.
2. Search for the removed fields (`display_applied_on`, `percent_price`, `is_in_selected_section_of_order`, `exclude_for`, `is_pickup_location`, and the settings toggles) and remove or rework any code and views touching them.
3. Review every place you override or call the removed `copy_data` methods and the pricelist item `write`, and reimplement the naming or write logic your module actually needs.
4. Find any use of the removed extension points — `get_additional_create_params` and `get_total_routes` — and relocate that logic to the surviving code paths.
5. Rework pricelist item creation and pricing logic to fit the current item structure now that the applied-on selector and percentage price are gone.
6. Run your full test suite on Odoo 20 and exercise every affected workflow end to end before go-live.
