PROVIDENT/i — Provident Fund / Retirement Savings

SteelFrame X application operation manual  ·  ← back to Operation Manuals  ·  Sign On

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).

Honesty note up front. PROVIDENT/i is an internally consistent, fully built and tested application (8/8 focused suites, 140 assertions green per 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.

A. Overview & Architecture ↑ top

A.1 What it does

PROVIDENT/i administers a defined-contribution provident fund — one member holds one account:

A.2 Layering & the PROVFND library

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:

A.3 Component & flow

  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.

A.4 Object inventory

ObjectTypeRole
EMPLRPFEmployer master (code, name, status).
MEMMSTPFMember master — balance + YTD EE/ER (the heart of the app).
CONTRNPF (journaled)Contribution/transaction ledger, unique TRANID.
CONLFLFCONTRN keyed by member (statement history + subfile).
DIVRATEPFPeriod → dividend crediting rate table.
LOANMSTPFMember loan account (one open loan per member).
LOANLFLFLOANMST keyed by status (open-loan report).
CONBILLPFContribution-dues billing/receivables (MEMNO+PERIOD).
BILLLFLFCONBILL keyed DUEDT (arrears forward-scan).
PFRCPTPFEmployer remittance receipts (arrival, unique RCPTNO).
GLLEDGERSQL tableBalanced DR/CR GL feed (RUNSQLSTM DDL).
MEMDSPF / LDGDSPF / PFMENUDDSPFMember inquiry / ledger subfile / operator menu.
STMTP / ARREARPPRTFStatement register / aged-arrears register.
SEEDDATARPGLELoads employers, members, rates, opening rows.
ELIGCHKRPGLELoan-eligibility subprogram (dcl-pi entry parms).
CONTRIB / WDRAW / LOANPAY / DIVCRDRPGLEContribution / withdrawal / loan / dividend posting.
PFBILL / CASHAPP / ARRRPTRPGLEBilling / cash application / aged arrears.
GLBUILDRPGLEGL consolidation (embedded EXEC SQL, committed).
STMTPR / MEMINQ / LDGINQ / PFMENURPGLEStatement print / inquiry / subfile / menu.
FUNDRPTCBLLECOBOL active-balance vs GL fund-balance cross-check.
PFSETUP / PFNIGHTCLPBuild+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.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

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 thisType on the command line
Open the operator menu (routes to member/ledger inquiry)CALL PROVFND/PFMENU
Member inquiry directlyCALL PROVFND/MEMINQ
Ledger inquiry (subfile) directlyCALL PROVFND/LDGINQ
Post a pay-period contribution (EE + ER)CALL PROVFND/CONTRIB PARM('M000001' 200.00 300.00)
Take a withdrawalCALL 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 chainCALL PROVFND/PFNIGHT (or SBMJOB it)
Build/seed the whole library from sourceCALL 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).

B.2 The menu & inquiry screens

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 / PFMENUD — operator menu

PFMENU EXFMTs the MENUFMT format, reads the one-character MOPT, and routes: option 1CALL 'MEMINQ', option 2CALL '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.

PROVIDENT/i Main Menu 1. Member Inquiry 2. Ledger Inquiry Option . . . . : _ F3=Exit Enter=Select

MEMINQ / MEMDSPF — member inquiry (plain WORKSTN)

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.

Member Inquiry - PROVIDENT/i Member number: M000001 Name . . . . . : ADAMS SARAH Employer . . . : EMP01 Status . . . . : A Balance . . . : 10000.00 YTD employee . : 0.00 YTD employer . : 0.00 Member found. F3=Exit Enter=Inquire
FieldType (DDS)Shows
IMEMNO7A BMember number keyed for inquiry.
DNAME / DEMPNO / DSTAT25A / 5A / 1A OMember name / employer / status.
DBAL / DYEE / DYER15A OBalance / YTD employee / YTD employer (rendered from packed).
DMSG50A OHighlighted status/result message line.

LDGINQ / LDGDSPF — ledger inquiry (SFL / SFLCTL subfile)

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.

