Skip to main content
iVentureTeam

stock_move_one2many

The operations list on a transfer. Its distinguishing feature is a button that adds every line in a package at once, and it saves the whole picking before it can even offer that.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated September 7, 20266 min read
Technical namestock_move_one2many
Field typesone2many, many2many
Viewsform, with an embedded list
Modulestock, the Inventory app
Used in core1 occurrence, the operations tab of the transfer form in stock
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. It depends on picking-specific server methods and context keys
Alternativessml_x2_many, one2many, package_m2m, move_product_label_field

What the picking operations list does

A transfer's operations list is where a warehouse says what is actually moving. Normally that is line by line: a product, a quantity, a destination. Sometimes it is not: an entire package has to move as a unit, and typing out its contents defeats the point of having packaged it.

This widget is the operations list with that case built in. It renders the moves with the same product name and description behavior sales and purchase lines use, and adds an action that opens a list of packages and, on selection, asks the server to add every line inside them.

The action has a precondition that is easy to miss. Packages are filtered by the transfer's source location, and the server needs a real picking to attach the lines to, so the widget saves the picking before opening the dialog. If that save fails, the dialog does not open at all.

What this means for your team

Moving stock by package rather than by line is the difference between a two minute transfer and a twenty minute one in any warehouse that packs goods. It also reduces transcription errors, because nobody retypes contents that the system already knows.

The conditions on when the action appears are worth knowing, because they are the usual reason someone reports the button missing. Package tracking has to be switched on, the transfer must still be editable, and it cannot be an incoming transfer: goods arriving are not yet in a package the system knows about.

The forced save is the other thing to brief people on. Adding packages commits the transfer, so a draft you were still assembling becomes a saved record at that moment. On a picking that is the right trade, but it surprises people who treat the form as scratch space.

Supported options in Odoo 19

The widget declares no options of its own, and neither does the x2many descriptor it spreads: the options dictionary is forwarded to the embedded list as CRUD settings. What governs its behavior are context keys and a user group, listed below and verified against the Odoo 19.0 source.

OptionTypeWhat it does
picking_statecontext keyRead from the embedded list's context. The package action is hidden when the transfer is done or canceled.(since Odoo 19.0)
picking_type_codecontext keyRead from the embedded list's context. The package action is hidden on incoming transfers.(since Odoo 19.0)
editablestring (list attribute)Set on the embedded list. Governs inline editing of the operations themselves.

The gating is read from context, not from options. The widget checks the transfer's state and its operation type code from the list's context, plus the package tracking group on the user. A custom view that does not pass those context keys will show the action in situations where core would hide it.

Working examples

The core usage, trimmed

<field name="move_ids_without_package"
       widget="stock_move_one2many">
  <list editable="bottom">
    <field name="product_id" widget="move_product_label_field"/>
    <field name="package_ids" widget="package_m2m" readonly="1"/>
  </list>
</field>

The widget is designed alongside its companion cell widgets, which is why they appear together in the core view.

The context it reads

# from the embedded list's context
picking_state       # hides the action on done or canceled
picking_type_code   # hides the action on incoming transfers

Both are read from the list's context rather than from the record, so a custom subview has to supply them.

What the action calls

# server side, with the picking id and the chosen package ids
stock.picking.action_add_entire_packs(...)
# then a client-side soft reload

The lines are created by the server, not assembled in the browser, which is why a reload follows.

Why the picking saves before the dialog opens

The sequence behind the package action is worth following, because every step exists for a reason.

First it saves. The comment in the source says the picking may not exist yet, and that an outdated location would produce the wrong package list. So the save both creates the record and refreshes the location the filter is built from. If the save is rejected, the method returns and no dialog opens.

Then it builds a domain restricted to packages under the source location, and opens a multi-select dialog on the package model with creation disabled and a named simplified list view. Choosing nothing closes without effect.

On selection it calls a server method with the picking id and the chosen package ids, and if the server reports success it triggers a soft reload of the whole action rather than inserting rows locally. That is heavier than a local update but correct: adding a package can change quantities, reservations and availability across the transfer, none of which the browser could recompute.

The availability of the button is computed from three things: the package tracking group, resolved once when the component starts, and two context keys naming the transfer's state and operation type. Because the group check is asynchronous and happens on startup, the button can appear a moment after the list renders.

