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

> Odoo 20 trims the product, pricelist and barcode plumbing that Inventory customisations usually hook into: controller hooks, duplication overrides, packaging and unit-of-measure fields, and several pricelist rule fields are gone or renamed, so code that extends products and pricing needs the most attention.

Start by grepping your addon for every Odoo field, method and route named in this guide. Most of what breaks here is removal rather than reshaping, so a failed lookup or a dead `super()` call is the usual symptom. Fix the Python model and controller references first, then your XML views and any JS that calls server routes. That order surfaces the hard errors before the silent ones. This guide tracks the Odoo 20 development branch, which is still pre-release, and will be refreshed when it reaches general availability.

The areas most likely to bite are pricelist rules, product packaging and units of measure, the barcode app's server endpoints, and record duplication. Also expect breakage in the small action methods behind product form buttons, such as updating on-hand quantity. If your module touches any of those, budget real review time. Everything else is mostly a quick rename pass.

## What needs the closest look

### Product document upload hook is gone

*Removed method — `ProductDocumentController.get_additional_create_params`*

The product document controller used to call `get_additional_create_params` so subclasses could inject extra values when a document was uploaded and created. That hook no longer exists, so an override in your module will simply never be called. Move that logic onto the document model's create flow or another supported extension point.

Removed in Odoo 20 — it was defined as:

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

### Batched barcode data route removed

*Removed method — `StockBarcodeController.get_specific_barcode_data_batch`*

`/stock_barcode/get_specific_barcode_data_batch` let the barcode client fetch records from several models in one request, doing one search per model plus related records such as units of measure. The route and its controller method are gone. Any custom JS, scanner integration or controller override that calls or extends it will fail and must be repointed at the barcode endpoints that remain.

Removed in Odoo 20 — it was defined as:

```python
@http.route('/stock_barcode/get_specific_barcode_data_batch', type='json', auth='user')
def get_specific_barcode_data_batch(self, kwargs):
```

### Carrier duplication no longer has its own naming override

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

`delivery.carrier` used to override `copy_data` to append "(copy)" to the name when a carrier was duplicated. That model-level override is removed. If your module extends `copy_data` on carriers, or tests or depends on the duplicated name, re-check the behaviour on Odoo 20 and adjust your override.

Removed in Odoo 20 — it was defined as:

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

### Pricelist duplication override removed

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

`product.pricelist` likewise dropped its `copy_data` override, which suffixed "(copy)" to the name unless a name was passed in. Custom duplication logic that chained onto it, or that relied on that naming, should be retested and simplified accordingly.

Removed in Odoo 20 — it was defined as:

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

### Pricelist rule 'applied on' toggle removed

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

`display_applied_on` was the simplified Product/Category selector on pricelist items that drove which target a rule applied to. It no longer exists. Remove it from your inherited views, domains, defaults and import data, and set rule scope through the fields that remain on `product.pricelist.item`.

Present in Odoo 18, **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 price field on pricelist rules removed

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

`percent_price` stored the percentage discount (or mark-up, when negative) for percentage-based pricelist rules. Any code, view, data file or report that reads or writes it will break. Rework those spots against the pricing fields still available on Odoo 20 pricelist items, and check that migrated rules still compute the prices you expect.

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

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

### 'Update quantity on hand' action removed from products

*Removed method — `product.product.action_update_quantity_on_hand`*

`action_update_quantity_on_hand` on `product.product` was the button action that opened the on-hand quantity editor, delegating to the template with the variant preselected. It is gone, and so is the template version. Remove buttons, server actions or overrides in your module that point at it, and route users to the stock quantity flow Odoo 20 provides instead.

Removed in Odoo 20 — it was defined as:

```python
def action_update_quantity_on_hand(self):
```

## Also worth checking

**Pricelists and pricing rules.** Beyond the removed fields above, the unit field on `product.pricelist.item` is renamed from `product_uom` to `product_uom_name`, so update related fields, view references and reports that display a rule's unit. The `open_pricelist_rules` action is removed from both `product.product` and `product.template`. Any smart button or menu in your module that called it needs a new target.

**Packaging and units of measure.** `packaging_ids` is removed from both `product.product` and `product.template`. Custom code that walks a product's packagings, or views that embed that one2many, must be reworked. On the replenishment wizard (`product.replenish`), `product_uom_id` becomes `uom_id` and `product_uom_category_id` is dropped. Fix any wizard inheritance or default context that sets them. Vendor pricelines (`product.supplierinfo`) also lose `product_uom`, so purchase-side customisations that read the supplier's unit need revisiting.

**Duplication and product setup.** `product.tag` no longer overrides `copy_data`, matching the carrier and pricelist changes. If you overrode duplication on tags, retest it. On variants, `exclude_for` is removed from `product.template.attribute.value`, which affects any code that inspected attribute exclusions. `product.product` also drops its `init` method, which only matters if your module extended it.

**Company and settings.** The `stock_move_sms_validation` flag on `res.company` is removed, so drop it from settings views and any logic that sent SMS on move validation. In `res.config.settings`, `property_stock_journal` is renamed to `account_interco_clearing_journal_id`. Update settings view inheritance and any code reading it.

Beyond these, 1204 smaller internal changes exist. They are only relevant if you override Odoo's private helpers.

## Your migration checklist

1. Grep your addon (Python, XML, JS, CSV/XML data) for every field, method and route named above, and list each hit.
2. Fix pricelist rule references first: `display_applied_on`, `percent_price`, `product_uom` → `product_uom_name`, and `open_pricelist_rules`. Then re-verify computed prices on a copy of real data.
3. Rework packaging and unit-of-measure code: `packaging_ids`, `product.replenish` (`uom_id`), and `product.supplierinfo` units.
4. Replace removed controller hooks and routes (`get_additional_create_params`, the batched barcode endpoint) and update any JS or external scanner clients that call them.
5. Remove or repoint buttons and actions using `action_update_quantity_on_hand`, and retest duplication of carriers, pricelists and tags.
6. Update settings and company views for the removed SMS flag and the renamed journal setting, then run your module's tests on the Odoo 20 branch.
