BAZAAR/i is a multi-store specialty-retail application: a store and SKU master with a
store-level price book, POS basket capture with mixed tender and returns/refunds, nightly stock
depletion and sales/tax GL distribution, per-till cash-up and variance reporting, weekly store
replenishment and slow/fast-mover ranking, monthly markdown management and department margin analysis,
and an annual stocktake, price-book roll and sales archive. It is a classic IBM i mixed-language
estate: RPG (fixed-form and free-form) for the batch and online programs, one SQLRPGLE program for the
GL distribution, ILE COBOL for the till audit report, DDS for the physical/logical/display/printer files,
and CL for the setup and the four job cycles. This manual is the reference for the operator who runs the
online screens and the periodic cycles, and for the developer maintaining the application. It is grounded
entirely in the committed source (retail-app/src/sources.mjs, src/seed.mjs, and
the test/rt_*.mjs drivers).
BAZAAR/i runs the merchandising and till life of a small chain across trading days, weeks, months and the trading year:
Everything runs in library BAZAAR. The application is a conventional IBM i layering,
not a two-layer SQL-PL design:
RTGLDIST) plus an index is created through SQL DDL (RUNSQLSTM) for the GL
distribution./free blocks) — pricing, depletion, cash-up, replenishment, markdown, margin,
stocktake, roll and archive. One program, RTGLPST, is SQLRPGLE and writes the GL
rows with embedded EXEC SQL. One program, RTTLRPT, is ILE COBOL and
independently re-proves the till invariant.RTSETUP creates every object and compiles every program;
the four cycle drivers (RTDAILY, RTWEEK, RTMONTH,
RTYEAR) simply CALL the programs in the right order and send a completion
message.The benefit for operations: each cycle is a single CL you submit; each program is a small, single-purpose step that DSPLYs a one-line result you can read back; and the data model's composite (STORE, SKU) keys mean every figure is genuinely per-store — the same SKU can carry a different price, a different promotion and a different book at each store.
ONLINE DAILY WEEKLY / MONTHLY / ANNUAL
------ ----- -------------------------
RTMENU (5250) RTDAILY: RTWEEK:
opt 1 -> RTSKUIQ RTPOSCP (price) RTREPLG (suggest+rank)
opt 2 -> RTTXNIQ RTPOSPT (deplete) RTONORD (raise on-order)
(subfile) RTTILLR (cash-up) RTMONTH:
RTTILPR (print) RTMKDNA (markdown)
RTGLPST (GL, SQL) RTDSUMA (dept margin)
RTTLRPT (COBOL) RTYEAR:
RTSTKTA (stocktake var)
RTPBRLL (price-book roll)
RTARCH (archive+revalue)
MASTER / TXN FILES (library BAZAAR)
RTSTORE RTDEPT RTSKU RTPRICE RTSTOCK <-- reference (RTREFLD seeds)
RTSALE (hdr) RTSLIN (lines) RTTEND (tender) <-- POS basket
RTTILL RTGLDIST RTREPL RTMKDN RTSTKTK RTDSUM <-- cycle outputs
LFs: RTSTKLF (SKU,STORE) RTSLLF (SKU,TXNO) RTSALF (STORE,DATE,TXNO)
A single trading day flows: the lane writes captured baskets (TXSTAT='C') into
RTSALE/RTSLIN/RTTEND → RTDAILY prices them
(RTPOSCP), posts them and depletes stock (RTPOSPT, flipping C→P),
cashes up the tills (RTTILLR), prints the reconciliation (RTTILPR) and distributes
the money to the GL (RTGLPST), and the COBOL audit (RTTLRPT) re-proves the till
invariant.
| Object | Type | Role |
|---|---|---|
| RTSTORE | PF | Store master (tax rate, till float). |
| RTDEPT | PF | Department master (margin target). |
| RTSKU | PF | SKU / merchandise master. |
| RTPRICE | PF | Store-level price book (STORE,SKU). |
| RTSTOCK | PF | Store stock (STORE,SKU). |
| RTSALE / RTSLIN / RTTEND | PF | POS basket header / lines / tenders. |
| RTTILL | PF | Till reconciliation / cash-up. |
| RTREPL | PF | Weekly replenishment suggestions. |
| RTMKDN | PF | Markdown log (before image). |
| RTSTKTK | PF | Stocktake counts & variance. |
| RTDSUM | PF | Department sales summary. |
| RTGLDIST | SQL table | Sales/tax/cost GL distribution. |
| RTSTKLF / RTSLLF / RTSALF | LF | Alternate access paths (SKU-first, store-day). |
| RTSKUD / RTTXND / RTMENUD | DSPF | SKU inquiry / subfile inquiry / menu. |
| RTTILLP | PRTF | Till reconciliation report. |
| RTREFLD | RPG | Seed reference data. |
| RTPOSCP / RTPOSPT | RPG | Lane pricing / stock depletion & post. |
| RTTILLR / RTTILPR | RPG | Cash-up / print reconciliation. |
| RTGLPST | SQLRPGLE | Daily GL distribution. |
| RTREPLG / RTONORD | RPG | Replenishment suggest+rank / raise on-order. |
| RTMKDNA / RTDSUMA | RPG | Markdown / department margin. |
| RTSTKTA / RTPBRLL / RTARCH | RPG | Stocktake / roll / archive. |
| RTSKUXR | RPG | Chain-wide SKU stock cross-reference (LF demo). |
| RTSKUIQ / RTTXNIQ / RTMENU | RPG | Online inquiry / subfile inquiry / menu. |
| RTTLRPT | ILE COBOL | Till audit report / invariant re-proof. |
| RTSETUP | CL | Create objects + compile programs. |
| RTDAILY / RTWEEK / RTMONTH / RTYEAR | CL | The four job cycles. |
The full catalogue is 13 PFs + 3 LFs + 3 DSPFs + 1 PRTF + 1 SQL table (with index), driven by 17 RPG programs, 1 ILE COBOL program and 5 CL programs (setup + four cycles). Sections D and F expand each.
BAZAAR/i has no CICS transaction identifiers and no menu-driven transid switch. On IBM i,
each program is reached by name from a 5250 command-entry line (or a JOBQ/scheduler for the batch
cycles). The operator equivalent of "type a transid and Enter" is "type a CALL command and
Enter". Before invoking anything, the job's library list must include BAZAAR — the tested
jobs run with LIBL = QSYS QGPL BAZAAR QTEMP and CURLIB = BAZAAR. (The cycle CLs
also issue their own ADDLIBLE LIB(BAZAAR) defensively.)
| To do this | Type on the command line |
|---|---|
| Open the operator main menu | CALL BAZAAR/RTMENU |
| SKU price / stock inquiry (direct) | CALL BAZAAR/RTSKUIQ |
| Transaction (basket) inquiry (direct) | CALL BAZAAR/RTTXNIQ |
| Create objects & compile everything (once) | CALL BAZAAR/RTSETUP |
| Seed reference data (once, after setup) | CALL BAZAAR/RTREFLD |
| Run the daily POS cycle | CALL BAZAAR/RTDAILY (or SBMJOB it) |
| Run the weekly / monthly / annual cycle | CALL BAZAAR/RTWEEK / RTMONTH / RTYEAR |
The batch cycles take no CALL parameters. Rather than a control table, each individual program
derives its own scope from the data or from a compile-time period literal (see the honest note in C.2): the
GL post finds the earliest undistributed trading day; the weekly run stamps run number 202632;
the monthly summary uses period 202608; the annual roll targets effective date
20270101. Only RTMENU, RTSKUIQ and RTTXNIQ are
interactive; every batch program runs to completion and DSPLYs a one-line result.
Three programs are interactive, driven by three DDS display files:
A plain menu with two options; it CALLs the chosen inquiry program-to-program and returns.
MOPT='1' calls RTSKUIQ, MOPT='2' calls RTTXNIQ, any other
non-blank value shows Invalid option. F3 (CA03) ends the
program.
A plain (non-subfile) screen. Key a store and a SKU, Enter, and it renders the
SKU description and department (from RTSKU), the shelf price and promotion (from the
RTPRICE row keyed on both STORE and SKU) and the store stock (from RTSTOCK).
Two figures are derived on the screen and stored nowhere: the promotional price
(price − half-up(price × promopc / 100)) and the available quantity
(onhand − resvqty). A SKU not found, or a real SKU the store does not carry, shows
SKU not found at that store. and clears the detail.
The same SKU keyed for ST02 renders the same 12.99 shelf but a 0.00 promotion
(so the promo price is the shelf price) and the ST02 book of 30 — proving the screen reads
the (STORE, SKU) composite key, not the SKU alone.
RTTXNIQ is the app's subfile screen. Key a transaction number, Enter, and
it renders the basket header (store, type, net, total from RTSALE) and loads the basket's sale
lines into a subfile (DDS record TSFL under control record TCTL,
SFLPAG(5) per page, SFLSIZ(20), ROLLUP/ROLLDOWN paging).
Each subfile row shows sequence, SKU, quantity, price, discount and net.
SFLCLR on a control-format WRITE once the
subfile is populated, so the program remembers how many rows the last enquiry loaded (WPREV) and
overwrites exactly those rows with blanks — a deterministic clear that needs no keyword support.
This is an engine-compatibility accommodation documented in the source; operationally the screen behaves as
described (an unknown basket reports not found and shows zero lines).Honest statement: BAZAAR/i does not model a four-eyes maker–checker /
separate-authorization workflow, and it has no user-level audit trail table. There is no "one clerk captures,
a supervisor approves" gate, and no *AUDIT-style log of who did what. The control posture the
application does have is built into the batch logic:
RTTILLR) accumulates the
expected tender by type off RTTEND, compares declared cash to expected, writes the
difference to VARCASH, and sets TLSTAT='V' when it is non-zero — the
till is visibly out of balance rather than quietly balanced. The declared over/short is never
rolled into TLSALES or EXPCASH.EXPCASH + EXPCARD + EXPVOUC = TLSALES (the tender ledger must foot to the day's posted
totals); the ILE COBOL RTTLRPT re-reads RTTILL and re-proves it, counting any
out-of-balance till rather than hiding it.RTGLPST) writes balanced DR/CR pairs (cash vs
revenue+tax, and cost-of-sale vs merchandise stock) so the whole batch's debits equal its credits.RTPOSPT) will not drive a book
quantity negative: a sale larger than on-hand floors the book at zero and accumulates the shortfall
(WOVERS) so the day reports the oversell rather than carrying a silent negative.C→P then P→A;
cash-up, replenishment, markdown, summary, stocktake, roll and archive each have an idempotency guard
(section F.3) that refuses to process an already-processed row.In sum, the control model is variance reporting + balancing invariants + idempotency guards, enforced in the batch programs, rather than a segregation-of-duties approval workflow or a user-audit trail.
BAZAAR/i's processing runs as four periodic cycles, each a CL that CALLs its programs in a fixed order:
a daily cycle (price, deplete, cash-up, print, GL, audit), a weekly cycle (replenishment +
raise on-order), a monthly cycle (markdown + department margin) and an annual cycle (stocktake,
price-book roll, archive). All four take no CALL parameters. This is the SBMJOB idiom — a
scheduled submission is a bare CALL:
-- one-time build, then seed the reference data CALL PGM(BAZAAR/RTSETUP) CALL PGM(BAZAAR/RTREFLD) -- then submit a (parameterless) cycle SBMJOB CMD(CALL PGM(BAZAAR/RTDAILY)) JOB(RTDAILY)
| Program | Cycle | Purpose | Reads | Writes | DSPLY result |
|---|---|---|---|---|---|
| RTPOSCP | Daily | Price captured baskets off the store price book; total incl. store tax. | RTSALE(C), RTSLIN, RTPRICE, RTSTORE, RTSKU | RTSLIN, RTSALE (line/hdr money) | RTPOSCP PRICED=n SKIP=n |
| RTPOSPT | Daily | Deplete store stock line by line; flip basket to posted. | RTSALE(C), RTSLIN, RTSTOCK | RTSTOCK, RTSALE (TXSTAT='P') | RTPOSPT POSTED=n LINES=n SKIP=n |
| RTTILLR | Daily | Cash up each (store,date,till) against its tenders; record variance. | RTSALE, RTSALF, RTTEND, RTSTORE | RTTILL | RTTILLR TILLS=n SKIP=n VAR=v |
| RTTILPR | Daily | Print the till reconciliation report (PRTF, page overflow). | RTTILL | spool (RTTILLP) | RTTILPR TILLS=n VARIANCE=v |
| RTGLPST | Daily | Distribute the day's sales/tax/cost into the SQL GL (balanced DR/CR). | RTSALE(P), RTSLIN, RTSTORE | RTGLDIST (SQL) | RTGLPST BATCH=d ROWS=n |
| RTTLRPT | Daily | ILE COBOL: re-prove the till invariant; count out-of-balance tills. | RTTILL | — (display only) | RTTLRPT TILLS/TENDER/SALES/VARIANCE/OUTOFBAL |
| RTREPLG | Weekly | Suggest an order for each store/SKU at/below reorder point; rank movers. | RTSTOCK, RTSKU, RTSLIN, RTSALE | RTREPL | RTREPLG RUN=r SUGGESTED=n SKIP=n |
| RTONORD | Weekly | Raise the suggested quantities onto the store on-order book. | RTREPL, RTSTOCK | RTSTOCK, RTREPL (RPMSG='RD') | RTONORD RAISED=n SKIP=n |
| RTMKDNA | Monthly | Burn a permanent markdown into the price book (discontinued 30%, class C 15%). | RTPRICE, RTSKU | RTPRICE, RTMKDN | RTMKDNA MARKED=n SKIP=n |
| RTDSUMA | Monthly | Summarise sales/cost/margin by store & department; grade vs target. | RTDEPT, RTSTORE, RTSLIN, RTSALE, RTSKU | RTDSUM | RTDSUMA PER=p ROWS=n SKIP=n |
| RTSTKTA | Annual | Variance counted stocktake against book; correct the book. | RTSTKTK, RTSTOCK | RTSTKTK, RTSTOCK | RTSTKTA COUNTED=n SKIP=n VAR=v |
| RTPBRLL | Annual | Roll the price book: clear promotions/markdowns, new effective date, close discontinued rows. | RTPRICE, RTSKU | RTPRICE | RTPBRLL ROLLED=n CLOSED=n SKIP=n |
| RTARCH | Annual | Archive posted sales (TXSTAT='A'); re-value store stock at SKU cost. | RTSALE(P), RTSTOCK, RTSKU | RTSALE, RTSTOCK (SKVAL) | RTARCH ARCHIVED=n VALUED=n SKIP=n |
RTREFLD (seed) and RTSKUXR (chain-wide SKU cross-reference demo)
are on-demand utilities, not part of any cycle.
Runs RTPOSCP→RTPOSPT→RTTILLR→RTTILPR→RTGLPST→RTTLRPT.
Pricing is per line: gross = qty × price, disc = half-up(gross × promopc / 100),
net = gross − disc; the header sums the line nets, then
tax = half-up(net × store taxpc / 100) and total = net + tax. A return carries
negative quantities, so every figure comes out negative through the same arithmetic — no
separate refund path.
Expected DSPLY (rt_daily oracle: ST01 tax 8.25%, three baskets): RTPOSCP PRICED=3 SKIP=0 RTPOSPT POSTED=3 LINES=4 SKIP=0 stock 50-3+1=48, 8-1=7, 100-2=98 RTTILLR TILLS=1 SKIP=0 VAR=200 200.00 float left in drawer, TLSTAT='V' RTGLPST BATCH=20260801 ROWS=5 DR cash 54.38 = CR sale 50.23 + tax 4.15; COS pair 30.50
RTGLPST derives its batch as the
earliest posted TXDATE not yet present in RTGLDIST — so each trading day is
distributed exactly once under its own batch, and a re-run with nothing new reports
RTGLPST BATCH=0 ROWS=0 NOTHING TO POST. This replaced an earlier compile-time literal
(20260801) that froze the GL permanently after day one (measured: GL stuck at 588.01 while the
register grew to 1157.53). The fix is in the shipped source.Runs RTREPLG then RTONORD, under run number 202632. For each active
store/SKU: available = onhand − resvqty; when available ≤ REORDP, suggest
REORDQ − available − onorder floored at zero, and set the message
OR (order) or OS (out of stock, when available ≤ 0). The mover rank off the
week's posted units is F (≥10), M (≥3) else S. Discontinued SKUs
are skipped. RTONORD then adds each suggestion to ONORDER and flips
RPMSG to RD (raised).
Expected DSPLY (rt_cycles oracle: 9 stock rows, 1 discontinued): RTREPLG RUN=202632 SUGGESTED=3 SKIP=1 8 planned, 3 need ordering (53, 9, 84 units) RTONORD RAISED=3 SKIP=... ST01 plates on-order 53, ST02 coffee 84
Runs RTMKDNA then RTDSUMA, over period 202608. Markdown ladder:
discontinued SKUs take 30%, class-C slow lines take 15%, everything else nothing;
new = half-up(old × (100 − pc) / 100), logged with a before image and burned into the
price book. The department summary accumulates posted sale lines by (store, dept):
margin = sales − cost, margin% = half-up(margin × 100 / sales) (zero
sales → 0), graded P when margin% ≥ the department target else F.
Expected DSPLY (rt_cycles oracle): RTMKDNA MARKED=1 SKIP=... sea salt 4.40 -> half-up(3.08) = 3.08, reason 'D' RTDSUMA PER=202608 ROWS=6 2 stores x 3 depts; ST02/APP 49.96% misses 50% -> F
Runs RTSTKTA→RTPBRLL→RTARCH. Stocktake varies the counted
quantity against the book (CTVAR = count − book), records it and corrects the book to the
count. The roll moves every price row to effective 20270101, clears
PROMOPC/PRMKDN, and closes (PRSTAT='X') the price rows of discontinued
SKUs. The archive flips posted baskets to TXSTAT='A' and re-values each stock row at SKU cost
(SKVAL = onhand × cost).
Expected DSPLY (rt_cycles oracle: counts seeded short/over): RTSTKTA COUNTED=2 SKIP=0 VAR=-2 ST01 45 vs 48 = -3 (shrinkage); ST02 19 vs 18 = +1 RTPBRLL ROLLED=9 CLOSED=1 SKIP=0 all 9 rows to 20270101; 1 discontinued closed RTARCH ARCHIVED=5 VALUED=9 SKIP=0 ST01 jumpers 45 x 6.50 = 292.50 valuation
RTPOSPT depletes off the
lines RTPOSCP priced); post before cash-up and GL (both read TXSTAT='P');
cash-up before the printed report and before the COBOL audit (both read RTTILL).P) sale lines, so the day's trade must be posted
first. Markdown reads the price book but the margin summary needs the posted trade.A;
run it last so the weekly and monthly reads (which look for P) have already happened.
Within ANNUAL the order is stocktake → roll → archive (correct the book, then roll the price
book, then archive and re-value on the corrected book).RTSETUP then RTREFLD precede all cycles.All files are in library BAZAAR, grounded in src/sources.mjs. Money is packed
decimal (P); dates are stored as zoned 8S 0 in YYYYMMDD form (or
YYYYMM/YYYYWW periods); tax and margin percentages are packed 5P 2
(e.g. 8.25) so a rate survives exactly.
| Field | Type | Meaning |
|---|---|---|
| STORE | 4A | Store code (key), e.g. ST01. |
| STNAME | 30A | Store name. |
| STREGN | 3A | Region. |
| TAXPC | 5P 2 | Store sales-tax rate (8.25 / 6.00 seeded). |
| STCASH | 9P 2 | Till opening float (backed out at cash-up). |
| STSTAT | 1A | A active. |
| Field | Type | Meaning |
|---|---|---|
| DEPT | 3A | Department code (key): APP / HOM / GRO. |
| DPNAME | 25A | Department name. |
| MARGTG | 5P 2 | Target gross-margin % (50 / 40 / 20 seeded). |
| DPSTAT | 1A | A active. |
| Field | Type | Meaning |
|---|---|---|
| SKU | 8A | SKU number (key), e.g. SK000101. |
| SKDESC | 30A | Description. |
| DEPT | 3A | Owning department. |
| RETAIL / COST | 9P 2 | Chain list price / unit cost. |
| SKUOM | 3A | Unit of measure (EA). |
| REORDP / REORDQ | 7P 0 | Reorder point / order-up-to quantity. |
| SKCLS | 1A | Movement class A/B/C (C is a markdown trigger). |
| SKSTAT | 1A | A active, D discontinued. |
| Field | Type | Meaning |
|---|---|---|
| STORE / SKU | 4A / 8A | Composite key — a price per SKU per store. |
| PRICE | 9P 2 | Shelf price (what POS charges). |
| PROMOPC | 5P 2 | Promotion % off the shelf price. |
| PRMKDN | 5P 2 | Permanent markdown % burned into the shelf price. |
| PREFFDT | 8S 0 | Effective date (rolled annually). |
| PRSTAT | 1A | A active, X closed (roll closes discontinued rows). |
| Field | Type | Meaning |
|---|---|---|
| STORE / SKU | 4A / 8A | Composite key. |
| ONHAND | 9P 2 | Sellable book quantity (floored at zero on oversell). |
| ONORDER | 9P 2 | Requested but not received (raised by RTONORD). |
| RESVQTY | 9P 2 | Reserved / laid-away. |
| LSTSOLD | 8S 0 | Last-sold date (stamped by RTPOSPT). |
| SKVAL | 11P 2 | Stock valuation (set annually by RTARCH). |
| Field | Type | Meaning |
|---|---|---|
| RTSALE.TXNO | 8A | Basket number (key). |
| RTSALE.STORE / TXDATE / TXTILL | 4A / 8S / 2A | Store, trading date, till. |
| RTSALE.TXTYPE | 1A | S sale, R return. |
| RTSALE.TXGROSS/TXDISC/TXNET/TXTAX/TXTOTAL | 9P 2 | Basket money (set by RTPOSCP). |
| RTSALE.TXSTAT | 1A | C captured, P posted, V voided, A archived. |
| RTSALE.TXORIG | 8A | Original basket for a return. |
| RTSLIN.TXNO / LNSEQ | 8A / 3S | Line key. |
| RTSLIN.SKU / LNQTY | 8A / 7P 2 | SKU / signed quantity (return is negative). |
| RTSLIN.LNPRICE/LNDISC/LNNET/LNCOST | 9P 2 | Line money (derived by RTPOSCP). |
| RTTEND.TXNO / TNSEQ | 8A / 3S | Tender key. |
| RTTEND.TNTYPE | 1A | C cash, K card, V voucher. |
| RTTEND.TNAMT / TNREF | 9P 2 / 12A | Amount / reference (a basket can carry several). |
| Field | Type | Meaning |
|---|---|---|
| STORE / TLDATE / TILL | 4A / 8S / 2A | Composite key (one row per till per day). |
| EXPCASH / EXPCARD / EXPVOUC | 9P 2 | Expected tender by type (off RTTEND). |
| DECCASH / DECCARD | 9P 2 | Declared (counted) cash / card. |
| TLSALES | 9P 2 | Day's posted transaction totals for the till. |
| VARCASH | 9P 2 | Declared − expected cash (reported, never absorbed). |
| TLSTAT | 1A | B balanced, V out of balance (variance ≠ 0). |
| Field | Type | Meaning |
|---|---|---|
| GDSEQ | DECIMAL(8,0) | Distribution sequence (PK; disjoint block per store). |
| GDBATCH | DECIMAL(8,0) | Trading day this row distributes. |
| GDSTORE / GDACCT | CHAR(4) / CHAR(9) | Store / GL account (1010-CASH, 4010-SALE, 2200-TAX, 5010-COS, 1400-STK). |
| GDDRCR | CHAR(1) | D debit, C credit. |
| GDAMT / GDREF / GDDT | DECIMAL(11,2)/CHAR(8)/DECIMAL(8,0) | Amount / reference / date. |
| Field | Type | Meaning |
|---|---|---|
| RUNNO | 6S 0 | Weekly run number (e.g. 202632); re-run of the same is refused. |
| STORE / SKU | 4A / 8A | Store / SKU. |
| RPONHND / RPSOLD / RPSUG | 9P 2 | On-hand / week units sold / suggested order. |
| RPRANK | 1A | Mover rank F/M/S. |
| RPMSG | 2A | OR order, OS out-of-stock, RD raised, blank none. |
| Field | Type | Meaning |
|---|---|---|
| MKSEQ | 8S 0 | Stable markdown key (SKU digits + store ordinal, disjoint range). |
| STORE / SKU | 4A / 8A | Store / SKU. |
| OLDPRC / NEWPRC | 9P 2 | Before image / new shelf price. |
| MKPC / MKDT | 5P 2 / 8S 0 | Markdown % / date. |
| MKRSN | 1A | D discontinued, S slow (class C). |
| Field | Type | Meaning |
|---|---|---|
| CTSEQ | 8S 0 | Count sequence (key). |
| STORE / SKU | 4A / 8A | Store / SKU. |
| CNTQTY / BOOKQTY / CTVAR | 9P 2 | Counted / book / variance (count − book). |
| CTDT / CTSTAT | 8S 0 / 1A | Count date / O open, P processed. |
| Field | Type | Meaning |
|---|---|---|
| DSPER | 6S 0 | Period YYYYMM (e.g. 202608); re-close refused. |
| STORE / DEPT | 4A / 3A | Store / department. |
| DSSALES / DSCOST / DSMARG | 11P 2 | Net sales / cost / margin. |
| DSMGPC | 5P 2 | Margin % (half-up). |
| DSUNITS | 9P 2 | Units sold. |
| DSGRADE | 1A | P pass (≥ target), F fail. |
K SKU, K STORE — the SKU-first "where is this
line selling?" path; a partial keyed READE on a SKU walks every store carrying it (exercised by
RTSKUXR).K SKU, K TXNO — sales-by-SKU across the chain.K STORE, K TXDATE, K TXNO — the store-day path the
cash-up reads so a store's day is a keyed partial read, not a whole-file scan.2 stores (ST01 tax 8.25% float 200.00; ST02 tax 6.00% float 150.00), 3 departments (APP 50% / HOM 40% / GRO 20%), 6 SKUs across them (one discontinued: SK000302), 9 price-book rows (ST01 runs a 10% jumper promotion; every other price equals the SKU retail so the promo arithmetic is the only variable), and 9 opening store-stock rows.
BAZAAR and that the reference data is seeded
(once: RTSETUP then RTREFLD).TXSTAT='C') into RTSALE / RTSLIN
/ RTTEND.SBMJOB CMD(CALL PGM(BAZAAR/RTDAILY)).CALL BAZAAR/RTMENU.RTWEEK and review the RTREPL suggestions
and the on-order raises.Post-checks after the daily cycle (reconciling figures — the same ones the
rt_daily volume driver checks against an independent JS oracle):
RTPOSCP PRICED= equals the captured basket count; each basket
header foots to its own line nets; the promotion is half-up (3.897 → 3.90, not truncated).RTPOSPT POSTED=/LINES= reconcile; a sale of 3 and a return
of 1 net to −2 on the book (50 → 48); depletion is store-scoped; no store/SKU carries a
negative on-hand (oversell floored and counted).EXPCASH + EXPCARD + EXPVOUC = TLSALES for each till (e.g.
33.16 + 21.22 + 0.00 = 54.38); the same footing holds against the raw RTTEND vs posted TXTOTAL. Any
declared over/short shows as VARCASH with TLSTAT='V', never absorbed.RTGLPST ROWS=5 per trading store; the sale block foots
(cash = revenue + tax) and the whole batch balances (DR total = CR total, e.g. 84.88 = 84.88).RTTLRPT re-proves TENDER = SALES and counts OUTOFBAL tills.P).RTMONTH. Confirm RTMKDNA MARKED= and
RTDSUMA ROWS=, then reconcile:SELECT DSPER, STORE, DEPT, DSSALES, DSCOST, DSMARG, DSMGPC, DSGRADE FROM BAZAAR.RTDSUM WHERE DSPER = 202608;
F; a department that traded nothing summarises to zero and grades
F (it is not omitted).For the annual close, load the physical counts into RTSTKTK (CTSTAT='O') then submit
RTYEAR; reconcile the stocktake net variance, confirm all price rows carry the new effective
date (20270101) with promotions/markdowns cleared and the discontinued row closed (PRSTAT='X'), and confirm
every posted basket is archived (TXSTAT='A') with each stock row valued at cost.
Each program DSPLYs its result. Every cycle in BAZAAR/i is re-runnable — the
rt_daily and rt_cycles drivers run each cycle two and three times and prove the
books do not move. The guards are status flips and stable business keys:
| Situation | Behaviour | Action |
|---|---|---|
| Re-run RTPOSPT / RTPOSCP | Only TXSTAT='C' baskets are priced/posted; the flip to P is the guard. | Safe. A re-run posts 0 and stock does not double-deplete (still 48, not 45). |
| Re-run RTTILLR | The (STORE,DATE,TILL) key CHAIN(EN) guard refuses a second cash-up. | Safe. TILLS=0; still exactly one reconciliation row. |
| Re-run RTGLPST same day | Batch = earliest undistributed day; nothing new to post. | Safe. BATCH=0 ROWS=0 NOTHING TO POST; a genuinely new trading day IS distributed under its own batch. |
| Crash mid-basket (post) | The C→P flip is written AFTER the line work. | Re-run RTPOSPT: the unposted basket re-posts cleanly rather than half-posting. |
| Re-run RTWEEK | (RUNNO,STORE,SKU) guard; on-order only raised for un-raised suggestions. | Safe. SUGGESTED=0 / RAISED=0; on-order does not compound (still 53, not 106). |
| Re-run RTMONTH | Markdown key + (PER,STORE,DEPT) summary key guards. | Safe. MARKED=0 / ROWS=0; the price does not compound down (still 3.08). |
| Re-run RTYEAR | CTSTAT='P', PREFFDT≥new-date and TXSTAT≠'P' skip already-processed rows. | Safe. COUNTED=0 / ROLLED=0 / ARCHIVED=0; the book is not corrected twice. |
| Oversell | On-hand floors at zero, shortfall counted in the run's oversell total. | No negative book anywhere; investigate the counted shortfall and adjust stock. |
Implementation detail from src/sources.mjs. All objects are in library BAZAAR.
The RPG is written column-exact fixed-form (the C()/D() helpers emit C- and
D-specs) with /free blocks for the arithmetic.
%dech(v : len : 2)
— line discount, tax, markdown, margin percent. The tests assert the real rounding (3.897 →
3.90, 5.10840 → 5.11, 49.961 → 49.96).LNQTY, so gross/disc/net/tax
and stock movement all flow through the same code negative — no separate refund path,
and a return puts stock back with the same subtraction that a sale takes it out.The three logical files are genuine alternate access paths, not decoration. RTSKUXR
demonstrates it: it opens RTSTKLF (keyed SKU-first) and does a partial keyed READE on the
SKU alone — a read sequence the physical file (STORE-first) cannot do without a full scan. The
build oracle: 9 stock rows over 6 SKUs, SK000101 carried by both stores, 268 total units
(RTSKUXR SKUS=6 ROWS=9 MULTI=2 UNITS=268). Because chains move the file cursor, the batch
programs use explicit KLISTs and re-CHAIN the header/line after each lookup (documented inline as
"re-CHAIN ... the cursor moved").
| Program | Guard |
|---|---|
| RTPOSCP / RTPOSPT | Only TXSTAT='C' processed; RTPOSPT flips to P after the line work. |
| RTTILLR | CHAIN(EN) on the (STORE,TLDATE,TILL) key + LEAVESR. |
| RTGLPST | Batch = MIN(TXDATE) posted and not already in RTGLDIST; GDSEQ continues past MAX(GDSEQ). |
| RTREPLG | CHAIN(EN) on the (RUNNO,STORE,SKU) key + LEAVESR. |
| RTONORD | Processes only RPMSG OR/OS with RPSUG>0; flips RPMSG to RD. |
| RTMKDNA | CHAIN(EN) on the stable MKSEQ key + LEAVESR; also skips when new price = old. |
| RTDSUMA | CHAIN(EN) on the (DSPER,STORE,DEPT) key + LEAVESR. |
| RTSTKTA | Skips CTSTAT='P' count rows. |
| RTPBRLL | Skips rows already at PREFFDT ≥ 20270101. |
| RTARCH | Only TXSTAT='P' baskets are archived. |
RTGLPST is the one SQLRPGLE program. It selects the batch date and the current max sequence
with EXEC SQL SELECT ... INTO, accumulates each store's posted day (net, tax, total, cost of
sale) with RPG file reads, and inserts balanced rows with free-form embedded SQL:
-- batch = earliest posted day not yet distributed exec sql select coalesce(min(TXDATE),0) into :wbatch from BAZAAR.RTSALE where TXSTAT='P' and TXDATE not in (select distinct GDBATCH from BAZAAR.RTGLDIST); -- one balanced insert per GL leg exec sql insert into BAZAAR.RTGLDIST values (:wseq,:wbatch,:wst,:wacct,:wdc,:wamt,:wref,:wbatch);
Accounts: DR 1010-CASH = TXTOTAL, CR 4010-SALE = TXNET, CR
2200-TAX = TXTAX; DR 5010-COS / CR 1400-STK = Σ(line cost ×
qty). A disjoint 10-sequence block per store keeps a later trading day from colliding on the primary key.
Every program ends with a single DSPLY the operator (and the tests) read back. Section C.1 lists the format of each; a healthy daily cycle reads:
RTPOSCP PRICED=3 SKIP=0 RTPOSPT POSTED=3 LINES=4 SKIP=0 RTTILLR TILLS=1 SKIP=0 VAR=200 RTTILPR TILLS=1 VARIANCE=200 RTGLPST BATCH=20260801 ROWS=5 RTTLRPT TILLS 1.00 / TENDER 54.38 / SALES 54.38 / VARIANCE 200.00 / OUTOFBAL 1.00
%dech). Applied to every derived money figure.SBMJOB CMD(CALL PGM(BAZAAR/RTDAILY)).