Set a shop up with no code — the recipe (reasons, IMEI, line detail, member pricing)

Once the configuration is applied, the till gives a member their price, asks for reasons, checks a line detail, asks for a phone's IMEI and asks why a sale is voided.
This recipe makes the till ask for and keep a coded reason, track a phone by its IMEI, capture one more detail per line, and price members differently. It uses only what iVendNext already has, switched on by configuration. It is for a partner, our Custom Development team or a shop's own administrator. Reference implementation: samples/s5_config_pack/: config.json is the whole set-up, apply_config.py applies it over the record API, and --remove undoes it.
When to use it. Rung 1 of the build ladder: configure first, always. Nothing here runs partner code in the shop. The script is a one-time set-up run by the shop's administrator; after that it is not needed again.
What each part is
| You want | The shop's own setting | What the sample sets |
|---|---|---|
| A coded reason for a void, a return, a discount | Reason Code Master rows (a type each: Void Sale, Item Return, Sale Return, Sale Discount, Item Discount, Price Override …) and the Retail Setting switch for that type (reason_void_sale, reason_item_return, reason_sale_return, reason_sale_discount, reason_item_discount; a void reason also needs save_voided_transaction) |
five reason codes, six switches on |
| The IMEI of every phone sold, once | the item's serial numbers (Item → Has Serial No); the IMEI is the serial number, received with the stock | one tracked item, three IMEIs in stock |
| One more detail on a line, for goods with no serial number | Transaction Item Attribute (a name, a pattern, an order) | "Warranty card", pattern ^WC-[0-9]{8}$ |
| A member price | a Customer Group per tier, and one promotion per tier that applies to the sale and excludes every other customer group | group Member Gold, a 10% promotion, a member in the group |
Steps
-
Read
config.json, and change the names, codes and numbers to the shop's. Names start withPP CFGin the sample only so its test can find and remove them; use the shop's own. -
Give the script a key: a user who may edit those records. These roles together are enough: System Manager, Retail Manager, Item Manager, Stock Manager, Sales Master Manager. The key is the shop's administrator's, used once.
-
Run it:
PP_BASE=https://your-shop PP_KEY=<api key> PP_SECRET=<api secret> python3 samples/s5_config_pack/apply_config.py- Run it twice and nothing doubles. The second run says kept for everything, and receives no stock.
- Each run recomputes the member promotion's exclusion list, so a customer group added since is excluded the next time you run it.
--removeputs the switches back to what they were and deletes what the script made. A record a booked sale points at cannot be deleted, so it is retired (a reason code made inactive, the member disabled, the line detail switched off); a later run switches it back on.- ⚠ It does not undo the stock. The item, its price, the unsold serial numbers and the submitted Material Receipt (which posts the units' value to the books) stay. On a real shop, use your own IMEIs and quantities, or cancel the receipt if it was a trial.
-
Make sure the shop has a default Company. Any shop that finished its set-up has one. A sale of a serial-tracked item builds a Serial and Batch Bundle, which takes its company from that default. Without it, the first IMEI sale fails with "Error: Value missing for Serial and Batch Bundle: Company".
-
The cashier's side: nothing to do. The till offers the reasons it finds, asks for the IMEI of a tracked item, offers the line detail, and applies the member promotion when the member is attached to the sale.

The gold member's 10%, in the totals.

A 10% markdown on the phone asks for a reason; "price match" also asks for a comment.

The line detail "Warranty card" refuses a value that does not match its pattern.

The phone's IMEI, captured before it can be charged.

A void asks why, and keeps the voided sale on record.
What is refused, and why
- A phone line with no IMEI, two units on one line, or one unit with two IMEIs: the till refuses the sale, so every phone sold has exactly one IMEI on record. The same IMEI cannot be sold twice.
- A line detail that breaks its pattern: refused on the till screen only. The server saves a value that breaks the pattern.
Limits to know before you build
- The server does not refuse a wrong line detail, or a sale composed without a reason. The till asks; the server does not check. If a wrong value must be refused on save, that needs an extension point (P-RULES), not configuration.
- Configuration cannot make a field required or hidden at the till. That needs an extension point (P-FIELDS) that has not shipped.
- A customer group created later gets the member discount until the script is run again (it recomputes the exclusion list each run), or until the group is added to the promotion's exclusions in Desk.
- One unit per line for a serial-tracked item, by the till's design.
- Offline was not tested for any part.
- Reason codes, the IMEI and the line detail each cost the cashier an extra step on every line they apply to. Switch on only what the shop will use.
Technical notes
Proven on our test bench.
Notes moved from the steps
- Step 2: the roles were measured on the bench.
- Step 4: the programme bench has no default Company, and the first IMEI sale there failed on the bundle's missing Company (the framework words that as "Error: Value missing for Serial and Batch Bundle: Company", and the failed save's own error ends
: company) until the test set one for its run (finding F1 in the evidence file). - Step 5: walked in a real browser (our test bench, 2026-10-07, iVendNext POS app at
8dc36aab8; frame: the till, 1440 wide; the sale voided at the end). The returns' reasons were not walked. The GIF is built from the same screens, every frame cut above the till's key row.
What the sample was proven to do (bench :8110, 22 + 2 checks)
- Every switch, reason code, item, IMEI, line detail, group, member and promotion read back from the database; the till's own reason list offers each reason; the line detail comes with its pattern.
- Member pricing: the same basket, the member pays exactly 10% less (2 338.20 against 2 598.00), the other customer pays full price; the booked sale equals the till's preview.
- Reasons: a sale discount keeps Sale Discount + its code on the invoice; a line markdown keeps Item Discount + its code + the cashier's comment.
- IMEI: a line with no IMEI is refused (scan the unit's serial); two units on one line are refused (one unit per line); one unit with two IMEIs is refused (2 of 1 serials captured); a right sale books, takes the IMEI out of stock, and the same IMEI cannot be sold twice.
- Line detail: saved with the sale against the right item.
- Applied twice: nothing created the second time. Removed: the switches back, nothing live left.
- The money kit with the pack applied: green on our test bench (
recipes/money_kit.md).
Limits, stated
- The line detail's pattern is checked on the till screen only. The server saves a value that breaks the pattern (measured:
not-a-cardwas saved). If a wrong value must be refused, that is an extension point (P-RULES), not configuration. - A reason is asked for and kept; a sale composed without one is not refused by the server — measured: with the switches on, the money kit's recorded basket with a 7% sale discount and no reason code booked and matched its answer. The till's own code comment says a return has always demanded a reason; that was not exercised here, and neither was capturing an item return or sale return reason on a sale (only Sale Discount and Item Discount reach an invoice in the tests): the switches and the reason codes for those types are proven present and offered by the till's own list, nothing more. The till screen asking was walked (2026-10-07, row SHOTS2, hold 3, run
pp-20261007T110827Z-35077): the markdown asked for a reason and, for price match, a comment; the IMEI was captured; the void asked for its reason (Void: scanned in error) and the sale was voided with its record kept. Not proven: the return reasons on the till screen, and the voided record's stored reason read back from the database. - What the recipe cannot do: make a field required or hidden at the till (an extension point, P-FIELDS, has not shipped), or refuse a wrong value on save (P-RULES).
Tests
- The pack's bench test (applied, three sales, removed) passed on our test bench.
- The money kit with the pack applied (
recipes/money_kit.md).