LEDGER/i is a double-entry general-ledger and financial-close application: chart of
accounts, journal-batch capture and validation, posting to the ledger and to account/period balances,
a trial balance re-derived from the detail, recurring and accrual journals, period close, financial
statement generation (P&L / balance sheet) and the year-end retained-earnings roll. The business
logic is hand-written RPG (fixed-form C-specs plus /free) over keyed DDS physical and
logical files, with one ILE COBOL statement generator and one SQLRPGLE summary-publish program. One
accounting invariant is enforced everywhere: for every posted document SUM(DR) = SUM(CR),
and because every ledger amount is stored debit-positive, SUM(GAMT) over the whole ledger
is exactly zero. This manual is the reference for the operator who runs the four job cycles and the
online screens, and for the developer maintaining the application. It is grounded entirely in the
committed source (gl-app/src/sources.mjs, gl-app/src/seed.mjs, and the
gl-app/test/gl_*.mjs drivers).
LEDGER/i runs the full life of a general ledger as four periodic job cycles:
Two storage decisions carry the whole design and are worth stating up front:
GLJDTL) stores the
amount LAMT as an always-positive magnitude and the side DRCR as
'D' or 'C'. Storing an unsigned amount plus a side is exactly what lets the
edit run add debits and credits independently and compare them — the real double-entry check.GAMT,
the account balance ACBAL and the period net PNET are all held
debit-positive: a credit is stored negative. Under this one convention the whole ledger sums
to zero when it balances, which is the cheapest possible integrity check — and it is the check
GLBALCK (daily) and GLFINST (COBOL) assert directly. A credit-normal
account therefore carries a negative life-to-date balance in storage; the online inquiry and
the trial balance flip it back into normal-side presentation for the reader.Everything runs in library LEDGER. The tested jobs run with
LIBL = QSYS QGPL LEDGER QTEMP and CURLIB = LEDGER.
DAILY GLDAILY WEEKLY GLWEEK MONTHLY GLMONTH ANNUAL GLYEAR
-------------- -------------- --------------- --------------
GLJEGEN (capture) GLRECUR (gen R+A) GLREVACC (reverse A) GLYRCLS (close P&L->RE)
GLEDIT (validate) GLPOST (post) GLTRIAL (re-derive) GLNEWYR (open new FY)
GLPOST (post) GLTRIAL (re-derive) GLCLOSE (lock period) GLBALCK (prove)
GLBALCK (prove) GLTBPR (print TB) GLSUMPB (SQL extract) GLFINST (statements)
GLFINST (statements)
ONLINE (5250) files touched by the batch chain
------------- --------------------------------
GLMENU GLBAT batch header (control totals)
1 -> GLACCTIQ (GLACCTD) GLJHDR journal header GLJDTL journal lines
2 -> GLJRNIQ (GLJRND SFL) GLLEDG posted ledger GLBAL period balances
GLACCT chart (ACBAL) GLPERD period calendar
GLRECR templates GLFSLN statement layout
GLCLOG close log GLSUMM SQL summary extract
A single daily event flows: GLJEGEN writes journal headers/lines at JSTAT='E'
and recomputes the batch control totals → GLEDIT validates each document to
'V' or 'X' and rejects any batch carrying a failure → GLPOST
turns each 'V' document's lines into GLLEDG rows, moves ACBAL and
accumulates GLBAL, then flips the document to 'P' → GLBALCK
re-derives SUM(DR) vs SUM(CR) from the ledger and reports OK/BAD.
| Object | Type | Role |
|---|---|---|
| GLACCT | PF | Chart of accounts (the spine; carries ACBAL). |
| GLACCLF | LF | Chart keyed by ATYPE then ACCTNO (statement order). |
| GLPERD | PF | Accounting-period calendar (open/closed/future). |
| GLBAT | PF | Journal-batch header + control totals. |
| GLJHDR | PF | Journal entry header (one per document). |
| GLJDTL | PF | Journal entry lines (unsigned magnitude + side). |
| GLJDLF | LF | Journal lines keyed by ACCTNO (account-activity path). |
| GLLEDG | PF | Posted GL ledger (durable, debit-positive audit trail). |
| GLBAL | PF | Period balances by (year, period, account). |
| GLRECR | PF | Recurring/accrual journal templates. |
| GLFSLN | PF | Financial-statement line layout. |
| GLCLOG | PF | Period-close log. |
| GLSUMM | SQL table | Summarised GL extract for downstream consolidation. |
| GLACCTD / GLJRND / GLMENUD | DSPF | Account inquiry / journal subfile / menu. |
| GLTBP | PRTF | Trial-balance printer file. |
| GL* (13) | RPGLE | Reference load, capture, edit, post, balance-check, recurring, trial, print, reverse, close, year-close, new-year, plus 3 online programs. |
| GLSUMPB | SQLRPGLE | Publish the closed period into the SQL extract. |
| GLFINST | ILE COBOL | Financial statement generator (assets=liab+equity+NI). |
| GLSETUP | CL | Create every object & compile every program. |
| GLDAILY / GLWEEK / GLMONTH / GLYEAR | CL | The four job cycles. |
The catalogue is 10 PFs + 2 LFs + 3 DSPFs + 1 PRTF + 1 SQL table + 1 SQL index, driven by 16 RPG programs (15 RPGLE + 1 SQLRPGLE-flavoured GLSUMPB), 1 COBOL program and 5 CL programs. Sections D and F expand each.
LEDGER/i has no CICS transaction identifiers. 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". The job's library list must
include LEDGER.
| To do this | Type on the command line |
|---|---|
| Open the operator main menu | CALL LEDGER/GLMENU |
| Open the account inquiry screen directly | CALL LEDGER/GLACCTIQ |
| Open the journal-line subfile inquiry directly | CALL LEDGER/GLJRNIQ |
| Run the daily cycle | CALL LEDGER/GLDAILY (or SBMJOB it) |
| Run the weekly cycle | CALL LEDGER/GLWEEK |
| Run the monthly close | CALL LEDGER/GLMONTH |
| Run the annual roll | CALL LEDGER/GLYEAR |
| Build/compile the whole application (once) | CALL LEDGER/GLSETUP |
All batch programs take no CALL parameters. Notably the app carries no online control-date
table: the seeded worked example is hard-wired to FY2026, period 1 (GLJEGEN,
GLRECUR, GLTRIAL, GLCLOSE and GLSUMPB all set
wfy=2026; wpn=1 in-line), and the annual programs target FY2026→FY2027. This is an
honest limitation of the sample: the periodicity is real (four distinct cycles) but the processing period
is coded, not operator-supplied. See section F.3.
Three interactive programs exist. GLMENU (over GLMENUD) is the driver;
option 1 program-CALLs GLACCTIQ, option 2 program-CALLs GLJRNIQ,
any other non-blank option shows Invalid option. and stays on the menu. F3
(CA03) returns from an inquiry to the menu, and again from the menu ends the session.
A non-subfile screen. Key an account number into IACCT and Enter;
the program CHAINs GLACCT and renders the master record plus two balances:
the stored Ledger balance (DBAL, the raw debit-positive ACBAL) and the
Normal-side bal (DNBAL, derived — a credit-normal account's ACBAL
is negated so an accountant reads it in its own sense). A miss clears every output field and shows
Account not found.
| Field | Type (DDS) | Shows |
|---|---|---|
| IACCT | 6A input/output | Account number to look up. |
| DDESC / DTYPE / DNORM | 30A / 1A / 1A output | Description, account type (A/L/Q/R/E), normal side (D/C). |
| DBAL | 14A output | Stored ACBAL, edit-code K (thousands, trailing minus). |
| DNBAL | 14A output | Normal-side presentation balance (credit-normal accounts flipped). |
| DPOST / DSTAT | 1A output | Postable flag / account status. |
| DMSG | 50A output | Found / not-found message (highlighted). |
The app's subfile screen. Key a document number into IDOCNO; the program
CHAINs GLJHDR for the header (description and the recomputed control totals),
then SETLL/READE-walks GLJDTL to load one subfile row
(JSFL under control JCTL, SFLPAG(5)/SFLSIZ(20),
ROLLUP(25)/ROLLDOWN(26)) per line. Each enquiry clears and reloads the
subfile, so switching from a 3-line to a 2-line document leaves exactly 2 rows; an unknown document shows
Journal document not found. with an empty subfile.
| Subfile field (JSFL) | Type (DDS) | Shows |
|---|---|---|
| SSEQ | 2Y,0 output | Line sequence (LSEQ). |
| SACCT | 6A output | Account number. |
| SDC | 1A output | Side: D debit, C credit. |
| SAMT | 14A output | Line magnitude, edit-code K. |
| SMEMO | 22A output | Line memo. |
SFLCLR actually fire on this emulator.Honest statement: LEDGER/i does not model a four-eyes maker–checker workflow, and
the online screens are inquiry-only — GLACCTIQ and GLJRNIQ read
and display; neither posts, edits or authorises anything. Journals are captured and posted by the batch
chain, not from a screen. The control model the application does have is enforced in the edit/post
data layer:
GLJEGEN recomputes the batch control totals
(BDRTOT/BCRTOT) from the detail it wrote; GLEDIT validates
every document and, if any document in a batch failed, rejects the whole batch
(BSTAT='R', BERRCD='DB') — which is what a real GL does.GLEDIT proves: debits=credits (else
'DB'), every account exists and is active (else 'AC'), every account is
postable (else 'NP' — the seeded header account 999000 is
deliberately non-postable to exercise this), and the target period is OPEN (else 'PC').
A failing document goes JSTAT='X'; a clean one goes 'V'.PSTAT='C' is still rejected with 'PC' and never reaches the ledger.GLPOST only posts documents at
JSTAT='V', then flips them to 'P'; the ledger write itself is idempotent
(stable key + CHAIN(EN) guard), so a re-run posts nothing.GLLEDG row tagged by source (GSRC
M/R/A/V/Y), keyed by a stable business-derived LEDSEQ, so the full history is
reconstructable and every key is unique. The period close writes a GLCLOG row per
close event so a close can prove it did not close the same period twice.In sum, the control posture is batch-level validation + state gating + a durable, uniquely-keyed audit ledger, all enforced in the edit/post layer, rather than a segregation-of-duties approval workflow or an online-entry authorisation gate.
LEDGER/i runs as four cycles rather than one monolithic job: a daily capture/edit/post/prove
chain, a weekly recurring-and-trial-balance chain, a monthly reverse/close/publish/statement
chain, and an annual retained-earnings roll. Each cycle is a CL program that
ADDLIBLE LIB(LEDGER) and CALLs its steps in order; each takes no parameters. A scheduled run
is a bare SBMJOB.
-- one-time build, then the daily cycle CALL PGM(LEDGER/GLSETUP) CALL PGM(LEDGER/GLREFLD) seed chart / periods / templates / statement layout SBMJOB CMD(CALL PGM(LEDGER/GLDAILY)) JOB(GLDAILY)
| Program | Cycle | Purpose | Reads | Writes / banner |
|---|---|---|---|---|
| GLREFLD | setup | Seed reference data. | — | GLACCT(13), GLPERD(4), GLRECR(4), GLFSLN(7); ACCTS=13 PERIODS=4 TMPL=4 FSLINES=7. |
| GLJEGEN | daily | Capture the seeded manual journal batch; recompute batch control totals. | — | GLBAT, GLJHDR, GLJDTL (JSTAT=E); GLJEGEN DOCS= SKIP=. |
| GLEDIT | daily | Validate each document (DB/AC/NP/PC); reject any batch with a failure. | GLJHDR, GLJDTL, GLACCT, GLPERD, GLBAT | JSTAT V/X, BSTAT V/R; GLEDIT VALID= REJECT=. |
| GLPOST | daily/weekly | Post V documents: ledger rows + ACBAL + period balances; flip to P. | GLJHDR, GLJDTL, GLACCT | GLLEDG, GLBAL, GLACCT; GLPOST DOCS= LINES= SKIP=. |
| GLBALCK | daily/annual | Re-derive ledger control totals; prove DR=CR and NET=0. | GLLEDG | GLBALCK ROWS= DR= CR= NET= OK|BAD. |
| GLRECUR | weekly | Generate recurring + accrual journals from templates (already validated). | GLRECR | GLJHDR/GLJDTL (JSTAT=V); GLRECUR GENERATED= SKIP=. |
| GLTRIAL | weekly/monthly | Re-derive GLBAL for the period straight from the detail ledger. | GLLEDG, GLBAL | GLBAL rewritten; GLTRIAL ROWS= DR= CR=. |
| GLTBPR | weekly | Print the trial balance (normal-side), totals and the DR-CR difference. | GLBAL, GLACCT | GLTBP print; GLTBPR LINES= DR= CR= DIFF=. |
| GLREVACC | monthly | Reverse each posted accrual (JSRC=A) into the following period. | GLJHDR, GLJDTL, GLACCT | GLLEDG (GSRC=V); GLREVACC DOCS= LINES= SKIP=. |
| GLCLOSE | monthly | Prove period balances, compute net income, write close log, lock period. | GLPERD, GLBAL, GLACCT | GLCLOG, GLPERD(PSTAT=C); GLCLOSE PER= DR= CR= NETINC= ST=. |
| GLSUMPB | monthly | Publish (restate) the closed period's account summary to the SQL extract. | GLBAL, GLACCT | GLSUMM; GLSUMPB ROWS= DR= CR=. |
| GLFINST | monthly/annual | COBOL: P&L + balance sheet from the ledger; assert assets=liab+equity+NI. | GLLEDG, GLACCT | GLFINST ASSETS/LIABS/EQUITY/REVENUE/EXPENSE/NETINC/BSCHECK/LEDROWS. |
| GLYRCLS | annual | Close every R/E account to zero; post the year result to retained earnings 310000. | GLACCT | GLLEDG (GSRC=Y), GLACCT zeroed; GLYRCLS CLOSED= NETINC= SKIP=. |
| GLNEWYR | annual | Build the FY2027 12-period calendar; carry balance-sheet accounts forward. | GLPERD, GLACCT, GLBAL | GLPERD (FY2027), GLBAL (POPEN); GLNEWYR YEAR= PERIODS= CARRIED=. |
The seeded worked example is five documents into batch 100001: four balanced
(JE000001..JE000004) and one deliberately out of balance
(JE000005, DR 1200.00 vs CR 1000.00). The edit run validates the four and rejects the fifth;
the post writes 9 ledger lines (2+2+2+3), moves the account and period balances, and the balance-check
proves the books.
Expected banners:
GLJEGEN DOCS= 5 SKIP= 0
GLEDIT VALID= 4 REJECT= 1
GLPOST DOCS= 4 LINES= 9 SKIP= 0
GLBALCK ROWS= 9 DR= 38500.00 CR= 38500.00 NET= 0.00 OK
GLRECUR generates two documents into period 1 from the seeded templates: recurring
JR100101 (DR 610000 100.00 / CR 150000 100.00) and accrual JA200101
(DR 600000 750.00 / CR 210000 750.00, flagged for reversal). Both enter at JSTAT='V' because
they were built from a controlled template, so GLPOST picks them up (+4 ledger rows → 13).
GLTRIAL then re-derives GLBAL for the period straight from the detail
ledger (zeroing the period's rows first so a stale account cannot survive), which is exactly what
catches a balance file that has drifted from the detail. GLTBPR prints it with a DR-CR
difference of zero.
Expected banners: GLRECUR GENERATED= 2 SKIP= 0 GLTRIAL ROWS= 9 DR= 39350.00 CR= 39350.00 38500 + 100 + 750 GLTBPR LINES= ... DR= 39350.00 CR= 39350.00 DIFF= 0.00
GLREVACC reverses each posted accrual document (JSRC='A') by writing opposite-sign ledger
rows into the following period (+2 rows → 15) — the standard accrue-and-reverse mechanic
so the real invoice, when it arrives, is not double-counted. GLCLOSE accumulates the period
from GLBAL, proves SUM(PDR)=SUM(PCR), computes net income
(revenue − expenses, both restated positive), writes a GLCLOG row, and
locks the period to PSTAT='C' only if it balanced (CSTAT='C').
GLSUMPB restates the period into GLSUMM (DELETE-then-INSERT, continuing
SUMSEQ past other periods). GLFINST (COBOL) emits the statements.
Expected banners (period 1 close): GLREVACC DOCS= 1 LINES= 2 SKIP= ... GLCLOSE PER= 1 DR= 39350.00 CR= 39350.00 NETINC= 3150.00 ST= C GLSUMPB ROWS= ... DR= 39350.00 CR= 39350.00 GLFINST REVENUE 15500.00 GLFINST EXPENSE 11600.00 life-to-date: 9000 COGS + 2500 rent + 100 insurance GLFINST NETINC 3900.00 life-to-date; period-1-only net income is 3150.00 GLFINST BSCHECK 0.00 assets+liabs+equity+rev+exp = 0
GLCLOSE reports
period-1 net income of 3150.00 (from period-1 rows only), while GLFINST reports
life-to-date net income of 3900.00 (across every posted period, including the period-2 rent
reversal that removes the 750.00 accrual). Know which one you are reading.GLYRCLS writes, for each revenue/expense account, a closing ledger entry equal to the
opposite of its life-to-date ACBAL (zeroing it), then posts the net of those entries
(the year's net income) to retained earnings 310000 so the whole set balances. After the
roll every P&L account is zero and equity carries the result. GLNEWYR builds the FY2027
calendar (12 periods, period 1 open, the rest future) and carries only the balance-sheet accounts
(A/L/Q) forward as GLBAL.POPEN for FY2027 period 1. GLBALCK and
GLFINST re-prove the books.
Expected banners: GLYRCLS CLOSED= 4 NETINC= 3900.00 SKIP= ... GLNEWYR YEAR= 2027 PERIODS= 12 CARRIED= 5 GLFINST REVENUE 0.00 GLFINST EXPENSE 0.00 GLFINST EQUITY 3900.00 retained earnings now carries the year result GLFINST BSCHECK 0.00
'V'), then post (only 'V' documents), then prove. GLPOST is
also the weekly cycle's post step for the generated journals.GLREVACC reverses posted
accrual documents; if the accrual was never generated and posted there is nothing to reverse.All files are in library LEDGER, grounded in sources.mjs. Dates are stored as
packed/zoned integers in YYYYMMDD form; money is 13P 2 (packed decimal, 2 dp);
the ledger sequence LEDSEQ is 15S 0 (widened from 8S0, see F.1).
| Field | Type | Meaning |
|---|---|---|
| ACCTNO | 6A | Account number (key), e.g. 100000. |
| ACDESC | 30A | Account description. |
| ATYPE | 1A | A asset, L liability, Q equity, R revenue, E expense — decides BS vs P&L, and whether the year-end roll closes it. |
| NORMDC | 1A | Normal balance side, D or C (stored, not forced — a debit-normal account can legitimately carry a credit balance). |
| ACBAL | 13P 2 | Life-to-date balance, debit-positive signed (credits subtract). |
| ACSTAT | 1A | Account status (A active). |
| ACPOST | 1A | Postable? Y postable, N header/no-post (e.g. 999000). |
Seeded chart (13 accounts): 100000 CASH, 110000 A/R, 120000 INVENTORY, 150000 PREPAID INSURANCE (assets, D); 200000 A/P, 210000 ACCRUED EXPENSES (liabilities, C); 300000 COMMON STOCK, 310000 RETAINED EARNINGS, 999000 SUMMARY HEADER (equity, C; 999000 is non-postable); 400000 SALES REVENUE (revenue, C); 500000 COGS, 600000 RENT EXPENSE, 610000 INSURANCE EXPENSE (expenses, D).
| Field | Type | Meaning |
|---|---|---|
| FYEAR / PERNO | 4S 0 / 2S 0 | Fiscal year + period number (key). |
| PERBEG / PEREND | 8S 0 | Inclusive period date window (YYYYMMDD). |
| PSTAT | 1A | O open, C closed, F future. A journal may only post into an OPEN period. |
| PCLSDT | 8S 0 | Close date, set when the period is locked. |
Seeded: FY2026 periods 1–3 open, period 4 future.
| Field | Type | Meaning |
|---|---|---|
| BATNO | 6S 0 | Batch number (key), e.g. 100001. |
| BDESC | 30A | Batch description. |
| BFYEAR / BPERNO | 4S 0 / 2S 0 | Target fiscal year / period. |
| BSTAT | 1A | E entered, V validated, P posted, R rejected. |
| BDRTOT / BCRTOT | 13P 2 | Control totals recomputed from the detail and compared. |
| BDOCS | 5S 0 | Document count in the batch. |
| BERRCD | 2A | Error code when rejected (e.g. DB). |
| Field | Type | Meaning |
|---|---|---|
| DOCNO | 8A | Document number (key) — the stable business key posting derives its ledger key from. |
| BATNO | 6S 0 | Owning batch. |
| JDESC | 30A | Document description. |
| JFYEAR / JPERNO / JDATE | 4S 0 / 2S 0 / 8S 0 | Target year / period / posting date. |
| JSTAT | 1A | E entered, V validated, P posted, X rejected. |
| JSRC | 1A | M manual, R recurring, A accrual, V reversal, Y year-end. |
| JDRTOT / JCRTOT | 13P 2 | Document debit / credit totals (recomputed by the edit run from its own lines). |
| Field | Type | Meaning |
|---|---|---|
| DOCNO / LSEQ | 8A / 3S 0 | Document + line sequence (key). |
| ACCTNO | 6A | Account the line hits. |
| DRCR | 1A | Side: D or C (the sign lives here). |
| LAMT | 13P 2 | Line amount — ALWAYS the unsigned magnitude. |
| LMEMO | 25A | Line memo. |
| LSTAT | 1A | Line status (E entered). |
| Field | Type | Meaning |
|---|---|---|
| LEDSEQ | 15S 0 | Ledger sequence (key) — derived from the stable business key; disjoint ranges per program (see F.1). |
| ACCTNO | 6A | Account. |
| DOCNO | 8A | Source document. |
| LFYEAR / LPERNO / GDATE | 4S 0 / 2S 0 / 8S 0 | Year / period / posting date. |
| GDRCR | 1A | Side D/C. |
| GAMT | 13P 2 | Amount, debit-positive signed (a credit is negative). SUM(GAMT) over the ledger must be zero. |
| GSRC | 1A | Source class M/R/A/V/Y. |
| GMEMO | 25A | Memo. |
| Field | Type | Meaning |
|---|---|---|
| BFYEAR / BPERNO / ACCTNO | 4S 0 / 2S 0 / 6A | Year / period / account (key). |
| PDR / PCR | 13P 2 | Period gross debit / credit turnover (both unsigned). |
| PNET | 13P 2 | Period net, debit-positive. |
| POPEN | 13P 2 | Opening balance (set only by the new-year carry-forward). |
| Field | Type | Meaning |
|---|---|---|
| TMPLID / TSEQ | 6A / 3S 0 | Template id + line sequence (key). |
| TDESC | 30A | Template description. |
| TKIND | 1A | R recurring, A accrual (accrual is reversed next period). |
| ACCTNO / TDRCR / TAMT | 6A / 1A / 13P 2 | Account, side, amount for the generated line. |
| TREVSW | 1A | Y if the generated journal must be auto-reversed. |
| TSTAT | 1A | Template status (A active). |
Seeded: RC0001 (recurring, DR 610000 / CR 150000, 100.00, not reversed) and AC0001 (accrual, DR 600000 / CR 210000, 750.00, reversed).
| Field | Type | Meaning |
|---|---|---|
| STMT / FSLINE | 1A / 3S 0 | P profit&loss / B balance sheet, then print-order line (key). |
| FSDESC | 30A | Line caption. |
| FSTYPE | 1A | D detail (sum accounts of type FSATYP) / T total. |
| FSATYP / FSSIGN | 1A / 1A | Account type this detail line sums / presentation sign. |
Seeded: 3 P&L lines (revenue, expenses, net income) + 4 BS lines (assets, liabilities, equity, total). This turns a chart of accounts into a formatted statement without hard-coding layout in the program.
| Field | Type | Meaning |
|---|---|---|
| CLSEQ | 8S 0 | Close sequence = FYEAR×100 + PERNO (key, the idempotency guard). |
| CFYEAR / CPERNO / CDATE | 4S 0 / 2S 0 / 8S 0 | Closed year / period / date. |
| CDRTOT / CCRTOT | 13P 2 | Period DR / CR proof totals. |
| CNETINC | 13P 2 | Period net income (revenue − expenses). |
| CSTAT | 1A | C closed-clean (balanced) / E error (out of balance — period not locked). |
DB2 table published by the monthly close for downstream consolidation:
SUMSEQ, SFYEAR, SPERNO, ACCTNO, ATYPE, SDR, SCR, SNET, with index
GLSUMACC (SFYEAR, SPERNO, ACCTNO). GLSUMPB restates one (year, period) per run
(DELETE-then-INSERT), continuing SUMSEQ past other periods, and only totals a row into its
banner when the INSERT actually succeeded (SQLCOD = 0).
CALL LEDGER/GLSETUP then CALL LEDGER/GLREFLD to build the
objects/programs and seed the chart, calendar, templates and statement layout.PSTAT='O' for FY2026 period 1 in the seeded sample).SBMJOB CMD(CALL PGM(LEDGER/GLDAILY)).GLWEEK; check GLRECUR GENERATED=, the trial
balance and GLTBPR ... DIFF= 0.00.CALL LEDGER/GLMENU (option 1 or 2).Pre-checks: the job's library list includes LEDGER; the seeded example targets
FY2026 period 1 and is open.
Post-checks after the daily cycle:
GLEDIT VALID= 4 REJECT= 1 — the four balanced documents validated, the out-of-balance one rejected; the batch is BSTAT='R' with BERRCD='DB'.GLPOST DOCS= 4 LINES= 9 — only 'V' documents posted; the rejected document contributed nothing.GLBALCK ... DR= 38500.00 CR= 38500.00 NET= 0.00 OK — a trailing OK, DR=CR, net zero.GLMONTH. Confirm GLREVACC DOCS= 1 LINES= 2,
GLCLOSE PER= 1 ... NETINC= 3150.00 ST= C (a ST= E means the period did NOT
balance and was not locked), and the SQL extract / COBOL statements below.GLPERD period 1 now PSTAT='C' with a
PCLSDT. A later journal aimed at it will be edit-rejected with 'PC'.GLYEAR. Confirm GLYRCLS CLOSED= 4 NETINC= 3900.00,
GLNEWYR YEAR= 2027 PERIODS= 12 CARRIED= 5, and that every R/E account is now zero with
retained earnings 310000 carrying -3900.00 (a credit, i.e. a profit).Review the close log and the SQL extract: SELECT CFYEAR, CPERNO, CDRTOT, CCRTOT, CNETINC, CSTAT FROM LEDGER.GLCLOG; SELECT SUM(SDR), SUM(SCR), SUM(SNET) FROM LEDGER.GLSUMM; 39350.00 = 39350.00, 0 SELECT SUM(SCR) FROM LEDGER.GLSUMM WHERE ATYPE = 'R'; 15500.00 revenue
These are the same figures the daily and cycle suites derive by hand from the five seeded documents and assert against an independent oracle — every one is checkable in production the same way. The single master check is the signed ledger sums to zero.
GLBALCK re-derives
this from GLLEDG and must print OK.SUM(PDR) and SUM(PCR)
over the period each equal 38500.00 (daily) / 39350.00 (after weekly), and SUM(PNET)=0.
A cash period row keeps turnover separately: PDR 12000.00, PCR 2500.00, PNET 9500.00.GLTRIAL rebuilds GLBAL
straight from GLLEDG and GLTBPR must print DIFF= 0.00. A
non-zero DIFF means the incremental balance file has drifted from the ledger — investigate
before closing.GLCLOSE CNETINC). Life-to-date after the reversal: 15500.00 − 11600.00 =
3900.00 (GLFINST NETINC).GLFINST BSCHECK (assets+liabs+equity+rev+exp, debit-positive) must be 0.00.Every program DSPLYs a one-line banner; a healthy daily run ends in GLBALCK ... OK. All
four cycles are safely re-runnable because every write is keyed off a stable business key and guarded.
| Situation | Behaviour | Action |
|---|---|---|
| Re-run GLJEGEN / GLPOST / GLRECUR / GLREVACC / GLYRCLS / GLNEWYR | Stable keys + CHAIN(EN) / JSTAT guards make a second run a no-op. | Safe. E.g. a second post shows DOCS= 0 LINES= 0; balances and ledger row counts are unchanged. |
| Batch carries an out-of-balance document | GLEDIT rejects that document ('X') and the whole batch (BSTAT='R', BERRCD='DB'). | Correct the entry, re-capture, re-edit. Nothing from a rejected batch reaches the ledger. |
| Journal aimed at a closed period | Edit-rejected with error 'PC' even if perfectly balanced. | Re-open the period (or re-date the journal into an open period) and re-edit. |
| Journal aimed at a non-postable account (e.g. 999000) | Edit-rejected with error 'NP'. | Re-point the line at a postable detail account. |
| Period fails to balance at close | GLCLOSE writes the log row with CSTAT='E' and does not lock the period. | Fix the imbalance (check GLTRIAL/GLBALCK), then close again. |
| Second close of the same period | Refused via the GLCLOG key; banner GLCLOSE ALREADY CLOSED PER=. | No action — the period is already closed; the log stays one row. |
| Two documents share the last 3 body digits (e.g. JE000123 / JE100123) | Historically collided on a lossy ledger key; now folded losslessly into the widened 15S0 LEDSEQ. | Both post under distinct keys. See F.1. |
GLLEDG row (debit-positive, source-tagged,
uniquely keyed) and the incremental balances can always be re-derived from that detail by
GLTRIAL, any cycle's effect is fully reconstructable and independently provable after the
fact for reconciliation and recovery.Fixed-form RPG (C-specs) plus /free over keyed DDS, one ILE COBOL program and one
embedded-SQL RPG program. All objects are in library LEDGER, held as source-physical-file
members (loaded by seed.mjs' seedLedger()).
Every posting program contributes to one accumulating GLLEDG history keyed UNIQUE on
LEDSEQ, derived entirely from a stable business key so a re-run regenerates the same key and
the CHAIN(EN) guard refuses it rather than double-posting. Ranges are disjoint per program so
keys never collide across cycles:
10000000000000 + prefixcode×10^9 + docbody6×1000 + line.30000000000000 + ... (same fold).50000000 + account digits.The 2-character document prefix is folded in via a character-set scan (%scan(ch:WCSET)) so
JE000001 and SM000001 can't collide, and the full 6-digit document body is
folded in losslessly. LEDSEQ was widened from 8S0 to 15S0 precisely
to hold this: an 8-digit field could only keep a lossy 3-digit slice of the 6-digit body, so
JE000123 and JE100123 (same prefix, same last 3 digits) collided — the
second validated cleanly but its lines were silently swallowed by the idempotency guard, stuck at
JSTAT='V' with no diagnostic. The widened, full-body fold makes distinct documents impossible
to collide while a genuine re-post still regenerates the identical key.
DOCNOs, recomputes each document's DR/CR and
the batch control totals from the detail. CHAIN(EN) guards on (DOCNO,LSEQ) for lines and
DOCNO for headers make it idempotent (SKIP= counts skips).'E' document: checks period open (else 'PC'), walks the lines summing
DR/CR and checking account exists+active ('AC') and postable ('NP'), then
DR=CR ('DB'). Sets JSTAT V/X and recomputes JDRTOT/JCRTOT. A separate pass
rejects any batch that carried a failing document.'V' document: for each line writes a debit-positive GLLEDG row under the
stable key (F.1), moves ACBAL, and accumulates the (year,period,account)
GLBAL row (gross PDR/PCR + signed PNET); then flips the header to 'P'.GLLEDG, sums positive amounts as DR and negatives as CR and totals the signed net;
prints OK when DR=CR.TMPLID; each template becomes one document numbered stably from the
template id + period (JR100101, JA200101), source R/A, entering at
'V'. Copies ACCTNO out to a work field before the write because GLRECR and
GLJDTL share the field name (see F.3).GLBAL rows, then re-accumulate straight from
GLLEDG — catching any drift between the incremental balances and the detail.OFLIND(*IN90).JSRC='A' document: writes opposite-sign GLLEDG rows (source
'V') into jperno+1 and moves ACBAL back.GLBAL, computes net income from R/E accounts, writes a
GLCLOG row (CSTAT C if DR=CR else E) and locks the period
(PSTAT='C') only when it balanced. Guarded by the GLCLOG key.DELETE of the period then INSERT per non-zero balance,
continuing SUMSEQ past other periods. Only totals a row into the banner when
SQLCOD = 0 — a refused insert is not reported as published.GLLEDG row and zeroes it;
pass 2 posts the net (year income) to retained earnings 310000 so the set balances.
Keyed 50000000 + account.GLBAL.POPEN for FY2027 period 1.GLLEDG and randomly reads GLACCT by account, accumulates assets /
liabilities / equity / revenue / expense (debit-positive), computes net income
(0 − revenue) − expense, and the balance-sheet check
assets+liabs+equity+rev+exp which must be zero.SFLCLR is conditioned
on indicator 31 (not 3) and actually clears, SFLDSP/SFLDSPCTL on
N31, and SFLEND on 90 (not 9). The build suite asserts the parsed
conditions, not just keyword presence.GLRECR and GLJDTL both carry a
field called ACCTNO, sharing one storage location; the template account is copied to
WACC before the GLJDTL write, or the write would clobber the value it needs.%editc(...:'K'): thousands
separators and a trailing minus, so a stored −15500.00 shows as 15,500.00-
and its flipped normal-side balance as a clean 15,500.00. This is real AS/400 edit-code
behaviour, not a defect.| Code / banner | Raised by | Meaning |
|---|---|---|
| DB | GLEDIT / GLBAT | Document debits ≠ credits (and the batch rejected with it). |
| AC | GLEDIT | Account does not exist, or is not active. |
| NP | GLEDIT | Account is not postable (header account). |
| PC | GLEDIT | Target period is not open (future or closed). |
| CSTAT=E | GLCLOSE | Period did not balance; period NOT locked. |
| ALREADY CLOSED | GLCLOSE | Period already has a close-log row; refused. |
| ... OK / ... BAD | GLBALCK | Ledger DR=CR (OK) or not (BAD). |
| BSCHECK 0.00 | GLFINST | Balance-sheet identity holds (assets = liab + equity + NI). |
jperno+1.SBMJOB CMD(CALL PGM(LEDGER/GLDAILY))).