LINKS/i — Golf Course / Country Club

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

LINKS/i runs the operations and member billing of a golf course / country club: membership classes and recurring dues, tee-time booking and played rounds (green fees with a per-class discount), pro-shop inventory and sales, F&B chits with per-class minimum-spend tracking, cart rentals, member payments, and a chargeable-to-account member ledger (the receivables book) that ages into late fees and closes into a balanced GL feed. Every business rule lives in RPG (with one ILE COBOL control report and one SQLRPGLE GL close); the three CL job cycles — GCDAILY, GCMONTH, GCPERIOD — chain the programs. 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 (golf-app/src/sources.mjs, src/seed.mjs, and the test/gc_*.mjs drivers).

Contents

A. Overview & Architecture ↑ top

A.1 What it does

LINKS/i runs a club's member-facing operations and the receivables billing behind them:

A.2 The member-ledger spine

The application is organised around one central book: GCMEMB.MBAL, the live chargeable-to-account member balance, mirrored by the GCLEDG member ledger (one immutable row per posting). The invariant every test asserts is that MBAL moves only by postings (up) and payments (down), and always reconstructs exactly as SUM(GCLEDG.LEDAMT) over the member's own rows. Charges raise the balance and write a positive ledger row; a receipt lowers it and writes a negative one.

Everything runs in library LINKS. There is no SQL-PL layer here (unlike LOANSVC/i): the logic is hand-written RPG over keyed physical files, with a single SQLRPGLE program (GCGLPST) for the GL feed and one ILE COBOL program (GCARRPT) for the control report.

A.3 Component & flow

  ONLINE                 BATCH (three job cycles)
  ------                 ------------------------
  GCMENU (5250)          GCDAILY  --> GCRNDPST   (green fee/cart/guest -> ledger 'G')
    opt 1 -> GCMEMBIQ                 GCSALEPST  (pro-shop sale, relieve stock -> 'P')
    opt 2 -> GCLEDIQ                  GCCHITPST  (F&B chit -> 'F', accumulate MFBSPND)
             (subfile over GCLEDG)    GCCARTPST  (standalone cart rental -> 'C')
                                      GCRCPTPST  (receipt -> 'R', negative amount)
                       GCMONTH  --> GCDUES     (recurring dues -> 'D')
                                      GCMINCHK   (F&B min-spend shortfall -> 'M', reset MFBSPND)
                                      GCAGE      (AR ageing report, PRTF)
                       GCPERIOD --> GCLATE     (ageing -> late fee -> 'L')
                                      GCGLPST    (balanced GL close -> GCGLDIST)
                                      GCARRPT    (COBOL member-ledger control report)
       \                    /
        v                  v
     GCMEMB  (MBAL: the live member balance)   <-- moved ONLY by postings / payments
        |
        +--> GCLEDG   (one immutable row per posting; balance = sum(LEDAMT))
        +--> GCROUND / GCITEM / GCGUEST ...    (per-stream durable records)

A single event — say a played round — flows: GCRNDPST reads the played (TSTAT='P') tee-time, prices the green fee against the member's class discount, sums cart and guest fees, writes a GCROUND row, flips the tee-time to 'G', raises GCMEMB.MBAL and writes one GCLEDG 'G' row. Every posting program derives its ledger sequence number from stable source digits so a re-run lands on the identical key and the CHAIN(EN) guard refuses it — each cycle is idempotent by construction.

A.4 Object inventory

ObjectTypeRole
GCCLASSPFMembership-class tariff (dues / min-spend / green-fee discount).
GCMEMBPFMember master — the live ledger balance MBAL.
GCGUESTPFGuest register (sponsored guests, full-rate fee).
GCTEEPFTee-time bookings (lifecycle B/P/X/N/G).
GCROUNDPFPlayed-round history (one per posted green fee).
GCITEMPFPro-shop inventory master (ONHAND).
GCSALEPFPro-shop sales.
GCCHITPFF&B chits.
GCCARTPFStandalone cart rentals.
GCLEDGPFMember ledger (the receivables book).
GCLEDLFLFLedger alternate access path, keyed by LEDTYPE.
GCRCPTPFMember payments / receipts.
GCGLDISTSQL tableGL distribution feed (+ index GCGLDACC).
GCMEMBD / GCLEDD / GCMENUDDSPFMember inquiry / ledger subfile / menu display files.
GCAGEPPRTFAR ageing printer file.
GCREFLDRPGLESeed reference & transactional data.
GCRNDPST / GCSALEPST / GCCHITPST / GCCARTPST / GCRCPTPSTRPGLEDaily posting programs.
GCDUES / GCMINCHK / GCAGERPGLEMonthly billing / ageing programs.
GCLATERPGLEPeriod ageing → late fee.
GCGLPSTSQLRPGLEPeriod balanced GL close.
GCARRPTILE COBOLMember-ledger control report.
GCMEMBIQ / GCLEDIQ / GCMENURPGLEInteractive inquiry & menu programs.
GCSETUP / GCDAILY / GCMONTH / GCPERIODCLPBuild + the three job cycles.

