DEPOSIT/i is an AS/400-native core banking / demand-deposit (DDA) system: customer
and account masters, teller postings (deposit / withdrawal / transfer) against a running-balance
ledger, holds that reduce available balance, tiered daily interest accrual with month-end
crediting, service charges plus dormancy flagging, overdraft-limited withdrawals, a balanced
double-entry general-ledger feed, and passbook/trial-balance print. It is a classic RPG IV +
ILE COBOL + DDS + embedded-SQL application: the business math lives in packed-decimal RPG that
reads and updates keyed physical files and appends to a journaled ledger, while the GL feed and its
cross-checks run through EXEC SQL against a DB2 table. This manual is the reference for
the operator who runs the teller line, the online inquiry screens and the nightly batch chain, and
for the developer maintaining the application. It is grounded entirely in the committed source
(banking-app/src/sources.mjs, src/seed.mjs, and the
test/dep_*.mjs drivers).
DEPOSIT/i runs a single line of business — retail demand-deposit banking (savings + checking):
ACCTMNP → ACCTMNT).TXNPEND → TELLER), each appending a running-balance row to the
journaled ledger TXNJRNL.HOLDPOST recomputes AVLBAL = CURBAL − Σ(open holds).INTACCR accrues one day of tiered simple interest into the
ACCRINT bucket per active account; INTCRED credits the accrued interest
into the balance at month-end and posts an interest ledger row.SVCCHG posts a monthly maintenance fee
(unless the minimum-balance waiver applies) and flags stale accounts dormant.GLPOST consolidates the day's ledger activity
into balanced DR/CR rows in the DB2 GLLEDGER table; TRIALRPT and the
COBOL DDABAL prove and cross-check DR=CR; STMTGEN prints a passbook /
statement register.Unlike a stored-procedure design, DEPOSIT/i keeps all business logic in the application programs themselves. There are two data substrates with a clear division of use:
CHAIN/READ/SETLL
them, do packed-decimal money math in-line, and UPDATE/WRITE the
result. The ledger TXNJRNL is journaled (STRJRNPF) so every posting captures a
before/after image.GLLEDGER. GLPOST inserts balanced DR/CR rows through
EXEC SQL INSERT and cross-checks the batch with an aggregate query;
TRIALRPT reads it with an SQL cursor; the COBOL DDABAL totals the
account master via a COMP-3 FD and cross-checks it against an SQL SUM over the ledger. The next
ledger transaction id is also read with an EXEC SQL SELECT MAX(TXNID).The consequence for operations: money movement is native-file and immediate (and journaled for
audit), while the general ledger — the place a balanced control total is proven — is SQL, and
is the only substrate under commitment control (see F.3). Everything runs in library
DEPOSIT.
MAINTENANCE ONLINE (5250) BATCH (night chain / on demand)
----------- ------------- ------------------------------
ACCTMNP --READ--> TELMENUP (menu) INTACCR --> ACCTMST.ACCRINT (tiered daily)
ACCTMNT opt 1 -> ACCTINQ INTCRED --> ACCRINT into CURBAL + 'I' TXN
(O/C/R) (plain inquiry) SVCCHG --> 'C' fee TXN + dormant flag
opt 2 -> REGINQ GLPOST --> GLLEDGER DR/CR (SQL, balanced)
TXNPEND --READ--> (SFL register) TRIALRPT --> TRIALPR trial balance (DR=CR)
TELLER STMTGEN --> STMTPR passbook/statement
(D/W/X) DDABAL --> COBOL COMP-3 total vs GL SUM
| \ /
| \ /
v v v
ACCTMST <--update-- HOLDPOST (AVLBAL = CURBAL - open holds)
|
+--> TXNJRNL (append-only, journaled to TXNJRN; running balance per row)
|
+--> TXNLF (keyed by account) --> REGINQ subfile + STMTGEN register
A single teller posting flows: an op is written to TXNPEND →
TELLER reads it, CHAINs ACCTMST, validates available balance
(+ overdraft), updates CURBAL/AVLBAL, and WRITEs a
TXNJRNL row carrying the running balance → the journal captures the ledger WRITE.
A transfer is two such postings (a TRANSFER OUT debit leg and a TRANSFER IN
credit leg).
| Object | Type | Role |
|---|---|---|
| CUSTMST | PF | Customer master. |
| ACCTMST | PF | Account master (the heart of the app). |
| TXNJRNL | PF (journaled) | Teller transaction ledger (append-only). |
| TXNLF | LF | Ledger keyed by account then txn id (register/passbook). |
| HOLDMST | PF | Holds against available balance. |
| HOLDLF | LF | Holds keyed by account. |
| INTPLAN | PF | Interest rate tiers (product + balance band). |
| CHGPLAN | PF | Service-charge plans. |
| TXNPEND | PF | Pending teller-op work file. |
| ACCTMNP | PF | Pending account-maintenance work file. |
| GLLEDGER | DB2 table | Double-entry GL feed (SQL DDL). |
| ACCTDSPF / REGDSPF / TELMENU | DSPF | Inquiry / subfile register / menu displays. |
| STMTPR / TRIALPR | PRTF | Statement/passbook & trial-balance printer files. |
| SEEDDATA | RPGLE | Data-seed loader (customers/accounts/tiers/plans/hold). |
| ACCTMNT | RPGLE | Account master maintenance (open/change/re-rate). |
| HOLDPOST | RPGLE | Recompute available balance from open holds. |
| TELLER | RPGLE + SQL | Post deposits/withdrawals/transfers. |
| INTACCR | RPGLE | Daily tiered interest accrual (packed-decimal). |
| INTCRED | RPGLE + SQL | Month-end interest crediting. |
| SVCCHG | RPGLE + SQL | Service charges + dormancy flagging. |
| GLPOST | RPGLE + SQL | Balanced DR/CR GL feed + control total. |
| STMTGEN | RPGLE | Passbook/statement PRTF register. |
| TRIALRPT | RPGLE + SQL | GL trial-balance PRTF (DR=CR). |
| ACCTINQ | RPGLE | Plain WORKSTN account inquiry. |
| REGINQ | RPGLE | SFL/SFLCTL account-register subfile. |
| TELMENUP | RPGLE | Menu program routing to the two inquiries. |
| DDABAL | ILE COBOL + SQL | COMP-3 ledger total vs GL SUM cross-check. |
| DBSETUP / DBNIGHT | CL | Build-everything driver / night-batch chain. |
The catalogue is 8 PFs + 2 LFs + 1 DB2 table, 3 DSPFs + 2 PRTFs, driven by 13 RPG programs + 1 ILE COBOL program + 2 CL drivers. Sections D and F expand each.
DEPOSIT/i has no CICS transaction identifiers and no transid switch. On IBM i, each program
is reached by name from a 5250 command-entry line (or a JOBQ/scheduler for the batch 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 DEPOSIT — the tested
jobs run with LIBL = QSYS QGPL DEPOSIT QTEMP and CURLIB = DEPOSIT.
| To do this | Type on the command line |
|---|---|
| Open the teller menu (routes to the two inquiries) | CALL DEPOSIT/TELMENUP |
| Open the plain account inquiry directly | CALL DEPOSIT/ACCTINQ |
| Open the account register (subfile) directly | CALL DEPOSIT/REGINQ |
| Apply pending account maintenance | CALL DEPOSIT/ACCTMNT |
| Post the pending teller ops | CALL DEPOSIT/TELLER (or SBMJOB it) |
| Recompute available balances from holds | CALL DEPOSIT/HOLDPOST |
| Build/rebuild the whole app + seed | CALL DEPOSIT/DBSETUP |
| Submit the night batch chain | CALL DEPOSIT/DBNIGHT |
The maintenance/teller/batch programs take no CALL parameters — each reads its work from
a pending file (ACCTMNP, TXNPEND) or processes every account, and DSPLYs a
one-line result. There is no interactive posting screen: postings and maintenance are driven
from the pending work files (the classic AS/400 work-file → poster idiom), and the interactive
programs (TELMENUP, ACCTINQ, REGINQ) are inquiry only.
Three WORKSTN programs make up the online tier. TELMENUP displays a two-option menu
(TELMENU / record MENUFMT) and dynamically CALLs
ACCTINQ for option 1 or REGINQ for option 2. Both inquiry programs
loop on EXFMT until F3 (CA03), and both look an
account up by CHAINing ACCTMST then CUSTMST for the customer
name.
Option 1 sets indicator 81 and calls ACCTINQ; option 2 sets
82 and calls REGINQ; anything else shows Invalid option. (The fixed-form
CALLs are indicator-conditioned in cols 9–11 — see quirk PQ-1.)
A single, non-subfile display (ACCTINQ record ACCTINQF). The operator keys
an account number in IACCTNO; on Enter the program chains the master and shows the customer
name, product (SAVINGS / CHECKING from PRODCD), status (ACTIVE / DORMANT / CLOSED from
ASTAT), and the current / available / accrued figures. An unknown account yields
Account not found: nnnnnnnnnn.
REGINQ is the app's one subfile screen (DDS record REGSFL under control record
REGCTL, SFLPAG(0012) per page, SFLSIZ(0030)). The operator keys
an account in IREACCT; the program clears the subfile
(SFLCLR, indicator 33), then loops the ledger logical TXNLF keyed by that
account (SETLL / READE), WRITEing one REGSFL row per
transaction, and EXFMTs the register — a real "clear, load, display" subfile pass,
reloaded each time an account is inquired.
| Field | Type (DDS) | Shows |
|---|---|---|
| SXDT | 8A output | Transaction date (from TXNDT). |
| SXTYP | 1A output | Type: D deposit, W withdrawal, X transfer, I interest, C charge. |
| SXAMT | 14A output | Transaction amount (from TXNAMT). |
| SXBAL | 14A output | Running balance after this posting (from RUNBAL). |
| SXDESC | 20A output | Posting description (from DESCR). |
TELLER, the batch jobs). The load indicators mirror the DDS
numbering: 31 SFLDSP, 32 SFLDSPCTL, 33 SFLCLR, 34
SFLEND(*MORE).Honest statement: DEPOSIT/i does not model a true four-eyes maker–checker /
separate-authorization workflow. There is no "one user posts, a second user approves" step in the code:
a teller op written to TXNPEND is applied immediately when TELLER runs, and a
maintenance action in ACCTMNP is applied immediately by ACCTMNT. The manual
documents the control model the application does have:
TXNJRN (STRJRNPF, IMAGES(*BOTH)) so every WRITE
produces a captured before/after image — a full, reconstructable audit trail visible through
DSPJRN. This is after-the-fact accountability, not an approval gate.TELLER rejects a posting whose amount exceeds
AVLBAL + ODLIM (overdraft-limited withdrawal / the debit leg of a transfer), and
rejects an op against an unknown account — counted into the REJECT= total, with
the account left untouched. ACCTMNT rejects an open (MTYP='O') whose
account number already exists (a CHAIN before WRITE), so a duplicate
never overwrites an existing master.HOLDMST rows reduce AVLBAL
via HOLDPOST, so a legal/check hold constrains what a teller can withdraw or transfer
without touching the current balance.ASTAT='A' (active) accounts; dormant / closed accounts are skipped.GLPOST only reports its batch
IN BALANCE when the batch's DR total equals its CR total, and TRIALRPT /
DDABAL independently re-prove DR=CR across the whole ledger — the accounting
control that a day's postings reconcile.STRCMTCTL both legs COMMIT together or ROLLBACK
together (see F.3). Native record I/O is not under commitment control here.In sum, the control posture is journaled audit + validation/status gating + a balanced-GL control total + atomic (SQL) transfers, rather than a segregation-of-duties approval workflow.
DEPOSIT/i's processing is a set of poster and periodic programs rather than one monolithic nightly
job: on-demand posters (ACCTMNT maintenance, TELLER teller line,
HOLDPOST), a daily accrual (INTACCR), and a month-end / night
chain (INTCRED → SVCCHG → GLPOST) plus reporting
(STMTGEN, TRIALRPT, DDABAL). None take CALL parameters. The night
chain is packaged in the CL driver DBNIGHT, which SBMJOBs each step onto its
own night job queue so each step's writes are visible to the next (see quirk PQ-3).
-- DBNIGHT: create the night queue, then submit each step onto it
CRTJOBQ JOBQ(DEPOSIT/NITEQ) TEXT('Deposit night batch')
SBMJOB CMD(CALL DEPOSIT/INTACCR) JOB(DBACCR) JOBQ(DEPOSIT/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL DEPOSIT/INTCRED) JOB(DBCRED) JOBQ(DEPOSIT/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL DEPOSIT/SVCCHG) JOB(DBSVC) JOBQ(DEPOSIT/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL DEPOSIT/GLPOST) JOB(DBGL) JOBQ(DEPOSIT/NITEQ) HOLD(*NO)
| Program | Purpose | Reads / writes | Output (DSPLY) | Frequency |
|---|---|---|---|---|
| ACCTMNT | Apply pending account maintenance: Open / Change status / Re-rate. | Reads ACCTMNP; writes/updates ACCTMST. |
ACCTMNT APPLIED=n REJECT=m |
On demand. |
| HOLDPOST | Recompute available balance = current − sum of open holds. | Reads HOLDLF; updates ACCTMST.AVLBAL. |
HOLDPOST: available recomputed for n accounts |
After hold changes / in DBSETUP. |
| TELLER | Post deposits / withdrawals / transfers from the pending work file. | Reads TXNPEND; updates ACCTMST; writes TXNJRNL. |
TELLER POSTED=n REJECT=m |
On demand (teller line). |
| INTACCR | Accrue one day of tiered interest into ACCRINT for each active account. |
Reads INTPLAN; updates ACCTMST.ACCRINT. |
INTACCR: accrued interest for n accounts |
Daily. |
| INTCRED | Move accrued interest into the balance, zero ACCRINT, post an 'I' ledger row. |
Updates ACCTMST; writes TXNJRNL (TXNID via MAX+1). |
INTCRED: credited n accts total t |
Month-end. |
| SVCCHG | Post monthly maintenance fees (unless waived) and flag dormant accounts. | Reads CHGPLAN; updates ACCTMST; writes TXNJRNL ('C'). |
SVCCHG: fees=n dormant=m |
Month-end. |
| GLPOST | Consolidate ledger activity into balanced DR/CR GL rows; cross-check DR=CR. | Reads TXNJRNL; EXEC SQL INSERT into GLLEDGER. |
GLPOST BATCH b IN BALANCE DR=d (or OUT OF BALANCE) |
Night / month-end. |
| TRIALRPT | Print the GL trial balance from GLLEDGER (SQL cursor), prove DR=CR. |
Reads GLLEDGER; spools TRIALPR. |
TRIALRPT rows=n DR=d CR=c |
Night / month-end. |
| STMTGEN | Print each account's statement/passbook register (running balance). | Reads ACCTMST/CUSTMST/TXNLF; spools STMTPR. |
STMTGEN: printed n register lines |
Statement cycle. |
| DDABAL | ILE COBOL: total ledger balance by product (COMP-3 FD), cross-check GL deposit liability. | Reads ACCTMST; EXEC SQL SUM over GLLEDGER. |
DDABAL SAVINGS.. CHECKING.. TOTAL.. GL NET DEPOSIT LIABILITY.. |
Reconciliation. |
Reads the current high-water TXNID once at entry
(EXEC SQL SELECT COALESCE(MAX(TXNID),0)) and advances a local counter per WRITE
(quirk PQ-3: a program's own native WRITEs are not visible to its same-program embedded SQL, so
per-row MAX+1 would collide on the UNIQUE key). Then per pending op:
deposit credits CURBAL+AVLBAL and writes a 'D' row;
withdrawal is rejected if PAMT > AVLBAL + ODLIM, else debits and writes 'W';
transfer debits the FROM account (writes TRANSFER OUT) then credits the TO account
(writes TRANSFER IN) — two ledger rows, one balanced double-entry.
Expected DSPLY (dep + wd + transfer(2 legs) + dep, with 2 overdraft rejects): TELLER POSTED=5 REJECT=2 5 ledger rows written, 2 ops rejected
dep_daycycle: seed ACS0000001 5000, deposits
1500 + 75 → 6575.00; ACC0000002 1200 − 200 (wd) + 5000 (transfer-in) → 6000.00;
ACS0000003 25000 − 5000 (transfer-out) → 20000.00; ACC0000004 stays 300.00 (both its ops
overdraft-rejected). The transfer's OUT leg amount == IN leg amount == 5000.00.For each ASTAT='A' account: pick band B when CURBAL ≥ 10000.00 else band
A, CHAIN (PRODCD:BAND) into INTPLAN (falling back to band A if the tier is
missing), and if a tier is found with FACTOR > 0, accrue one day's simple interest
ACCRINT += CURBAL × (FACTOR / 365) in packed-decimal math and UPDATE.
Savings earns; the 0%-rate checking product accrues nothing. INTACCR posts no ledger row (it only
grows the ACCRINT bucket).
Expected packed-decimal accrual (band A, 5000 @ 2.5% APY, one day): ACS0000001 ACCRINT ~= 5000 * 0.025 / 365 = 0.3425 (11P4, < $1/day) ACS0000003 (25000, band B @ 3.5%) accrues MORE than band A ACC0000002 (checking, 0%) accrues 0
INTCRED: for each active account with ACCRINT ≥ 0.01, add the accrued interest
to CURBAL+AVLBAL, zero ACCRINT, and write an 'I'
INTEREST CREDIT ledger row (TXNID from MAX+1 advanced locally).
SVCCHG: for each active account, CHAIN its CHGPLAN; if the fee is
positive and CURBAL < WAIVBAL, debit the MFEE and write a 'C'
SERVICE CHARGE row; then, independently, if LSTACT < cutoff (20251001), set
ASTAT='D' (dormant). GLPOST: sum the ledger by type, then post the double-entry
legs (deposits DR 1000-CASH / CR 2000-DEPL; withdrawals reverse; interest DR 5000-INTX /
CR 2000-DEPL; fees DR 2000-DEPL / CR 4000-FEEI; transfers net within 2000-DEPL), then
cross-check the batch DR total equals its CR total.
Expected DSPLY (month-end chain): INTCRED: credited 2 accts total 0.68 SVCCHG: fees=1 dormant=1 ACC0000004: 300 - 8 fee = 292, then flagged D GLPOST BATCH 1 IN BALANCE DR=... batch DR total = CR total
CHAINs for a fresh read between them so both persist
(quirk PQ-9 / PHASE22-2 avoidance). A regression would silently drop the dormancy flag.HOLDPOST recomputes it; DBSETUP runs it once after seeding so the seeded
legal hold (2000 on ACS0000003) reduces available to 23000.GLPOST summarises the ledger, so all of the day's
teller/interest/charge rows must be written before it runs; the night chain orders
INTACCR→INTCRED→SVCCHG→GLPOST for exactly this reason.GLLEDGER, so the GL feed must be posted before they can prove DR=CR.OUTQ before asserting.All files are in library DEPOSIT, grounded in the DDS/SQL members in
src/sources.mjs. Money is packed decimal (11P 2); accrued interest is
11P 4; interest rates are 5P 4 (0.0250 = 2.50% APY) and tier factors
7P 5; dates are stored as zoned 8S 0 in YYYYMMDD form.
| Field | Type | Meaning |
|---|---|---|
| CUSTNO | 6A | Customer number (key). |
| CNAME | 25A | Customer name. |
| ADDR / CITY / ST / ZIP | 20A / 15A / 2A / 5A | Address components. |
| OPNDT | 8S 0 | Customer open date (YYYYMMDD). |
| Field | Type | Meaning |
|---|---|---|
| ACCTNO | 10A | Account number (key), e.g. ACS0000001 (S) / ACC0000002 (C). |
| ACUSTNO | 6A | Owning customer (→ CUSTMST). |
| PRODCD | 1A | Product: S savings, C checking/DDA. |
| ASTAT | 1A | Status: A active, D dormant, X closed. |
| CURBAL | 11P 2 | Current (ledger) balance. |
| AVLBAL | 11P 2 | Available balance = current − open holds. |
| ACCRINT | 11P 4 | Accrued-unpaid interest bucket (4dp). |
| INTRATE | 5P 4 | Account interest rate (0.0250 = 2.50% APY). |
| ODLIM | 9P 2 | Overdraft limit (extends withdrawable available). |
| CHGCD | 2A | Service-charge plan code (→ CHGPLAN). |
| AOPNDT / LSTACT | 8S 0 | Account open date / last-activity date (drives dormancy). |
| Field | Type | Meaning |
|---|---|---|
| TXNID | 9S 0 | Arrival-sequence transaction id (key). |
| TACCTNO | 10A | Account posted to. |
| TXNTYPE | 1A | D deposit, W withdrawal, X transfer, I interest, C charge. |
| TXNDT | 8S 0 | Transaction date (YYYYMMDD). |
| TXNAMT | 11P 2 | Transaction amount. |
| RUNBAL | 11P 2 | Running balance AFTER this posting (passbook idiom). |
| DESCR | 20A | Posting description, e.g. TRANSFER IN. |
Logical TXNLF (PFILE(TXNJRNL)) keys the same rows by TACCTNO then
TXNID — the passbook/register access path used by REGINQ and STMTGEN.
| Field | Type | Meaning |
|---|---|---|
| HACCTNO / HLDSEQ | 10A / 2S 0 | Account + hold sequence (composite key). |
| HLDAMT | 11P 2 | Held amount (reduces available when open). |
| HLDEXP | 8S 0 | Hold expiry date. |
| HSTAT | 1A | Hold status: O open (counted by HOLDPOST). |
| HDESC | 20A | Hold description, e.g. LEGAL HOLD. |
Logical HOLDLF (PFILE(HOLDMST)) keyed by HACCTNO — the
per-account access path HOLDPOST sums.
| Field | Type | Meaning |
|---|---|---|
| PRODCD / BAND | 1A / 1A | Product + band (A base, B high-balance) (key). |
| MINBAL | 11P 2 | Minimum balance for the band. |
| FACTOR | 7P 5 | Annual rate (0.02500 = 2.5% APY); daily rate = FACTOR / 365. |
Seeded tiers: S/A 0→0.02500, S/B 10000→0.03500, C/A 0→0.00000 (checking earns nothing).
| Field | Type | Meaning |
|---|---|---|
| CHGCD | 2A | Charge plan code (key), matched from ACCTMST.CHGCD. |
| MFEE | 7P 2 | Monthly maintenance fee. |
| WAIVBAL | 11P 2 | Minimum balance that waives the fee. |
| CDESCR | 20A | Plan description. |
Seeded plans: S1 fee 2.00 / waive 1000.00; C1 fee 8.00 / waive 2500.00.
| Field | Type | Meaning |
|---|---|---|
| PSEQ | 5S 0 | Arrival sequence. |
| PTYP | 1A | Op type: D deposit, W withdrawal, X transfer. |
| PFRM / PTO | 10A / 10A | From/primary account; to account (transfer only). |
| PAMT | 11P 2 | Amount to post. |
| Field | Type | Meaning |
|---|---|---|
| MSEQ | 5S 0 | Arrival sequence. |
| MTYP | 1A | Action: O open, C change status, R re-rate. |
| MACCT | 10A | Account being maintained. |
| MCUST / MPROD / MCHG | 6A / 1A / 2A | Customer / product / charge-plan (open only). |
| MNSTAT | 1A | New status A/D/X (change only). |
| MRATE | 5P 4 | New interest rate (re-rate). |
| MBAL | 11P 2 | Opening balance (open only). |
| Field | Type | Meaning |
|---|---|---|
| BATCHID | INTEGER | GL batch number (from MAX(BATCHID)+1). |
| ACCT | CHAR(10) | GL account: 1000-CASH, 2000-DEPL, 4000-FEEI, 5000-INTX. |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(13,2) | Leg amount. |
| SRCPGM | CHAR(10) | Source program (e.g. GLPOST). |
(PRODCD:BAND), band chosen from CURBAL.ACCTMNP, then CALL DEPOSIT/ACCTMNT; confirm ACCTMNT APPLIED=n REJECT=m.CALL DEPOSIT/HOLDPOST to recompute available balances.TXNPEND, then CALL DEPOSIT/TELLER (or SBMJOB it); confirm TELLER POSTED=n REJECT=m.CALL DEPOSIT/TELMENUP (option 1 = account inquiry, option 2 = register).SBMJOB CMD(CALL DEPOSIT/INTACCR).CALL DEPOSIT/STMTGEN, CALL DEPOSIT/TRIALRPT.Pre-checks: confirm the job's library list includes DEPOSIT; confirm the pending
work files (TXNPEND/ACCTMNP) hold the intended rows before running the poster.
Post-checks after teller/accrual:
TELLER POSTED= count = 1 row per deposit/withdrawal + 2 per transfer; rejects are overdraft/unknown-account ops that left the master untouched.CURBAL/AVLBAL reconcile, and each TXNJRNL row's RUNBAL equals the post-transaction balance.TRANSFER OUT and a TRANSFER IN row of equal amount (balanced double-entry).INTACCR grew ACCRINT for active interest-bearing accounts only (band B for CURBAL≥10000); the 0%-rate checking product accrued nothing.DSPJRN JRN(DEPOSIT/TXNJRN) FILE(DEPOSIT/TXNJRNL) (single-paren form; see F.3).INTACCR runs are complete (accrued interest is up to date).CALL DEPOSIT/DBNIGHT (or the four SBMJOBs manually), which runs INTCRED → SVCCHG → GLPOST in order on DEPOSIT/NITEQ.INTCRED: credited n accts, SVCCHG: fees=n dormant=m, GLPOST BATCH b IN BALANCE.SELECT SUM(CASE WHEN DRCR='D' THEN AMT ELSE 0 END) AS DR,
SUM(CASE WHEN DRCR='C' THEN AMT ELSE 0 END) AS CR
FROM DEPOSIT.GLLEDGER;
Reconciling figures (the same ones the day-cycle and interest suites assert):
SUM(DR) = SUM(CR) and > 0. GLPOST logs its batch IN BALANCE; TRIALRPT prints TRIAL BALANCE IN BALANCE DR=CR=....INTCRED appears as a DR to 5000-INTX (CR to 2000-DEPL) in the GL.SERVICE CHARGE row nets DR 2000-DEPL / CR 4000-FEEI; e.g. ACC0000004 300 − 8 fee = 292, then flagged dormant.DDABAL totals the account master by product (COMP-3 FD) and reports the net GL deposit liability from SUM(CASE WHEN DRCR='C' THEN AMT ELSE -AMT END) over 2000-DEPL, the accounting cross-check between the operational store and the GL.Statement cycle: CALL DEPOSIT/STMTGEN spools one STMTPR passbook
register per account (one DETAIL line per ledger txn, an END OF ACCOUNT total line, paginated
past the 66-line page via OVRFLW).
Each program DSPLYs a one-line result to the joblog; the SQL programs
(GLPOST/TRIALRPT) additionally report IN BALANCE /
OUT OF BALANCE. A healthy month-end ends with a balanced GL.
| Situation | Behaviour | Action |
|---|---|---|
| Overdraft-rejected teller op | TELLER increments REJECT=, leaves the account untouched, writes no ledger row. | Correct the amount (or the overdraft limit) and re-post only the failed op — already-posted ops are consumed. |
| Duplicate account open | ACCTMNT rejects an MTYP='O' whose MACCT already exists (CHAIN before WRITE); existing master unchanged. | Rejected count is expected; fix the account number to open a genuinely new one. |
| Re-run INTACCR same day | Not idempotent — each run accrues another day into ACCRINT. | Run INTACCR exactly once per business day; a duplicate over-accrues. Correct by re-seeding / adjusting before crediting. |
| Re-run INTCRED | Credits only accounts with ACCRINT ≥ 0.01; after crediting, ACCRINT=0 so a second run is a no-op. | Naturally idempotent once accrual is zeroed — safe to re-run. |
| Re-run SVCCHG same month | Not idempotent — posts another fee to still-low-balance accounts. | Run once per month; do not resubmit. A dormancy flag re-set is harmless (already D). |
| GLPOST OUT OF BALANCE | Batch DR total ≠ CR total (a data / posting problem). | Investigate the batch's rows in GLLEDGER WHERE BATCHID=b; the double-entry legs per type should net. |
| Night step fails partway | Each step is a separate SBMJOB; a later step may not have run. | Re-submit the failed step; because steps are ordered on the queue, confirm each reaches OUTQ before the next (PQ-3). |
| DSPJRN shows Entries:0 | The list/double-paren FILE((lib obj)) form returns no entries (PG5-401). | Use the single-paren form: DSPJRN JRN(DEPOSIT/TXNJRN) FILE(DEPOSIT/TXNJRNL). |
TXNJRNL (with a post-txn
running balance) and the ledger is under STRJRNPF IMAGES(*BOTH), any day's effect is fully
reconstructable after the fact for reconciliation and recovery; the GL transfer legs are additionally
atomic on the SQL path under commitment control.The complete program surface, from src/sources.mjs. All objects are in library
DEPOSIT; the build/seed path is DBSETUP (CL) → seedDeposit()
(src/seed.mjs) which loads each member into QDDSSRC/QRPGLESRC/
QCBLLESRC/QCLSRC/QSQLSRC.
DBSETUP.ACCTMNP; per MTYP: Open (CHAIN-guard against duplicate, then WRITE a UNIQUE-keyed master — escapes PHASE22-1), Change status, Re-rate. At most one UPDATE per read record, re-CHAINing for a fresh read (escapes PHASE22-2). Nested IF, not elseif (PQ-1).HOLDLF rows and set AVLBAL = CURBAL − hsum.TXNPEND; deposit / withdrawal (overdraft-checked) / transfer (two legs). TXNID from MAX(TXNID) read once then advanced by a local counter (PQ-3). Writes TXNJRNL with running balance.CURBAL, CHAIN (PRODCD:BAND) INTPLAN (PQ-2), ACCRINT += CURBAL × FACTOR/365 in packed decimal.ACCRINT into balance, zero it, write an 'I' ledger row.CURBAL ≥ WAIVBAL; independently flag ASTAT='D' when LSTACT < cutoff. Re-CHAINs between the two UPDATEs (PQ-9).GLLEDGER, cross-check batch DR=CR.TXNLF, forcing a page overflow (OFLIND(*INOF)).GLLEDGER ordered by batch/account/DRCR; prints each row and a DR=CR total line.EXFMT ACCTINQF account inquiry: chain master + customer, render product/status/balances.REGSFL row per TXNLF txn by RRN, EXFMT REGCTL.EXFMT MENUFMT; indicator-conditioned fixed-form CALL 'ACCTINQ' / CALL 'REGINQ' (PQ-1).ACCTMST via a COMP-3 FD (zoned 8S 0 dates as PIC S9(8) DISPLAY, PQ-6); totals savings/checking ledger balances; EXEC SQL computes net GL deposit liability from GLLEDGER.The RPG and COBOL programs use these embedded-SQL idioms against the DB2 layer:
exec sql select coalesce(max(TXNID),0) into :ntid from DEPOSIT.TXNJRNL; then advance a local counter (PQ-3).exec sql insert into DEPOSIT.GLLEDGER values(:bat,'1000-CASH','D',:gdep,'GLPOST');CASE sum to prove DR=CR per batch, e.g.
select coalesce(sum(case when DRCR='D' then AMT else 0 end),0) ... into :drtot ... where BATCHID = :bat;TRIALRPT reads GLLEDGER ordered by batch/account, looping dow sqlcod = 0.DDABAL mixes a native COMP-3 FD read with an EXEC SQL SELECT ... INTO :GL-DEPL END-EXEC.COMMIT(*CHG), then ROLLBACK (both vanish) or COMMIT (both persist).Journaling. DBSETUP creates the receiver TXNRCV and journal
TXNJRN, then STRJRNPF FILE(DEPOSIT/TXNJRNL) JRN(DEPOSIT/TXNJRN) IMAGES(*BOTH).
Every RPG WRITE/UPDATE to the ledger is captured as an R/PT (record-put) entry stamped with the posting
job; jrn.entries() and DSPJRN both surface them. Use the single-paren
FILE(DEPOSIT/TXNJRNL) form of DSPJRN — the double-paren list form returns
Entries:0 (PG5-401).
Commitment control. Honored on the SQL/RUNSQL path only, not native record I/O
(PQ-5). So the transfer's atomicity is proven on the GL: STRCMTCTL LCKLVL(*CHG), two
RUNSQL ... COMMIT(*CHG) INSERTs of the DR and CR legs, then ROLLBACK (row count
returns to baseline — both legs discarded) or COMMIT (both persist), then
ENDCMTCTL. The native TELLER two-leg transfer relies instead on the journaled
ledger for reconstructability.
The source is written to sidestep several documented emulator behaviours; these are the ones that shape the application code and matter to a maintainer:
| PQ | Behaviour | How the app handles it |
|---|---|---|
| PQ-1 | Free-form elseif can mis-scope the trailing END-IF. | Nested if/else throughout (TELLER, ACCTMNT, TELMENUP). |
| PQ-2 | Free-form CHAIN can't reference a fixed-form named KLIST. | Parenthesised composite key: chain (prodcd:band) intr; (INTACCR). |
| PQ-3 | A program's own native WRITEs aren't visible to its same-program embedded SQL; SBMJOB writes visible to the next step only across jobs. | Read MAX(id) once, advance a local counter; night chain uses one SBMJOB per step. |
| PQ-4 | PRTF field-position digits are column-sensitive (end at DDS col 44). | STMTPR / TRIALPR field positions authored accordingly. |
| PQ-5 | Commitment control / ROLLBACK honored on the SQL path, not native record I/O. | Transfer atomicity proven via RUNSQL on GLLEDGER. |
| PQ-6 | Zoned nS 0 fields map to COBOL PIC S9(n) DISPLAY, not COMP-3. | DDABAL FD maps AC-OPNDT/AC-LSTACT as DISPLAY. |
| PQ-8 | *EXCLUDE + *USE-only on a batch-written file can throw SFF9802 even for a privileged user. | Adopted authority: USRPRF(*OWNER) program lets a weak profile post to the *PUBLIC *EXCLUDE ledger; the same program without it is denied. |
| PQ-9 | PHASE22-2: a second UPDATE of the same read record can be dropped. | SVCCHG re-CHAINs before the dormancy UPDATE so both the fee and the flag persist. |
ACCRINT bucket. Here: simple daily interest, 365-day convention, CURBAL × FACTOR/365 (INTACCR), in packed decimal.AVLBAL = current balance minus the sum of open holds (HOLDPOST). Withdrawals/transfers are limited to AVLBAL + ODLIM.INTPLAN: band A base, band B for high balances (CURBAL ≥ 10000). INTACCR chooses the band from the balance.ACCRINT.ASTAT='D') when its last activity date predates a cutoff (SVCCHG). Dormant accounts are skipped by the interest/charge passes.SUM(DR)=SUM(CR) (GLPOST / TRIALRPT / DDABAL).TXNJRNL is journaled (STRJRNPF IMAGES(*BOTH)) to TXNJRN, viewable via DSPJRN.11P 2) and the accrual math (11P 4).TXNLF access path; rendered by REGINQ and STMTGEN).DEPOSIT/NITEQ so its writes are visible to the next step.TRANSFER OUT) on the from-account and an equal credit leg (TRANSFER IN) on the to-account — one balanced double-entry (TELLER).TXNPEND, ACCTMNP) holding requested actions that a poster program (TELLER, ACCTMNT) reads and applies — the classic AS/400 batch idiom.