VINTAGE/i runs a small winery's own operation end to end: harvest lots brought in from
vineyard blocks, fermentation batches blended from those lots, movements of wine through the cellar's
tanks and barrels, bottling runs into finished-goods SKUs, sales orders and invoicing into accounts
receivable, a standard-cost roll-up from grape cost to bottle cost, and a monthly GL post and close.
The logic is classic IBM i: fixed-form and free-form RPG/ILE programs over keyed DB2 for i
physical and logical files, two 5250 inquiry screens (one with a subfile), an ILE COBOL trial-balance
report, an SQLRPGLE GL-feed poster, and CL job cycles that chain the batch programs. This manual is the
reference for the operator who runs the online screens and the daily/monthly cycles, and for the
developer maintaining the application. It is grounded entirely in the committed source
(winery-app/src/sources.mjs, src/seed.mjs, and the four
test/vintage_*.mjs battle drivers). Everything runs in library VINTAGE.
VINTAGE/i covers the full life of a bottle of wine, from fruit on the crush pad to a receivable on the books:
VNLOT, per-tonne cost held to 4 decimal places).VNBATCH) draws
tonnage from one or more open lots (a blend at the must stage, VNBCOMP);
VNBATREL allocates the lot tonnage and rolls the grape cost onto the batch.VNCELL); VNCELLPOST moves the gallons in, prices the vessel per gallon,
and writes a transfer-in row to the durable cellar-movements ledger (VNCMOV).VNBTL) draws gallons out of a vessel, converts
them to bottles at a fixed rate, and adds finished goods to a SKU (VNSKU);
VNBTLPOST writes the transfer-out movement and completes the run.VNORD/VNORDL) are
allocated against SKU on-hand (VNORDREL), then invoiced (VNINVPOST):
inventory is relieved, an invoice (VNINV) is cut, and a debit is posted to the AR
ledger (VNAR) and the customer balance.VNCSTRL rolls each SKU's standard
bottle cost up from its completed bottling runs; VNGLPST posts the month's cellar and
AR movement into the GL feed as balanced DR/CR pairs; VNARRPT prints the AR trial
balance.Two properties are designed in and verified by the test battles, and both matter operationally:
BATCOST), becomes a cost-per-gallon in the cellar vessel
(CSTGAL) when the batch is cellared, is carried out into each bottling run's
RUNCOST as gallons are drawn, and finally becomes a per-bottle standard cost
(STDCOST) at the monthly roll-up. No cost is invented or lost along the chain: a SKU's
bottle cost is its runs' cost divided by its runs' bottles.Every posting program is idempotent: durable ledgers (VNCMOV, VNAR,
VNCSTH) are keyed on a sequence derived from a stable business key (the batch, run,
order or SKU number), so a re-run regenerates the same key and the CHAIN(EN) existence check
refuses to double-post. State-flag guards (BSTAT, RSTAT, OSTAT,
LSTAT, and BATCOST>0) skip work already done. This is what makes the whole
daily and monthly chains safely re-runnable, which section E leans on.
PRODUCTION DISTRIBUTION CLOSE (monthly)
---------- ------------ ---------------
VNBATREL grape cost -> batch VNORDREL allocate VNCSTRL cost roll-up
VNLOT -> VNBATCH (+VNBCOMP) VNORDL -> VNSKU.ALLOC runs -> VNSKU.STDCOST
VNCELLPOST batch -> cellar VNINVPOST invoice (+ VNCSTH history)
VNBATCH -> VNCELL (+VNCMOV 'I') VNSKU relieved VNGLPST GL post
VNBTLPOST cellar -> bottles VNINV + VNAR 'I' VNCMOV+VNAR -> VNGLFEED
VNCELL -> VNSKU (+VNCMOV 'O') + VNCUST.ARBAL VNARPT AR trial bal.
ONLINE (5250) LEDGERS (durable, idempotent)
------------- -----------------------------
VNMENU VNCMOV cellar movements (I/O/... keyed MOVSEQ)
1 -> VNCELLIQ (plain) VNAR receivables (I/P keyed ARSEQ)
2 -> VNMOVIQ (subfile) VNCSTH cost history ( keyed CSEQ)
VNGLFEED SQL GL feed (DR/CR keyed GLSEQ)
CYCLES: VNDAILY = VNBATREL -> VNCELLPOST -> VNBTLPOST -> VNORDREL -> VNINVPOST
VNMONTH = VNCSTRL -> VNGLPST -> VNARRPT
A single production event flows left to right: the harness (or an order-entry front end) seeds the planning rows — a batch header and its blend recipe, a bottling run, a sales order — and the daily cycle posts them, each program advancing one stage, updating masters and writing one durable ledger row per event. The monthly cycle then values and closes what the month produced.
| Object | Type | Role |
|---|---|---|
| VNVYD | PF | Vineyard-block master. |
| VNLOT | PF | Harvest / grape lot (priced per tonne). |
| VNLOTLF | LF | Lots by vineyard (where-grown access path). |
| VNBATCH | PF | Fermentation batch header. |
| VNBCOMP | PF | Batch composition (which lots, how many tons). |
| VNCELL | PF | Cellar location master (tank/barrel, book inventory). |
| VNCMOV | PF | Cellar-movements ledger (durable audit trail). |
| VNCMOVLF | LF | Movements by vessel (enquiry access path). |
| VNSKU | PF | Finished-goods SKU master (on-hand, alloc, std cost). |
| VNBTL | PF | Bottling-run header. |
| VNCUST | PF | Customer master. |
| VNORD / VNORDL | PF | Sales-order header / lines. |
| VNINV | PF | Invoice header (one per order invoiced). |
| VNAR | PF | AR ledger (durable receivables history). |
| VNARLF | LF | AR by customer (statement access path). |
| VNCSTH | PF | Standard-cost change history. |
| VNGLFEED | SQL table | GL feed the monthly close posts into (+ index VNGLFACC). |
| VNCELLD / VNMOVD / VNMENUD | DSPF | Cellar inquiry / movements subfile / menu display files. |
| VNARPRT | PRTF | AR aging / trial-balance printer file. |
| VNREFLD | RPGLE | Seed reference data (vineyards, lots, cellars, SKUs, customers). |
| VNBATREL | RPGLE | Grape-cost roll-up onto the batch. |
| VNCELLPOST | RPGLE | Cellar a fermented batch (daily). |
| VNBTLPOST | RPGLE | Post bottling runs (daily). |
| VNORDREL | RPGLE | Allocate sales-order lines (daily). |
| VNINVPOST | RPGLE | Invoice orders, post AR (daily). |
| VNCSTRL | RPGLE | Standard-cost roll-up (monthly). |
| VNGLPST | SQLRPGLE | GL post of cellar + AR movement (monthly). |
| VNCELLIQ / VNMOVIQ / VNMENU | RPGLE | Cellar inquiry / movements subfile / menu. |
| VNARRPT | CBLLE | ILE COBOL AR trial-balance report (monthly). |
| VNSETUP / VNDAILY / VNMONTH | CLP | Build CL + the two job cycles. |
The catalogue is 14 physical files, 3 logical files, 1 SQL table + 1 SQL index, 3 display files and 1 printer file, driven by 11 RPG programs, 1 ILE COBOL program and 3 CL programs. Sections D and F expand each.
VINTAGE/i has no CICS transaction identifiers and no transid switch. On IBM i each program
is reached by name from a 5250 command-entry line (or, for the batch programs, from a JOBQ /
scheduler). 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 VINTAGE — the
tested jobs run with LIBL = QSYS QGPL VINTAGE QTEMP and CURLIB = VINTAGE.
| To do this | Type on the command line |
|---|---|
| Open the operator main menu | CALL VINTAGE/VNMENU |
| Open the cellar inquiry screen directly | CALL VINTAGE/VNCELLIQ |
| Open the cellar-movements subfile screen directly | CALL VINTAGE/VNMOVIQ |
| Run the daily production/distribution cycle | CALL VINTAGE/VNDAILY (or SBMJOB it) |
| Run the monthly cost roll-up & close | CALL VINTAGE/VNMONTH |
| Build/rebuild every object & program | CALL VINTAGE/VNSETUP |
| Load the reference (master) data | CALL VINTAGE/VNREFLD |
The batch drivers take no CALL parameters. They are date-independent postings: each program scans
its driving file (batches, runs, orders, SKUs) and posts every row that is in a state ready to advance, so
a scheduled submission is a bare CALL. Only VNMENU, VNCELLIQ and
VNMOVIQ are interactive; the batch programs run to completion and DSPLY a one-line
POSTED=/SKIP= result.
VINTAGE/i's interactive surface is a menu that routes by program-to-program CALL to two
read-only inquiry screens. There is no online data-entry or maintenance screen — masters and orders
are created by the seed program / harness (or an upstream order-entry front end) and advanced by the batch
posters; the 5250 screens are for looking, not editing.
A plain menu record. The operator keys an option and Enter; option 1 issues
CALL 'VNCELLIQ', option 2 issues CALL 'VNMOVIQ', any other non-blank
option redisplays with Invalid option. in the message line. F3 ends
the program.
The operator keys a cellar/tank ID and Enter. VNCELLIQ CHAINs VNCELL and, on a
hit, renders the vessel type, capacity, current gallons, contents variety, source batch, and a
derived fill percentage computed as (CURGAL × 100) / CAPGAL. A miss clears the
detail and shows Cellar location not found. — never stale values.
The app's one subfile screen (DDS record MSFL under control record MCTL,
SFLPAG(5) per page, SFLSIZ(20), ROLLUP/ROLLDOWN paging,
SFLEND(*MORE)). The operator keys a cellar/tank ID; VNMOVIQ shows the vessel's
current gallons in the header, then loads every movement for that vessel — in sequence order via the
VNCMOVLF access path — into the subfile. It uses the classic load-then-display
discipline: clear (SFLCLR) and reload happen after the EXFMT that read
the operator's key, so a fresh enquiry genuinely clears the previous vessel's rows (an empty vessel shows
zero rows). Negative (bottling-draw) gallons render with a trailing minus, e.g. 500.00-.
| Field | Type (DDS) | Shows |
|---|---|---|
| MSEQD | 10Y,0 output | Movement sequence (from MOVSEQ). |
| MTYPD | 1A output | Movement type: I transfer-in, O transfer-out (draw). |
| MGALD | 12A output | Gallons (edited; negative on a draw). |
| MCSTD | 12A output | Cost (edited; negative on a draw). |
| MREFD | 8A output | Reference (source batch or run number). |
ROLLUP(25)/ROLLDOWN(26)).CA03.Honest statement: VINTAGE/i does not model a true four-eyes maker–checker / separate-authorization workflow, and it has no interactive posting at all — the 5250 screens are read-only inquiries. Postings are made by the unattended batch programs; there is no "one user posts, a second user approves" gate anywhere in the code. The manual documents the control model the application does have, all enforced in the data/posting layer:
VNCMOV (transfer-in I with a positive gallon/cost, bottling draw
O with a negative gallon/cost, each carrying a reference to its source batch or run and a
memo). Every receivable movement is journaled to VNAR (invoice debit I, cash
payment credit P). Every standard-cost change is journaled to VNCSTH with a
before/after image (OLDCOST→NEWCOST). These are the after-the-fact
accountability trails.10000000 + batch for cellaring, 30000000 + run for bottling,
50000000 + order for AR, 60000000 + SKU for cost history), so a re-submitted
cycle cannot double-post: the CHAIN(EN) existence check on the ledger refuses the second
write. This is the control that lets operations re-run a failed cycle safely (section E).VNBATREL skips a batch not BSTAT='F' or already
costed (BATCOST>0); VNCELLPOST skips a batch not fermented-with-gallons or
with no target vessel and rolls it to BSTAT='C'; VNBTLPOST skips a run not
RSTAT='P'; VNORDREL/VNINVPOST skip lines/orders not
'O' and flip them to 'I'.VNGLPST posts only the movement
since the prior close (each account's new total minus what it has already posted), always as balanced
DR/CR pairs, and continues the GLSEQ from the existing maximum — so a second run in
the same month posts nothing and the feed stays balanced.VNINV's IORDNO/ICUSTID, VNAR's ACUSTID)
so an invoice/AR value cannot be silently clobbered by a later CHAIN — see F.3.In sum, the control posture is durable ledgers + idempotency + state gating + GL balancing, enforced by the posting programs, rather than a segregation-of-duties approval workflow.
VINTAGE/i runs as two job cycles, not a monolithic nightly job: a daily cycle that posts
production and distribution, and a monthly cycle that rolls up cost and closes. There is deliberately
no weekly cycle — a winery's cadence is harvest-season driven, not a weekly MRP regen, so the
weekly-shaped work (invoicing) sits inside the daily distribution chain, matching real small-winery
practice. Both cycles are CL programs that simply CALL their component programs in order; all
the batch programs are parameterless.
-- run the whole daily production + distribution cycle SBMJOB CMD(CALL PGM(VINTAGE/VNDAILY)) JOB(VNDAILY) -- run the monthly cost roll-up + GL close SBMJOB CMD(CALL PGM(VINTAGE/VNMONTH)) JOB(VNMONTH)
The two CL cycles are, verbatim in intent from the source:
VNDAILY: CALL VNBATREL -> CALL VNCELLPOST -> CALL VNBTLPOST -> CALL VNORDREL -> CALL VNINVPOST VNMONTH: CALL VNCSTRL -> CALL VNGLPST -> CALL VNARRPT
| Program | Purpose | Reads / writes | Outputs (DSPLY + effect) | Cycle |
|---|---|---|---|---|
| VNBATREL | Roll grape cost from a batch's lot lines onto the batch; allocate lot tonnage. | Reads VNBCOMP; updates VNLOT (TONALLOC/LSTAT), VNBATCH (BATCOST/GALACT). |
VNBATREL POSTED=n SKIP=n; BATCOST=Σ(tons×toncost), GALACT=tons×150. |
Daily |
| VNCELLPOST | Transfer a fermented batch into its target cellar vessel; price the vessel per gallon. | Updates VNCELL (CURGAL/CSTGAL/contents), VNBATCH (→'C'); writes VNCMOV 'I'. |
VNCELLPOST POSTED=n SKIP=n; CSTGAL=BATCOST/gallons. |
Daily |
| VNBTLPOST | Draw gallons out of a vessel, convert to bottles, add to a SKU; complete the run. | Updates VNCELL (CURGAL−), VNSKU (ONHAND+), VNBTL (→'C', BTLQTY/RUNCOST); writes VNCMOV 'O'. |
VNBTLPOST POSTED=n SKIP=n; bottles=gal/0.2, RUNCOST=gal×CSTGAL. |
Daily |
| VNORDREL | Allocate open order lines' bottles against SKU on-hand. | Reads VNORDL; updates VNSKU (ALLOC+). |
VNORDREL ALLOC=n SKIP=n; ALLOC += cases×12. |
Daily |
| VNINVPOST | Invoice open orders: relieve inventory, cut the invoice, post AR debit. | Updates VNORDL/VNORD (→'I'), VNSKU (ONHAND−/ALLOC−), VNCUST (ARBAL+); writes VNINV, VNAR 'I'. |
VNINVPOST INVOICED=n SKIP=n; INVAMT=Σ(bottles×uprice). |
Daily |
| VNCSTRL | Roll each SKU's standard bottle cost up from its completed bottling runs. | Reads VNBTL; updates VNSKU (STDCOST); writes VNCSTH. |
VNCSTRL COSTED=n; STDCOST=ΣRUNCOST/ΣBTLQTY. |
Monthly |
| VNGLPST | Sum the month's cellar & AR movement, post balanced DR/CR pairs to the GL feed. | Reads VNCMOV, VNAR; inserts into SQL VNGLFEED under commitment. |
VNGLPST BATCH=202610 ROWS=n; 3 balanced pairs = 6 rows on a first close. |
Monthly |
| VNARRPT | ILE COBOL AR trial-balance report (invoiced vs. payments vs. txn count). | Reads VNAR sequentially. |
VNARRPT INVOICED /PAYMENTS /TXNS lines. |
Monthly |
The daily cycle walks one wine cohort from batch to receivable. Using the seeded scenario the test battle asserts against (two batches, two bottling runs, two orders):
BA000001 (Cabernet) draws LOT00001 (10 t @
1200) + LOT00002 (5 t @ 1245): BATCOST = 12000 + 6225 = 18225.0000,
GALACT = 15 × 150 = 2250.00; the lots' TONALLOC is bumped and a fully
allocated lot flips to LSTAT='A'. Batch BA000002 (Chardonnay) draws 8 t
@ 900: 7200.0000 / 1200.00 gal.CEL001 now holds 2,250.00 gal at CSTGAL = 18225/2250 =
8.1000; CEL003 holds 1,200.00 @ 6.0000; both batches roll to
BSTAT='C' and two 'I' movements land in VNCMOV.BT000001 draws 2,000 gal from CEL001:
bottles = 2000/0.2 = 10000 onto SKU00001, RUNCOST = 2000 × 8.1 =
16200.00, and CEL001 falls to 250.00 gal; run BT000002 draws all
1,200 gal from CEL003 → 6,000 bottles, 7200.00, vessel to 0.00. Each
draw writes a negative-gallon/cost 'O' movement.OR000001 (50 cases) allocates 600 bottles onto
SKU00001.ALLOC; OR000002 (30 cases) allocates 360.600 × 24.00 = 14400.00; 360 × 18.00 =
6480.00), posts two 'I' AR debits (summing 20880.00) and bumps each customer's
ARBAL.Expected DSPLY (the seeded two-cohort daily run):
VNBATREL POSTED=2 SKIP=0
VNCELLPOST POSTED=2 SKIP=0
VNBTLPOST POSTED=2 SKIP=0
VNORDREL ALLOC=2 SKIP=0
VNINVPOST INVOICED=2 SKIP=0
CEL001 gallons and each customer's AR balance do not move on a re-run.After a month of dailies, the monthly cycle values and closes:
SKU00001 = 16200.00 / 10000 = 1.6200, SKU00002 = 7200.00 / 6000 = 1.2000. A
cost-history row is written for every change (OLDCOST=0 → NEWCOST).202610: cellar-in DEBIT 1410-CELR / CREDIT 1420-PRAB =
25,425.00 (18225+7200); bottling CREDIT 1410-CELR / DEBIT 1430-FG =
23,400.00 (16200+7200); AR invoices DEBIT 1210-AR / CREDIT 4000-SAL =
20,880.00 (14400+6480). Total debits equal total credits.INVOICED 20880.00,
PAYMENTS (cash credits, if any) and TXNS 2.Expected DSPLY (monthly close of the seeded month):
VNCSTRL COSTED=2
VNGLPST BATCH=202610 ROWS=6
VNARRPT INVOICED 20880.00
VNARRPT PAYMENTS 0.00
VNARRPT TXNS 2
VNCSTRL writes no new
cost-history when nothing changed, and VNGLPST posts ROWS=0 because the movement
since the last close is zero — the feed stays at 6 rows.VNDAILY encodes exactly this order.VNARRPT reports what VNINVPOST posted to AR.VNAR directly), but VNMONTH runs the roll-up first so the GL post reflects
rolled-up costs.Every file is a DDS-described DB2 for i object in library VINTAGE, created by
VNSETUP from source members in VINTAGE/QDDSSRC (the SQL GL feed from
QSQLSRC). Packed decimals carrying money are held to enough precision that the standard-cost
roll-up rounds cleanly: per-tonne grape cost and per-gallon / per-bottle cost are 11P 4
(four decimals), while ledger amounts and balances are 11P 2. Field lengths below are
the DDS declarations verbatim (nA=character, nP d=packed with d decimals,
nS d=zoned/signed with d decimals). Keys are marked K.
| Field | Type | Meaning |
|---|---|---|
| VYDID | 6A K | Vineyard block / parcel ID (VYD001). |
| VYDNAME | 25A | Block name (RIDGE BLOCK). |
| VARIETY | 15A | Grape variety grown in the block. |
| ACRES | 6P 2 | Planted acres. |
| VSTAT | 1A | Status (A=active). |
Tonnage brought in from a vineyard block, priced per tonne. TONCOST is 11P 4
so a fractional-cent per-tonne price still rolls up cleanly through fermentation and bottling. The lot is the
entry point of grape cost into the whole chain.
| Field | Type | Meaning |
|---|---|---|
| LOTID | 8A K | Harvest-lot ID (LOT00001). |
| VYDID | 6A | Source vineyard block. |
| HVDT | 8S 0 | Harvest date (CCYYMMDD). |
| VARIETY | 15A | Grape variety. |
| TONS | 9P 2 | Tonnage received. |
| TONCOST | 11P 4 | Cost per tonne (grape cost enters here). |
| TONALLOC | 9P 2 | Tonnage allocated to batches (bumped by VNBATREL). |
| LSTAT | 1A | Status: O=open (still assignable), A=assigned (fully allocated), C=closed. |
Non-master logical file keyed VYDID, LOTID — a where-grown enquiry path (all lots from
one block). The base VNLOT's own key is LOTID only.
One batch is one tank's worth of must/wine through fermentation. BATCOST accumulates grape
cost as lots are assigned (the standard-cost roll-up's starting point); GALACT is set by
VNBATREL from tonnage at the fixed 150 gal/ton yield.
| Field | Type | Meaning |
|---|---|---|
| BATCHNO | 8A K | Batch number (2 letters + 6 digits, BA000001). |
| VARIETY | 15A | Wine variety. |
| VINTAGE | 4S 0 | Vintage year. |
| STARTDT | 8S 0 | Fermentation start date. |
| GALEXP | 9P 2 | Expected gallons (planning figure). |
| GALACT | 9P 2 | Actual gallons = tons × 150 (set by VNBATREL). |
| TGTCELL | 6A | Target cellar vessel this batch will be transferred into. |
| BSTAT | 1A | Status: F=fermenting, C=cellared, X=cancelled. |
| BATCOST | 11P 4 | Rolled-up grape cost (Σ tons×toncost). |
Which lots, and how many tons of each, went into a batch. A batch can draw multiple lots (a blend at the must stage); a lot can feed multiple batches (split across tanks). VNBATREL reads these lines to roll grape cost onto the batch and allocate the lots.
| Field | Type | Meaning |
|---|---|---|
| BATCHNO | 8A K | Batch this line belongs to. |
| BSEQ | 3S 0 K | Composition line sequence within the batch. |
| LOTID | 8A | Harvest lot drawn. |
| TONSUSED | 9P 2 | Tons of that lot used in this batch. |
A tank or barrel. CURGAL is the book inventory the movements ledger keeps in step with;
CSTGAL is the vessel's cost per gallon, set when a batch is cellared into it.
| Field | Type | Meaning |
|---|---|---|
| CELLID | 6A K | Vessel ID (CEL001). |
| CTYPE | 1A | Vessel type: T=tank, B=barrel. |
| CAPGAL | 9P 2 | Capacity in gallons. |
| CURGAL | 9P 2 | Current gallons held (book inventory). |
| CSTGAL | 11P 4 | Cost per gallon = BATCOST / gallons cellared. |
| CONTVAR | 15A | Contents variety. |
| CONTBAT | 8A | Source batch that filled the vessel. |
| CSTAT | 1A | Status (A=active). |
One bottled product. STDCOST is the rolled-up per-bottle standard cost (11P 4);
ONHAND and ALLOC are in bottles, not cases.
| Field | Type | Meaning |
|---|---|---|
| SKUID | 8A K | SKU ID (SKU00001). |
| SKUDESC | 30A | Description (CABERNET SAUVIGNON 750ML). |
| VARIETY | 15A | Variety. |
| VINTAGE | 4S 0 | Vintage year. |
| BTLSIZE | 4P 0 | Bottle size in ml (750). |
| STDCOST | 11P 4 | Standard per-bottle cost (set by VNCSTRL). |
| PRICE | 11P 2 | List price per bottle. |
| ONHAND | 9P 2 | On-hand bottles. |
| ALLOC | 9P 2 | Allocated bottles (reserved by open orders). |
| SSTAT | 1A | Status (A=active). |
Takes gallons out of a cellar vessel and produces bottles of a SKU at the fixed 0.2 gal/bottle rate.
RUNCOST carries the vessel's cost/gallon out into the run — the middle link of the cost
chain, which VNCSTRL later divides by BTLQTY to price the bottle.
| Field | Type | Meaning |
|---|---|---|
| RUNNO | 8A K | Run number (BT000001). |
| CELLID | 6A | Source vessel drawn from. |
| SKUID | 8A | Target finished-goods SKU. |
| BTLDT | 8S 0 | Bottling date. |
| GALUSED | 9P 2 | Gallons drawn from the vessel. |
| BTLQTY | 9P 0 | Bottles produced = GALUSED / 0.2 (set by VNBTLPOST). |
| RSTAT | 1A | Status: P=planned, C=complete. |
| RUNCOST | 11P 2 | Run cost = GALUSED × vessel CSTGAL. |
The durable audit trail for wine in the cellar. Every posting program contributes to one accumulating
history keyed on MOVSEQ, whose value is derived from a stable business key so a re-run
regenerates the same key and is refused rather than double-posted. A transfer-in (I) carries a
positive gallon/cost; a bottling draw (O) carries a negative gallon/cost.
| Field | Type | Meaning |
|---|---|---|
| MOVSEQ | 8S 0 K | Movement sequence (10000000+batch for xfer-in, 30000000+run for draw). |
| CELLID | 6A | Vessel this movement is against. |
| MTYPE | 1A | Type: I=transfer-in, O=transfer-out (draw). |
| MDT | 8S 0 | Movement date. |
| MGAL | 9P 2 | Gallons (negative on a draw). |
| MCOST | 11P 2 | Cost (negative on a draw). |
| MREF | 8A | Reference: source batch (I) or run (O). |
| MMEMO | 25A | Free-text memo (BATCH TRANSFER TO CELLAR / BOTTLING DRAW). |
Non-unique logical file keyed CELLID, MOVSEQ. The base PF's MOVSEQ-only key has
no relationship to CELLID, so the movements-inquiry screen (VNMOVIQ) reads this path to browse
one vessel's history in sequence order.
| Field | Type | Meaning |
|---|---|---|
| CUSTID | 6A K | Customer ID (CUS001). |
| CUSTNAME | 30A | Customer name. |
| TERMS | 2A | Payment terms in days (30). |
| CRLIMIT | 11P 2 | Credit limit. |
| ARBAL | 11P 2 | Outstanding AR balance (bumped by VNINVPOST). |
| CSTAT | 1A | Status (A=active). |
VNORD is UNIQUE keyed ORDNO; VNORDL is UNIQUE keyed
ORDNO, OSEQ. A case is 12 bottles (fixed, matching the bottling conversion).
| File.Field | Type | Meaning |
|---|---|---|
| VNORD.ORDNO | 8A K | Order number (OR000001). |
| VNORD.CUSTID | 6A | Ordering customer. |
| VNORD.ORDDT | 8S 0 | Order date. |
| VNORD.OSTAT | 1A | Status: O=open, I=invoiced, X=cancelled. |
| VNORD.ORDTOT | 11P 2 | Order total (set at invoice). |
| VNORDL.ORDNO | 8A K | Owning order. |
| VNORDL.OSEQ | 3S 0 K | Line sequence. |
| VNORDL.SKUID | 8A | SKU ordered. |
| VNORDL.CASES | 7P 2 | Cases ordered (×12 = bottles). |
| VNORDL.UPRICE | 11P 2 | Unit price per bottle. |
| VNORDL.LSTAT | 1A | Status: O=open, I=invoiced. |
One row per order invoiced. Note the deliberately distinct field names IORDNO and
ICUSTID (not the shared ORDNO/CUSTID) — the structural fix for
this emulator's flat-symbol-table hazard, detailed in F.3.
| Field | Type | Meaning |
|---|---|---|
| INVNO | 8A K | Invoice number (= order number in this app). |
| IORDNO | 8A | Order invoiced (distinct name, see F.3). |
| ICUSTID | 6A | Customer billed (distinct name, see F.3). |
| INVDT | 8S 0 | Invoice date. |
| INVAMT | 11P 2 | Extended total = Σ(bottles × UPRICE). |
| INVCOST | 11P 2 | Extended standard cost = Σ(bottles × STDCOST). |
Durable receivables history. ARSEQ = 50000000+order for an invoice, so a re-run
regenerates the same key and the CHAIN(EN) guard refuses the second write. ACUSTID
is a distinct name (not CUSTID) for the same F.3 reason. VNARLF is a logical file
keyed ACUSTID, ARSEQ — the customer-statement access path.
| Field | Type | Meaning |
|---|---|---|
| ARSEQ | 8S 0 K | AR sequence (50000000+order digits). |
| ACUSTID | 6A | Customer (distinct name, see F.3). |
| ATYPE | 1A | Type: I=invoice (debit), P=cash payment (credit). |
| ADT | 8S 0 | Posting date. |
| AAMT | 11P 2 | Amount. |
| AREF | 8A | Reference (order number). |
Every standard-cost change on a SKU, with a before/after image, so the monthly close has an audit trail.
CSEQ = 60000000+SKU.
| Field | Type | Meaning |
|---|---|---|
| CSEQ | 8S 0 K | Cost-history sequence (60000000+SKU digits). |
| SKUID | 8A | SKU whose cost changed. |
| OLDCOST | 11P 4 | Cost before the change. |
| NEWCOST | 11P 4 | Cost after the change. |
| CDT | 8S 0 | Change date. |
| CREASON | 1A | Reason code (R=roll-up). |
An SQL table (not a DDS PF), created by RUNSQLSTM over QSQLSRC/VNGLFD, that the
monthly close (VNGLPST) inserts balanced DR/CR pairs into under commitment control. Index
VNGLFACC is on (ACCT, DRCR) — the access path VNGLPST's
“already-posted” sub-selects use.
| Column | SQL type | Meaning |
|---|---|---|
| GLSEQ | DECIMAL(8,0) PK | Feed sequence, continued from the prior close's max. |
| GLBATCH | DECIMAL(6,0) | Close batch = period (202610). |
| ACCT | CHAR(9) | GL account (see the account map in F.2). |
| DRCR | CHAR(1) | D=debit, C=credit. |
| AMT | DECIMAL(11,2) | Posted amount. |
| GLREF | CHAR(8) | Reference (CELRIN / BOTTLE / ARINV). |
| GLDT | DECIMAL(8,0) | Post date (20261031). |
| Conversion | Value | Where used |
|---|---|---|
| Grape yield | 1 ton → 150.00 gallons | VNBATREL: GALACT = tons × 150. |
| Bottle fill | 0.2 gal / bottle | VNBTLPOST: bottles = GALUSED / 0.2. |
| Case size | 12 bottles / case | VNORDREL / VNINVPOST: bottles = CASES × 12. |
ONHAND/ALLOC/CURGAL are gallons
or bottles as noted, never cases. The one place cases appear is VNORDL.CASES; everything
downstream is bottles. This is why a 50-case order shows as 600 bottles allocated and relieved.This section is the operator's procedural guide: what to run when, what a good run looks like, and what to do when one fails. It uses the seeded two-cohort scenario the test battles assert against, so every figure here is a number you can reconcile against a real run.
The daily job is VNDAILY, which chains the five daily posters in the fixed order
VNBATREL → VNCELLPOST → VNBTLPOST → VNORDREL → VNINVPOST. Run it once per
operating day after the day's planning rows (batch headers + blend recipes, bottling runs, sales orders) have
been seeded by the harness / order-entry front end.
VINTAGE on the library list
(LIBL = QSYS QGPL VINTAGE QTEMP, CURLIB = VINTAGE). VNDAILY
issues its own ADDLIBLE LIB(VINTAGE) as a belt-and-braces step.SBMJOB CMD(CALL PGM(VINTAGE/VNDAILY)) JOB(VNDAILY)Or
CALL VINTAGE/VNDAILY interactively for an attended run.VNBATREL POSTED=2 SKIP=0 VNCELLPOST POSTED=2 SKIP=0 VNBTLPOST POSTED=2 SKIP=0 VNORDREL ALLOC=2 SKIP=0 VNINVPOST INVOICED=2 SKIP=0followed by the banner
VINTAGE daily production and distribution cycle complete.BA000001: BATCOST=18225.0000, GALACT=2250.00;
BA000002: 7200.0000 / 1200.00.CEL001: CSTGAL=8.1000, drawn down to CURGAL=250.00
after bottling; CEL003: 6.0000, drawn to 0.00.SKU00001=9400 (10000−600),
SKU00002=5640 (6000−360); both ALLOC back to 0.CUS001 ARBAL=14400.00, CUS002 ARBAL=6480.00.VNCMOV before running the monthly close.The monthly job is VNMONTH, chaining VNCSTRL → VNGLPST → VNARRPT. Run it
once after the month's dailies have all posted.
SBMJOB CMD(CALL PGM(VINTAGE/VNMONTH)) JOB(VNMONTH)
SKU00001 = 16200.00 / 10000 = 1.6200,
SKU00002 = 7200.00 / 6000 = 1.2000. A cost-history row is written per change
(OLDCOST=0 → NEWCOST). DSPLY: VNCSTRL COSTED=2.VNGLFEED under batch 202610. DSPLY: VNGLPST BATCH=202610 ROWS=6.VNARRPT INVOICED 20880.00 VNARRPT PAYMENTS 0.00 VNARRPT TXNS 2
These are the numbers a correct close produces, and how they tie together. Every one is asserted by the monthly battle.
| GL pair | Debit account | Credit account | Amount | Derivation |
|---|---|---|---|---|
| Cellar-in | 1410-CELR | 1420-PRAB | 25,425.00 | batch costs 18,225 + 7,200 |
| Bottling | 1430-FG | 1410-CELR | 23,400.00 | run costs 16,200 + 7,200 |
| AR invoices | 1210-AR | 4000-SAL | 20,880.00 | invoices 14,400 + 6,480 |
1410-CELR is debited 25,425 (wine in) and credited
23,400 (wine bottled out), a net debit of 2,025 — the standard-cost value of the 250 gallons
still in CEL001 (250 × 8.10 = 2,025.00). Cost is conserved across the cellar exactly
as gallons are.1210-AR debit (20,880.00) equals
VNARRPT INVOICED and equals the sum of the two customers' AR balances
(14,400 + 6,480).PAYMENTS line is 0.00 in the seeded scenario because no
ATYPE='P' cash credits were posted. When the harness posts payments, they show here as the
credit side of the AR trial balance and reduce VNCUST.ARBAL; the GL AR pair still reflects only
the invoice movement.Because every posting program is idempotent (durable ledgers keyed on a stable business key + state-flag guards, see A.2 and B.3), the standing rule is simple: if a cycle fails part-way, just run it again. Nothing double-posts.
VNDAILY. Programs that already posted DSPLY
POSTED=0 (or ALLOC=0/INVOICED=0) and skip; the ones that had not
yet run post normally. A full re-run over already-posted data prints all zeros and moves nothing —
the battle asserts CEL001 gallons and each customer's ARBAL are unchanged on a
second run.VNMONTH. VNCSTRL writes no new
cost-history when nothing changed (COSTED=0); VNGLPST posts
ROWS=0 because the movement since the last close is zero and the feed stays at 6 rows;
VNARRPT is a read-only report and can be re-run freely.CALL VINTAGE/VNCELLPOST, etc.), so you can re-run just one stage; the same idempotency
guards apply.VNGLPST continues
GLSEQ from the existing maximum and posts only the movement not yet posted, so a re-run
completes the close without colliding on the primary key or double-counting.This section is for the developer maintaining VINTAGE/i. Everything below is drawn from the committed
source (winery-app/src/sources.mjs), where each program is held as a fixed-form/free RPG,
ILE COBOL, CL or SQL member and loaded into VINTAGE's source physical files by
src/seed.mjs.
Eleven RPG programs, one ILE COBOL program, three CL programs. The batch posters share one shape: an
F-spec block declaring the files, D-spec work fields, a SETLL *LOVAL + READ-loop
over the driving file calling a per-row subroutine, and a closing DSPLY of the
POSTED=/SKIP= result. All are parameterless.
| Program | Type | Files open together (F-spec) | Driving loop |
|---|---|---|---|
| VNREFLD | RPGLE | VNVYD, VNLOT, VNCELL, VNSKU, VNCUST (all O) | straight-line writes of seed masters. |
| VNBATREL | RPGLE | VNBATCH (UF), VNBCOMP (IF), VNLOT (UF) | READ VNBATCH → READE VNBCOMP per batch. |
| VNCELLPOST | RPGLE | VNBATCH (UF), VNCELL (UF), VNCMOV (UF A) | READ VNBATCH. |
| VNBTLPOST | RPGLE | VNBTL (UF), VNCELL (UF), VNSKU (UF), VNCMOV (UF A) | READ VNBTL. |
| VNORDREL | RPGLE | VNORDL (UF), VNSKU (UF) | READ VNORDL. |
| VNINVPOST | RPGLE | VNORD (UF), VNORDL (UF), VNSKU (UF), VNCUST (UF), VNINV (O A), VNAR (UF A) | READ VNORD → READE VNORDL per order. |
| VNCSTRL | RPGLE | VNSKU (UF), VNBTL (IF), VNCSTH (UF A) | READ VNSKU → nested READ VNBTL per SKU. |
| VNGLPST | SQLRPGLE | VNCMOV (IF), VNAR (IF) + embedded SQL over VNGLFEED | two accumulate loops, then SQL inserts. |
| VNCELLIQ | RPGLE | VNCELLD (CF WORKSTN), VNCELL (IF) | EXFMT / CHAIN inquiry loop. |
| VNMOVIQ | RPGLE | VNMOVD (CF WORKSTN SFILE), VNCELL (IF), VNCMOVLF (IF) | load-then-display subfile loop. |
| VNMENU | RPGLE | VNMENUD (CF WORKSTN) | EXFMT / CALL router. |
| VNARRPT | CBLLE | AR (VNAR, INDEXED SEQUENTIAL) | READ NEXT / EVALUATE AR-TYPE. |
| VNSETUP | CLP | — | DLTF/CRTPF/CRTLF/CRTDSPF/CRTPRTF/RUNSQLSTM/CRTBNDRPG/CRTBNDCBL. |
| VNDAILY / VNMONTH | CLP | — | ADDLIBLE + ordered CALLs + banner. |
Batch-ID convention: business IDs are 2 letters + 6 digits (BA000001, BT000001,
OR000001). The posters extract the numeric part with
%dec(%subst(id:3:6):8:0) to build the ledger key.
Two kinds of constants govern the whole application: the physical conversions (D.3) and the ledger-key
ranges. The key ranges are disjoint per program and each derived from a stable business key, which is
what makes the CHAIN(EN) existence check a correct idempotency guard.
| Ledger | Key formula | Written by | Range |
|---|---|---|---|
| VNCMOV | 10000000 + batch digits | VNCELLPOST (transfer-in I) | 10,000,001… |
| VNCMOV | 30000000 + run digits | VNBTLPOST (draw O) | 30,000,001… |
| VNAR | 50000000 + order digits | VNINVPOST (invoice I) | 50,000,001… |
| VNCSTH | 60000000 + SKU digits | VNCSTRL (roll-up) | 60,000,001… |
| VNGLFEED | max(GLSEQ) + 1 | VNGLPST (running) | continued from prior close |
The GL account map VNGLPST posts against (hard-coded in the program's glrow calls):
| Account | Meaning | Movement |
|---|---|---|
| 1410-CELR | Cellar WIP inventory | DEBIT on cellar-in, CREDIT on bottling. |
| 1420-PRAB | Grape / production cost absorbed | CREDIT on cellar-in. |
| 1430-FG | Finished-goods inventory | DEBIT on bottling. |
| 1210-AR | Accounts receivable | DEBIT on invoice. |
| 4000-SAL | Sales revenue | CREDIT on invoice. |
select coalesce(sum(AMT),0) … where ACCT=…
and DRCR=…) and posts a pair only if the difference is positive. This is why a second close in
the same month posts ROWS=0 and the feed stays balanced.This emulator's RPG resolves all unqualified field names through one flat global symbol table
shared by every open DISK file in the program. There is no per-file qualification and no PREFIX/RENAME
equivalent for F-spec files (unlike the WORKSTN/PRINTER format-conflict check the engine does perform). So if
two files open together both declare a field named ORDNO, they are the same variable at
runtime, and a CHAIN/READ against one file silently overwrites the value the other
file's record just held.
VNINVPOST is where this bites: it has VNORD, VNORDL,
VNINV and VNAR all open at once. If VNINV reused
ORDNO/CUSTID, then building the invoice header and then CHAINing VNCUST
or writing VNAR afterward would clobber the invoice's order/customer with no compile or runtime
diagnostic. The structural fix, applied in the DDS, is to give these files distinct field names:
| File | Renamed field | Instead of | Why |
|---|---|---|---|
| VNINV | IORDNO | ORDNO | open alongside VNORD/VNORDL which own ORDNO. |
| VNINV | ICUSTID | CUSTID | open alongside VNCUST/VNORD which own CUSTID. |
| VNAR | ACUSTID | CUSTID | open alongside VNCUST in VNINVPOST. |
The program itself stages the volatile values into work fields (WORD, WCUST,
WSEQ) up front and writes from those, and it uses two further defensive patterns that recur
across the posters:
VNCSTRL re-CHAINs the SKU on
WSKU (not bare SKUID) because the inner VNBTL scan leaves
SKUID holding the last bottling run read, not the SKU being costed.VNINVPOST updates each order line right after
READE, while the cursor still sits on it — a (ORDNO,OSEQ)-keyed file
cannot be safely re-CHAINed on ORDNO alone (that always lands on the first line for the
order).CHAIN(EN) idempotency guard before relieving inventory or touching masters, so a
refused re-run touches nothing.The only program that crosses the DDS/SQL boundary. It reads the two DDS ledgers (VNCMOV,
VNAR) with native RPG READ loops to accumulate the month's movement, then uses
embedded SQL to read and write the VNGLFEED table:
select coalesce(max(GLSEQ),0) into :wseq from VINTAGE.VNGLFEED — continue the feed
sequence from the prior close.select coalesce(sum(AMT),0) into :wpin/:wpout/:wparinv … where ACCT=… and
DRCR=… — how much of each account/side is already posted, so only the net movement
is written.insert into VINTAGE.VNGLFEED values (:wseq,:wbatch,:wacct,:wdc,:wamt,:wref,20261031) in the
glrow subroutine, which increments WROWS only when
sqlcod = 0 — a refused insert is not counted as a posted row.VNCELLIQ (plain) clears every output field and sets the not-found message before the
CHAIN, then overwrites them only on a hit, so a miss never shows a previous vessel's values. The fill
percentage is derived, not stored: wpct = (curgal * 100) / capgal, guarded by
capgal > 0.
The one subfile program (DDS record MSFL under control MCTL,
SFLPAG(5)/SFLSIZ(20)). The critical ordering: the SFLCLR (indicator
31) write and the reload happen after the EXFMT MCTL that reads the operator's key,
never before it — clearing ahead of the display would throw away the rows just loaded. Rows are read
through the VNCMOVLF access path (keyed CELLID) so one vessel's movements come back
in sequence order; an empty vessel genuinely shows zero rows. Numeric subfile fields are edited with
%editc(…:'K'), which renders a negative draw with a trailing minus.
A hand-written ILE COBOL program that opens VNAR as an INDEXED/
SEQUENTIAL file (record key AR-SEQ), reads it to end, and
EVALUATEs AR-TYPE: I adds to the debit (invoiced) accumulator,
P to the credit (payments) accumulator. It DISPLAYs three lines
(INVOICED, PAYMENTS, TXNS) — the read-only cross-check that the
AR ledger sums to what VNINVPOST posted.
VNSKU.ALLOC and cleared when
the order is invoiced (VNINVPOST).VNBATCH. Draws grape tonnage from one or
more lots via its composition lines (VNBCOMP) and accumulates grape cost in
BATCOST.VNBCOMP lines. VINTAGE/i blends at the
must stage only; there is no blending after a batch is cellared (a vessel is filled fresh by exactly one
batch).VNBTL. Posted by
VNBTLPOST at the fixed 0.2 gal/bottle rate.T) or barrel (B), VNCELL. Holds CURGAL
gallons at cost-per-gallon CSTGAL.VNCMOV: transfer-in (I, positive) when a batch is
cellared, transfer-out (O, negative) when wine is drawn for bottling.VNGLFEED the monthly close posts balanced DR/CR pairs into. Not a general
ledger itself — the feed a downstream GL would consume.CHAIN(EN) existence check, backed by state-flag
guards.base + business-digits (10M/batch, 30M/run, 50M/order,
60M/SKU) so a re-run regenerates the same key. See F.2.VNLOT. The entry point of grape
cost into the chain.VNSKU. On-hand and allocation are in bottles; standard cost
(STDCOST) is rolled up monthly.STDCOST to the weighted average cost of its
completed bottling runs, writing a before/after row to VNCSTH.BSTAT, RSTAT, OSTAT,
LSTAT, BATCOST>0) and advancing them on post, so nothing posts twice by
state.MSFL/MCTL), loaded after the key
is read (load-then-display).