Research asset for Establish price and savings semantics, part of Map: Restore POS price-reduction visibility in receipts.
Observed: For WCPOS 1.8.11 through 1.9.x, _woocommerce_pos_data.price and _woocommerce_pos_data.regular_price are the only stable order-line snapshots that preserve the pre-coupon selling price and the recorded regular/reference price. The current catalog product must not be used as a historical fallback.
Observed: Since 1.9.0, the stored WooCommerce line subtotal equals the WCPOS selling price. Consequently, subtotal - total and WooCommerce discount_total represent coupon discount only. This is intentional WooCommerce sale-price parity, not missing order data. WCPOS v1.9 documentation, server PR with design intent, current receipt builder.
Observed: The metadata cannot distinguish a catalog sale from a cashier price override. Both produce the same observable pair, such as regular_price=20, price=16. If a cashier reduces an already-on-sale item from 16 to 14, the final pair is regular_price=20, price=14; the original 16 sale price is not retained. Therefore the contract may truthfully expose recorded regular/reference price, pre-coupon selling price, and pre-coupon savings, but not a universal cashier-only discount.
Decision input: The next ticket must choose whether combined catalog-sale and cashier savings is the desired product meaning. A cashier-only figure would require new metadata from the client and would not be recoverable for existing orders.
| Concept | Authoritative source | Confidence and limitations |
|---|---|---|
| Recorded regular/reference unit price | Numeric _woocommerce_pos_data.regular_price |
Observed. Snapshotted from the product/variation when added, but the edit form also lets the cashier change it. It is not an immutable catalog price. |
| Pre-coupon selling unit price | Numeric _woocommerce_pos_data.price |
Observed. Starts as WooCommerce's current product price and is overwritten by a till price edit. WooCommerce documents price as the current effective product price and regular_price separately: REST product properties. |
| Pre-coupon price reduction | max(regular_price - price, 0) per unit, multiplied by positive quantity |
Inferred from observed producer and coupon-sale logic. The same comparison is used to treat WCPOS-reduced items as on sale. It includes catalog sale savings and till overrides together. |
| Coupon discount after 1.9.0 | Stored Woo line subtotal - total, with the corresponding subtotal/total tax difference; order totals use WooCommerce discount totals |
Observed. The 1.9 client sends subtotal = price × qty; server tests assert coupons use that base. |
| Coupon discount before 1.9.0 | Metadata selling-price line amount minus final Woo line total | Inferred and reconstructible when metadata and tax conversion are valid. The old Woo subtotal - total conflates regular-to-selling savings and coupons. |
| Final charged line amount | Woo line total and total_tax |
Observed. Already exposed by Receipt Data. |
| Catalog-sale component vs cashier-override component | No authoritative stored source | Not recoverable. sale_price is not snapshotted, and price is overwritten on edit. |
| Historical fallback without valid metadata | None | Not recoverable. Current product prices may have changed and must not rewrite history. |
Client evidence: the product/variation converter snapshots price and regular_price into _woocommerce_pos_data; line edits write both values back; splitting clones all metadata except the UUID. See metadata producer, line editing and splitting, and edit form.
Observed: The client calculated subtotal = regular_price × quantity and total = price × quantity before coupons. Thus existing Receipt Data discounts represented regular-to-selling savings plus any coupon allocation.
Example: regular 20, selling 16, then 10% coupon:
- Woo subtotal: 20
- Woo final total: 14.40
- Existing
discounts: 5.60, conflating 4.00 pre-coupon savings and 1.60 coupon savings - Metadata reconstruction: pre-coupon savings 4.00; coupon savings 1.60
Observed: The client change made both the pre-coupon subtotal and total use price × quantity; coupon application then reduces total only. The companion server change removed the fragile subtotal filters. Client parity commit, server PR.
The same example now stores:
- Woo subtotal: 16
- Woo final total: 14.40
- Existing
discounts: 1.60, coupon-only - Metadata reconstruction: pre-coupon savings 4.00; coupon savings 1.60
Conclusion: New fields derived from metadata can be stable across the version boundary; existing subtotal-derived fields cannot be redefined without changing their meaning for old orders.
Observed: Metadata prices remain in the store's configured entered-price basis. Woo line subtotal/total values are stored excluding tax, with tax in separate fields. The order records whether prices were entered including tax.
For a taxable line with a non-zero stored subtotal, derive an effective historical conversion ratio from the stored pre-coupon subtotal, rather than looking up today's tax rates:
subtotal_incl = subtotal_excl + subtotal_tax- If prices were entered including tax:
- metadata raw value is the inclusive value
value_excl = value_raw × subtotal_excl / subtotal_incl
- If prices were entered excluding tax:
- metadata raw value is the exclusive value
value_incl = value_raw × subtotal_incl / subtotal_excl
- For tax status
none, inclusive and exclusive values are equal.
Inferred: This ratio works on both sides of 1.9 because the tax rate is proportional even though the stored subtotal basis changed from regular price to selling price. It also avoids using mutable current tax configuration and accommodates compound rates through the amounts actually stored on the order.
Limitation: If the stored subtotal is zero while regular_price is positive (for example, a free 100% reduction created on 1.9), the opposite tax basis is not provable from the line's amounts (0/0). The raw entered-price-basis value is still known. Looking up current tax rates would be historically unsafe. The public contract must choose an explicit unavailable/fallback policy for this edge case.
Rounding: Calculate at WooCommerce internal precision, then round only the published/display values. Scaling already-rounded line tax ratios can produce small cent-level differences; this requires tests in inclusive, exclusive, compound, and fractional-quantity scenarios.
Amounts below omit tax for clarity.
| Scenario | Metadata (regular, price) |
Woo values | Reliably reportable |
|---|---|---|---|
| Full-price item | 20, 20 | subtotal 20; total 20 | Reference 20; selling 20; pre-coupon savings 0 |
| Till reduction | 20, 16 | 1.9 subtotal 16; total 16 | Reference 20; selling 16; pre-coupon savings 4 |
| Catalog sale, no till edit | 20, 16 | Same as till reduction | Same values; cause cannot be distinguished |
| Catalog sale 16, then till edit to 14 | 20, 14 | subtotal 14; total 14 | Reference 20; selling 14; combined savings 6; catalog/till split unknown |
| Till reduction plus 10% coupon | 20, 16 | subtotal 16; total 14.40 | Pre-coupon savings 4; coupon savings 1.60; final 14.40 |
| Quantity 3 | 20, 16 | subtotal 48; total 48 | Unit savings 4; line savings 12 |
| Split quantity | 20, 16 on every split row | Separate rows | Same unit semantics per split row; metadata is cloned |
| Price increase | 20, 25 | subtotal 25; total 25 | Reference 20; selling 25; savings 0; signed adjustment would be -5 if exposed |
| Free item | 20, 0 | subtotal 0; total 0 | Raw-basis reference 20 and savings 20; opposite tax-basis values may be unavailable |
| Misc product | Values present in metadata | Normal line totals | Same semantics as a catalog product |
| Missing/malformed/non-numeric metadata | Unusable | Line totals only | Final/subtotal values only; no historical reference or pre-coupon savings should be invented |
| Refunded order | Original item retains metadata | Refund lines link to original item | Original sale values remain reportable. Refund-specific reference/savings requires joining the refund row to its original line; it should not be inferred from refund totals alone. |
| Non-positive quantity | Metadata may exist | Receipt builder normalizes quantity to zero | Do not calculate unit or line savings; negative-quantity behavior is outside this effort |
- Pre-1.9-shaped order: no coupon and coupon-stacked.
- 1.9-shaped order: no coupon and coupon-stacked.
- Tax-exclusive and tax-inclusive stores.
- Multiple/compound tax rates and rounding-at-subtotal modes.
- Quantity greater than one, fractional quantity, and split rows.
- Catalog sale indistinguishable from till override.
- Price increase and zero price.
- Misc product.
- Missing, malformed, partial, and non-numeric
_woocommerce_pos_data. - Refund receipt with original-line linkage.
- Observed: No production behavior was changed by this research.
- Risk for the next decision: Naming the value
cashier_discountorpos_discountwould overstate the evidence because catalog sale savings are included. - Risk for implementation: Falling back to the current product's regular price would make historical receipts change when catalog prices change.
- Risk for implementation: Reusing existing
lines[].discountsas pre-coupon savings would break its coupon-only meaning for 1.9 orders and preserve conflation for older orders.