v2.23.0
v2.23.0: a dozen has a price, a reverse leaves nothing behind, one ship is one row
Seventy-four commits. A dozen is sold as a dozen, with the price in the same unit. An opening-inventory reverse moves the ledger as much as the stock. One shipped unit writes one SHIP row, in the same transaction that moves it.
Seventy-four commits since v2.22.0. One additive migration. The tag is cut after this page, not by it.
Shipped
UOM-SELL-01 (#411): a dozen is sold as a dozen. DOZEN is a ProductUomCode and the factor is ProductUomConversion.qtyInBase. Sales and purchase lines store priceUomCode, priceAmount and priceQty; the line amount is priceQty times priceAmount when that code is set. Retail Price, Wholesale Price and Standard Cost can be tagged with an active conversion. In New Sales Order the UOM field offers DOZEN when the product has the conversion.
UOM-SELL-01-HF1 (#415): a saved DOZEN line can be confirmed. Ship Confirm breaks the minimum number of stock cases and writes a same-dimension TRANSFER pair in the same transaction, so bucket replay sees the opened case.
LEDGER-GUARD-07 (#410): one shipped unit, one SHIP row. The SHIP is written in the same transaction that moves the stock. A warehouse pick that has not shipped writes no movement. In-transit and legacy picks that already wrote a physical SHIP do not get a second one. The historical rows are classified (PICK_SHIP_BEFORE_GUARD_07), not rewritten: no compensating script, no deletes.
ONBOARD-SCALE-01-HF1 (#413): an opening-inventory reverse moves the ledger as much as the stock. A TRANSFER pair, destination minus q and ROD plus q, in the same transaction as the stock move. Nothing is left behind.
RELOCATE-01 (#412): moving a pallet is a normal operation. A confirmed PUTAWAY, PICK or REPLENISHMENT line no longer blocks relocate. The task header palletId still blocks. An opening LPN can be moved.
#417: a CASE or MASTER_CASE transfer line stores the loose remainder and the case bucket, and baseQtyPieces stays the single base. New Transfer compares the requested base with the pallet base, so a partial take from a case-only pallet is no longer rejected for having zero loose pieces.
DOCS-REHEARSAL-01 (#409): the 2026-09-30 onboarding rehearsal is closed. The split-01 quarantine behind #402 is extended to 2026-10-10.
#414: BASE-01-GAPS Phase 1 audit. No product code.
Known and open
The nine products from v2.21.0: the path that created the gaps is closed by #410, and the historical rows are classified, not rewritten. Reconciliation still reports them.
The split-01 check runs, reports, and does not block until 2026-10-10 (#402).
Migrations: one, additive (20261002120000_uom_sell_01_dozen_and_price_uom): DOZEN as a ProductUomCode value; orderedDozenQty on sales lines; priceUomCode, priceAmount and priceQty on sales and purchase lines; priceWholesaleUomCode, priceRetailUomCode and unitCostUomCode on products. No DROP, no DELETE, no UPDATE, no RENAME, no backfill of money.
Rollback: return to v2.22.0. No flags to switch. The previous build ignores the new columns. Rolling back lets a warehouse pick write SHIP before the stock moves again. Do not roll back after a DOZEN conversion or a DOZEN price tag has been saved: v2.22.0 does not know that enum value, and that product can fail to load.