The full catalogue is 11 PFs + 1 LF + 1 SQL table (with index), 3 DSPFs and 1 PRTF, driven by 14 RPG programs, 1 ILE COBOL program and 4 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

LINKS/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 cycles). 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 LINKS — the tested jobs run with LIBL = QSYS QGPL LINKS QTEMP and CURLIB = LINKS.

To do thisType on the command line
Open the operator menuCALL LINKS/GCMENU
Member inquiry directlyCALL LINKS/GCMEMBIQ
Ledger subfile inquiry directlyCALL LINKS/GCLEDIQ
Run the daily posting cycleCALL LINKS/GCDAILY (or SBMJOB it)
Run the monthly dues/min-spend cycleCALL LINKS/GCMONTH
Run the period ageing/GL cycleCALL LINKS/GCPERIOD
Build/rebuild all objectsCALL LINKS/GCSETUP then CALL LINKS/GCREFLD

The batch programs take no CALL parameters. There is no LOANSVC-style single control row; instead each program reads its processing/billing date and period from hard-coded working-storage defaults in the source (e.g. GCDUES.WDT INZ(20260901), GCDUES.WPERIOD INZ(9), GCLATE.WRUNDT INZ(20260930)). A scheduled submission is therefore a bare CALL; to process a different date/period the source constant is changed and the program recompiled. This is honest to the source — the app models the business rules, not a production date-parameter surface.

B.2 The menu & member inquiry (GCMENU / GCMEMBIQ / GCMEMBD)

GCMENU is the operator entry point: a plain menu (GCMENUD record GCMENU) offering 1. Member Inquiry and 2. Ledger Inquiry. It program-to-program CALLs the chosen inquiry, redisplays on return, reports Invalid option for anything else, and exits on F3.

GCMEMBIQ is a plain (non-subfile) inquiry over GCMEMBD record MEMINQ. The operator keys a member number; the program CHAINs GCMEMB then GCCLASS and renders the master record plus the class tariff and the live F&B-spend figure. An unknown member shows Member not found. with the detail cleared.

Member Inquiry - LINKS/i Member number: M00001 Name . . . . . : HARRIET NORTHGATE Class . . . . : FL Monthly dues . : 250.00 Min spend . . : 150.00 F&B spend YTD : 90.00 Ledger balance : 191.00 On hold . . . : N Member found. F3=Exit Enter=Inquire

Member-inquiry fields (MEMINQ)

FieldType (DDS)Shows
IMEMB6A input/outputMember number to inquire.
DNAME30A outputMember name (MNAME).
DCLASS2A outputMembership class (MCLASS).
DDUES14A outputClass monthly dues (MCDUES, edited).
DMINSP14A outputClass F&B minimum (MCMINSP, edited).
DFBSP14A outputPeriod F&B spend (MFBSPND, edited).
DBAL14A outputLive ledger balance (MBAL, edited).
DHOLD1A outputManual-hold flag (MHOLD).
DMSG50A outputResult message (Member found / not found).

B.3 The ledger subfile screen (GCLEDIQ / GCLEDD)

GCLEDIQ is the app's subfile screen. It presents a member header plus a subfile (DDS record LSFL under control record LCTL, SFLPAG(5) per page, SFLSIZ(0050), ROLLUP/ROLLDOWN) of that member's ledger entries. The operator keys a member number; the program clears the subfile, CHAINs GCMEMB for the header, then does a keyed partial read of GCLEDG (SETLL + READE on MEMNO) loading one subfile row per ledger entry, and redisplays.

Ledger Inquiry - LINKS/i Member number: M00001 Name . . . . . : HARRIET NORTHGATE Balance . . . : 191.00 Seq Date T Amount Ref Description 20000001 2026-08-01 G 65.00 RN00001 GREEN FEE / CART / GUEST 30000001 2026-08-01 P 36.00 SALE0001 PRO SHOP SALE 40000001 2026-08-01 F 90.00 CHIT0001 F&B CHIT Member found. F3=Exit Roll=Page Enter=Inquire

Subfile columns (LSFL)

FieldType (DDS)Shows
SSEQ8Y,0 outputLedger sequence (LEDSEQ).
SDT10A outputPosting date (LEDDT).
STYPE1A outputLedger type (LEDTYPE): D/G/P/F/C/M/L/R.
SAMT12A outputSigned amount (LEDAMT, edited).
SREF8A outputSource reference (LEDREF).
SDESC25A outputDescription (LEDDESC).

