v2.24.0
v2.24.0: a shipped order can be invoiced, a pick is not yet a shipment
A shipped wholesale order can open a draft customer invoice from its header. An ecommerce pick is recorded as picked and not yet shipped, and on-hand stock waits for the shipment.
Seventy-eight commits since v2.23.0. Two additive database changes. The tag is cut after this page, not by it.
Shipped
A shipped wholesale sales order can create a draft customer invoice from the order header. The button reads Create Invoice while shipped quantity is still unbilled, and View Invoice once that quantity is billed. The draft opens under Customer Invoices. A marketplace order does not offer the button. Each invoice line remembers which sales order line it billed, how many base pieces that covers, and the price unit. Invoices that already existed are left untouched.
An ecommerce pick writes a ledger row labeled Picked, not shipped. On-hand stock does not drop at pick. When the order ships, that row is consumed and the shipment is written in the same step that moves the stock. A wholesale pick still writes no movement until the order ships.
A new company starts with base-quantity writing and reading turned on. Companies that already exist are unchanged.
Production will not start unless role checks are turned on. That setting is already on in production.
Migrations: two, both additive. Invoice lines gain a nullable link to the sales order line, the billed base quantity, and the price unit, plus a foreign key that refuses to delete a sales order line an invoice still points at. The ledger gains a status for goods that have been picked and have not shipped. No rows are rewritten.
Rollback: return to v2.23.0. No switches to flip. The previous build still opens an invoice from the columns it already knew, and it does not read the new line link. Do not roll back after an ecommerce pick has written a picked-not-shipped row: the previous build does not know that status, and the movement ledger can fail to load. If none has been written, the extra status is unused.