Stock Secondary Unit

This module lets a product be tracked, throughout the whole stock flow,
in a secondary unit of measure alongside its normal one - typically
a count (pieces, boxes) next to a product that is actually measured by
weight or volume. A classic case: fish sold by weight, but an order is
placed for “40 pieces” because each of a fixed number of guests gets
exactly one - the real weight per piece varies, but the piece count must
travel through the whole warehouse route exactly as ordered.
It builds on product_secondary_unit’s per-product configuration (the
conversion factor, and the dependency_type that decides how the two
quantities relate to each other) and extends it to stock.move and
stock.move.line, so a secondary quantity can be demanded on a
transfer, counted on its operations, and shown on delivery documents -
not just converted once on the product form.
Two independent secondary-unit settings live on the product:
- secondary_uom_ids / the sale-facing secondary unit (from
product_secondary_unit) - the one that ends up on stock.move /
stock.move.line records for an actual transfer.
- stock_secondary_uom_id (“Second unit for inventory”, added by this
module) - purely for the on-hand quantity display: pick one of the
product’s secondary units and a “Secondary Unit” smart button appears
on the product form next to “On Hand”, showing the current stock
quantity converted into it. This is read-only, informational, and
independent of whatever secondary unit an individual move ends up
using.
Why ``dependency_type`` matters here in particular: a stock move’s
product_uom_qty (demand) and a move line’s quantity (what was
actually done) are two different fields with different needs:
- dependent: the secondary quantity is always a factor conversion of
the primary one, in both directions - fine for informational units
where nothing needs to survive an inexact conversion.
- independent: the secondary quantity is entered on its own and
never recomputed from the primary one, and vice versa - used when the
two units don’t relate to each other at all.
- secondary_priority: the primary quantity is estimated from the
secondary one (like dependent), but the secondary one is never
recomputed back from the primary (like independent) - this is the
one built for the fish-by-weight/pieces-by-count case: pieces are the
real order, weight is only ever a convenience estimate derived from
them, and must never silently drift because of how much something
happened to weigh.
For independent and secondary_priority (together, the
“count-preserving” types), the module makes sure the secondary quantity
survives every stock operation exactly, instead of being silently
re-derived from the primary quantity and losing precision or meaning:
- Splitting a move for a backorder carries over exactly what’s left
of the secondary demand (e.g. 40 pieces demanded, 30 actually picked
-> the backorder is for exactly 10 pieces, never a weight-based
guess), and keeps the original move’s own secondary quantity in sync
with what was actually processed.
- Merging moves back together (e.g. two operations for the same
product ending up as one line, or a backorder’s pushed leg merging
back into an already-existing move) sums the secondary quantities the
same way core sums the primary one - core has no notion of this field
at all, so without this the count would silently be lost on every
merge.
- A negative move (e.g. a decreased sale order line) absorbed into -
or absorbing - an existing one rebalances the secondary quantity the
same way core rebalances the primary one for that specific code path,
which is separate from the ordinary merge above. When the negative
move is bigger than the pending demand, the remaining return gets a
positive secondary quantity, as core only flips the primary one.
- A pull-chain procurement (e.g. a 2-step reception, or an internal
make-to-order replenishment) carries the secondary unit forward into
the next move it creates, the same way the primary quantity already
does.
- ``secondary_uom_qty_done``, a new aggregate field on
stock.move, mirrors what core’s own quantity field does for
the primary unit: it sums what was actually counted across the move’s
lines, so other code needing “how many pieces were actually done”
doesn’t have to re-derive that sum ad hoc every time.
Table of contents
- On a product, go to Inventory tab > Secondary unit and choose which
of the product’s configured secondary units should be used for the
on-hand display (“Second unit for inventory”).
- A “Secondary Unit” smart button appears on the product form (next to
“On Hand”), showing the current stock quantity converted into that
unit. This is purely informational: it does not affect any transfer.
- On a transfer’s operations (the move lines list, or a move’s own
detail form when Detailed Operations is enabled), the Secondary
Qty and its unit are editable columns/fields (needs the “Units of
Measure” feature enabled, uom.group_uom).
- What happens when the secondary quantity is entered depends on the
product’s secondary unit dependency_type:
- dependent: entering either quantity recomputes the other
through the conversion factor.
- independent / secondary_priority: the secondary quantity is
entered on its own; for secondary_priority the primary quantity
is estimated from it, but never the other way round once a
secondary quantity has actually been set.
For a count-preserving secondary unit (independent or
secondary_priority), if what was actually counted is less than
demanded and a backorder gets created:
- The backorder is created for exactly what’s left of the secondary
demand (e.g. 40 pieces demanded, 30 counted -> the backorder demands
exactly 10 pieces) - never a proportional estimate derived from
whatever the primary quantity (e.g. weight) happened to be.
- The original, now-done transfer keeps its secondary quantity in sync
with what was actually counted, not the original demand.
If instead more was counted than demanded, the move’s own demand is left
untouched by this module alone; reconciling that difference is out of
scope for this module.
When the quantity of a confirmed sale order line is decreased, core
creates a move with a negative quantity and merges it into the pending
one. For a count-preserving secondary unit, the surviving move’s
secondary quantity reflects the net effect of the decrease, not just
whatever it already had before. If the decrease is bigger than the
pending demand, the remaining quantity becomes a return whose secondary
quantity is positive too.
When a transfer is part of a pull chain (e.g. the “Input -> Stock” leg
of a 2-step reception, or an internal make-to-order move), whatever
secondary unit and quantity is set on it is carried forward into the
next transfer created for that chain - the destination move doesn’t
start without one.
The delivery slip report shows the secondary quantity and unit next to
the primary one for every line, both for a picking still being processed
and once it is done.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
feedback.
Do not contact contributors directly about support or help with technical issues.
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the OCA/stock-logistics-warehouse project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.