Counted units, weight by pieces and base unit replacement
Sell in packs, boxes or pieces over a product measured in kilos or litres, record the pieces on variable-weight lines, replace a product by one with a different base unit with a preview and an irreversible execution, and read the real unit in FacturaE and UBL. Two new operations, two MCP tools, the product.replaced event and new fields on products and document lines.
15 October 2026 — A product measured in kilos, litres or metres can now be sold in counted units such as packs, boxes or pieces, and a product that already has history can change its base unit by being replaced by a new one. Everything is additive: no existing field changes meaning.
Counted units
There are 22 unit codes in total: 9 measure units and 13 counted units.
| Kind | Units (UN/ECE code) | Where it can be used |
|---|---|---|
| Measure | unit C62, kilogram KGM, gram GRM, litre LTR, millilitre MLT, metre MTR, square metre MTK, hour HUR, day DAY | Base unit, presentations, supplier offers, price lists and document lines |
| Counted | piece H87, pack XPK, box XBX, tray XDS, bag XBG, bottle XBO, demijohn XDJ, can XCA, tub XTB, jar XCI, drum XJY, envelope XEN, case XCS | Presentations, supplier offers, price lists and document lines |
base_unit only accepts the 9 measure units; the counted units are commercial units.
- Presentations and supplier offers. A fixed presentation in a counted unit can rest
on a measured base if it declares its equivalence (
conversion_factorornominal_base_quantity): «1 pack = 0.5 kg». A measure unit must belong to the same family as the base. A presentation or an offer whose unit cannot be expressed over the base unit of the product is rejected with422 unit_not_expressible. - Weight by pieces. A variable-weight presentation whose unit is a counted unit
accepts the pieces the customer asked for in
counted_quantity. The line is still charged, and the stock still discounted, byquantity, the real weight in the base unit; the pieces are informational. - Documents. Invoice, quote, pro-forma, delivery note and recurring invoice lines
carry
counted_quantityandcounted_unit, bothnullwhen the line has no counted pieces. The editor, the PDF, the ticket and the public link show «4 packs = 2 kg» or «3 pieces · 1.520 kg × 4.55 €/kg». - FacturaE and UBL. FacturaE declares the real
UnitOfMeasureof each line and UBL sends its real UN/ECEunitCode.
Base unit replacement
The base unit of a product with history (stock, movements, presentations, supplier offers
or variants with stock) is not edited in place. The product now reports it in
base_unit_change, and the change is made by replacing the product by a new one.
- Preview a base unit replacement
computes, without writing anything, the plan and an
impact_tokenthat confirms that exact plan. You send the new base unit (target_base_unit, one of the 9 measure units), theequivalencebetween both units, an optionalbase_priceand onekeeporretiredecision per presentation. - Replace a product by changing its base unit
executes it and answers
201with the new product. It is irreversible, soIdempotency-Keyis mandatory. The stock, the presentations you keep, the variants, the supplier offers, the price-list entries and the pack components move to the new product, converted to its unit. The previous product is deactivated, keeps its whole history and can never be reactivated. It does not consume plan quota. - If the conversion loses precision or has no equivalence,
base_pricemust be confirmed (422 base_unit_replacement_price_confirmation_required). If the catalog, the price lists or the packs change after the preview, the confirmation is rejected with422 base_unit_replacement_preview_staleand a new preview is requested. - The other rejections of the replacement are
422 business_rule_violationwith subcodeproduct_already_replaced,base_unit_replacement_conversion_unavailableorbase_unit_replacement_blocked_by_packs, and422 parameter_invalid_valuewith subcodebase_unit_replacement_same_unit(the target is already the base unit of the product),base_unit_replacement_equivalence_invalidorbase_unit_replacement_decision_invalid. - The
equivalenceis the number of units of the current base that equal one unit of the new one. It is required between a counted unit and a measure unit, in either direction; within the same measure family it is calculated by itself, and between two different measure families there is none. - A product that takes part in a replacement, as the previous product or as the new one,
cannot be deleted: Delete a product
and Delete multiple products in bulk
answer
422with subcodeproduct_in_base_unit_replacement, and in the bulk operation the product is listed infailed[]with that reason. The MCP toolsdelete_productandbulk_delete_productsapply the same rule. - Editing
base_uniton a product whosebase_unit_change.modeisreplacementanswers422 business_rule_violationwith subcodeproduct_base_unit_locked.
Fields, events and MCP
- Product.
base_unit_change(modeand the quantifiedblockers),replacesandreplaced_by(id,name,base_unit,replaced_at,equivalenceandstock_transferred). They are returned on single-product reads and omitted in lists. - Event.
product.replacedis published with the new product as its object. See Events. - Stock movements. The reasons
base_unit_replacement_outandbase_unit_replacement_inand the sourcebase_unit_replacementrecord the transfer between both products. - Price lists. The withdrawal reason
product_replacedidentifies the entries of the replaced product. - MCP. Two new tools,
preview_product_base_unit_replacementandreplace_product_base_unit, both with theproducts:writescope; the second is irreversible and acceptsidempotency_key. See the tool catalog.
Pack components do not accept pieces: components.*.counted_quantity answers 422 when
it carries a value, because pieces belong to the lines of sales documents.
The operations that read or write products, presentations, supplier offers, price lists and sales documents keep their scopes, and their responses and bodies gain the fields and unit codes described above.
New endpoints2
| Endpoint | Description |
|---|---|
POST/v1/products/{product}/base_unit_replacement/preview | Preview a base unit replacement |
POST/v1/products/{product}/base_unit_replacement | Replace a product by changing its base unit |