The Odoo 19 rewrite is substantial. The earlier version extended an auto-sizing column renderer and injected an extra details column into the list; the current one uses the plain list renderer and instead shares the product name and description behavior with sales and purchase lines through a patched mixin. Anything customizing the old column arrangement has to be redone.

Version compatibility

VersionStatusNotes
Odoo 20.0Partial / changedNot released. One template constant renamed; behavior unchanged.
Odoo 19.0VerifiedVerified against the shipped source. Rewritten, with the package action introduced.
Odoo 18.0Partial / changedSame widget name, different implementation: an auto-sizing renderer with an injected details column, and no package action.
Odoo 17.0Partial / changedSame as Odoo 18 in shape.
Odoo 16.0Partial / changedEarliest version of the widget, again without the package action.

Upgrade note. The widget name is stable back to Odoo 16, but the implementation was rewritten in Odoo 19: a different base renderer, the removal of an injected details column, and the arrival of the package action. Any JavaScript customization of this renderer written for Odoo 18 or earlier will not apply. View XML is unaffected.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and this may still change; we re-verify the page after release.

One rename, no behavior change. The template constant naming the rows template is renamed on the development branch. The package action, its preconditions, the server call and the soft reload are identical.

Common problems and fixes

SymptomCause and fix
The add package action is missingPackage tracking is off, the transfer is done or canceled, or it is an incoming transfer. Enable package tracking, and check the transfer's state and operation type.
The transfer saved itself when I clicked the actionIntended. The picking must exist and its location must be current before packages can be filtered. Expected. Assemble the transfer knowing that the action commits it.
No packages are offeredThere are no packages under the transfer's source location. Check the source location, and that the packages are stored beneath it.
The whole page reloads after addingIntended. The lines are created server side and can change availability across the transfer. Nothing to fix; a local insert would show stale quantities.
The action appears when it should notA custom subview does not pass the state or operation type context keys. Supply both keys in the field's context, as the core view does.
A renderer customization stopped working on Odoo 19The widget was rewritten onto a different base renderer. Redo the customization against the current implementation.

Picking operations list vs the alternatives

WidgetBest forKey difference
stock_move_one2manyTransfer operations, especially where whole packages move as a unitAdds a package selection that saves the picking first and lets the server create the lines
sml_x2_manyThe detailed operations under a movePicks from real quants rather than adding package contents
one2manyAn ordinary child listNo package action and no picking context awareness
package_m2mShowing which packages a move touchesA read-only cell inside this list, not the list itself
move_product_label_fieldThe product cell inside this listA cell widget handling the product and its description

Use a plain child list wherever the lines are not stock moves, since everything characteristic here depends on picking context and server methods. For the detailed operations underneath a move, the move line widget is the one that lets you pick from real stock, and the two are designed to be used together on a transfer.

Frequently asked questions

Why does the transfer save when I add packages?+
The server needs a real picking to attach the new lines to, and the package list is filtered by the transfer's source location, which has to be current. The source comment says both outright.
Why is the add package action missing?+
Three conditions gate it: package tracking must be enabled for the user, the transfer must not be done or canceled, and it must not be an incoming transfer.
Why does the page reload after adding a package?+
The lines are created by a server method, and adding a package can change quantities and availability across the transfer. A soft reload is how the form picks all of that up.
Does it support any options?+
None of its own. It spreads the x2many descriptor, which declares no options and forwards the dictionary to the embedded list. Its behavior is driven by context keys instead.
Is it the same widget as in Odoo 18?+
The name is, the implementation is not. Odoo 19 rewrote it onto a different renderer, dropped an injected details column and added the package action.

Warehouse screens that match how your team moves stock

Packages, lots and multi-step routes each change what a transfer screen should show. We configure and extend Odoo Inventory around your actual operations, on versions 16 through 19.

Book a free consultation

How this page was produced

The package action, the forced save with its source comment, the domain, the server call, the soft reload and the three gating conditions were read from stock_move_one2many.js on the Odoo 19.0 branch, with the usage taken from stock/views/stock_picking_views.xml. The Odoo 19 rewrite was established by comparing the file across the 16.0, 17.0 and 18.0 branches, and the Odoo 20 note from the public development branch. Spotted an error? Tell us and we will correct the page.