FITHUB/i is a single-location fitness-club membership and billing application: member and
agreement maintenance, recurring monthly dues billing with freeze/pause and first-month pro-ration,
auto-pay receipt posting, dunning/late-fee assessment, capacity-checked class booking and check-in,
personal-training package usage with session-by-session revenue recognition, and a balanced GL close
posted under commitment control. The whole estate is column-exact IBM i source — DDS physical and
logical files, an SQL DDL table for the GL feed, RPG (fixed-form and **FREE) cycle programs,
a COBOL cross-check report, and CL cycle drivers — loaded into library FITHUB. This
manual is the reference for the operator who runs the online screens and the periodic batch cycles, and
for the developer maintaining the application. It is grounded entirely in the committed source
(fitness-app/src/sources.mjs, src/seed.mjs, and the
test/fithub_build.mjs / fithub_daily.mjs drivers).
FITHUB/i services the life of a fitness-club membership:
FHMEMBR) and their
plan agreements (FHAGREE): create/change, inquire, and freeze/unfreeze an agreement.
Each agreement links a member to a plan (FHPLAN: BAS/PLS/PRM) and carries a
recurring-billing cursor (NEXTBILL).NEXTBILL one calendar month. Charges raise the
member's running balance (MBAL) and write a dues ledger row.FHRCPT) against the member's
running balance and journal each payment to the ledger.MSTAT='D') and assess a flat late fee, once per crossing.FHCLASS), maintaining a live seat count (CAPCNT vs
CAPMAX).FHPTPKG) at a time, recognizing its slice of the pre-paid package price and sweeping the
rounding residual on the final session so recognized revenue sums to the package price exactly.FHGLF) under commitment control, committing only if debits equal credits; a COBOL
report (FHGLRPT) independently cross-checks the A/R posting.FITHUB/i is a classic record-level-access IBM i application, not an SQL-PL app. A few shape points matter to operations:
MBAL) on
FHMEMBR, not a per-invoice A/R. Dues billing always adds the full or pro-rated
charge; auto-pay subtracts a receipt (the balance may legitimately go negative — a
credit balance). This is deliberate: club dues are a running account, not a per-invoice receivable.FHLEDG is a UNIQUE-keyed PF, not a bare arrival
file, on purpose: an output-opened non-keyed PF truncates on each program's OPEN, so a shared arrival
ledger would only hold the last posting program's rows. The UNIQUE key routes writes through the
keyed (append) path so the ledger accumulates across the whole cycle chain into a genuine audit
trail. Every posting program assigns LEDGSEQ from a disjoint numeric band (section F.2)
so keys never collide.Everything runs in library FITHUB. Journaling (FHJRN/FHRCV) is
active on the two audit files FHAGREE and FHLEDG.
MAINTENANCE / ONLINE BATCH (periodic, via *JOBQ)
-------------------- ---------------------------
FHMNT (member/agr, MONTHLY FHMONTH --> FHDUES(cutoff) recurring dues
freeze/unfreeze) --> FHDUESPR dues statements
FHBOOK1 (book/checkin/ DAILY FHDAILY --> FHAUTOPAY receipt posting
cancel) PERIOD FHPERIOD--> FHDUNNING(dt) delinquency + late fee
FHMENU (5250 menu) --> FHPTUSE PT usage recognition
1 -> FHMBRIQ (plain) --> FHGLPST balanced GL close
2 -> FHLEDIQ (subfile) cross-check: FHGLRPT (COBOL)
every posting program --writes--> FHLEDG (durable keyed ledger, disjoint LEDGSEQ bands)
FHMEMBR.MBAL (running balance)
FHGLPST --summarizes FHLEDG by LTYPE--> FHGLF (balanced DR/CR, commit if in balance)
A single billing event flows: operator (or the FHMONTH CL) submits
FHDUES with a cutoff date → it scans open agreements in NEXTBILL order via
the FHAGEL access path → for each due, unfrozen agreement it computes the (possibly
pro-rated) dues, raises FHMEMBR.MBAL, writes an FHLEDG 'D' row, and advances
NEXTBILL one calendar month on FHAGREE (journaled).
| Object | Type | Role |
|---|---|---|
| FHMEMBR | PF | Member master (running balance MBAL, status MSTAT). |
| FHPLAN | PF | Membership plan / dues-rate catalog (BAS/PLS/PRM). |
| FHAGREE | PF | Membership agreements (plan link, freeze, NEXTBILL cursor). |
| FHCLASS | PF | Classes / sessions (capacity CAPMAX / CAPCNT). |
| FHBOOK | PF | Bookings / check-ins. |
| FHPTPKG | PF | PT packages (deferred-revenue liability drawdown). |
| FHPTUSG | PF | PT usage-event trail (one row per session recognized). |
| FHLEDG | PF | Member ledger / A/R audit trail (durable keyed). |
| FHRCPT | PF | Receipts / auto-pay input. |
| FHAGRML / FHAGEL | LF | Agreements by member / by NEXTBILL (billing cursor). |
| FHBOKCL / FHBOKML | LF | Bookings by class / by member. |
| FHLEDML | LF | Ledger by member (inquiry access path). |
| FHGLF | SQL table | DB2 GL posting feed (DR/CR rows). |
| FHMBRD / FHLEDGD / FHMENUD | DSPF | Member inquiry / ledger subfile / menu. |
| FHDUESP | PRTF | Monthly dues statement printer file. |
| FHPLNLD | RPGLE | Plan-catalog loader (reference data). |
| FHMNT | RPGLE | Member/agreement maintenance + freeze/unfreeze. |
| FHDUES | RPGLE | Recurring dues billing (freeze + pro-ration aware). |
| FHBOOK1 | RPGLE | Class booking / check-in / cancel (capacity-checked). |
| FHAUTOPAY | RPGLE | Auto-pay / receipt posting. |
| FHDUNNING | RPGLE | Dunning + flat late-fee assessment. |
| FHPTUSE | RPGLE | PT session drawdown + revenue recognition. |
| FHGLPST | SQLRPGLE | Balanced GL posting under commitment control. |
| FHDUESPR | RPGLE | Dues-statement print (spool). |
| FHMBRIQ / FHLEDIQ / FHMENU | RPGLE | Member inquiry / ledger subfile / menu. |
| FHGLRPT | CBLLE | COBOL GL cross-check report. |
| FHSETUP | CLP | Build all objects, journal, authority, seed plans. |
| FHMONTH / FHDAILY / FHPERIOD | CLP | Cycle drivers (monthly / daily / period). |
The full catalogue is 9 PFs + 5 LFs + 1 SQL table, 3 DSPFs + 1 PRTF, 12 RPG programs + 1 COBOL program,
and 4 CL programs, plus journal (FHJRN) and receiver (FHRCV). Sections D and F
expand each.
FITHUB/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 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 FITHUB — the tested jobs
run with LIBL = QSYS QGPL FITHUB QTEMP and CURLIB = FITHUB.
| To do this | Type on the command line |
|---|---|
| Open the operator menu (routes to the two inquiries) | CALL FITHUB/FHMENU |
| Member inquiry directly (plain screen) | CALL FITHUB/FHMBRIQ |
| Ledger inquiry directly (subfile screen) | CALL FITHUB/FHLEDIQ |
| Maintain a member / agreement, freeze / unfreeze | CALL FITHUB/FHMNT PARM(...) |
| Book / check-in / cancel a class booking | CALL FITHUB/FHBOOK1 PARM(...) |
| Recognize one PT session | CALL FITHUB/FHPTUSE PARM(...) |
| Run a cycle (monthly / daily / period) | SBMJOB CMD(CALL FITHUB/FHMONTH) (or FHDAILY/FHPERIOD) |
| Bill dues to a specific cutoff date | SBMJOB CMD(CALL FITHUB/FHDUES PARM('YYYYMMDD')) |
FHMNT, FHBOOK1 and FHPTUSE are callable programs with an
*ENTRY parameter interface (dcl-pi): they are the program-to-program CALL idiom
used by the test harness and by any wrapper. FHMENU, FHMBRIQ and
FHLEDIQ are the three interactive 5250 programs. The batch cycle programs are covered in
section C.
FHMENU displays a two-option menu (FHMENUD) and routes by a
dynamic CALL: option 1 calls FHMBRIQ (member inquiry, plain WORKSTN),
option 2 calls FHLEDIQ (ledger inquiry, subfile). F3 exits.
Keys a member number; CHAINs FHMEMBR for name/status/balance and
FHAGRML (agreements by member) for the plan, freeze flag and next-bill date, then redisplays.
Enter re-inquires; F3 exits back to the menu.
The app's one subfile screen (FHLEDGD: record LSFL under control
LCTL, SFLPAG(0012), SFLSIZ(0030)). Keys a member; on a match it
clears the subfile (SFLCLR), loads the member's ledger rows from FHLEDML (ledger by member),
and displays them. Control indicators are 41=SFLDSP, 42=SFLDSPCTL,
43=SFLCLR, 44=SFLEND(*MORE).
| Subfile field (LSFL) | Type (DDS) | Shows |
|---|---|---|
| SLTYPE | 1A output | Ledger type: D dues, P payment, F fee, R PT revenue. |
| SLDT | 8A output | Ledger date (from LDT). |
| SLAMT | 14A output | Amount (rendered from LAMT). |
| SLMEMO | 20A output | Memo (e.g. DUES A0000003, LATE FEE). |
FHMNT is the member/agreement maintenance program, dispatched by a two-character action:
| Action | Does |
|---|---|
| MC | Member create/change (keyed by pMbrno). CHAIN + UPDATE if found, else WRITE a fresh member (MSTAT='A', MBAL=0). |
| AC | Agreement create/change (keyed by pKey=AGRNO; pMbrno links it). A create sets FRZSTAT='N', NEXTBILL=STARTDT, AGRSTAT='O'. |
| MI | Member inquire (returns name/city/state via OUT parms). |
| FZ | Freeze agreement: FRZSTAT='Y', FRZDT set. |
| UF | Unfreeze agreement: FRZSTAT='N', FRZDT=0. |
Create/change is validate-before-write and idempotent: a change re-CHAINs on the full key and UPDATEs the found record; a create WRITEs a fresh record. The program re-CHAINs immediately before each UPDATE so it never double-UPDATEs a stale read.
FHBOOK1 is the class booking program, also dispatched by action:
| Action | Does |
|---|---|
| BK | Book a member into a class. Capacity-checked: rejected with CLASS FULL if CAPCNT ≥ CAPMAX; otherwise increments CAPCNT, writes a BSTAT='B' booking, returns the new BOOKNO. |
| CI | Check a booked member in: finds their open ('B') booking for the class, sets BSTAT='C' and CKINDT. |
| CX | Cancel a booking: sets BSTAT='X' and decrements CAPCNT back so the seat frees up. |
FHLEDG) recording every dues/payment/fee/PT-revenue movement;
journaling (FHJRN, IMAGES *BOTH) on FHAGREE and
FHLEDG; capacity gating on bookings; a dunning idempotency guard (a member
already MSTAT='D' is not re-flagged/re-feed); and object-authority scoping on the ledger
(FHSETUP revokes *PUBLIC *ALL and grants only *CHANGE). The GL
close (FHGLPST) enforces its own control: it commits only if debits equal credits,
else ROLLBACK.FITHUB/i's processing runs as three periodic cycles rather than one monolithic nightly job: a
monthly cycle (recurring dues + statements), a daily cycle (auto-pay), and a period
cycle (dunning + PT revenue + GL close). Each cycle is a small CL driver that ADDLIBLE FITHUB
then calls its programs in order. The drivers are meant to be submitted through a *JOBQ
(the tested queue is FITHUB/NITEQ) so each program fully closes its output files before the
next opens them — the same single-threaded-batch idiom the setup notes call out.
-- create the batch queue once CRTJOBQ JOBQ(FITHUB/NITEQ) TEXT('FITHUB batch queue') -- submit a cycle driver (parameterless) SBMJOB CMD(CALL FITHUB/FHMONTH) JOB(FHMONTH) JOBQ(FITHUB/NITEQ) HOLD(*NO) -- or drive dues billing to a SPECIFIC cutoff date directly SBMJOB CMD(CALL FITHUB/FHDUES PARM('20260801')) JOB(FHDUES1) JOBQ(FITHUB/NITEQ) HOLD(*NO)
The dues and dunning programs take a cutoff/run date parameter (PRUNDT, YYYYMMDD,
via the *ENTRY PLIST). The FHMONTH / FHPERIOD wrappers pass the
sentinel '99999999' to bill/dun the whole due backlog in one pass; a caller that needs to
stop at a specific business date instead calls FHDUES/FHDUNNING directly with
that date, exactly as the daycycle test does.
| Program | Purpose | Files touched | Inputs | Outputs (DSPLY / effect) | Frequency |
|---|---|---|---|---|---|
| FHDUES | Recurring monthly dues billing (freeze + first-month pro-ration aware). | FHAGEL/FHAGREE, FHPLAN, FHMEMBR, FHLEDG. | PRUNDT cutoff (YYYYMMDD PARM). |
FHDUES BILLED=n FROZEN-SKIPPED=n PRORATED=n; raises MBAL, D ledger rows, advances NEXTBILL +1 month. |
Monthly. |
| FHDUESPR | Print a dues statement per dues ('D') ledger row. | FHLEDG, FHMEMBR, FHDUESP (PRTF). | none. | FHDUESPR PRINTED=n; spooled statements. |
Monthly (after FHDUES). |
| FHAUTOPAY | Apply cash receipts against member balances. | FHRCPT, FHMEMBR, FHLEDG. | none (one pass over FHRCPT). | FHAUTOPAY POSTED=n NOMEMBER=n; reduces MBAL, P ledger rows. |
Daily. |
| FHDUNNING | Flag delinquent members over threshold, assess a flat late fee. | FHMEMBR, FHLEDG. | PRUNDT (YYYYMMDD PARM) stamps the fee date. |
FHDUNNING FLAGGED=n; MSTAT→'D', +15.00 fee, F ledger rows. |
Period. |
| FHPTUSE | Draw one PT session down, recognize its revenue slice. | FHPTPKG, FHPTUSG, FHMEMBR, FHLEDG. | PARM: pPkgno, pUsgdt. |
FHPTUSE PKGnnnnn <result>; usage-event row, R ledger row, PKGSTAT→'X' at exhaustion. |
Per session (period cycle drives repeatedly). |
| FHGLPST | Summarize the ledger, post balanced GL rows under commitment control. | FHLEDG (read via SQL), FHGLF (insert). | none. | FHGLPST IN BALANCE ... COMMITTED or OUT OF BALANCE ... ROLLED BACK. |
Period (last). |
| FHGLRPT | COBOL cross-check: member-balance total + GL A/R vs dues+fees. | FHMEMBR (COMP-3 FD), FHLEDG + FHGLF (embedded SQL). | none. | FHGLRPT ... CROSS-CHECK: IN BALANCE / OUT OF BALANCE. |
Period (verification). |
FHDUES does a forward SETLL *LOVAL / READ pass over
FHAGEL (agreements keyed by NEXTBILL), stopping at the first agreement whose
NEXTBILL > PRUNDT. For each open agreement due for billing:
FRZSTAT='Y'): billing is skipped and NEXTBILL is NOT advanced
— freeze pauses the billing cursor entirely, so a later unfreeze resumes from the same date
with no backlog and no gap. Counted in FROZEN-SKIPPED.NEXTBILL = STARTDT (the agreement's very first bill)
and the start day-of-month is not the 1st, dues are pro-rated for the partial month:
MONDUES × remaining-days / days-in-month, where remaining-days =
days-in-month − day + 1 (bills the start day itself; leap-Feb handled by the 4/100/400 rule).
Counted in PRORATED. The result truncates to cents.MONDUES.It then raises FHMEMBR.MBAL, writes an FHLEDG 'D' row, and advances
NEXTBILL one calendar month (month +1, wrapping the year at 12→1, day forced to 01).
FHDUESPR then prints one statement per 'D' ledger row.
Expected DSPLY (cycle 1: 3 agreements, one prorated first bill, none frozen): FHDUES BILLED=3 FROZEN-SKIPPED=0 PRORATED=1 M1 BAS full 39.99, M2 PLS prorated 59.99*16/31 -> 30.96 (truncated), M3 PRM full 99.99 Expected DSPLY (cycle 2, M3 frozen): FHDUES BILLED=2 FROZEN-SKIPPED=1 PRORATED=0
FHAUTOPAY makes one pass over FHRCPT (arrival). Each receipt reduces the
member's MBAL directly (MBAL = MBAL − RCPTAMT; may go negative — a
valid credit balance) and writes an FHLEDG 'P' row. There are no per-invoice rows to apply
against — this is a running account.
Expected DSPLY (one receipt applied):
FHAUTOPAY POSTED=1 NOMEMBER=0
FHDUNNING does a forward pass over FHMEMBR: any active ('A') member with
MBAL over the threshold (WTHRESH, seeded 0.00) is flagged MSTAT='D'
and assessed a flat 15.00 late fee (added to MBAL, 'F' ledger row). A member already
'D' is not re-flagged/re-feed on a second pass — only a fresh crossing from 'A'
triggers a new fee.
FHPTUSE (called once per session) draws one session from a package: the per-session
recognized amount is PKGPRC / SESSBUY, and the final session recognizes whatever
balance of PKGPRC remains (sweeping the rounding residue) and sets PKGSTAT='X',
so a package's recognized revenue sums to PKGPRC exactly. It writes an FHPTUSG
usage-event row and an FHLEDG 'R' row; it does not touch MBAL (the member
paid up front).
FHGLPST sums FHLEDG by LTYPE and inserts balanced DR/CR rows into
FHGLF under commitment control, then re-reads the batch and COMMITs only if
DR=CR (else ROLLBACK).
Expected DSPLY: FHDUNNING FLAGGED=2 e.g. M2 + M3 both over threshold FHPTUSE PKG00001 SESSION RECOGNIZED 33.33, 33.33, 33.34 -> sums 100.00 FHGLPST IN BALANCE DR=... CR=... COMMITTED FHGLRPT ... CROSS-CHECK: IN BALANCE
FHDUES before FHDUESPR — statements print
from the 'D' ledger rows dues billing just wrote.MBAL: dues must have raised it
and auto-pay must have reduced it, so a member paid to (or below) the threshold is correctly spared.
In the tested cycle M1 auto-paid to exactly 0.00 escapes dunning; M2 (never paid) and M3 (frozen but
still carrying its balance) are flagged.FHGLPST summarizes the whole ledger including the 'R'
rows, and cross-checks depend on them.FHGLPST posts, then FHGLRPT
independently verifies A/R.*JOBQ so each
closes its output files before the next opens them.All files are in library FITHUB, grounded in the DDS/SQL in
fitness-app/src/sources.mjs. Dates are stored as signed numeric YYYYMMDD
(8S 0); money is packed decimal (xP 2); A is character.
| Field | Type | Meaning |
|---|---|---|
| MBRNO | 6A | Member number (key). |
| MNAME | 25A | Member name. |
| MADDR / MCITY / MST / MZIP | 25A / 15A / 2A / 5A | Address, city, state, zip. |
| MJOINDT | 8S 0 | Join date (YYYYMMDD). |
| MSTAT | 1A | A active, F frozen, D delinquent/hold, C cancelled. |
| MBAL | 9P 2 | Running account balance (dues raise it, payments reduce it; may be negative). |
| Field | Type | Meaning |
|---|---|---|
| PLANCD | 3A | Plan code (key): BAS, PLS, PRM. |
| PDESC | 20A | Plan description. |
| MONDUES | 7P 2 | Monthly recurring dues rate. |
| PTINCL | 3S 0 | PT sessions bundled free per month. |
Seeded catalog (FHPLNLD): BAS “Basic Monthly” 39.99 / 0 PT; PLS “Plus Monthly” 59.99 / 1 PT; PRM “Premium Monthly” 99.99 / 4 PT.
| Field | Type | Meaning |
|---|---|---|
| AGRNO | 8A | Agreement number (key), e.g. A0000001. |
| MBRNO | 6A | Owning member. |
| PLANCD | 3A | Plan link (→ FHPLAN). |
| STARTDT | 8S 0 | Agreement start date (YYYYMMDD). |
| TERMMO | 3S 0 | Contract term in months (0 = month-to-month). |
| FRZSTAT | 1A | N not frozen, Y frozen. |
| FRZDT | 8S 0 | Date freeze began (0 if not frozen). |
| NEXTBILL | 8S 0 | Next dues billing date (YYYYMMDD, day always 01) — the recurring-billing cursor. |
| AGRSTAT | 1A | O open/active, X cancelled. |
| Field | Type | Meaning |
|---|---|---|
| CLSNO | 8A | Class number (key). |
| CNAME | 20A | Class name. |
| CDATE | 8S 0 | Session date. |
| CINSTR / CROOM | 15A / 8A | Instructor / room. |
| CAPMAX | 3S 0 | Seat capacity. |
| CAPCNT | 3S 0 | Current booked count (maintained by FHBOOK1). |
| CSTAT | 1A | Class status. |
| Field | Type | Meaning |
|---|---|---|
| BOOKNO | 8S 0 | Booking number (key; deterministic ascending sequence). |
| MBRNO / CLSNO | 6A / 8A | Member / class. |
| BOOKDT / CKINDT | 8S 0 | Booked date / check-in date. |
| BSTAT | 1A | B booked, C checked-in, X cancelled. |
| Field | Type | Meaning |
|---|---|---|
| PKGNO | 8A | Package number (key). |
| MBRNO | 6A | Owning member. |
| SESSBUY | 3S 0 | Sessions purchased. |
| SESSUSE | 3S 0 | Sessions used to date (drawn down by FHPTUSE). |
| PKGPRC | 9P 2 | Total package price = deferred-revenue liability at purchase. |
| PKGSTAT | 1A | O open (sessions remain), X exhausted/closed. |
| Field | Type | Meaning |
|---|---|---|
| USGSEQ | 8S 0 | Usage-event sequence (key). |
| PKGNO / MBRNO | 8A / 6A | Package / member. |
| USGDT | 8S 0 | Session date. |
| USGAMT | 9P 2 | Revenue recognized for this session (PKGPRC/SESSBUY, residual swept on the last). |
| Field | Type | Meaning |
|---|---|---|
| LEDGSEQ | 8S 0 | Ledger sequence (key). Assigned from a per-program disjoint band (section F.2) so keys never collide and writes append. |
| MBRNO | 6A | Member. |
| LTYPE | 1A | D dues, P payment, F fee, R PT revenue. |
| LDT | 8S 0 | Ledger date. |
| LAMT | 9P 2 | Amount. |
| LMEMO | 20A | Memo (e.g. DUES A0000001, AUTOPAY RCPT, LATE FEE, PT USE PKG00001). |
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8S 0 | Receipt number (key). |
| MBRNO | 6A | Member. |
| RCPTDT | 8S 0 | Receipt date. |
| RCPTAMT | 9P 2 | Receipt amount (applied against MBAL). |
| RTYPE | 1A | A auto-pay, M manual. |
| Field | Type | Meaning |
|---|---|---|
| BATCHID | CHAR(8) | Posting batch id (GLB00001). |
| ACCT | CHAR(6) | GL account (section F.3). |
| DRCR | CHAR(1) | D debit, C credit. |
| AMT | DECIMAL(11,2) | Posting amount. |
| SRCPGM | CHAR(8) | Source program (FHGLPST). |
*LOVAL/READ scan of every open agreement in due order).FITHUB and the batch queue exists
(CRTJOBQ JOBQ(FITHUB/NITEQ), once).CALL FITHUB/FHMENU for inquiries;
CALL FITHUB/FHMNT PARM(...) to create/change members and agreements or freeze/unfreeze;
CALL FITHUB/FHBOOK1 PARM(...) to book/check-in/cancel; CALL FITHUB/FHPTUSE
PARM(...) to recognize a PT session.FHRCPT, then submit the daily cycle:
SBMJOB CMD(CALL FITHUB/FHDAILY) JOBQ(FITHUB/NITEQ).Pre-checks: confirm the plan catalog is loaded (FHPLNLD ran; BAS/PLS/PRM present);
confirm the job's library list includes FITHUB.
Post-checks after the daily cycle:
FHAUTOPAY POSTED=n NOMEMBER=0 — NOMEMBER should be 0; a non-zero count
means a receipt referenced a member not in FHMEMBR.MBAL and wrote a 'P' ledger row of the same
amount.FHMONTH (bills the whole backlog via the
'99999999' sentinel), or drive a specific cutoff:
SBMJOB CMD(CALL FITHUB/FHDUES PARM('<YYYYMMDD>')). Confirm
FHDUES BILLED=/FROZEN-SKIPPED=/PRORATED= matches expectation and statements printed
(FHDUESPR PRINTED=).FHPERIOD (FHDUNNING → FHPTUSE
→ FHGLPST); recognize outstanding PT sessions with FHPTUSE before the
GL post. Confirm FHGLPST IN BALANCE ... COMMITTED.CALL FITHUB/FHGLRPT and confirm
CROSS-CHECK: IN BALANCE.SELECT ACCT, DRCR, SUM(AMT) FROM FITHUB.FHGLF GROUP BY ACCT, DRCR ORDER BY ACCT, DRCR;
Reconciling figures (the same ones the daycycle simulation checks against a hand-derived oracle; note RPG division truncates):
FHGLPST only commits when this holds.120000 DR = total dues billed + late fees assessed
(the two ledger types that post to A/R). Example from the tested cycle: dues (39.99×2 + 30.96 +
59.99 + 99.99) + two 15.00 late fees.230000 DR = recognized PT revenue = SUM of
the package's 'R' rows = PKGPRC exactly (e.g. 33.33 + 33.33 + 33.34 = 100.00).FHGLRPT reads the packed member balances (COMP-3) and
confirms the GL A/R debit equals dues+fees (IN BALANCE).Each program DSPLYs a one-line result (section F.5). The critical safety points are the ledger key bands (no cross-program collisions), the freeze/NEXTBILL cursor discipline, and the dunning guard.
| Situation | Behaviour | Action |
|---|---|---|
| Re-run FHDUES same period, no calendar advance | Not naturally idempotent: anything still NEXTBILL ≤ PRUNDT re-bills (by design — the job is scheduled once per period). | Do not re-invoke FHDUES for the same billing month without advancing the cutoff. Within one invocation each due agreement is billed exactly once (the forward *LOVAL scan advances NEXTBILL). |
| Re-run FHDUES at an earlier cutoff | Bills nothing new (every NEXTBILL now > cutoff). | Safe no-op — the daycycle test verifies this. |
| Unfreeze after a frozen period | Freeze never advanced NEXTBILL, so billing resumes from the same date — no backlog, no gap. | Unfreeze (FHMNT UF), then run FHDUES normally. |
| Re-run FHDUNNING same period | A member already MSTAT='D' is not re-flagged/re-feed. | Safe: only a fresh 'A'→over-threshold crossing assesses a new fee. |
| Re-run a posting program (FHDUES/FHAUTOPAY/FHDUNNING) | The high-water LEDGSEQ scan resumes above the last key in this program's band, so a second run never collides with the first's rows. | Safe re-key; watch the DSPLY counts for double-posting from a same-period re-run. |
| FHPTUSE on an exhausted package | Rejected (PACKAGE ALREADY EXHAUSTED); PKGSTAT already 'X'. | Naturally idempotent — a package recognizes at most SESSBUY sessions, summing to PKGPRC. |
| FHGLPST debits ≠ credits | ROLLBACK; no FHGLF rows committed. | Investigate the ledger totals, correct, and re-post. The commitment-control gate prevents an unbalanced batch. |
FHAUTOPAY NOMEMBER>0 | A receipt's MBRNO had no member; that receipt is skipped (no ledger row). | Fix the receipt's member reference and re-run only those receipts. |
FHLEDG (and FHAGREE
status changes to FHJRN), any cycle's effect is fully reconstructable after the fact for
reconciliation and recovery (DSPJRN JRN(FITHUB/FHJRN) FILE(FITHUB/FHLEDG)).The complete program surface, from fitness-app/src/sources.mjs. All objects are in library
FITHUB.
FHPLAN. Called from FHSETUP.dcl-pi *ENTRY callable. Actions MC/AC/MI/FZ/UF (section B.3). Validate-before-write; re-CHAIN immediately before each UPDATE. Returns pOutFound and pOutMsg.SETLL *LOVAL/READ over FHAGEL (NEXTBILL order); skips frozen (no NEXTBILL advance), pro-rates the first partial month, bills full months otherwise; raises MBAL, writes 'D' ledger row, advances NEXTBILL +1 month on FHAGREE. PARM PRUNDT cutoff.dcl-pi *ENTRY callable. Actions BK/CI/CX; capacity-checked booking (CAPCNT vs CAPMAX), cancel decrements CAPCNT. BOOKNO from a whole-file high-water scan.FHRCPT; MBAL = MBAL − RCPTAMT, writes 'P' ledger row. LEDGSEQ band 20000000.FHMEMBR; active member with MBAL > WTHRESH (0.00) → MSTAT='D', +WLATEFEE (15.00), 'F' ledger row. Guard: already-'D' not re-feed. PARM PRUNDT.dcl-pi *ENTRY callable. Per-session amount PKGPRC/SESSBUY; final session sweeps the residual and sets PKGSTAT='X'. Writes an FHPTUSG event and an FHLEDG 'R' row; does not touch MBAL. Rejects not-found / already-exhausted.ctl-opt commit(*yes). Sums FHLEDG by LTYPE, inserts balanced DR/CR rows (section F.3), then re-reads the batch: COMMIT if DR=CR, else ROLLBACK.FHDUESP statement lines to spool.LSFL. Indicators 41/42/43/44 = SFLDSP/SFLDSPCTL/SFLCLR/SFLEND.CALL 'FHMBRIQ' (opt 1) / 'FHLEDIQ' (opt 2).IN BALANCE / OUT OF BALANCE.FHLEDG is UNIQUE-keyed so writes append instead of truncating on OPEN. Each posting
program seeds its LEDGSEQ from a disjoint band and, at start, does a high-water scan of that
band to resume above the last key it wrote — so a second same-period run never collides with the
first run's rows.
| Program | Ledger type | LEDGSEQ band |
|---|---|---|
| FHDUES | D (dues) | 10000000 – 10999999 |
| FHAUTOPAY | P (payment) | 20000000 – 20999999 |
| FHDUNNING | F (late fee) | 30000000 – 30999999 |
| FHPTUSE | R (PT revenue) | 40000000 + |
| Ledger source | Debit | Credit |
|---|---|---|
| Dues ('D') | 120000 A/R | 400000 Dues Revenue |
| Auto-pay ('P') | 100000 Cash | 120000 A/R |
| Late fee ('F') | 120000 A/R | 410000 Fee Revenue |
| PT usage ('R') | 230000 PT Package Liability | 420000 PT Revenue |
PT usage debits the package-liability account (not A/R) because the member already paid cash for the package at purchase (booked as a liability then); recognizing revenue as sessions are used draws that liability down.
FHMNT, FHBOOK1, FHPTUSE
take a dcl-pi parameter list (action + keys + OUT status), the program-to-program
CALL-with-parameters idiom.FHDUES and FHDUNNING take a YYYYMMDD
via *ENTRY PLIST; the wrappers pass '99999999' for “whole
backlog”.FHAGEL keyed NEXTBILL) and the
dunning/statement passes all use SETLL *LOVAL / READ to walk a file in key
order.+ 0.005 half-cent nudge in FHPTUSE's per-session
slice.)FHGLPST uses
ctl-opt commit(*yes) with SET OPTION COMMIT=*CS, a balance re-read, and
COMMIT/ROLLBACK; FHGLRPT uses embedded SQL SELECT
... INTO against the same feed.if blocks
rather than elseif, per a documented free-form elseif/end-if
scoping quirk.| Program | Result line |
|---|---|
| FHDUES | FHDUES BILLED=n FROZEN-SKIPPED=n PRORATED=n |
| FHDUESPR | FHDUESPR PRINTED=n |
| FHAUTOPAY | FHAUTOPAY POSTED=n NOMEMBER=n |
| FHDUNNING | FHDUNNING FLAGGED=n |
| FHMNT | FHMNT <action> <MEMBER CREATED / CHANGED / AGREEMENT FROZEN / ...> |
| FHBOOK1 | FHBOOK1 <action> <BOOKED / CHECKED IN / CANCELLED / CLASS FULL / ...> |
| FHPTUSE | FHPTUSE <pkgno> <SESSION RECOGNIZED / PACKAGE ALREADY EXHAUSTED / PACKAGE NOT FOUND> |
| FHGLPST | FHGLPST IN BALANCE DR=.. CR=.. COMMITTED / OUT OF BALANCE .. ROLLED BACK |
| FHGLRPT | FHGLRPT MEMBERS n TOTBAL a, DUES+FEES a, GLAR a, CROSS-CHECK: IN BALANCE |
commit(*yes)). FHGLPST posts GL rows, checks balance, and COMMITs only if debits equal credits, else ROLLBACKs.