Multi-code & pricing rows
A product normally has one barcode and one sell price. A multi-code row adds a second layer: an alternative barcode, box code, price, discount, supplier or buy price — optionally limited to one warehouse and/or a schedule.
Multi-code rows are managed in the product editor's multi-code / supplier tab (visible when Multi code & supplier is enabled on the product). When the feature is off for a product, its multi-code rows are ignored everywhere.
Row fields
| Field | Meaning |
|---|---|
| Code | An additional unit barcode for the product. Scanning this code identifies the product and activates this row's pricing. |
| Box code | A barcode for a whole box/case of the product. Scanning it adds box_qty units at once and activates this row's pricing. |
| Price | Sell price including VAT. Overrides the product's own price, but only while this row is the winning row. Leave empty to keep the price from other rows or the product. |
| Discount | Discount percentage that applies while this row wins. An empty discount is no discount — see the flags below for what it then blocks. |
| Buy price | Optional cost-price override (used for margin reporting). |
| Supplier | Optional supplier provenance for the row. |
| Warehouse | Limits the row to one warehouse. Empty = the row applies in all warehouses. |
| Schedule | Optional date range, daily time window and weekday list — the row only participates while the schedule is active (a "happy hour" price). A row with no schedule is always active. |
| Lock discount | Checkbox chip — while this row wins, its discount replaces the product's own discount and quantity tiers are skipped. Customer prices always still apply. Does nothing when the row's discount is empty. |
| Ignore quantity discounts | Checkbox chip — quantity discount tiers are skipped on lines priced from this row. |
| Ignore BXGX | Checkbox chip — Buy-X-Get-X free units are not granted on lines priced from this row. |
Price and discount are independent. A row may set only the price, only the discount, or both. An empty field does not clear anything — it simply leaves that field to be decided by other rows (and finally by the product itself).
Row order matters
Rows are processed from top to bottom (row 1, row 2, row 3 …) and the last row that carries a value wins that value. In practice:
- Start from the product's own price and discount.
- Walk the matching rows top to bottom.
- Every time a row has a price, it becomes the current price. Every time a row has a discount, it becomes the current discount.
So if row 1 sets a 10 % discount and row 2 sets a lower price with no discount, the result is row 2's price with row 1's 10 % discount — the fields resolve separately.
The last row that supplied either the price or the discount is the winning row — its three checkbox flags are the ones that steer what that discount blocks (see Discount priorities).
Worked example
| Row | Price | Discount |
|---|---|---|
| 1 | — | 10 % |
| 2 | 9.00 € | — |
| 3 | 8.00 € | 20 % |
Walking down: price ends at 8.00 € (row 3), discount ends at 20 % (row 3). If row 3 had no discount, the result would be 8.00 € with 10 %.
The three row flags
The checkbox chips at the end of each row control how the row's discount competes — they matter only while this row is the winning row:
| Chip | What it does |
|---|---|
| Lock discount ("applies even when smaller") | The row's discount replaces the product's own discount (even when smaller) and quantity discount tiers are skipped. Customer-specific prices and VIP discounts are never blocked — the bigger discount still wins. Only meaningful together with a discount: a locked row with an empty discount keeps only its price override. |
| Ignore quantity discounts | Quantity tiers do not apply on lines priced from this row — even tiers that would be bigger. |
| Ignore BXGX | The product's Buy-X-Get-X campaign does not generate free units on lines priced from this row. |
The chips are orthogonal — combine them freely. Lock does not remove BXGX by itself; to freeze a row's pricing completely check all three (Lock
- Ignore quantity + Ignore BXGX). See the combination table in Discount priorities.
Barcode-specific rows
The key business rule:
A price or discount attached to a specific barcode is only used when that exact barcode is scanned.
| What you scan or pick | Which rows participate |
|---|---|
| The product's main barcode (or main box code) | Only rows with both code and box code empty |
| A multi-code row's code | That row + fully-generic rows (both codes empty) |
| A multi-code row's box code | That row + fully-generic rows (both codes empty) |
| Product picked by name from the search list | Only rows with both codes empty (a code-specific row needs its scan) |
When you scan a row's own barcode, that row always wins for the fields it sets — a generic row can only fill the fields the scanned row leaves empty.
Warehouse-limited rows
A row with a warehouse only participates where its warehouse qualifies:
- Orders & invoices — the row's own warehouse must have the location's Is main warehouse flag checked (any main-flagged warehouse qualifies — the line's stock location is never a pricing input here). Rows bound to non-main (shop) warehouses are POS-only.
- Label maker — the context is the warehouse selected on the label sheet.
- POS — the row's warehouse must match the register's location. This is the only place a shop-bound row prices.
Rows with an empty warehouse apply everywhere and always participate.
A warehouse only decides whether the row participates — it never reorders precedence. Among the participating rows, row order decides as usual.
Schedules (happy-hour pricing)
A row with a schedule is only considered while all of these hold:
- today's date is inside the row's date range (if set),
- the current time is inside the row's time window (if set),
- today's weekday is in the row's weekday list (if set).
Outside the window the row is silently skipped — the other rows decide the price. A typical setup uses three rows for one location:
| Row | Price | Schedule |
|---|---|---|
| 1 — base price | 12.90 € | none (always active) |
| 2 — lunch | 9.90 € | Mon–Fri, 11:00–14:00 |
| 3 — weekend brunch | 10.90 € | Sat–Sun, 10:00–13:00 |
Because rows merge top to bottom, put the base price first and the scheduled overrides below it — while a schedule is active its values overwrite the base; when it ends the row stops participating and the base price returns automatically. Nothing needs to be switched back manually.
Lines already on an order keep the price they resolved when the document was last opened or edited — see the FAQ in How the price row is chosen.
Box codes and quantities
Scanning a box code adds the product with quantity multiplied by the
product's box quantity (box_qty), at the scanned row's pricing. If the
same value is registered as both a unit barcode and a box code, the cashier is
asked which one was scanned.
Prices from stock adding (v3.102)
Whenever stock enters with a price that differs from the product's own price / purchase price, the differing price never overwrites the product — it lands on a multi-code price row instead, filling only the column that changed (buy, sell, or both):
| Where you enter the price | Row created |
|---|---|
| Product editor → Stock → Add stock (arrival today) | Row bound to the stock's location |
| Product editor → Stock → Coming to stock, later accepted as received | Row bound to the stock's location |
| Purchase order received | Row bound to the order's supplier |
| Stock transfer received | Row bound to the destination location — but only when the source location's stock carries a different purchase price (a source-location price row); when the source price equals the product's own, nothing is written |
| Inventory-app / API stock add with a price | Row bound to the supplier and/or location the call carries |
If a matching price row already exists (same supplier and/or location, no code, no box code), its price columns are updated — repeated price changes never pile up rows and never touch the product's own prices.