Ledger Inquiry - PROVIDENT/i Member number: M000001 Name . . . . . : ADAMS SARAH Balance: 10000.00 Date Typ Amount Run Bal 20260101 EE 100.00 10100.00 20260102 EE 200.00 10200.00 ... More... F3=Exit Enter=Inquire
FieldType (DDS)Shows
ILMEMNO7A BMember number keyed for the ledger.
DLNAME / DLBAL25A / 15A OMember name / current balance in the header.
SLTRDT / SLTRTYP8A / 2A OLedger row transaction date / type.
SLTRAMT / SLRUNBL14A ORow amount / post-transaction running balance.
The subfile is an inquiry-only list — there is no option column and no maintenance action on 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.

B.3 Controls & audit workflow (no four-eyes maker–checker)

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:

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.

C. Batch Jobs & the Periodic Cycle ↑ top

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)

C.1 Full batch program set

ProgramPurposeReads / writesInputsOutputs (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.

C.2 Contribution / dividend / billing detail

Contribution — CONTRIB

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

Dividend crediting — DIVCRD

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

Billing & cash application — PFBILL / CASHAPP / ARRRPT

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

GL feed & cross-check — GLBUILD / FUNDRPT

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
The account codes 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.

C.3 The night chain & ordering

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

C.4 Idempotency & re-run hazards (read before scheduling)

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:

ProgramRe-run behaviourOperator 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.

D. Data Files (data dictionary) ↑ top

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.

EMPLR — Employer master (PK EMPNO, UNIQUE)

FieldTypeMeaning
EMPNO5AEmployer code (PK), e.g. EMP01.
ENAME25AEmployer name.
ESTAT1AEmployer status (A active).

MEMMST — Member master (PK MEMNO, UNIQUE)

FieldTypeMeaning
MEMNO7AMember number (PK), e.g. M000001.
MNAME25AMember name.
EMPNO5AOwning employer (references EMPLR).
HIREDT8S 0Hire date (YYYYMMDD).
MSTAT1AMember status: A active, T terminated, R retired.
BALANCE11P 2Current member account balance.
YTDEE11P 2Year-to-date employee contributions.
YTDER11P 2Year-to-date employer contributions.

CONTRN — Contribution / transaction ledger (PK TRANID, UNIQUE; journaled)

FieldTypeMeaning
TRANID9S 0Transaction sequence number (PK). Programs generate the next id via a SETGT *HIVAL/READP max-id scan.
MEMNO7AMember the row belongs to.
TRTYPE2AType: 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.)
TRANDT8S 0Transaction date (YYYYMMDD).
TRAMT11P 2Signed amount (negative for WD).
RUNBAL11P 2Member balance snapshot AFTER this posting (running-balance audit).

CONLF — Ledger logical file over CONTRN (K MEMNO, TRANID)

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.

DIVRATE — Dividend rate table (PK PERIOD, UNIQUE)

FieldTypeMeaning
PERIOD6APeriod code (PK), e.g. 2026Q1.
DRATE5P 3Annual crediting rate (e.g. 0.060 = 6%). DIVCRD applies DRATE/4 per quarter.
DDESC20ADescription, 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).

LOANMST — Member loan account (PK MEMNO, UNIQUE)

FieldTypeMeaning
MEMNO7AMember (PK) — one open loan account per member.
LNPRIN11P 2Cumulative disbursed principal.
LNBAL11P 2Outstanding loan balance.
LNSTAT1ALoan status: O open, P paid.

LOANLF — Loan logical file over LOANMST (K LNSTAT, MEMNO)

Keyed by status then member, for an open-loan report. Exposes LNSTAT, MEMNO, LNPRIN, LNBAL.

CONBILL — Contribution-dues billing / receivables (PK MEMNO+PERIOD, UNIQUE)

FieldTypeMeaning
MEMNO7AMember billed (PK part 1).
PERIOD6ABilling period (PK part 2).
DUEDT8S 0Due date (YYYYMMDD).
BILLAMT11P 2Amount billed (PFBILL: 5% of balance).
PAIDAMT11P 2Amount applied so far (by CASHAPP).
BSTAT1ASettlement status: O open (nothing applied), D partially paid, P fully paid.

