PROVIDENT/i is a defined-contribution provident / retirement-savings administration core:
member accounts fed by employer + employee pay-period contributions, periodic dividend/interest
crediting, balance-aware withdrawals and loans, a contribution-dues billing and cash-application
receivables cycle, aged-arrears reporting, member statements and inquiries, and a balanced double-entry
GL feed. The business logic lives in fixed- and free-form RPG, one ILE COBOL report, and DDS/SQL data
definitions; CL binds and sequences it. This manual is the reference for the operator who runs the
online screens and the batch cycles, and for the developer maintaining the application. It is grounded
entirely in the committed source (provident-app/src/sources.mjs and src/seed.mjs,
library PROVFND, and the test/pf_*.mjs drivers).
PLAN.md), but its
batch programs carry known, documented app-logic gaps around idempotency that the operator must
work around by convention: GLBUILD and CASHAPP are not re-run safe, and
DIVCRD's dividend period is hardcoded. These are called out plainly in sections C and
E, exactly as the source and its sim-loop findings record them — they are not hidden behind polish.
PROVIDENT/i administers a defined-contribution provident fund — one member holds one account:
CONTRIB posts both as
immutable ledger rows, bumps the member balance and the YTD EE/ER buckets, and carries a running
balance.DIVCRD reads the period's declared rate from
DIVRATE and credits each active member a quarterly share of its balance, posting a
DV ledger row per member.WDRAW takes a balance-aware withdrawal, rejecting an
over-balance request and otherwise posting a negative-amount WD ledger row and
decrementing the balance.LOANPAY calls the ELIGCHK eligibility subprogram
(active member, max 50% of balance) and, on approval, creates or accumulates a LOANMST
loan and posts an LN ledger row.PFBILL bills each active member the expected
pay-period contribution dues (5% of balance) into CONBILL; CASHAPP applies
employer remittances (PFRCPT) against the oldest still-open due, full/partial-pay aware;
ARRRPT ages the outstanding dues into a printed register.GLBUILD consolidates the ledger into balanced
double-entry GLLEDGER rows via embedded EXEC SQL; the COBOL
FUNDRPT cross-checks active-member balances against the fund-balance GL credits.STMTPR prints a member-statement register;
MEMINQ (plain 5250), LDGINQ (subfile ledger) and the PFMENU
operator menu serve online inquiry.PROVIDENT/i is a classic program-driven IBM i application: the business rules are coded directly in the
RPG/COBOL programs (there is no SQL-PL stored-procedure layer here — that is a different
application, LOANSVC/i). Data lives in DDS-described physical/logical files plus one SQL DDL table; the
programs use native record-level I/O (CHAIN/READ/WRITE/UPDATE)
for the master and ledger files, and embedded EXEC SQL only where commitment control matters
(the GL post). Everything is built into and runs in one library, PROVFND, whose source physical
files (QDDSSRC, QRPGLESRC, QCBLLESRC, QCLSRC,
QSQLSRC) are populated by src/seed.mjs exactly the way the emulator seeds its own
DEMOLIB.
Two boundaries the operator should know:
CONTRIB/WDRAW/LOANPAY/DIVCRD master updates commit
as they are written. The only committed posting path is GLBUILD's embedded
EXEC SQL INSERT into GLLEDGER.CONTRN (the transaction ledger) is under
STRJRNPF ... IMAGES(*BOTH) to PFJRN, so every WRITE/UPDATE is captured
before/after for audit and recovery. ONLINE (5250) INTRADAY POSTING BATCH / NIGHT CHAIN (PFNIGHT)
------------- ----------------- -----------------------------
PFMENU ---opt1--> MEMINQ CONTRIB (EE+ER) PFBILL --> CONBILL dues (5% bal)
\--opt2--> LDGINQ WDRAW (-WD, bal-aware) DIVCRD --> DV credit (DIVRATE)
(SFL LOANPAY --CALL--> ELIGCHK GLBUILD --> GLLEDGER DR/CR (EXEC SQL)
ledger) CASHAPP (PFRCPT -> CONBILL) FUNDRPT --> COBOL cross-check
ARRRPT --> aged-arrears PRTF spool
\ | /
\ v /
+----------> CONTRN (journaled ledger, RUNBAL) <+
| TRTYPE: OB/EE/ER/DV/WD/LN
v
MEMMST (BALANCE / YTDEE / YTDER) LOANMST (loan account)
GLLEDGER (balanced DR/CR) STMTP / ARREARP (spool)
A single contribution flows: CONTRIB is called with member + EE + ER → it scans
CONTRN for the next TRANID, writes an EE then an ER row
(each carrying the post-write RUNBAL), and updates the member's BALANCE,
YTDEE and YTDER. Because CONTRN is journaled, both rows are captured
to PFJRN. At night, GLBUILD reads the day's ledger and emits the balanced GL pairs.
| Object | Type | Role |
|---|---|---|
| EMPLR | PF | Employer master (code, name, status). |
| MEMMST | PF | Member master — balance + YTD EE/ER (the heart of the app). |
| CONTRN | PF (journaled) | Contribution/transaction ledger, unique TRANID. |
| CONLF | LF | CONTRN keyed by member (statement history + subfile). |
| DIVRATE | PF | Period → dividend crediting rate table. |
| LOANMST | PF | Member loan account (one open loan per member). |
| LOANLF | LF | LOANMST keyed by status (open-loan report). |
| CONBILL | PF | Contribution-dues billing/receivables (MEMNO+PERIOD). |
| BILLLF | LF | CONBILL keyed DUEDT (arrears forward-scan). |
| PFRCPT | PF | Employer remittance receipts (arrival, unique RCPTNO). |
| GLLEDGER | SQL table | Balanced DR/CR GL feed (RUNSQLSTM DDL). |
| MEMDSPF / LDGDSPF / PFMENUD | DSPF | Member inquiry / ledger subfile / operator menu. |
| STMTP / ARREARP | PRTF | Statement register / aged-arrears register. |
| SEEDDATA | RPGLE | Loads employers, members, rates, opening rows. |
| ELIGCHK | RPGLE | Loan-eligibility subprogram (dcl-pi entry parms). |
| CONTRIB / WDRAW / LOANPAY / DIVCRD | RPGLE | Contribution / withdrawal / loan / dividend posting. |
| PFBILL / CASHAPP / ARRRPT | RPGLE | Billing / cash application / aged arrears. |
| GLBUILD | RPGLE | GL consolidation (embedded EXEC SQL, committed). |
| STMTPR / MEMINQ / LDGINQ / PFMENU | RPGLE | Statement print / inquiry / subfile / menu. |
| FUNDRPT | CBLLE | COBOL active-balance vs GL fund-balance cross-check. |
| PFSETUP / PFNIGHT | CLP | Build+journal+authority / night-batch chain. |
The catalogue is 8 PFs + 3 LFs + 1 SQL table, 3 DSPFs + 2 PRTFs, 14 RPG programs + 1 COBOL program +
2 CL programs, all in library PROVFND, plus the PFJRN/PFJRNRCV
journal pair over CONTRN. Sections D and F expand each.
PROVIDENT/i has no CICS transaction identifiers and no menu-driven transid switch. On IBM i,
each program is reached by name from a 5250 command-entry line (or via a JOBQ/scheduler for the
batch jobs). 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 PROVFND —
the tested jobs run with LIBL = QSYS QGPL PROVFND QTEMP and CURLIB = PROVFND.
| To do this | Type on the command line |
|---|---|
| Open the operator menu (routes to member/ledger inquiry) | CALL PROVFND/PFMENU |
| Member inquiry directly | CALL PROVFND/MEMINQ |
| Ledger inquiry (subfile) directly | CALL PROVFND/LDGINQ |
| Post a pay-period contribution (EE + ER) | CALL PROVFND/CONTRIB PARM('M000001' 200.00 300.00) |
| Take a withdrawal | CALL PROVFND/WDRAW PARM('M000003' 5000.00) |
| Disburse a loan (calls ELIGCHK) | CALL PROVFND/LOANPAY PARM('M000003' 5000.00) |
Run a batch step (or SBMJOB it) | CALL PROVFND/DIVCRD, .../PFBILL, .../CASHAPP, ... |
| Run the whole night chain | CALL PROVFND/PFNIGHT (or SBMJOB it) |
| Build/seed the whole library from source | CALL PROVFND/PFSETUP then CALL PROVFND/SEEDDATA |
Parameter reality — honest: the interactive programs take no parameters; the posting
programs CONTRIB/WDRAW/LOANPAY take entry parameters via
dcl-pi as shown. The batch programs DIVCRD, PFBILL,
CASHAPP, GLBUILD, ARRRPT, STMTPR and
FUNDRPT take no parameters at all — their working values (period code, due date,
dividend period) are hardcoded dcl-s ... INZ(...) literals inside the program
(DIVCRD/DIVRATE period '2026Q1', PFBILL period
'2026Q2' / due date 20260401, ARRRPT as-of 20260601).
There is no control table and no parameter to steer them (see C.4).
Three DSPF programs make up the online surface: the PFMENU operator menu, the plain
MEMINQ member inquiry, and the LDGINQ ledger inquiry — the app's one real
subfile screen.
PFMENU EXFMTs the MENUFMT format, reads the one-character MOPT,
and routes: option 1 → CALL 'MEMINQ', option 2 →
CALL 'LDGINQ' (fixed-form indicator-conditioned CALL). Any other option shows
Invalid option and re-displays. When the called program exits (F3), control returns to the
menu.
Key a member number into IMEMNO and Enter; MEMINQ
CHAINs MEMMST and displays the header (name, employer, status, balance, YTD EE, YTD ER) or
Member not found: <n>. A blank key shows Enter a member number.
F3 exits.
| Field | Type (DDS) | Shows |
|---|---|---|
| IMEMNO | 7A B | Member number keyed for inquiry. |
| DNAME / DEMPNO / DSTAT | 25A / 5A / 1A O | Member name / employer / status. |
| DBAL / DYEE / DYER | 15A O | Balance / YTD employee / YTD employer (rendered from packed). |
| DMSG | 50A O | Highlighted status/result message line. |
LDGINQ is the one interactive subfile program. It CHAINs MEMMST by
ILMEMNO, populates the member header, clears the subfile (SFLCLR), then loops the
CONLF logical file (CONTRN keyed by member) writing each ledger row into the LDGSFL
subfile (date, type, amount, running balance) under control record LDGCTL
(SFLPAG(10) per page, SFLSIZ(30)). Indicators mirror the DDS: 41=SFLDSP,
42=SFLDSPCTL, 43=SFLCLR, 44=SFLEND(*MORE). With a member that has 12
ledger rows, page 1 shows the first 10 with More...; Page Down
shows the remaining 2 and Bottom.
| Field | Type (DDS) | Shows |
|---|---|---|
| ILMEMNO | 7A B | Member number keyed for the ledger. |
| DLNAME / DLBAL | 25A / 15A O | Member name / current balance in the header. |
| SLTRDT / SLTRTYP | 8A / 2A O | Ledger row transaction date / type. |
| SLTRAMT / SLRUNBL | 14A O | Row amount / post-transaction running balance. |
LDGINQ; it renders the member's immutable ledger. All money movement
happens through the posting programs (CONTRIB/WDRAW/LOANPAY/
DIVCRD), never through a screen edit.Honest statement: PROVIDENT/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user posts, a second user approves" step: a contribution, withdrawal or loan posted through the CALL is applied immediately by the program itself. The control model the application does have is data-layer accountability and gating:
OB,
EE, ER, DV, WD, LN) is an append-only
row with a unique TRANID and a post-transaction RUNBAL, and
CONTRN is journaled to PFJRN under IMAGES(*BOTH) — so the
WRITE (and any later UPDATE) is captured before/after and rendered by DSPJRN. This is the
after-the-fact reconstruction trail.ELIGCHK is a separate callable subprogram: a loan is declined
unless the member is active and the request is within 50% of current balance.
LOANPAY writes nothing on a decline.WDRAW rejects any request greater than the current
BALANCE (no ledger row written, balance untouched); CONTRIB/WDRAW/
LOANPAY all reject an unknown member (REJECT NOMEMBER).PFSETUP revokes *PUBLIC *ALL on the sensitive
masters and grants QPGMR *USE on MEMMST/LOANMST (RVKOBJAUT /
GRTOBJAUT), exercising the authority path.GLBUILD cross-checks DR = CR over the
batch and DSPLYs IN BALANCE / OUT OF BALANCE; FUNDRPT
independently re-totals active-member balances against the GL fund-balance credits.In sum, the posture is immutable+journaled audit + eligibility/balance gating + object authority + balanced-GL control totals, enforced program-by-program, rather than a segregation-of-duties approval workflow.
PROVIDENT/i's processing splits into intraday postings (member-initiated: contributions,
withdrawals, loans, applied on demand through a CALL) and batch cycles (the billing, dividend, GL
and reporting programs, typically run once per period through the PFNIGHT chain). Unlike a
control-table-driven design, none of the batch programs read a processing date from a control row
— each carries its period/date as a hardcoded literal (see B.1 and C.4). A scheduled submission is
therefore a bare CALL/SBMJOB.
-- submit the whole night chain onto a job queue SBMJOB CMD(CALL PGM(PROVFND/PFNIGHT)) JOB(PFNIGHT) -- or submit one step SBMJOB CMD(CALL PGM(PROVFND/DIVCRD)) JOB(DIVCRD)
| Program | Purpose | Reads / writes | Inputs | Outputs (DSPLY + files) | Frequency |
|---|---|---|---|---|---|
| CONTRIB | Post a pay-period contribution (EE + ER shares). | UPDATE MEMMST; WRITE 2×CONTRN. | PARM(memno, EE, ER). |
CONTRIB MEM=.. EE=.. ER=.. NEWBAL=..; two ledger rows (EE, ER), fresh sequential TRANIDs, RUNBAL per row. |
Per pay period / on demand. |
| WDRAW | Balance-aware withdrawal. | UPDATE MEMMST; WRITE CONTRN (WD, negative). | PARM(memno, amount). |
WDRAW MEM=.. AMT=.. NEWBAL=.. or WDRAW REJECT INSUFFICIENT (no row). |
On demand. |
| LOANPAY | Loan disbursement; CALLs ELIGCHK. | CALL ELIGCHK; WRITE/UPDATE LOANMST; WRITE CONTRN (LN). | PARM(memno, requested). |
LOANPAY APPROVE MEM=.. + ELIGCHK STAT=A ..., or LOANPAY DECLINE (nothing written). |
On demand. |
| DIVCRD | Dividend/interest crediting for the period. | CHAIN DIVRATE; walk+UPDATE MEMMST; WRITE CONTRN (DV). | Period hardcoded '2026Q1' (dcl-s). |
DIVCRD PERIOD=.. RATE=.. MEMBERS=.. TOTAL=..; one DV row per active member. |
Per dividend declaration. |
| PFBILL | Bill contribution dues (5% of balance) for a period. | Walk MEMMST; CHAIN+WRITE CONBILL. | Period hardcoded '2026Q2', due date 20260401. |
PFBILL PERIOD=.. GENERATED=n SKIPPED=n; one due row per active member. |
Per billing period. |
| CASHAPP | Apply employer remittances against oldest-open dues. | READ PFRCPT; CHAIN+UPDATE CONBILL. | None (walks all of PFRCPT from BOF). | CASHAPP FULL=n PARTIAL=n NOBILL=n; CONBILL PAIDAMT/BSTAT updated. |
Per cash run. |
| GLBUILD | Consolidate CONTRN into balanced GLLEDGER DR/CR. | READ CONTRN; EXEC SQL INSERT GLLEDGER. |
None (walks all of CONTRN from BOF, batch id 1). | GLBUILD BATCH=1 ROWS=n DR=.. CR=.. IN BALANCE; balanced GL pairs. |
Per period close. |
| FUNDRPT | COBOL cross-check: active balances vs GL fund credits. | READ MEMMST; EXEC SQL SUM GLLEDGER. |
None. | FUNDRPT ACTIVE-MEMBERS n BAL .. + FUNDRPT GLFUND-CREDITS n AMT ... |
Per period close. |
| ARRRPT | Aged contribution-arrears register (PRTF). | SETLL/READ BILLLF; WRITE ARREARP spool. | As-of hardcoded 20260601. |
ARRRPT ROWS=.. CURRENT=.. D30=.. D60=.. D90PLUS=.. GRAND=..; ARREARP spool. |
Periodic. |
| STMTPR | Member-statement register (PRTF). | READ MEMMST; WRITE STMTP spool. | None. | STMTPR MEMBERS=n TOTAL=..; STMTP spool. |
Periodic. |
Given (memno, EE, ER), CONTRIB scans CONTRN for the current highest
TRANID (SETGT *HIVAL + READP), then writes an EE row and an
ER row with the next two IDs, each carrying the post-write RUNBAL. It bumps
MEMMST.BALANCE, YTDEE and YTDER. An unknown member is rejected with no
row written.
CALL PROVFND/CONTRIB PARM('M000001' 200.00 300.00) -- opening balance 10000 CONTRIB MEM=M000001 EE=200.00 ER=300.00 NEWBAL=10500.00 -> MEMMST: BALANCE=10500, YTDEE=200, YTDER=300 -> CONTRN: EE row TRANID=3 RUNBAL=10200 ; ER row TRANID=4 RUNBAL=10500
DIVCRD CHAINs DIVRATE for its (hardcoded) period, then walks
MEMMST and credits each active member BALANCE × rate / 4 (a quarterly
slice of the annual rate), updating the balance and writing a DV ledger row. Terminated /
retired members are skipped.
CALL PROVFND/DIVCRD -- 2026Q1 @ 0.060 annual, 3 active members DIVCRD PERIOD=2026Q1 RATE=0.06 MEMBERS=3 TOTAL=1050.00 10000 -> 10150 (+150), 20000 -> 20300 (+300), 40000 -> 40600 (+600) terminated member (MSTAT='T') NOT credited
PFBILL walks active members and bills each BALANCE × 0.05 into
CONBILL for its period, guarded by a full composite-key CHAIN dup-check — so a re-run for
the same period generates nothing and skips everyone (idempotent per period). CASHAPP reads each
PFRCPT receipt and applies it to the member's oldest still-open due row (a bounded
full-key scan over the two known period codes): full cover → BSTAT='P',
PAIDAMT=BILLAMT; partial → BSTAT='D', PAIDAMT += receipt; no open
due → counted NOBILL. ARRRPT forward-scans BILLLF (oldest due
first) and buckets outstanding rows CURRENT / 30 / 60 / 90+ against its as-of date, writing the
ARREARP spool.
CALL PROVFND/PFBILL -- first run, 3 active members, none billed yet PFBILL PERIOD=2026Q2 GENERATED=3 SKIPPED=0 dues 500 / 250 / 1250 (5% of 10000/5000/25000) CALL PROVFND/PFBILL -- immediate re-run (idempotent) PFBILL PERIOD=2026Q2 GENERATED=0 SKIPPED=3 CALL PROVFND/CASHAPP -- M2 remits 250 (full), M3 remits 600 (partial), M1 remits 100 (partial) CASHAPP FULL=1 PARTIAL=2 NOBILL=0
GLBUILD reads every CONTRN row and emits a balanced DR/CR pair per posting type
through embedded EXEC SQL INSERT into GLLEDGER (batch id 1), plus a
3100-FUNDBAL fund-balance credit for EE/ER rows so FUNDRPT can cross-check. It
accumulates DR and CR and DSPLYs whether they balance. FUNDRPT (COBOL) totals active-member
balances and independently SUMs the GL fund-balance credits.
CALL PROVFND/GLBUILD -- ledger: EE 200 + ER 300 + DV 1050 GLBUILD BATCH=1 ROWS=.. DR=1550.00 CR=1550.00 IN BALANCE CALL PROVFND/FUNDRPT FUNDRPT ACTIVE-MEMBERS 3 BAL .. FUNDRPT GLFUND-CREDITS 2 AMT 500.00 EE+ER fund credits = 500
3000-DIVEXP and 3100-FUNDBAL are 11 characters
but GLLEDGER.ACCT is CHAR(10), so they store truncated (3000-DIVEX /
3100-FUNDB) — consistently on both INSERT and SELECT, so cross-checks still line up. This
is a documented app detail, not a defect.PFNIGHT is the batch chain, callable directly or via SBMJOB:
PGM CALL PGM(PROVFND/PFBILL) 1. bill the period's contribution dues CALL PGM(PROVFND/DIVCRD) 2. dividend credit CALL PGM(PROVFND/GLBUILD) 3. GL post (embedded EXEC SQL, committed) CALL PGM(PROVFND/FUNDRPT) 4. COBOL cross-check SNDPGMMSG MSG('PFNIGHT batch chain complete') ENDPGM
*inlr=*on before the next. This closes its files so the next
reader sees the written rows — the platform makes rows WRITEd to an output-opened file invisible
to a called program until the writer closes (a documented quirk, Q3). GLBUILD reads a
CONTRN that the earlier posting programs have fully written and closed.CONTRN before
GLBUILD consolidates the day, or the GL misses the DV movement.FUNDRPT cross-checks against the GL fund-balance credits
GLBUILD emits, so it must run after.These are the app's documented, reproduced behaviours (from PLAN.md's sim-loop
findings). They are app-logic characteristics, not platform bugs, and the operator must design around them:
| Program | Re-run behaviour | Operator rule |
|---|---|---|
| PFBILL | Idempotent per period. The full-key CHAIN dup-guard skips already-billed members; a re-run generates 0. | Safe to re-submit for the same period. |
| GLBUILD | NOT idempotent. It walks all of CONTRN from BOF every call with no batch-id/date filter and no "posted" marker. A second run with zero new ledger activity doubles every GLLEDGER row — and because DR and CR double together, its own "DR=CR" check still prints IN BALANCE on the corrupted result (SIM-2026-W6). |
Run GLBUILD exactly once per period. Never resubmit PFNIGHT/GLBUILD "just in case". The app enforces this by convention only — there is no programmatic guard. |
| CASHAPP | NOT idempotent across runs. PFRCPT has no applied/consumed marker, and CASHAPP reads from BOF each call, so a second run re-applies receipts already applied in a prior run (SIM-2026-W2). |
Do not re-run CASHAPP against a PFRCPT that still holds already-applied receipts. Clear/replace processed receipts before the next run (the tests CLRPFM PFRCPT between runs). |
| DIVCRD | Period is hardcoded. WPERIOD is a dcl-s ... INZ('2026Q1') with no dcl-pi, so every run credits the 2026Q1 rate regardless of calendar quarter; DIVRATE rows for other quarters are never consulted (SIM-2026-Y4). Crediting still compounds correctly off current balance — it is simply always priced off Q1's rate. |
Understand that DIVCRD as shipped always credits the 2026Q1 rate. Changing the crediting period is a source change (the literal), not a parameter. |
All files are in library PROVFND, grounded in src/sources.mjs (DDS) and the SQL
DDL member. Dates are stored as zoned 8S 0 in YYYYMMDD form; money is packed
11P 2; the dividend rate is packed 5P 3. Every native output file is UNIQUE-keyed.
| Field | Type | Meaning |
|---|---|---|
| EMPNO | 5A | Employer code (PK), e.g. EMP01. |
| ENAME | 25A | Employer name. |
| ESTAT | 1A | Employer status (A active). |
| Field | Type | Meaning |
|---|---|---|
| MEMNO | 7A | Member number (PK), e.g. M000001. |
| MNAME | 25A | Member name. |
| EMPNO | 5A | Owning employer (references EMPLR). |
| HIREDT | 8S 0 | Hire date (YYYYMMDD). |
| MSTAT | 1A | Member status: A active, T terminated, R retired. |
| BALANCE | 11P 2 | Current member account balance. |
| YTDEE | 11P 2 | Year-to-date employee contributions. |
| YTDER | 11P 2 | Year-to-date employer contributions. |
| Field | Type | Meaning |
|---|---|---|
| TRANID | 9S 0 | Transaction sequence number (PK). Programs generate the next id via a SETGT *HIVAL/READP max-id scan. |
| MEMNO | 7A | Member the row belongs to. |
| TRTYPE | 2A | Type: OB opening balance, EE employee contribution, ER employer contribution, DV dividend, WD withdrawal (negative), LN loan disbursement. (LR loan repayment is defined in the convention but not posted by a shipped program.) |
| TRANDT | 8S 0 | Transaction date (YYYYMMDD). |
| TRAMT | 11P 2 | Signed amount (negative for WD). |
| RUNBAL | 11P 2 | Member balance snapshot AFTER this posting (running-balance audit). |
Keyed by member then transaction id, so a member's ledger reads in order — the access path
LDGINQ loops to fill its subfile and STMTPR/statement history uses. Exposes
MEMNO, TRANID, TRTYPE, TRANDT, TRAMT, RUNBAL.
| Field | Type | Meaning |
|---|---|---|
| PERIOD | 6A | Period code (PK), e.g. 2026Q1. |
| DRATE | 5P 3 | Annual crediting rate (e.g. 0.060 = 6%). DIVCRD applies DRATE/4 per quarter. |
| DDESC | 20A | Description, e.g. Q1 2026 CREDIT RATE. |
Seeded rates: 2026Q1 → 0.060, 2026Q2 → 0.055. No Q3/Q4 rows
are seeded (relevant to the DIVCRD hardcoded-period note in C.4).
| Field | Type | Meaning |
|---|---|---|
| MEMNO | 7A | Member (PK) — one open loan account per member. |
| LNPRIN | 11P 2 | Cumulative disbursed principal. |
| LNBAL | 11P 2 | Outstanding loan balance. |
| LNSTAT | 1A | Loan status: O open, P paid. |
Keyed by status then member, for an open-loan report. Exposes LNSTAT, MEMNO, LNPRIN, LNBAL.
| Field | Type | Meaning |
|---|---|---|
| MEMNO | 7A | Member billed (PK part 1). |
| PERIOD | 6A | Billing period (PK part 2). |
| DUEDT | 8S 0 | Due date (YYYYMMDD). |
| BILLAMT | 11P 2 | Amount billed (PFBILL: 5% of balance). |
| PAIDAMT | 11P 2 | Amount applied so far (by CASHAPP). |
| BSTAT | 1A | Settlement status: O open (nothing applied), D partially paid, P fully paid. |
Keyed by due date then member, so rows arrive oldest-due first — exactly what
ARRRPT's forward-scan aging pass wants. Exposes DUEDT, MEMNO, PERIOD, BILLAMT, PAIDAMT,
BSTAT.
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8S 0 | Receipt number (PK). |
| MEMNO | 7A | Member the remittance is for. |
| RCPTDT | 8S 0 | Receipt date (YYYYMMDD). |
| RAMT | 11P 2 | Cash received. CASHAPP applies this against open CONBILL dues. |
Note: PFRCPT has no "applied" flag — the root of CASHAPP's non-idempotency (C.4).
| Field | Type | Meaning |
|---|---|---|
| BATCHID | INTEGER | Posting batch id (GLBUILD uses 1). |
| ACCT | CHAR(10) | GL account code (11-char codes stored truncated to 10 — see C.2 note). |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(11,2) | Posted amount. |
| SRCPGM | CHAR(10) | Source tag: GLBUILD for the DR/CR pairs, GLFUND for the fund-balance cross-check credits. |
CONTRN is journaled: CRTJRNRCV PFJRNRCV + CRTJRN PFJRN +
STRJRNPF FILE(CONTRN) JRN(PFJRN) IMAGES(*BOTH). WRITEs produce R PT entries,
UPDATEs produce UB (before) + UP (after) images; DSPJRN renders them
and CHGJRN rolls the receiver chain. This is the recovery/audit backbone for the ledger.
Build / first-time stand-up (once per environment, on a fresh IBMI_DATA root):
QSECOFR; create the library and seed the source members
(CRTLIB PROVFND + seedProvfnd() in the harness).CALL PROVFND/PFSETUP — deletes/recreates all files, compiles every RPG/COBOL/CL
program, runs the GLLEDGER DDL, sets up journaling on CONTRN, and applies the
authority grants. Expect PROVFND data model ready.CALL PROVFND/SEEDDATA — loads 2 employers, 4 members, 2 dividend rates and 4 opening
OB ledger rows. Expect SEEDDATA: 2 EMPLOYERS 4 MEMBERS 2 RATES 4 OPENING ROWS.Pre-checks each day: confirm the job's library list includes PROVFND; confirm the
masters and journal are present (CONTRN journaled to PFJRN).
Intraday:
CALL PROVFND/PFMENU (option 1 member, option 2 ledger).CALL PROVFND/CONTRIB PARM(memno EE ER); check the
NEWBAL= line and the two new ledger rows.CALL PROVFND/WDRAW PARM(memno amount) — a valid one prints
WDRAW MEM=.. NEWBAL=..; an over-balance one prints WDRAW REJECT INSUFFICIENT
and writes nothing.CALL PROVFND/LOANPAY PARM(memno requested) — expect
ELIGCHK STAT=A + LOANPAY APPROVE, or LOANPAY DECLINE for a
non-active member or a request over 50% of balance.Post-checks after postings:
REJECT/DECLINE line
with no file change.CONTRN gained exactly the expected rows with gap-free, unique TRANIDs and
correct RUNBAL per row; the journal (DSPJRN) shows matching R PT
entries.MEMMST.BALANCE/YTDEE/YTDER reconcile to the day's postings.CALL PROVFND/PFBILL (idempotent per period),
then apply cash: load PFRCPT with the period's receipts and CALL PROVFND/CASHAPP
once against them (see the re-run rule below), then CALL PROVFND/ARRRPT for the aged
register.SBMJOB CMD(CALL PGM(PROVFND/PFNIGHT)). It bills dues,
credits the dividend, posts the GL, and cross-checks. Confirm:
DIVCRD PERIOD=.. MEMBERS=.., GLBUILD BATCH=1 .. IN BALANCE, and
FUNDRPT's two lines.CALL PROVFND/STMTPR (STMTP spool).GLBUILD (hence
PFNIGHT) must be run exactly once per period. A second run silently doubles the GL feed
and still reports IN BALANCE (C.4). If in doubt whether it ran, verify GLLEDGER row counts
before re-submitting — do not resubmit blind.The figures the test suites assert against — use them as the reconciliation model:
CONTRIB(memno, EE, ER) raises BALANCE by
EE+ER, YTDEE by EE, YTDER by ER, and
writes two rows whose RUNBALs are the intermediate then final balance (e.g. 200+300 on a
10000 balance → RUNBAL 10200 then 10500).BALANCE × DRATE / 4. At 6% annual:
10000 → +150, 20000 → +300, 40000 → +600; total 1050 across 3 members. Terminated/retired
members get nothing.PFBILL dues = 5% of balance per active member (10000 → 500,
5000 → 250, 25000 → 1250); terminated members are skipped.BILLAMT−PAIDAMT)
→ that row goes P with PAIDAMT=BILLAMT; a smaller receipt →
D with PAIDAMT increased; a member with no open due → NOBILL.
An already-P period is skipped, proving oldest-open (not oldest) selection.GLBUILD DR total = CR total = sum of the posted movements (e.g. EE 200 +
ER 300 + DV 1050 = 1550). The GLFUND 3100-FUNDBAL credits equal the EE+ER contribution
total (e.g. 2 credits, 500).FUNDRPT ACTIVE-MEMBERS n BAL .. counts/sums active
MEMMST balances; FUNDRPT GLFUND-CREDITS n AMT .. re-sums the fund-balance GL
credits independently.ARRRPT's ROWS and GRAND equal the count and
summed BILLAMT−PAIDAMT of all non-P CONBILL rows; the spool has one
detail line per outstanding row, bucketed CURRENT/30/60/90+.| Situation | Behaviour | Action |
|---|---|---|
| A posting rejected (unknown member / insufficient balance / declined loan) | Visible REJECT/DECLINE DSPLY; no file change. | Correct the input and re-issue the CALL. Safe — nothing was written. |
| PFBILL re-run for the same period | Dup-guard skips all already-billed members; GENERATED=0. | Safe no-op. Idempotent per period. |
| CASHAPP re-run against un-cleared PFRCPT | Re-applies already-applied receipts (no consumed marker; reads from BOF). | Clear/replace processed receipts (CLRPFM PFRCPT) before the next run. Never re-run over the same receipts. |
| GLBUILD / PFNIGHT re-run for the same period | Doubles every GLLEDGER row; still prints IN BALANCE. | Do not re-run. If a re-run occurred, the batch's GL rows must be deleted/rebuilt from a clean state — the app has no undo. |
| Dividend appears to use the wrong rate | DIVCRD always credits 2026Q1 (hardcoded). | Expected as shipped. Correcting the crediting period is a source change, not an operational fix. |
| Night chain fails partway | Each step runs to completion before the next; a failed step leaves earlier steps' effects in place. | Because GLBUILD is not idempotent, do NOT simply resubmit PFNIGHT. Identify which step ran, reconcile GLLEDGER, and re-run only the remaining steps. |
| Recovery / audit reconstruction | CONTRN is journaled IMAGES(*BOTH). | Use DSPJRN JRN(PROVFND/PFJRN) FILE(PROVFND/CONTRN) to reconstruct every ledger write/update after the fact. |
All objects are in library PROVFND, sourced from src/sources.mjs as source
physical-file members and built by PFSETUP. RPG is column-exact fixed-form for file/D-specs and
/free for logic; the COBOL program is ILE COBOL with an EXEC SQL block.
EMPLR, 4 MEMMST, 2 DIVRATE rows and 4 opening
OB CONTRN rows. The reference seed for a fresh environment.dcl-pi entry: (pStat, pBal, pReq : pMaxElig, pOutStat). Rule: non-active member
→ pOutStat='D', pMaxElig=0; else pMaxElig = pBal/2 and
approve ('A') if pReq ≤ pMaxElig else decline ('D'). Proves
program-to-program CALL with parameters.dcl-pi (iMemno, iEE, iER). Max-id scan for next TRANID; CHAIN
MEMMST (reject if not found); write EE then ER rows; bump BALANCE/YTDEE/YTDER; UPDATE
member. RUNBAL set on each row.dcl-pi (iMemno, iAmt). Rejects unknown member and amt > BALANCE (no row);
else decrements BALANCE and writes a WD row with -amt.dcl-pi (iMemno, iReq) + dcl-pr eligchk extpgm('ELIGCHK'). CHAIN member; CALL
ELIGCHK; on 'A' create or accumulate LOANMST (LNSTAT='O') and
write an LN row; on decline write nothing.DIVRATE for the hardcoded WPERIOD '2026Q1'; walk MEMMST,
credit active members BALANCE × rate / 4, write a DV row each, accumulate
the control total.chain (wmemno : wperiod) billr dup-guard; write a CONBILL
due of BALANCE × 0.05 (BSTAT='O') if none exists. Hardcoded period
'2026Q2' / due date 20260401. No embedded SQL.PFRCPT; a flagged dow (wi≤2) scan over the two period codes finds
the member's oldest non-P due; re-CHAIN before the single UPDATE; full cover →
'P', partial → 'D', none → NOBILL. No embedded SQL.CONTRN; nested-if TRTYPE dispatch (never elseif) emitting
a balanced DR/CR pair per type via EXEC SQL INSERT (subroutines postpair/
postabs/postfund); accumulate DR/CR and DSPLY balance state.SETLL *LOVAL/forward-READ over BILLLF; bucket non-P rows by
WASOF − DUEDT (nested if/else: CURRENT / 30 / 60 / 90+); write the multi-format
ARREARP PRTF spool with page-overflow handling and a grand-total line.MEMMST; one STMTP DETAIL line per member with overflow re-heading; total
line.MEMMST matching the packed DDS layout byte-for-byte; totals active
balances; EXEC SQL SELECT COUNT(*), SUM(AMT) over the GLFUND/
3100-FUNDBAL credit rows; DISPLAYs both.Two CALL idioms are used, chosen to stay correct on the platform:
LOANPAY calls ELIGCHK through a
dcl-pr eligchk extpgm('ELIGCHK') prototype: eligchk(mstat : balance : wreq : wmax :
welig). This passes and receives the eligibility result by reference.PFMENU sets indicator 81/82
from the option, then CALL 'MEMINQ' / CALL 'LDGINQ' conditioned in cols 9-11
— a dynamic call by literal that the free-form CALLP path could not express here (Q5).| TRTYPE | Debit | Credit | Amount | Extra |
|---|---|---|---|---|
| EE | 1000-CASH | 2000-EEPAY | signed TRAMT | + GLFUND 3100-FUNDBAL credit (postfund) |
| ER | 1000-CASH | 2100-ERPAY | signed TRAMT | + GLFUND 3100-FUNDBAL credit (postfund) |
| DV | 3000-DIVEXP | 3100-FUNDBAL | signed TRAMT | — |
| WD | 4000-WDCLR | 1000-CASH | absolute TRAMT | — |
| LN | 1500-LOANREC | 1000-CASH | absolute TRAMT | — |
OB opening-balance rows are not posted by GLBUILD. Codes 3000-DIVEXP/
3100-FUNDBAL store truncated to CHAR(10) (C.2 note). The GLFUND-tagged
fund-balance credits let FUNDRPT reconcile member-balance growth against the GL.
CRTJRNRCV → CRTJRN → STRJRNPF FILE(CONTRN) JRN(PFJRN)
IMAGES(*BOTH) in PFSETUP (re-runnable on every fresh build).R PT entry, caller job stamped; UPDATE → UB+UP
under IMAGES(*BOTH).DSPJRN renders seq/code/type/object/job; CHGJRN rolls to a new receiver and
entries keep accumulating; ENDJRNPF writes an F JE entry and lets
DLTJRN proceed (which is refused — SFF7021 — while the file is still
journaled).From PLAN.md — these shaped the code but are the operator/developer's context, not
defects to fix here:
| # | Quirk | How the app stays correct |
|---|---|---|
| Q1 | Free-form elseif mis-scopes the trailing END-IF. | GLBUILD/ARRRPT use nested if/else; PFMENU uses independent if blocks. |
| Q2 | Free-form CHAIN cannot use a fixed-form KLIST. | All multi-key CHAINs use the parenthesised composite form chain (k1:k2) rcd. |
| Q3 | Rows WRITEd to an output-opened file are invisible to a called program until the writer closes. | PFNIGHT sequences each program to *inlr=*on before the next reader runs. |
| Q4 | Fixed-form AN/OR conditioning lines unsupported. | Multi-condition tests use a /free AND/OR-expression if. |
| Q5 | Free-form CALLP cannot name a program by literal for an unprototyped dynamic call. | PFMENU uses fixed-form indicator CALL; ELIGCHK via a dcl-pr prototype. |
| Q6 | Commitment control honored only on the SQL path. | GLBUILD's committed post is embedded EXEC SQL INSERT; native master I/O is treated as immediately durable. |
| Q7 | PRTF field-position column sensitivity (last digit ends at DDS col 44). | STMTP/ARREARP positions authored to the col-44 rule so DETAIL fields land at their true columns. |
| Q8 | Free-form LEAVESR; rejected. | CASHAPP's oldest-open scan is a flagged dow (wi≤2) and (wfound='N') loop, not a leavesr. |
EE) and an
employer share (ER). Posted by CONTRIB as two immutable CONTRN rows.BALANCE × DRATE / 4 per quarter and posts a DV ledger row.O/D/P).DR=CR) proves the batch balances.SBMJOB CMD(CALL PGM(PROVFND/PFNIGHT)).