GRANARY/i runs the operations of a grain elevator cooperative: producer members drive
loaded trucks onto the scale, each load is weighed and sampled into a scale ticket, the ticket is
graded (net weight, then moisture/dockage shrink deducted to net bushels), priced against the
grower's contract, and posted — creating grain inventory in a storage bin and a settlement
liability the co-op owes the member. A separate settlement run pays those liabilities through the
member AR/AP ledger; at fiscal year-end a patronage dividend is allocated off each member's volume
of business and the general ledger closes. The logic is hand-written RPG/COBOL/CL/DDS plus a little
embedded SQL, driven as three CL 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 (grain-app/src/sources.mjs, src/seed.mjs, and
the test/gr_build.mjs / gr_daily.mjs / gr_settle.mjs /
gr_annual.mjs drivers). Everything runs in library GRANARY.
GRANARY/i covers the full life of a load of grain through a country elevator:
Unlike a SQL-PL application, GRANARY/i keeps its business rules in hand-written RPG (and one ILE
COBOL reconciliation report) operating over keyed DB2 for i physical and logical files, with a small
amount of embedded SQL only where a set-based total or the GL feed makes it natural
(GRGLPST). The structure has three layers:
GRGRADE grades,
GRPRICE prices and posts, GRSETLR pays, GRPATRN allocates
patronage, GRGLPST posts the GL, with report programs (GRTKTPR,
GRARBAL, GRRECON) and interactive inquiries (GRMENU,
GRBININQ, GRTKTIQ) alongside.GRDAILY (grade → price → report), GRSETLC (settle → balance
→ reconcile), GRYEAR (patronage → GL → reconcile). A fourth CL,
GRSETUP, creates every object and compiles every program.The benefit for operations: each step is a small, re-runnable program guarded by a status flag on the
record it processes, and each cycle is a single CALL that runs its steps in the fixed correct
order. Every money movement lands in the durable AR/AP ledger, so a member balance and the co-op's whole
position are always reconstructable.
ONLINE DAILY (GRDAILY) SETTLE (GRSETLC) ANNUAL (GRYEAR)
------ --------------- ---------------- ---------------
GRMENU (5250) GRGRADE grade tickets GRSETLR pay settls GRPATRN patronage
1 -> GRBININQ O -> G (shrink) N -> Y (paid) settled bu x rate
2 -> GRTKTIQ (subfile) GRPRICE price + post GRARBAL member bals GRGLPST GL feed
G -> P inventory in GRRECON conservation GRRECON conservation
settlement out
GRTKTPR status report
SCALE TICKET (GRTKT) O weighed-in -> G graded -> P priced/posted
| |
| grade: net lb / bushel wt | price: net bu x contract price
v v
net bushels ------ inventory ----> GRSTK (bin grain on hand)
\ GRSETL (one settlement per priced ticket)
+---- liability --> GRAR (member AR/AP ledger: S credit, Y debit, P credit)
GRCTR.CTFILL advanced GRMEMB.MEQTY grown by patronage
GRPATR (year-end accrual) GRGLFEED (balanced DR/CR)
A single load flows: a scale ticket is written OPEN → GRGRADE computes shrink and net
bushels, flipping it to GRADED → GRPRICE prices it against the contract, posting bushels
into GRSTK, a row into GRSETL, a credit into GRAR, advancing
GRCTR.CTFILL, and flipping the ticket to PRICED. The settle and annual cycles then draw the
liability down and close the books — all money, never grain, so grain conservation holds throughout.
| Object | Type | Role |
|---|---|---|
| GRMEMB | PF | Member (producer) master, incl. paid-in equity. |
| GRCOMM | PF | Commodity master (base moisture/test-wt, shrink rates). |
| GRBIN | PF | Storage bin master (capacity, committed commodity/crop). |
| GRSTK | PF | Grain inventory: bushels on hand by bin/commodity/crop. |
| GRCTR | PF | Price contract (cash/deferred). |
| GRTKT | PF | Scale ticket (the heart of the daily flow). |
| GRSETL | PF | Settlement — one row per priced ticket. |
| GRAR | PF | Member AR/AP ledger (durable running balance). |
| GRPATR | PF | Patronage accrual, one per member per fiscal year. |
| GRSTKLF / GRCTRLF / GRTKTLF / GRSETLF / GRARLF | LF (5) | By-commodity, by-member and by-status access paths. |
| GRGLFEED | SQL table | General-ledger feed (balanced DR/CR rows). |
| GRMENUD / GRBIND / GRTKTD | DSPF (3) | Menu, bin-inquiry, ticket-inquiry (subfile) displays. |
| GRSETP | PRTF | Settlement-statement printer file. |
| GRREFLD | RPG | Seed reference data (members/commodities/bins/contracts). |
| GRGRADE | RPG | Grade open tickets (shrink → net bushels). |
| GRPRICE | RPG | Price graded tickets; post inventory + settlement. |
| GRTKTPR | RPG | Ticket status-summary report. |
| GRSETLR | RPG | Settlement run — pay unpaid settlements. |
| GRARBAL | RPG | Member AR/AP balance report. |
| GRPATRN | RPG | Patronage dividend allocation. |
| GRGLPST | SQLRPGLE | Annual GL post (embedded SQL over the GL feed). |
| GRBININQ / GRTKTIQ / GRMENU | RPG (3) | Interactive inquiry / menu programs. |
| GRRECON | ILE COBOL | Grain-conservation reconciliation report. |
| GRSETUP | CL | Create objects + compile programs. |
| GRDAILY / GRSETLC / GRYEAR | CL (3) | The three job cycles. |
The full catalogue is 9 PFs + 5 LFs + 1 SQL table, 3 DSPFs + 1 PRTF, 11 RPG programs, 1 COBOL program and 4 CL programs. Sections D and F expand each.
GRANARY/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 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 GRANARY — the tested jobs run
with LIBL = QSYS QGPL GRANARY QTEMP and CURLIB = GRANARY.
| To do this | Type on the command line |
|---|---|
| Open the main menu (then 1=bins, 2=tickets) | CALL GRANARY/GRMENU |
| Open the bin / grain-inventory inquiry directly | CALL GRANARY/GRBININQ |
| Open the scale-ticket inquiry (subfile) directly | CALL GRANARY/GRTKTIQ |
| Run the daily grade-price-inventory cycle | CALL GRANARY/GRDAILY (or SBMJOB it) |
| Run the settlement cycle | CALL GRANARY/GRSETLC |
| Run the annual patronage + GL close | CALL GRANARY/GRYEAR |
| (First-time) create objects & seed reference data | CALL GRANARY/GRSETUP then CALL GRANARY/GRREFLD |
The batch cycles and programs take no CALL parameters — each processes whatever records are
in the right status, so a scheduled submission is a bare CALL. Only GRMENU,
GRBININQ and GRTKTIQ are interactive; the batch programs run to completion and
DSPLY a one-line result per step (e.g. GRGRADE GRADED= 3 SKIP= 0).
GRTKT directly (the test drivers write it with
pf.writeRecords); there is no separate interactive weigh-in screen program in v1 —
GRTKTIQ is inquiry-only. Grading and pricing of those tickets is the daily batch chain.GRMENU presents the two-option menu and dispatches by calling the chosen inquiry program.
Option 1 calls GRBININQ; option 2 calls GRTKTIQ; any other
non-blank option shows Invalid option. F3 exits.
The operator keys an elevator and bin; the program CHAINs the bin master (GRBIN)
and walks the bin's grain-inventory rows (GRSTK) to show what it holds and the fill percent
against capacity. A bin that exists but has no inventory shows Bin found -- no inventory yet.;
an unknown bin shows Bin not found.
| Field | Type (DDS) | Shows |
|---|---|---|
| IELEV / IBIN | 4A / 6A input | Keyed elevator and bin. |
| DDESC | 20A output | Bin description. |
| DCAP | 14A output | Capacity in bushels (edited). |
| DCOMM / DCROP | 4A / 6A output | Committed commodity & crop year. |
| DQTY | 14A output | Bushels on hand (from GRSTK.GQTY). |
| DPCT | 14A output | Fill percent = on-hand / capacity × 100. |
| DMSG | 50A output | Status message line. |
GRTKTIQ presents a subfile (DDS record TSFL under control record
TCTL, SFLPAG(5) per page, SFLSIZ(20)) of scale tickets, filtered by
the status the operator keys in Status filter: O open, G graded,
P priced, or * for all. Each pass clears and reloads the subfile from the
by-status/date logical file (GRTKTLF), so the filter takes effect immediately, and shows the
count and total net bushels matched.
| Field | Type (DDS) | Shows |
|---|---|---|
| IFILT | 1A input | Status filter: O / G / P / *. |
| STKT | 8A output | Ticket number. |
| SMEMB | 6A output | Member number. |
| SCOMM | 4A output | Commodity. |
| SNETBU | 12A output | Net bushels (from TNETBU, edited). |
| SSTAT | 1A output | Ticket status O/G/P/X. |
| DTCNT / DTBU | 12A output | Matched count / total net bushels. |
ROLLUP(25)/ROLLDOWN(26), SFLEND(*MORE)).CA03).Honest statement: GRANARY/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one clerk grades, a second approves" or "one user prices, a second releases the payment" step in the code: the daily chain grades and prices in one run, and the settlement run pays every unpaid settlement without a separate release. The manual documents the control model the application does have:
O → G → P and each
program only acts on its own inbound status: GRGRADE touches only TSTAT='O',
GRPRICE only 'G', and both flip the flag as they go, so a step can never
double-process a record. Settlements gate on SPDFLG (N→Y);
patronage gates on the GRPATR (FYEAR, MEMBNO) key.LTYPE='S', positive), a payment made ('Y', negative), a patronage accrual
('P', positive) — posts one signed, sequence-keyed row. A member's balance is the
running sum of their rows, and the whole ledger is the reconstructable audit trail of what the co-op
owes and has paid.CTSTAT='D' (not yet
priced by the grower) is refused by GRPRICE: no inventory, no settlement, no ledger row
— the ticket stays graded until the contract is priced. Only active members
(MSTAT='A') accrue patronage.CHAIN(EN) "already exists" test, so a re-run of
any cycle posts nothing new. This is the integrity backbone in place of a segregation-of-duties gate.GRRECON (ILE COBOL) independently sums bushels
ticketed in (net bushels on priced tickets) against bushels stored (GRSTK on hand)
every settle and annual cycle; the two must match. Settlement and the annual close move money and
equity, never grain, so the invariant is a continuous cross-check.In sum, the control posture is per-record status gating + a durable signed ledger + stable-key idempotency + a grain-conservation cross-check, all enforced in the programs and files, rather than a segregation-of-duties approval workflow.
GRANARY/i's processing runs as three CL job cycles rather than one monolithic job, matching a
country elevator's rhythm: a daily grade-price-inventory chain as trucks come off the scale
(GRDAILY), a settlement run that pays members (GRSETLC), and an
annual patronage allocation + GL close (GRYEAR). Every batch program takes no CALL
parameters — each simply processes whatever records are in the right status — so a
scheduled submission is a bare CALL (or SBMJOB). There is no control table:
the fiscal year and patronage rate that the annual programs need are compiled constants
(GRPATRN uses wfy = 2025, wrate = 0.1500; GRGLPST uses
wbatch = 202512), so the co-op re-hosts a new year by rebuilding those two programs.
-- run a cycle directly, or submit it to a job queue
CALL PGM(GRANARY/GRDAILY)
SBMJOB CMD(CALL PGM(GRANARY/GRSETLC)) JOB(GRSETLC)
SBMJOB CMD(CALL PGM(GRANARY/GRYEAR)) JOB(GRYEAR)
| Program | Type | Cycle | Purpose | Reads / Writes | DSPLY result line |
|---|---|---|---|---|---|
| GRREFLD | RPG | setup | Seed reference data. | Writes GRMEMB (4), GRCOMM (3), GRBIN (5), GRCTR (4). |
GRREFLD MEMBERS=4 COMMS=3 BINS=5 CONTRACTS=4 |
| GRGRADE | RPG | daily 1 | Grade every OPEN ticket: net lb → raw bushels, deduct moisture/dockage shrink → net bushels. | Reads GRCOMM; updates GRTKT (TBU/TSHKBU/TNETBU, TSTAT O→G). |
GRGRADE GRADED=n SKIP=n |
| GRPRICE | RPG | daily 2 | Price every GRADED ticket against its contract; post inventory + settlement + ledger credit; advance fill. | Reads GRCTR/GRBIN; writes GRSTK, GRSETL, GRAR(S); updates GRCTR.CTFILL, GRTKT (G→P). |
GRPRICE PRICED=n DEFERRED=n SKIP=n |
| GRTKTPR | RPG | daily 3 | Ticket status-summary report (counts by status + priced net bushels). | Reads GRTKT. No file writes. |
GRTKTPR OPEN=n GRADED=n PRICED=n NETBU=n |
| GRSETLR | RPG | settle 1 | Pay every unpaid settlement: post a debit ledger row, flip SPDFLG N→Y. |
Reads/updates GRSETL; writes GRAR(Y). |
GRSETLR PAID=n TOTAL=n SKIP=n |
| GRARBAL | RPG | settle 2 | Member AR/AP balance report (running sum per member, control-break). | Reads GRARLF, GRMEMB. No file writes. |
GRARBAL MEMB=m BALANCE=n per member, then MEMBERS=n GRANDTOTAL=n |
| GRPATRN | RPG | annual 1 | Allocate a flat per-bushel patronage dividend off each active member's settled-bushel volume. | Reads GRSETL (by member); writes GRPATR, GRAR(P); grows GRMEMB.MEQTY. |
GRPATRN ALLOCATED=n SKIP=n |
| GRGLPST | SQLRPGLE | annual 2 | Sum the AR ledger by type; post balanced DR/CR pairs into the SQL GL feed (delta since last close). | Reads GRAR; INSERTs into GRGLFEED. |
GRGLPST BATCH=202512 ROWS=n |
| GRRECON | ILE COBOL | settle 3 / annual 3 | Grain-conservation reconciliation: ticketed-in bushels vs stored bushels. | Reads GRTKT, GRSTK. No file writes. |
GRRECON TICKETED n / STORED n / TICKETS n / BINROWS n |
The three cycle drivers (GRDAILY, GRSETLC, GRYEAR) each
ADDLIBLE GRANARY then chain their steps with plain CALL PGM(...) and end with a
SNDPGMMSG completion banner. GRSETUP is the one-time object-build CL (delete +
CRTPF/CRTLF/CRTDSPF/CRTPRTF/RUNSQLSTM +
CRTBNDRPG/CRTBNDCBL for every member).
GRGRADE walks GRTKT and grades every TSTAT='O' ticket:
TBU = TNET / CBASETW (net pounds ÷ the commodity's legal bushel weight), then subtracts
shrink bushels for moisture over CBASEM and dockage over CDOCKPT
(TSHKBU = TBU × (moist-pts × CSHMOIS + dock-pts × CSHDOCK)), giving
TNETBU; it flips the ticket to 'G'. GRPRICE then prices every
'G' ticket against its contract: a cash contract prices at CTPRICE; a deferred
contract still carrying CTSTAT='D' is skipped (counted as DEFERRED, left
graded). A priced ticket posts TNETBU into the bin's GRSTK row, writes one
GRSETL settlement (SNETAMT = TNETBU × SPRICE) and one GRAR
credit (LTYPE='S'), advances CTFILL, and flips the ticket to 'P'.
GRTKTPR prints the resulting status counts.
Expected DSPLY (the tested 3-ticket day: two cash, one deferred-unpriced): GRGRADE GRADED=3 SKIP=0 GRPRICE PRICED=2 DEFERRED=1 SKIP=0 T0000003's deferred contract is unpriced GRTKTPR OPEN=0 GRADED=1 PRICED=2 NETBU=1945.00 962.00 + 983.00
GRSETLR walks GRSETL and pays every SPDFLG='N' settlement: it
posts a negative ('Y', debit) GRAR row for SNETAMT and flips
SPDFLG to 'Y'. Because the settlement credit and the payment debit are equal and
opposite, a fully-paid member's running ledger balance nets to exactly zero. GRARBAL
then reports each member's balance by control-break over GRARLF, and GRRECON
cross-checks grain conservation.
Expected DSPLY (paying the two tested settlements): GRSETLR PAID=2 TOTAL=15387.75 SKIP=0 4329.00 + 11058.75 GRARBAL MEMB=M00001 BALANCE=0.00 credit + payment net to zero GRARBAL MEMB=M00003 BALANCE=0.00 GRARBAL MEMBERS=2 GRANDTOTAL=0.00 GRRECON TICKETED 1945.00 settlement moved money, not grain GRRECON STORED 1945.00
GRPATRN reads every member, sums that member's settled bushels for fiscal year
2025 (walking the member-keyed settlement path, filtering on %int(sdt/10000)=wfy),
and allocates PALLOC = PBU × 0.1500. It writes a GRPATR accrual, a
GRAR credit ('P'), and grows MEQTY by the same amount. Only active
(MSTAT='A') members with settled bushels accrue. GRGLPST then totals the whole AR
ledger by type and posts balanced DR/CR pairs (see F.5), and GRRECON re-checks conservation.
Expected DSPLY (the tested year: M00001 962 bu, M00003 983 bu settled): GRPATRN ALLOCATED=2 SKIP=2 M00002 (unpriced) & M00004 (no tickets) skip M00001: 962.00 x 0.1500 = 144.30 -> equity 12500.00 -> 12644.30 M00003: 983.00 x 0.1500 = 147.45 -> equity 3200.00 -> 3347.45 GRGLPST BATCH=202512 ROWS=6 GRRECON TICKETED 1945.00 patronage/GL moved money and equity, not grain GRRECON STORED 1945.00
GRPRICE only acts on
TSTAT='G' tickets, which only GRGRADE produces, and it reads
TNETBU that grading computes. The report runs last so its counts reflect the day.GRPRICE has written it;
GRSETLR has nothing to pay until the day's pricing has run.CALL of any cycle
posts nothing new — inventory, settlements, payments, patronage and GL rows do not double
(F.3).All files are in library GRANARY, grounded in the DDS in src/sources.mjs.
Dates are packed/signed numerics in YYYYMMDD form (4S 0 crop year is
YYYY); money and quantities are packed decimal (P); prices and shrink rates carry
4 decimal places so fractional cents and fractional-percent shrink factors are exact. Nine physical files,
five logical (alternate-key) files and one SQL table.
| Field | Type | Meaning |
|---|---|---|
| MEMBNO | 6A | Member number (PK, e.g. M00001). |
| MEMBNM | 25A | Member name. |
| MADDR | 25A | Address. |
| MTYPE | 1A | P = patron/producer member. |
| MEQTY | 11P 2 | Paid-in equity (patronage capital); grown by the annual patronage run. |
| MSTAT | 1A | A = active (only active members accrue patronage). |
| Field | Type | Meaning |
|---|---|---|
| COMM | 4A | Commodity code (PK: CORN, SOYB, WHET). |
| CDESC | 20A | Description. |
| CBASEM | 5P 2 | Base (no-discount) moisture percent. |
| CBASETW | 5P 2 | Legal bushel weight, lb/bu (the pound→bushel divisor). |
| CDOCKPT | 5P 2 | Dockage-free threshold percent; shrink applies above it. |
| CSHMOIS | 7P 4 | Moisture shrink rate: fraction of bushels lost per point over base. |
| CSHDOCK | 7P 4 | Dockage shrink rate: fraction per point over the threshold. |
| CSTAT | 1A | A = active. |
Seeded set: CORN 15.00%/56.00lb/2.00%/.0140/.0100; SOYB 13.00%/60.00lb/1.00%/.0120/.0100; WHET 13.50%/60.00lb/1.00%/.0130/.0100.
| Field | Type | Meaning |
|---|---|---|
| ELEV | 4A | Elevator id (PK part 1, e.g. ELV1). |
| BIN | 6A | Bin id (PK part 2, e.g. A-100). |
| BDESC | 20A | Bin description. |
| BCAP | 11P 2 | Capacity in bushels. |
| BCOMM | 4A | Committed commodity (blank = unassigned annex); a bin holds one commodity/crop at a time. |
| BCROP | 4S 0 | Committed crop year. |
| BSTAT | 1A | A = active. |
| Field | Type | Meaning |
|---|---|---|
| ELEV / BIN / COMM / CROP | 4A / 6A / 4A / 4S 0 | Composite key: bushels on hand by bin, commodity, crop year. |
| GQTY | 11P 2 | Net (post-shrink) bushels currently stored — the co-op's physical grain position. |
| GLSTDT | 8S 0 | Last-updated ticket date (YYYYMMDD). |
| GSTAT | 1A | A = active. |
| Field | Type | Meaning |
|---|---|---|
| CTRNO | 8A | Contract number (PK, e.g. CT000001). |
| MEMBNO | 6A | Owning member. |
| COMM / CROP | 4A / 4S 0 | Commodity and crop year contracted. |
| CTTYPE | 1A | C = cash (priced at CTPRICE), D = deferred (priced later). |
| CTPRICE | 9P 4 | Contract price per bushel (0 on an unpriced deferred contract). |
| CTQTY | 11P 2 | Bushels contracted. |
| CTFILL | 11P 2 | Bushels delivered against it so far (advanced by GRPRICE). |
| CTSTAT | 1A | O = open/priced, D = deferred and not yet priced (tickets cannot settle). |
| Field | Type | Meaning |
|---|---|---|
| TKTNO | 8A | Ticket number (PK, e.g. T0000001). |
| TKTDT | 8S 0 | Ticket date (YYYYMMDD). |
| MEMBNO / CTRNO | 6A / 8A | Member and the contract this load settles against. |
| COMM / CROP | 4A / 4S 0 | Commodity and crop year. |
| ELEV / BIN | 4A / 6A | Bin the load unloaded to. |
| TGROSS / TTARE / TNET | 9P 0 | Gross, tare, net weight in pounds off the scale. |
| TMOIS / TTESTWT / TDOCKPT | 5P 2 | Sampled moisture %, test weight lb/bu, dockage %. |
| TBU | 11P 2 | Raw bushels (TNET / CBASETW), set by grading. |
| TSHKBU | 11P 2 | Shrink bushels deducted, set by grading. |
| TNETBU | 11P 2 | Net bushels after shrink — what gets inventoried and paid. |
| TSTAT | 1A | O open/weighed-in, G graded, P priced/posted, X cancelled. |
| Field | Type | Meaning |
|---|---|---|
| SETLNO | 8A | Settlement number (PK); derived from the ticket number ('S' + the ticket's 7-digit tail). |
| TKTNO / MEMBNO / CTRNO | 8A / 6A / 8A | Source ticket, member, contract. |
| SDT | 8S 0 | Settlement date (the ticket date). |
| SBU | 11P 2 | Net bushels settled. |
| SPRICE | 9P 4 | Price per bushel (the contract price). |
| SAMT | 13P 2 | Gross amount (SBU × SPRICE). |
| SDISC | 13P 2 | Additional dollar discount (reserved; currently 0). |
| SNETAMT | 13P 2 | Amount owed the member (SAMT - SDISC). |
| SPDFLG | 1A | N = booked/unpaid, Y = paid by the settlement run. |
| Field | Type | Meaning |
|---|---|---|
| LSEQ | 8S 0 | Ledger sequence (PK, a stable derived key — see F.3). |
| MEMBNO | 6A | Member. |
| LTYPE | 1A | S settlement liability (credit, +), Y payment (debit, −), P patronage accrual (credit, +). |
| LDT | 8S 0 | Posting date. |
| LAMT | 13P 2 | Signed amount; a member's balance is the running sum of their LAMT. |
| LREF | 8A | Reference (ticket or settlement number, or PATRON). |
| LMEMO | 25A | Human-readable memo. |
| Field | Type | Meaning |
|---|---|---|
| FYEAR / MEMBNO | 4S 0 / 6A | Composite key: one accrual per member per fiscal year. |
| PBU | 11P 2 | Member's settled bushels that year (volume of business). |
| PALLOC | 13P 2 | Dollar dividend allocated (PBU × rate). |
| PSTAT | 1A | O open, P posted to equity + AR ledger. |
| Field | Type | Meaning |
|---|---|---|
| GLSEQ | DECIMAL(8,0) | Row sequence (PK); continues across closes so a re-run posts only the delta. |
| GLBATCH | DECIMAL(6,0) | Close batch (YYYYMM, e.g. 202512). |
| ACCT | CHAR(9) | GL account (see F.5). |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(11,2) | Amount (always positive; direction is in DRCR). |
| GLREF / GLDT | CHAR(8) / DECIMAL(8,0) | Reference (SETTLE/PAYMENT/PATRON) and date. Index GRGLFACC on (ACCT, DRCR). |
| LF | Over | Leads on | Answers |
|---|---|---|---|
| GRSTKLF | GRSTK | COMM, CROP, ELEV, BIN | "How much of this commodity do we hold everywhere?" |
| GRCTRLF | GRCTR | MEMBNO, CTRNO | Every contract a member holds. |
| GRTKTLF | GRTKT | TSTAT, TKTDT, TKTNO | Tickets ready for a step / by status (the subfile inquiry). |
| GRSETLF | GRSETL | MEMBNO, SETLNO | A grower's settlement history (patronage bushel sum). |
| GRARLF | GRAR | MEMBNO, LSEQ | A member's ledger for balance/statement (GRARBAL). |
The worked figures below are the exact ones the test drivers hand-derive and assert
(test/gr_daily.mjs, gr_settle.mjs, gr_annual.mjs). They make the
best reconciling worked-example: a single day of two cash tickets and one deferred-unpriced ticket, carried
all the way through settlement and the annual close.
Trucks come off the scale and their OPEN scale tickets are written to GRTKT (in this build
the weigh-in row is written directly — there is no interactive weigh-in screen; B.1 note). Then run
the daily cycle:
-- first-time only: build the objects and seed reference data CALL GRANARY/GRSETUP CALL GRANARY/GRREFLD -- each day, after the day's OPEN tickets are on GRTKT: CALL GRANARY/GRDAILY GRGRADE -> GRPRICE -> GRTKTPR
The tested day's three tickets:
| Ticket | Member / Contract | Comm / Bin | Net lb | Raw bu | Shrink bu | Net bu | Priced? |
|---|---|---|---|---|---|---|---|
| T0000001 | M00001 / CT000001 cash @4.5000 | CORN / A-100 | 56,000 | 1000.00 | 38.00 | 962.00 | yes → $4,329.00 |
| T0000002 | M00003 / CT000003 cash @11.2500 | SOYB / B-200 | 60,000 | 1000.00 | 17.00 | 983.00 | yes → $11,058.75 |
| T0000003 | M00002 / CT000002 deferred, unpriced | CORN / A-101 | 28,000 | 500.00 | 0.00 | 500.00 | no (stays G) |
Shrink worked: T0000001 moisture 17.0% is 2.0 pts over CORN base 15.0 →
2.0×.0140=.0280; dockage 3.0% is 1.0 pt over threshold 2.0 →
1.0×.0100=.0100; total .0380 × 1000 = 38.00 bu.
T0000003 sits exactly at base moisture (15.0%) and exactly at the dockage threshold (2.0%), so zero shrink
either way. Its deferred contract is still CTSTAT='D', so GRPRICE defers it: no
inventory, no settlement, no ledger row; the ticket stays graded.
After GRDAILY: bin A-100 holds 962.00 bu, B-200 holds 983.00 bu, A-101 holds nothing;
GRSETL has 2 unpaid rows; GRAR has 2 'S' credit rows
(M00001 +4329.00, M00003 +11058.75); CT000001.CTFILL=962.00,
CT000003.CTFILL=983.00, CT000002.CTFILL=0.
GRTKTPR reports OPEN=0 GRADED=1 PRICED=2 NETBU=1945.00.
CALL GRANARY/GRSETLC GRSETLR -> GRARBAL -> GRRECON CALL GRANARY/GRYEAR GRPATRN -> GRGLPST -> GRRECON
Settlement. GRSETLR pays both unpaid settlements —
PAID=2 TOTAL=15387.75 (4329.00 + 11058.75) — posting a 'Y' debit of
−4329.00 for M00001 and −11058.75 for M00003 and flipping both SPDFLG to
Y. Each member's running balance now nets to exactly zero (credit + equal debit), which
GRARBAL confirms: MEMBERS=2 GRANDTOTAL=0.00.
Year-end. GRPATRN allocates a flat 0.1500/bu patronage off each active member's
settled fiscal-2025 bushels — ALLOCATED=2 SKIP=2 (M00002 was never priced, M00004 had no
tickets):
| Member | Settled bu (PBU) | Rate | PALLOC | Equity before → after |
|---|---|---|---|---|
| M00001 | 962.00 | 0.1500 | $144.30 | 12,500.00 → 12,644.30 |
| M00003 | 983.00 | 0.1500 | $147.45 | 3,200.00 → 3,347.45 |
Then GRGLPST posts the annual GL batch 202512 (six rows, three balanced DR/CR
pairs — see F.5) and GRRECON re-confirms conservation.
Three invariants must hold after every cycle; the operator can read them straight off the DSPLY lines and the reports:
GRRECON (ILE COBOL) independently sums bushels
ticketed in (TNETBU on every 'P' ticket) and bushels
stored (GQTY across GRSTK). Both must read
1945.00 (962.00 + 983.00). Settlement and the annual close move money and equity,
never grain, so this figure is byte-for-byte identical before and after those cycles.GRAR balance is 0.00, and GRARBAL's grand total is 0.00.GRGLPST batch is matched DR/CR pairs, so total debits equal
total credits (the tested year: 31,067.25 = 31,067.25).GRRECON shows TICKETED ≠ STORED, something wrote to
GRSTK outside GRPRICE, or a ticket flipped to 'P' without its
inventory posting — investigate before running settlement. The three cycles are each safely
re-runnable, so the safe recovery for a mid-cycle failure is simply to re-CALL the cycle: the
status flags and stable-key guards (F.3) skip everything already posted and finish the rest.Grading (GRGRADE). Net pounds come off the scale as TNET = TGROSS - TTARE (written
on the ticket). Raw bushels convert at the commodity's legal bushel weight, not the load's tested
weight:
TBU = TNET / CBASETW e.g. 56000 / 56.00 = 1000.00 moispts = max(TMOIS - CBASEM, 0) points over base moisture dockpts = max(TDOCKPT - CDOCKPT, 0) points over the dockage-free threshold TSHKBU = TBU * (moispts*CSHMOIS + dockpts*CSHDOCK) TNETBU = max(TBU - TSHKBU, 0) clamped non-negative
Test weight (TTESTWT) is sampled and stored but does not enter the pound-to-bushel
conversion in this model — a bushel is fixed by the commodity's legal weight (CBASETW);
test weight is a grade/discount input reserved for future quality discounting. A commodity with
CBASETW ≤ 0, or a ticket whose commodity is not found, is skipped (counted in
SKIP), never divided by zero.
Pricing (GRPRICE). A graded ticket prices at its contract price:
SNETAMT = TNETBU × CTPRICE. A deferred contract still carrying
CTTYPE='D' and CTSTAT='D' is deferred (counted in DEFERRED, ticket left graded).
Otherwise pricing performs five posts in order: (a) add TNETBU to GRSTK
(write the bin row if new); (b) write the GRSETL row; (c) write the GRAR
'S' credit; (d) advance CTFILL; (e) flip TSTAT to 'P'.
Each of (d) and (e) re-CHAINs its record defensively, since the intervening writes moved the
file cursor.
| Program | Files (F-spec) | Key logic |
|---|---|---|
| GRGRADE | GRTKT (UF), GRCOMM (IF) | Walk *LOVAL; grade every 'O' ticket via GRD1; flip to 'G'. |
| GRPRICE | GRTKT/GRCTR (UF), GRSTK/GRSETL/GRAR (UF A), GRBIN (IF) | Price every 'G' ticket via PRC1; the five-post sequence; stable settlement + ledger keys. |
| GRTKTPR | GRTKT (IF) | Count tickets by status; sum TNETBU on priced ones. |
| GRSETLR | GRSETL (UF), GRAR (UF A) | Pay every SPDFLG='N' row via PAY1; write 'Y' debit; flip to 'Y'. |
| GRARBAL | GRARLF (IF), GRMEMB (IF) | Control-break running-balance report; FLUSH per member. (Control-break bug fixed — see the source comment.) |
| GRPATRN | GRMEMB (UF), GRSETLF (IF), GRPATR/GRAR (UF A) | Per active member, sum settled fiscal-year bushels (READE on member key); allocate; write accrual + 'P' ledger; grow MEQTY. |
| GRGLPST | GRAR (IF) + embedded SQL over GRGLFEED | Sum ledger by type; post delta DR/CR pairs; continue GLSEQ from prior close. |
| GRBININQ / GRTKTIQ / GRMENU | WORKSTN + keyed DISK | Interactive inquiry / menu (B.2). |
| GRRECON | GRTKT, GRSTK (COBOL indexed) | Sum ticketed-in vs stored; DISPLAY both. |
Every posting program derives a stable key from the record it processes and refuses re-entry with
a CHAIN(EN) guard, so any cycle is safely re-runnable. The CHAIN(EN) polarity is
the standard one: the indicator is ON when the row is NOT found (genuinely new → write) and
OFF when it IS found (already posted → skip).
| Program | Stable key | Second guard |
|---|---|---|
| GRPRICE | Settlement SETLNO = 'S' + substr(TKTNO,2,7); ledger LSEQ = 10000000 + ticket-tail. | TSTAT='P'. |
| GRSETLR | Ledger LSEQ = 20000000 + ticket-tail. | SPDFLG='Y'. |
| GRPATRN | GRPATR keyed (FYEAR, MEMBNO); ledger LSEQ = 30000000 + FYEAR*1000 + per-member seq. | the (FYEAR,MEMBNO) accrual key. |
| GRGLPST | GLSEQ continues from max(GLSEQ); posts only the movement vs prior-posted per-account totals. | primary key on GLSEQ. |
GRPRICE
built the settlement key with 'S' + %editc(wtktnum:'X'). The X edit code renders
the field's full declared width (15 digits for a 7S0), so the 16-char result truncated into the 8A field
always collapsed to 'S0000000' — every ticket collided on one key and pricing silently
stopped after ticket one. The fix substrings the ticket's own 7 digits straight across
(%subst(wtkt:2:7)), avoiding the width mismatch. GRARBAL likewise had a
control-break bug (WMEMB stuck on the first member) since fixed.N31/31 indicators on SFLDSP/SFLDSPCTL/
SFLCLR are therefore written one column left of the standard position (the keyword
itself stays in column 45), exactly as wms-app's WHPCKD documents. Without this, the subfile would
never clear/reload.WRITE to a sibling file, posting programs re-CHAIN a
record before updating it, since the intervening write moved the file cursor — a house
convention shared with the wms-app programs.GRGLPST tracks prior-posted per-account credit totals in SQL
and posts only the movement, so re-totalling the whole ledger on a second close does not re-derive
the first close's primary keys (which would be silently refused by the insert).GRGLPST sums the AR ledger by LTYPE and posts three balanced pairs:
| Ledger source | Debit | Credit | Tested amount |
|---|---|---|---|
Settlement liability ('S') | 1400-INV | 2100-APM | 15,387.75 |
Payments ('Y', ledger holds negative) | 2100-APM | 1000-CSH | 15,387.75 |
Patronage accrued ('P') | 6100-PEX | 2900-PEQ | 291.75 |
1400-INV grain inventory/purchases, 2100-APM accounts payable – members, 1000-CSH cash, 6100-PEX patronage expense, 2900-PEQ member equity – patronage payable. Total debits 31,067.25 = total credits 31,067.25.
O (weighed in) → G (graded) →
P (priced/posted).CTPRICE) or deferred (grain stored,
priced later; unpriced deferred contracts cannot settle).GRSTK row is the net bushels on
hand — the co-op's physical grain position.SPDFLG='N'), then paid by the settlement run ('Y').S settlement credit,
Y payment debit, P patronage credit. A member's balance is the running sum.MEQTY).GRRECON.GRDAILY (grade/price), GRSETLC (settle),
GRYEAR (patronage + GL close).