BILLLF — Arrears logical file over CONBILL (K DUEDT, MEMNO)

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.

PFRCPT — Employer remittance receipts (PK RCPTNO, UNIQUE)

FieldTypeMeaning
RCPTNO8S 0Receipt number (PK).
MEMNO7AMember the remittance is for.
RCPTDT8S 0Receipt date (YYYYMMDD).
RAMT11P 2Cash received. CASHAPP applies this against open CONBILL dues.

Note: PFRCPT has no "applied" flag — the root of CASHAPP's non-idempotency (C.4).

GLLEDGER — GL feed (SQL DDL table, DB2)

FieldTypeMeaning
BATCHIDINTEGERPosting batch id (GLBUILD uses 1).
ACCTCHAR(10)GL account code (11-char codes stored truncated to 10 — see C.2 note).
DRCRCHAR(1)D debit / C credit.
AMTDECIMAL(11,2)Posted amount.
SRCPGMCHAR(10)Source tag: GLBUILD for the DR/CR pairs, GLFUND for the fund-balance cross-check credits.

Relationships

Journal — PFJRN / PFJRNRCV over CONTRN

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.

E. Operations Runbook ↑ top

E.1 Day-in-the-life

Build / first-time stand-up (once per environment, on a fresh IBMI_DATA root):

  1. Sign on QSECOFR; create the library and seed the source members (CRTLIB PROVFND + seedProvfnd() in the harness).
  2. 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.
  3. 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:

  1. Serve inquiries: CALL PROVFND/PFMENU (option 1 member, option 2 ledger).
  2. Post contributions as employers remit: CALL PROVFND/CONTRIB PARM(memno EE ER); check the NEWBAL= line and the two new ledger rows.
  3. Handle withdrawals: CALL PROVFND/WDRAW PARM(memno amount) — a valid one prints WDRAW MEM=.. NEWBAL=..; an over-balance one prints WDRAW REJECT INSUFFICIENT and writes nothing.
  4. Handle loans: 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:

E.2 Dividend & period close

  1. Confirm the period's contributions are all posted (the ledger is complete for the period).
  2. Run the billing/receivables cadence if due: 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.
  3. Run the night chain once: 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.
  4. Print statements as required: CALL PROVFND/STMTPR (STMTP spool).
The single most important close rule: 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.

E.3 Reconciling figures

The figures the test suites assert against — use them as the reconciliation model:

E.4 Failure & re-run rules

SituationBehaviourAction
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 periodDup-guard skips all already-billed members; GENERATED=0.Safe no-op. Idempotent per period.
CASHAPP re-run against un-cleared PFRCPTRe-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 periodDoubles 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 rateDIVCRD always credits 2026Q1 (hardcoded).Expected as shipped. Correcting the crediting period is a source change, not an operational fix.
Night chain fails partwayEach 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 reconstructionCONTRN is journaled IMAGES(*BOTH).Use DSPJRN JRN(PROVFND/PFJRN) FILE(PROVFND/CONTRN) to reconstruct every ledger write/update after the fact.

F. Developer Reference ↑ top

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.

F.1 Program set

SEEDDATA (RPGLE)
Writes 2 EMPLR, 4 MEMMST, 2 DIVRATE rows and 4 opening OB CONTRN rows. The reference seed for a fresh environment.
ELIGCHK (RPGLE, subprogram)
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.
CONTRIB (RPGLE)
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.
WDRAW (RPGLE)
dcl-pi (iMemno, iAmt). Rejects unknown member and amt > BALANCE (no row); else decrements BALANCE and writes a WD row with -amt.
LOANPAY (RPGLE)
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.
DIVCRD (RPGLE)
CHAIN DIVRATE for the hardcoded WPERIOD '2026Q1'; walk MEMMST, credit active members BALANCE × rate / 4, write a DV row each, accumulate the control total.
PFBILL (RPGLE)
Walk active members; 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.
CASHAPP (RPGLE)
Read each 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.
GLBUILD (RPGLE)
Read all 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.
ARRRPT (RPGLE)
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.
STMTPR (RPGLE)
Walk MEMMST; one STMTP DETAIL line per member with overflow re-heading; total line.
MEMINQ / LDGINQ / PFMENU (RPGLE, WORKSTN)
Plain member inquiry / SFL-SFLCTL ledger subfile / operator menu routing (fixed-form indicator CALL). See section B.
FUNDRPT (CBLLE)
COMP-3 FD over 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.
PFSETUP / PFNIGHT (CLP)
Build/journal/authority setup / the 4-step night chain (see C.3).