Actions & keys

Both inquiry screens are strictly read-only: they display the member master, class tariff and ledger. No balance or ledger row is edited from the screen — every money movement happens through the batch posting programs, so the ledger stays an immutable audit trail.

Documented engine quirk (FIND-001). When GCLEDIQ reloads for a member with zero ledger rows, the previously-displayed subfile rows can still render (the header name/balance updates correctly to the new member's own 0.00 figures). This is a re-confirmation of the same already-documented ORDERIS/i behaviour, not a new finding: the DDS parser reads a two-digit conditioning indicator (N31/31 on SFLDSP/SFLDSPCTL/SFLCLR) as its first digit only, so the intended "clear then don't display" split collapses to one indicator, and the renderer recomposes the last-displayed detail area on a genuinely empty pass. It is cosmetic/interactive-only with no data-integrity impact; test/gc_interactive.mjs asserts only the correct parts (the header update) and documents the stale-subfile behaviour inline rather than masking it. This is the same NA-05/NA-07 estate area recorded in golf-app/FINDINGS.md.

B.4 Controls & audit posture (no four-eyes maker–checker)

Honest statement: LINKS/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 booking that is marked played, a chit, a sale, a receipt are all posted by the next batch cycle with no interactive approval gate. The manual documents the control model the application does have:

In sum, the control posture is immutable-ledger audit + record-lifecycle/scope gating + conservation invariants + derived-key idempotency, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Job Cycles ↑ top

LINKS/i's processing runs as three CL-driven cycles rather than one monolithic nightly job: a daily posting cycle (GCDAILY), a monthly billing cycle (GCMONTH), and a period ageing/close cycle (GCPERIOD). Each CL ADDLIBLE LIB(LINKS), chains its member programs by bare CALL, and ends with a completion banner. All programs take no CALL parameters; dates/periods are working-storage constants in the source (B.1).

-- the three cycles are parameterless SBMJOBs:
SBMJOB CMD(CALL PGM(LINKS/GCDAILY))  JOB(GCDAILY)
SBMJOB CMD(CALL PGM(LINKS/GCMONTH))  JOB(GCMONTH)
SBMJOB CMD(CALL PGM(LINKS/GCPERIOD)) JOB(GCPERIOD)

C.1 Full batch program set

ProgramCyclePurposeReads / writesDSPLY line
GCREFLDsetupSeed classes, members, pro-shop items (build only).writes GCCLASS/GCMEMB/GCITEM.GCREFLD CLASS=4 MEMB=4 ITEM=4
GCRNDPSTdailyPost green fee/cart/guest for played tee-times.GCTEE→'G', GCROUND, GCGUEST→'P', GCMEMB, GCLEDG 'G'.GCRNDPST POSTED=n SKIP=n
GCSALEPSTdailyPost pro-shop sales, relieve inventory.GCSALE→'P', GCITEM ONHAND, GCMEMB, GCLEDG 'P'.GCSALEPST POSTED=n SKIP=n
GCCHITPSTdailyPost F&B chits, accumulate MFBSPND.GCCHIT→'P', GCMEMB (MBAL+MFBSPND), GCLEDG 'F'.GCCHITPST POSTED=n SKIP=n
GCCARTPSTdailyPost standalone cart rentals.GCCART→'P', GCMEMB, GCLEDG 'C'.GCCARTPST POSTED=n SKIP=n
GCRCPTPSTdailyApply member payments (lower balance).GCRCPT→'A', GCMEMB (MBAL−), GCLEDG 'R' (negative).GCRCPTPST APPLIED=n SKIP=n
GCDUESmonthlyBill recurring class dues to active members.GCMEMB, GCLEDG 'D'.GCDUES BILLED=n SKIP=n
GCMINCHKmonthlyCharge F&B minimum-spend shortfall; reset MFBSPND.GCMEMB (MBAL, MFBSPND→0), GCLEDG 'M'.GCMINCHK CHARGED=n MET=n SKIP=n
GCAGEmonthlyPrint AR ageing report (buckets by days since last pay).reads GCMEMB/GCLEDG, writes GCAGEP.GCAGE MEMBERS=n TOTAL=n
GCLATEperiodAge each account; charge a flat late fee past grace.GCMEMB, GCLEDG 'L'.GCLATE CHARGED=n OK=n SKIP=n
GCGLPSTperiodPost a balanced GL feed of the ledger movement.reads GCLEDG, writes GCGLDIST (SQL).GCGLPST BATCH=n ROWS=n
GCARRPTperiodILE COBOL member-ledger control report.reads GCMEMB.GCARRPT OWED/MEMBERS/OUTSTND/ONHOLD

C.2 Daily / monthly / period detail

Daily — GCDAILY

The CL chains, in order: GCRNDPSTGCSALEPSTGCCHITPSTGCCARTPSTGCRCPTPST. Green fees price the standard 45.00 fee against the member's class discount, add the standard 20.00 cart charge (if CARTYN='Y') and every sponsored guest's flat 45.00 fee (never discounted), write a GCROUND row and one GCLEDG 'G' row. Pro-shop sales relieve ONHAND by exactly the quantity sold (floored at zero). Chits post and add to MFBSPND. Receipts post as a negative amount and lower MBAL.

Expected DSPLY (gc_daily oracle: 3 played tee-times, 1 sale, 2 chits, 1 cart, 1 receipt):
  GCRNDPST POSTED=3 SKIP=1     the 4th tee-time is BOOKED, not played -> skipped
  GCSALEPST POSTED=1 SKIP=0
  GCCHITPST POSTED=2 SKIP=0
  GCCARTPST POSTED=1 SKIP=0
  GCRCPTPST APPLIED=1 SKIP=0
Resulting member balances: M00001=201.00 (65 round + 36 shop + 200 chit - 100 receipt),
M00002=121.00 (81 round + 40 chit), M00003=62.50 (42.50 round + 20 cart), M00004=0.00.
Pro-shop glove on-hand 40 -> 38. Every member's balance = sum of their own ledger rows.
Green-fee worked example (from the daily oracle). M00001 (Full, 0% discount) plays with a cart, no guests: 45.00 + 20.00 = 65.00. M00002 (Senior, 20%) plays with one guest: (45.00 − 20%) + 45.00 guest = 36.00 + 45.00 = 81.00. M00003 (Junior, 50%) plays with a cart: (45.00 − 50%) + 20.00 = 22.50 + 20.00 = 42.50.

Monthly — GCMONTH

The CL chains GCDUESGCMINCHKGCAGE. GCDUES bills every MSTAT='A' member their class dues (a manual hold does not skip billing). GCMINCHK compares each member's accumulated MFBSPND against the class minimum: a member below the minimum is charged the shortfall (ledger 'M', a positive amount); one who met it gets a zero-amount 'M' marker row (so the idempotency guard has something to find on a re-run); either way MFBSPND resets to zero for the next period. GCAGE prints the AR ageing report.

Expected DSPLY (gc_monthly oracle: FL/SR/JR/FL-held members; M00002 pre-set MFBSPND=80):
  GCDUES BILLED=4 SKIP=0
  GCMINCHK CHARGED=3 MET=1 SKIP=0   M00002 met 75.00 minimum (spent 80) -> no charge
  GCAGE MEMBERS=4 TOTAL=...
Balances: M00001=400.00 (250 dues + 150 shortfall), M00002=150.00 (dues only, met),
M00003=85.00 (60 + 25), M00004=400.00 (250 + 150; held still billed). MFBSPND all reset to 0.
Why the MET row matters. GCMINCHK writes an 'M' row even when the minimum was met (amount 0.00). Without that durable row, a re-run would find no idempotency guard, re-read MFBSPND (already reset to zero by the first run) and wrongly charge the full minimum a second time to every member who originally met it. The zero-amount marker is the guard.

Period — GCPERIOD

The CL chains GCLATEGCGLPSTGCARRPT. GCLATE ages each member from their last payment (the most recent 'R' ledger row; a member with no payment history is judged from their join date), on a 30/360 convention with a 30-day grace window, and charges a flat 25.00 late fee (ledger 'L') only while MBAL > 0. GCGLPST posts a balanced two-leg GL close (F.4). GCARRPT prints the COBOL control report.

Expected DSPLY (gc_period oracle, run date 20260930):
  GCLATE CHARGED=2 OK=1        M00002 (ancient join) + M00004 (89d) late; M00001 (29d) within grace; M00003 zero-bal skipped
  GCGLPST BATCH=202609 ROWS=...
  GCARRPT OWED    190.00 / MEMBERS 4.00 / OUTSTND 3.00 / ONHOLD 1.00
GL: DR total = CR total = 270.00 (230.00 charges leg + 40.00 receipts leg).

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library LINKS, grounded in the DDS/SQL of golf-app/src/sources.mjs. Dates are stored as signed 8S,0 in YYYYMMDD form; money is packed decimal (9P,2 or 11P,2); the green-fee discount is a packed percentage (5P,2, e.g. 20.00 = 20%).

GCCLASS — Membership-class tariff (PK MCCLASS)

FieldTypeMeaning
MCCLASS2AClass code (PK): FL / SR / JR / SO.
MCDESC20AClass description.
MCDUES9P,2Monthly dues for the class.
MCMINSP9P,2Period F&B minimum spend.
MCGFDISC5P,2Green-fee discount percentage.
MCSTAT1AClass status (A active).

Seeded classes: FL 250.00/150.00/0%, SR 150.00/75.00/20%, JR 60.00/25.00/50%, SO 40.00/50.00/0%.

GCMEMB — Member master (PK MEMNO)

FieldTypeMeaning
MEMNO6AMember number (PK), e.g. M00001.
MNAME30AMember name.
MCLASS2AMembership class (→ GCCLASS).
MJOINDT8S,0Join date (YYYYMMDD); the ageing fallback for a member with no payment history.
MBAL11P,2Live ledger balance — moved ONLY by postings (up) and payments (down).
MFBSPND9P,2Period F&B-spend accumulator; read then reset by GCMINCHK.
MSTAT1AMember status (A active).
MHOLD1AManual hold (Y). Blocks new privileges only, not billing.

Seeded members: M00001 HARRIET NORTHGATE (FL), M00002 DEREK PENNYFEATHER (SR), M00003 CASSIE OAKMOOR (JR), M00004 REGINALD ASHCOMBE (FL, MHOLD=Y). All start MBAL=0, MFBSPND=0.

GCGUEST — Guest register (composite PK MEMNO, GSTNO)

FieldTypeMeaning
MEMNO / GSTNO6A / 8ASponsoring member + guest number (composite key).
GSTNAME30AGuest name.
GSTDT8S,0Play date.
GSTFEE9P,2Guest green fee (stamped at the flat rate when the round posts).
GSTSTAT1AO open, P posted (charged to sponsor's round).

GCTEE — Tee-time bookings (PK TEENO)

FieldTypeMeaning
TEENO8ATee-time number (PK).
TEEDT / TEETIME8S,0 / 4S,0Date (YYYYMMDD) / time (HHMM).
MEMNO6ABooking member.
PLAYERS / GUESTS2S,0 / 2S,0Player and guest headcounts.
CARTYN1AY requests a cart at booking.
CARTAMT9P,2Cart charge, stamped when the round posts.
TSTAT1AB booked, P played, X cancelled, N no-show, G green-fee posted.
GFAMT9P,2Green fee, stamped when the round posts.

GCROUND — Played-round history (PK RNDNO)

FieldTypeMeaning
RNDNO8ARound number (PK), derived from the tee-time (RN0 + digits).
MEMNO6AMember who played.
RNDDT / TEENO8S,0 / 8ARound date / source tee-time.
GFAMT / CARTAMT9P,2 / 9P,2Green fee / cart charge on this round.
RNDSTAT1ARound status (P posted).

GCITEM — Pro-shop inventory (PK ITEMNO)

FieldTypeMeaning
ITEMNO8AItem number (PK).
IDESC30AItem description.
IPRICE9P,2Unit price.
ONHAND9P,2On-hand quantity; the conservation invariant (falls only by what ships).
ISTAT1AItem status (A active).

Seeded items: PS100001 glove 18.00/40, PS100002 balls 32.50/60, PS100003 head-cover set 45.00/3 (critically low), PS100004 polo 55.00/25.

GCSALE — Pro-shop sales (PK SALENO)

FieldTypeMeaning
SALENO8ASale number (PK).
MEMNO6AMember (blank = walk-in, out of scope for billing).
ITEMNO / SQTY8A / 7P,2Item sold / quantity.
SAMT9P,2Sale amount posted to the ledger.
SALEDT / SSTAT8S,0 / 1ASale date / status (O open, P posted).

GCCHIT — F&B chits (PK CHITNO)

FieldTypeMeaning
CHITNO8AChit number (PK).
MEMNO / CHITDT6A / 8S,0Member / date.
CHITAMT9P,2Chit amount; accumulates into MFBSPND on posting.
CDESC / CSTAT20A / 1ADescription / status (O open, P posted).

GCCART — Standalone cart rentals (PK CARTNO)

FieldTypeMeaning
CARTNO8ARental number (PK).
MEMNO / CARTDT6A / 8S,0Member / date.
CARTAMT9P,2Rental amount posted to the ledger.
CSTAT1AStatus (O open, P posted).

This is the genuinely separate rental stream (e.g. a non-golf outing); tee-time-attached cart charges live on GCTEE/GCROUND instead.

GCLEDG — Member ledger (composite PK MEMNO, LEDSEQ)

FieldTypeMeaning
MEMNO / LEDSEQ6A / 8S,0Member + derived sequence (composite key).
LEDDT8S,0Posting date (YYYYMMDD).
LEDTYPE1AD dues, G green fee, P pro-shop, F F&B, C cart, M min-spend, L late fee, R receipt.
LEDAMT11P,2Signed amount (charges positive, receipts negative).
LEDREF / LEDDESC8A / 25ASource reference / description.

Never updated once written; a member's balance is always SUM(LEDAMT) over their own rows.

GCLEDLF — Ledger logical file (keyed LEDTYPE, MEMNO, LEDSEQ)

PFILE(GCLEDG) alternate access path leading on LEDTYPE — the "everything of this kind this period" access the GL close wants without a scan. Same record format, a different leading key from the base PF (which leads on MEMNO).

GCRCPT — Member payments/receipts (PK RCPTNO)

FieldTypeMeaning
RCPTNO8AReceipt number (PK).
MEMNO / RCPTDT6A / 8S,0Member / date.
RCPTAMT11P,2Amount received; applied in full (balance may run credit).
RSTAT1AU unposted, A applied.

GCGLDIST — GL distribution feed (SQL, PK GLSEQ)

FieldTypeMeaning
GLSEQDECIMAL(8,0)GL sequence (PK).
GLBATCHDECIMAL(6,0)Batch (period, e.g. 202609).
ACCTCHAR(9)GL account (see F.4).
DRCRCHAR(1)D debit / C credit.
AMTDECIMAL(11,2)Posting amount.
GLREF / GLDTCHAR(8) / DECIMAL(8,0)Reference / date.

Index GCGLDACC on (ACCT, DRCR) supports the "already posted per account" net-off query the close runs to stay reconcilable across repeated period closes.

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. Confirm the job's library list includes LINKS (LIBL = QSYS QGPL LINKS QTEMP, CURLIB = LINKS).
  2. Ensure the day's raw records are in place: played tee-times marked TSTAT='P', guests with GSTSTAT='O', open sales/chits/carts ('O'), unapplied receipts ('U').
  3. Submit the daily cycle: SBMJOB CMD(CALL PGM(LINKS/GCDAILY)).
  4. Post-check the five DSPLY lines (below).
  5. Answer member enquiries through CALL LINKS/GCMENU (option 1 member, option 2 ledger).

Post-checks after the daily cycle:

E.2 Month-end & period close

  1. Confirm every business day's GCDAILY has run for the month (so MFBSPND is complete).
  2. Submit the monthly cycle: SBMJOB CMD(CALL PGM(LINKS/GCMONTH)). Confirm GCDUES BILLED=, GCMINCHK CHARGED=/MET=, and the GCAGE report. Verify every member's MFBSPND reset to 0.00.
  3. After the month's dailies and monthly are posted, submit the period cycle: SBMJOB CMD(CALL PGM(LINKS/GCPERIOD)). Confirm GCLATE CHARGED=/OK=, GCGLPST BATCH=/ROWS=, and the GCARRPT control lines.
  4. Reconcile the GL feed:
SELECT DRCR, SUM(AMT) FROM LINKS.GCGLDIST GROUP BY DRCR;
-- total debits must equal total credits (the balanced-legs invariant)
SELECT ACCT, DRCR, SUM(AMT) FROM LINKS.GCGLDIST GROUP BY ACCT, DRCR ORDER BY ACCT;

Reconciling figures (the same ones the period volume test checks against a hand-derived oracle):

E.3 Failure & re-run rules

Each program DSPLYs a POSTED/BILLED/CHARGED/APPLIED count and a SKIP count. A healthy re-run shows the work count at 0 (everything already posted) and the skip count carrying the already-done rows — the derived-key idempotency guard (F.3) makes every cycle safe to re-submit.

SituationBehaviourAction
Daily cycle fails partwayPosted rows are flipped to their posted status and have a ledger row; unposted ones do not.Re-submit GCDAILY: already-posted rows are refused by the CHAIN(EN) guard, the rest catch up. Idempotent.
Re-run the whole daily chainEvery posting reports POSTED=0; balances unchanged.Safe no-op — the daily suite asserts M00001 stays 201.00 and no duplicate ledger rows appear.
Re-run GCDUES same periodBILLED=0; no double-bill.Safe. The key encodes the period (WPERIOD), so a different period bills afresh.
Re-run GCMINCHK same periodCHARGED=0 MET=0; MFBSPND already reset.Safe — the zero-amount MET marker row is the guard that stops a re-charge from an emptied tracker.
Re-run GCLATE same periodCHARGED=0; no second late fee.Safe (period-keyed guard). A member within grace, or with MBAL≤0, is never charged.
Re-run GCGLPSTNets off already-posted amounts; posts only new movement.A no-op re-run adds no GL rows and the GL stays balanced (DR=CR).
Walk-in / blank-member sale or chitCounted in SKIP, never posted to a ledger.Expected — cash-and-carry is out of scope for member billing.
Held member (MHOLD=Y)Still billed dues, min-spend and late fees; status unchanged.Expected — the hold blocks new privileges, not billing.
Because every money movement is one immutable GCLEDG row and MBAL always reconstructs as the sum of those rows, any cycle's effect is fully reconstructable after the fact for reconciliation and recovery — the invariant every gc_* test re-checks per member.

F. Developer Reference ↑ top

The program surface, from golf-app/src/sources.mjs. All objects are in library LINKS. The RPG programs are column-exact fixed-form (C/D-spec helpers) with /free blocks; the pattern throughout is a *LOVAL SETLL + READ loop over the driving file, an EXSR per row, and a DSPLY summary line.

F.1 Posting programs (daily)

GCRNDPST — green fee / cart / guest
For each played tee-time (TSTAT='P'): derive RNDNO from the tee-time number ('RN0' + %subst(TEENO:4:5)); guard with CHAIN(EN) on GCROUND; price the green fee WBASE(45) − discount% from the member's class; add the cart (20) if CARTYN='Y'; walk every sponsored guest (READE on MEMNO, matching date and GSTSTAT='O') at the flat WGSTFEE(45); write GCROUND, flip the tee-time to 'G', raise MBAL, write one GCLEDG 'G' row keyed 20000000 + digits. Note the explicit wgst = 0 reset per round — RPG working storage is global, so without it a prior round's guest total would survive into a later guest-less round.
GCSALEPST — pro-shop sale
For each open sale (SSTAT='O') with a non-blank member: ledger key 30000000 + digits, CHAIN(EN) guard; relieve ONHAND by SQTY (floored at zero); flip the sale to 'P'; raise MBAL; write GCLEDG 'P'. Blank-member walk-ins are skipped.
GCCHITPST — F&B chit
For each open chit: key 40000000 + digits; flip to 'P'; raise both MBAL and MFBSPND; write GCLEDG 'F'.
GCCARTPST — standalone cart rental
For each open rental: key 50000000 + digits; flip to 'P'; raise MBAL; write GCLEDG 'C'.
GCRCPTPST — member payment
For each unapplied receipt (RSTAT='U'): key 80000000 + digits; flip to 'A'; lower MBAL by exactly RCPTAMT; write GCLEDG 'R' with a negative amount. The full amount always applies — a club account is a running balance, so it may run credit.

F.2 Billing / close programs (monthly & period)

GCDUES — recurring dues (monthly)
For each active member: ledger key 10000000 + (member-digits × 100) + WPERIOD, CHAIN(EN) guard; look up class dues; raise MBAL; write GCLEDG 'D'. A held member is still billed.
GCMINCHK — F&B minimum-spend shortfall (monthly)
For each active member: key 60000000 + (digits × 100) + WPERIOD; shortfall = max(0, MCMINSP − MFBSPND); raise MBAL by the shortfall; reset MFBSPND to 0 regardless; always write a GCLEDG 'M' row (positive shortfall → CHARGED, zero → MET marker). The zero-amount MET row is the re-run guard.
GCAGE — AR ageing report (monthly)
For each member with MBAL > 0: find the most recent 'R' ledger row (or the join date), compute days on a 30/360 serial, bucket 0/1/2/3 (≤30 / ≤60 / ≤90 / >90 days), and print AGEDTL on GCAGEP with an AGETOT footer. Overflow re-prints AGEHDR via OFLIND(*IN90).
GCLATE — ageing → late fee (period)
For each active member with MBAL > 0: serialise the run date and last-payment date on a 30/360 convention; if the gap is within the 30-day grace → OK; else key 70000000 + (digits × 100) + WPERIOD, guard, raise MBAL by the flat WFEE(25), write GCLEDG 'L'. A member with no payment history is judged from their join date.
GCGLPST — balanced GL close (period, SQLRPGLE)
Sums the ledger by type; nets off what each account already holds (EXEC SQL SELECT SUM(AMT) per account) so only new movement posts; then writes two independently balanced legs (F.4) via a glrow subroutine that INSERTs into GCGLDIST and only counts the row when SQLCOD=0.
GCARRPT — member-ledger control report (period, ILE COBOL)
Reads GCMEMB sequentially; totals owed over positive balances, counts members, outstanding accounts and held members; DISPLAYs OWED / MEMBERS / OUTSTND / ONHOLD.

F.3 The idempotency key convention

LINKS/i has no database sequence and no control-row cursor; instead every posting program derives its GCLEDG.LEDSEQ from stable source digits, so a re-run regenerates the identical key and the CHAIN(EN) guard on that key refuses the duplicate. The high digit namespaces the source stream; period-scoped charges fold in the period so a new period lands on a fresh key.

ProgramLEDSEQ formulaNamespace
GCDUES10000000 + (memdigits × 100) + perioddues, per member per period
GCRNDPST20000000 + teedigitsround, per tee-time
GCSALEPST30000000 + saledigitspro-shop, per sale
GCCHITPST40000000 + chitdigitsF&B, per chit
GCCARTPST50000000 + cartdigitscart, per rental
GCMINCHK60000000 + (memdigits × 100) + periodmin-spend, per member per period
GCLATE70000000 + (memdigits × 100) + periodlate fee, per member per period
GCRCPTPST80000000 + rcptdigitsreceipt, per receipt

Several programs re-CHAIN the driving record after the guard/lookup reads (which move the file cursor) before updating it — e.g. GCRNDPST re-CHAINs the tee-time after walking the class and guest files. This is a deliberate cursor-restore, noted inline in the source.

F.4 Ledger types & GL accounts

The GL close maps each ledger type to a revenue/asset account. The charges leg debits AR (1300-MBAR) for the gross and credits each type's own revenue account; the receipts leg debits cash and credits AR — two legs, each balanced by construction.

LEDTYPEMeaningGL account (CR unless noted)
DMonthly dues4100-DUES
GGreen fee / cart / guest4200-GRNF
PPro-shop sale4300-SHOP
FF&B chit4400-FOOD
CCart rental4500-CART
MMin-spend shortfall4600-MINS
LLate fee4700-LATE
RReceipt (payment)1000-CASH (DR), 1300-MBAR (CR)
Charges AR control1300-MBAR (DR, = Σ D/G/P/F/C/M/L)

Worked GL (gc_period oracle): DR 1300-MBAR 230.00 = CR 4100-DUES 180.00 + CR 4700-LATE 50.00; plus DR 1000-CASH 40.00 = CR 1300-MBAR 40.00. Grand total DR = CR = 270.00.

F.5 Engine-compatibility notes (FINDINGS)

Grounded in golf-app/FINDINGS.md. LINKS/i re-confirmed — but did not newly introduce — two already-documented ORDERIS/i engine behaviours in the DDS/subfile renderer (the NA-05/NA-07 estate area):

Both are cosmetic/interactive-only with no data-integrity impact. No workaround was applied silently: test/gc_interactive.mjs asserts only the correct parts (the header update) and documents the stale-subfile behaviour inline rather than asserting a false pass or masking it with different DDS. The batch posting logic and every balance/ledger/GL invariant are unaffected.

G. Glossary ↑ top

Member ledger (GCLEDG)
The chargeable-to-account receivables book: one immutable row per posting. A member's balance (GCMEMB.MBAL) always reconstructs as the sum of their own ledger rows.
Membership class (GCCLASS)
The tariff table (FL/SR/JR/SO) driving each member's monthly dues, F&B minimum spend, and green-fee discount percentage.
Green-fee discount
A per-class percentage taken off the standard 45.00 green fee (e.g. Junior 50%). Guest fees are never discounted — always the flat 45.00.
Round (GCROUND)
A durable record of a green fee posted from a played tee-time, keyed independently of the tee-time so the history / GL feed has a stable audit trail.
F&B minimum spend / shortfall
Each class sets a period food-and-beverage minimum. A member who spends below it (tracked in MFBSPND) is charged the shortfall (ledger type M); the tracker then resets.
Manual hold (MHOLD)
A flag parking a member for new privileges. It does not stop billing — a held member still owes dues, min-spend and late fees.
Inventory conservation
The invariant that pro-shop ONHAND falls by exactly the quantity a sale ships (floored at zero), never for an out-of-scope walk-in.
Walk-in (blank-member) sale
A cash-and-carry pro-shop sale or chit with no member number — out of scope for member billing; posted to no ledger.
Ageing (30/360)
Measuring days since a member's last payment on a 30-days-per-month serial. Past the 30-day grace window, an outstanding balance draws a flat 25.00 late fee (GCLATE).
Balanced GL legs
The GL close posts a charges leg (DR AR, CR each revenue account) and a receipts leg (DR cash, CR AR), each balanced on its own, so DR = CR regardless of the period's mix.
Movement-only posting
The GL close nets off what each account already holds and posts only the new movement, so repeated period closes stay reconcilable.
Derived-key idempotency
Deriving a posting's ledger key from stable source digits so a re-run regenerates the same key and the CHAIN(EN) guard refuses the duplicate (F.3).
SBMJOB
Submit Job — the IBM i command that queues a program as a batch job (e.g. SBMJOB CMD(CALL PGM(LINKS/GCDAILY))). The LINKS/i cycles take no parameters.
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). GCLEDIQ's ledger list is a subfile with paging (SFLPAG 5 / SFLSIZ 50) and roll keys.
SQLRPGLE / EXEC SQL
RPG with embedded SQL. Here only GCGLPST uses it, to sum the ledger and insert the GL distribution rows.