iVendNextDevelopers Request a sandbox

S26 — Derived price lists (a price list worked out from another, kept in step)

What the shop does

The shop keeps one price list worked out from another: the selling list is 1.5 times the buying list, or the wholesale list is 0.9 of the retail list. When someone changes a price on the source list, the app changes the derived price within seconds. Nothing changes at the till: the cashier sells at the list's price as before.

What happens When
The target list gets the item's price: source × factor + amount, rounded to the step at the shop's decimal places, never below the floor a price is saved on the source list, or a rule is added or its numbers change (every source price is worked out again)
Nothing is written the target price is already right (an event repeated, or a save that changed nothing)
The target price is kept and listed as a person's price someone set a price for that item on the target list that is still selling, or changed the app's price there after it was written
The target price is given an end date of yesterday (never deleted) its source price was deleted, or made a price for one customer, supplier or batch
The made price moves with its source the source price's item or unit was corrected
The next list down the chain follows too (A → B → C) a rule's target is another rule's source
The rule is refused and listed its lists are in two currencies, its step or floor is finer than its decimal places, another rule writes the same list, or the rules make a loop

Bounds, said plainly.

What the shop does to set it up

  1. Install manifest.json (the shop's administrator, on the install screen; on your sandbox, tools/deploy/deploy.py). It adds PP DPL Rule, the feed registration PP DPL Prices (switched off) and the role PP DPL Integration (empty).
  2. In Role Permissions, give PP DPL Integration read on Price List, and read, create and write on Item Price. A manifest grants rights only on its own record types (the integration user recipe). ⚠ With these rights the key can change any item price in the shop: give the role to this app's user alone.
  3. Make the app's integration user with that role and generate its API key.
  4. Choose that user on the feed registration PP DPL Prices and tick Item Price.
  5. Run the service with the user's key (below).
  6. Add a PP DPL Rule: a short name, the source and target price lists, the factor, an amount to add, the rounding step and direction, the floor, the decimal places; tick Enabled. The next pass writes every target price that no person has set.

What leaves the shop. The shop calls nothing outside. The service reads the shop with the user's key: the rules, the price lists, the item prices, and the event feed. It writes item prices on the rules' target lists.

Run it

python3 -m unittest discover -s samples/s26_derived_prices/tests -p 'test_*.py'
python3 samples/s26_derived_prices/tests/canaries.py

On a sandbox, with the integration user's key in the environment (never on the command line, never committed):

DPL_KEY=… DPL_SECRET=… python3 samples/s26_derived_prices/sync.py run --base https://your-sandbox.example --state state/dpl.sqlite --watch 5
DPL_KEY=… DPL_SECRET=… python3 samples/s26_derived_prices/sync.py reconcile --base https://your-sandbox.example --state state/dpl.sqlite

backfill --rule ⟨name⟩ works one rule's list out again from the start; status prints the rules, the refused ones and the feed's backlog.

The files

File What
prices.py the rules, pure: the formula and its rounding in Decimal at the rule's decimal places, the rule checks (two currencies, a step too fine, two rules on one list, a loop), the stamp
sync.py the service: the shop over the record API, the feed handler, the chain, the end date for a deleted source, the resumable backfill, the nightly reconcile
manifest.json the PP DPL Rule record type, the role, the feed registration on Item Price
tests/test_s26.py 29 laptop tests: the rounding table, the rule checks, one event twice, a person's price (and an ended one, which does not block), a rule switched off and on, a source's unit corrected, a chain and a loop, a deleted source, a crash in a backfill, a reconcile of each planted fault
tests/test_manifest.py the manifest: valid, every field the rules read declared, the user's rights, nothing sent outside
tests/canaries.py each guard removed in turn: its test must go RED (33 of 33)

Technical notes

What was proven where

Needs this serves

This page in the kit