F.2 Program-to-program CALL

Two CALL idioms are used, chosen to stay correct on the platform:

F.3 GL account map (GLBUILD)

TRTYPEDebitCreditAmountExtra
EE1000-CASH2000-EEPAYsigned TRAMT+ GLFUND 3100-FUNDBAL credit (postfund)
ER1000-CASH2100-ERPAYsigned TRAMT+ GLFUND 3100-FUNDBAL credit (postfund)
DV3000-DIVEXP3100-FUNDBALsigned TRAMT
WD4000-WDCLR1000-CASHabsolute TRAMT
LN1500-LOANREC1000-CASHabsolute 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.

F.4 Journaling patterns

F.5 Platform quirks designed around (app-level, no engine change)

From PLAN.md — these shaped the code but are the operator/developer's context, not defects to fix here:

#QuirkHow the app stays correct
Q1Free-form elseif mis-scopes the trailing END-IF.GLBUILD/ARRRPT use nested if/else; PFMENU uses independent if blocks.
Q2Free-form CHAIN cannot use a fixed-form KLIST.All multi-key CHAINs use the parenthesised composite form chain (k1:k2) rcd.
Q3Rows 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.
Q4Fixed-form AN/OR conditioning lines unsupported.Multi-condition tests use a /free AND/OR-expression if.
Q5Free-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.
Q6Commitment 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.
Q7PRTF 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.
Q8Free-form LEAVESR; rejected.CASHAPP's oldest-open scan is a flagged dow (wi≤2) and (wfound='N') loop, not a leavesr.

G. Glossary ↑ top

Provident fund
A defined-contribution retirement-savings scheme: each member holds one account funded by employer and employee contributions, credited with dividends/interest over time.
Contribution (EE / ER)
A pay-period payment into a member's account, split into an employee share (EE) and an employer share (ER). Posted by CONTRIB as two immutable CONTRN rows.
Dividend crediting
Periodic addition of investment return to each active member's balance. DIVCRD credits BALANCE × DRATE / 4 per quarter and posts a DV ledger row.
Running balance (RUNBAL)
The member balance snapshot stored on each CONTRN row after that posting — a per-transaction audit of how the balance evolved.
Dues / billing (CONBILL)
The expected pay-period contribution the fund bills for a member/period (PFBILL: 5% of balance), with the amount applied and settlement status (O/D/P).
Cash application (CASHAPP)
Matching an employer remittance (PFRCPT) to the member's oldest still-open due, marking it fully or partially paid.
Aged arrears
Outstanding dues classified by how overdue they are (CURRENT / 30 / 60 / 90+), produced by ARRRPT over BILLLF.
GL feed / balanced double-entry
The debit/credit rows GLBUILD emits into GLLEDGER so total debits equal total credits; a control total (DR=CR) proves the batch balances.
Eligibility (ELIGCHK)
The loan rule: an active member may borrow up to 50% of current balance. Implemented as a callable subprogram.
Idempotent
Safe to run again with the same result. PFBILL is idempotent per period; GLBUILD and CASHAPP are not (C.4) — they must be run once per period by operational convention.
Journaling (PFJRN / IMAGES(*BOTH))
Capturing every WRITE/UPDATE of CONTRN (before and after images) for audit and recovery; viewed with DSPJRN.
Subfile (SFL / SFLCTL)
A 5250 construct listing many rows on one screen. LDGINQ's ledger list is the app's one subfile, paged SFLPAG(10).
SBMJOB
Submit Job — queues a program to run as batch, e.g. SBMJOB CMD(CALL PGM(PROVFND/PFNIGHT)).
PROVFND
The library holding every PROVIDENT/i object and its source physical files.