HARBOR/i — Marina: Slips, Boat Registry, Rentals, Fuel Dock & Billing

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

HARBOR/i runs the day-to-day operation of a marina: a slip inventory by size-class and rate, a boat/owner registry, slip rental agreements (monthly, seasonal and transient), fuel-dock sales with inventory relief, service work orders, a customer ledger / accounts-receivable with open-item discipline, cash-receipt application, AR aging with late fees, and a balanced GL close. It is a classic IBM i application: DDS physical/logical files, fixed-form and free-form RPG batch programs, two interactive 5250 screens (one plain, one subfile), an ILE COBOL control report, an embedded-SQL GL post, and CL job cycles that string the batch programs together. This manual is the reference for the operator who runs the online screens and the three periodic cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (marina-app/src/sources.mjs, src/seed.mjs, and the test/hb_*.mjs drivers). Everything runs in library HARBOR.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

HARBOR/i covers the operational life of a boat's stay at the marina:

A.2 The ledger invariant & conservation rules

Two invariants run through the whole application and are the operator's and the developer's north star:

Each posting batch is also idempotent: a re-run never double-charges, double-relieves or double-pays. Idempotency is enforced either by a per-row posted-flag (FPOST/RPOST/WPOST), by a derived charge key (a charge number computed from the agreement plus the run period, so a same-period re-run regenerates the same key and is refused), or, for the GL, by a watermark-since-last-post subtraction.

A.3 Component & flow

  SETUP / SEED           ONLINE                 BATCH (three cycles)
  ------------           ------                 --------------------
  HBSETUP (CL)           HBMENU (5250)          DAILY  (HBDAILY CL):
    CRTPF/CRTLF/...        opt 1 -> HBAVAILQ      HBFUELRCV  (delivery in)
    CRTBNDRPG/...         opt 2 -> HBLEDGQ        HBFUELPOST (sales + relief + 'U' charge)
  HBREFLD (RPG)                                   HBWOPOST   (service 'S' charge)
    seeds MAR/SZCL/                               HBCASHAP   (cash apply, oldest-due-first)
    SLIP/BOAT/AGR/FUEL   HBAVAILQ (plain DSPF)  MONTHLY (HBMONTH CL):
                          slips by size class     HBRENTRL   (rent-roll 'R' charge)
                         HBLEDGQ  (subfile)       HBAGE      (AR aging -> HBDELQ)
                          boat's ledger history   HBLATEFEE  ('L' late fee)
                                                  HBGLPST    (balanced GL post)
                                                  HBAGERPT   (COBOL control report)
                                                 PERIOD (HBPERIOD CL):
                                                  HBTRNBILL  (transient checkout billing)

  Every charge/payment writes an HBCHG ledger row and moves HBSLIPAG.RBAL.
  Fuel sales/deliveries move HBFUEL.FQOH. The GL close reads HBCHG and writes
  balanced DR/CR rows into HBGLDIST (SQL table).

A single event — say a charged fuel sale — flows: the dock captures an HBFSALE ticket (FPOST='N') → the daily HBFUELPOST relieves HBFUEL.FQOH by the gallons sold, writes a 'U' ledger debit to HBCHG, raises the boat's HBSLIPAG.RBAL, and stamps the ticket FPOST='Y' so it is never posted twice.

A.4 Object inventory

ObjectTypeRole
HBMARPFMarina/basin master.
HBSZCLPFSlip size-class rate table (monthly/seasonal/transient rates).
HBSLIPPFSlip master (size class + status).
HBSLPLFLFSlips keyed by (MARCD, SSTAT, SZCLCD) for availability.
HBBOATPFBoat/owner registry.
HBSLIPAGPFSlip rental agreement (locked rate, live balance).
HBCHGPFCustomer ledger / charge journal (the heart of the app).
HBCHGLFLFLedger keyed by (BOATNO, CHGNO) for the subfile inquiry.
HBCHGDLFLFLedger keyed by (AGRNO, CDUEDT, CHGNO) for aging.
HBRCPTPFCash receipts.
HBDELQPFAR aging / late-fee detail (one row per agreement, upserted).
HBFUELPFFuel-dock inventory (QOH, cost, price).
HBFSALEPFFuel sale tickets (POS captures).
HBFUELRCPFFuel deliveries/receipts.
HBWOPFService work orders.
HBGLDISTSQL tableGL distribution (balanced DR/CR rows); index HBGLDACC.
HBAVAILD / HBLEDGD / HBMENUDDSPFAvailability / ledger-subfile / menu display files.
HBAGEPPRTFAR aging printer file.
HBREFLDRPGReference-data seed loader.
HBRENTRL / HBTRNBILLRPGMonthly/seasonal rent-roll / transient checkout billing.
HBFUELPOST / HBFUELRCVRPGFuel sale posting+relief / delivery receiving.
HBWOPOST / HBCASHAPRPGWork-order posting / cash application.
HBAGE / HBLATEFEERPGAR aging / late-fee assessment.
HBGLPSTSQLRPGLEGL post (embedded SQL over HBGLDIST).
HBAVAILQ / HBLEDGQ / HBMENURPGInteractive availability / ledger / menu drivers.
HBAGERPTILE COBOLAR aging control report over the ledger.
HBSETUP / HBDAILY / HBMONTH / HBPERIODCLPBuild/seed CL + the three job-cycle CLs.

The catalogue is 15 database files (12 PF + 3 LF), 1 SQL table + index, 4 DSPF/PRTF, 13 RPG/SQLRPGLE programs, 1 COBOL program and 4 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

HARBOR/i has no CICS transaction identifiers. On IBM i each program is reached by name from a 5250 command-entry line (or a JOBQ/scheduler for the batch cycles). Before invoking anything, the job's library list must include HARBOR — the tested jobs run with LIBL = QSYS QGPL HARBOR QTEMP and CURLIB = HARBOR. The everyday entry point is the menu; from there the operator picks the two inquiry screens.

To do thisType on the command line
Open the operator main menuCALL HARBOR/HBMENU
Open the slip availability inquiry directlyCALL HARBOR/HBAVAILQ
Open the customer ledger inquiry directlyCALL HARBOR/HBLEDGQ
Build/compile every object, first timeCALL HARBOR/HBSETUP
Seed the reference dataCALL HARBOR/HBREFLD
Run the daily cycleCALL HARBOR/HBDAILY (or SBMJOB it)
Run the monthly cycleCALL HARBOR/HBMONTH
Run the period (transient) cycleCALL HARBOR/HBPERIOD

The batch drivers take no CALL parameters: run dates and periods are compiled into the RPG as named constants (e.g. WRUNDT INZ(20260901), WPERIOD INZ('09')), so a scheduled submission is a bare CALL. Only the three HB*Q/HBMENU programs are interactive; the batch programs run to completion and DSPLY a one-line result summary each.

B.2 The main menu (HBMENU / HBMENUD)

HBMENU is the front door. It shows a two-option menu and program-to-program CALLs the chosen inquiry, returning to the menu when the operator presses F3 in the inquiry. An option other than 1/2/blank sets the "Invalid option." message.

HARBOR/i Main Menu 1. Slip Availability Inquiry 2. Customer Ledger Inquiry Option . . . . : _ F3=Exit Enter=Select

B.3 Slip availability inquiry (HBAVAILQ / HBAVAILD)

A plain (non-subfile) screen answering the everyday "have we got a slip of this class free?" question. The operator keys a marina code and a size class; the program CHAINs HBSZCL to render that class's description and its three rate tiers, then walks every slip at the marina (READE on HBSLIP by MARCD) and tallies live counts of vacant / occupied / maintenance slips of that class. Re-key and press Enter to inquire again; F3 exits.

Slip Availability - HARBOR/i Marina code . :MAR1 Size class . :M Description :MEDIUM SLIP TO 35FT Monthly rate : 400.00 Season rate : 1,800.00 Transient/day: 65.00 Vacant slips .: 1 Occupied . . .: 1 Maintenance .: 0 Size class found. F3=Exit Enter=Inquire
Rates and counts above are the seeded MAR1 / class M figures: monthly 400.00, season 1800.00, transient 65.00; MAR1 has two M slips (01A occupied, 01E vacant), so vacant=1, occupied=1. An unknown class leaves the fields blank with "Size class not found."

B.4 Customer ledger subfile inquiry (HBLEDGQ / HBLEDGD)

HBLEDGQ is the app's subfile screen (DDS record LSFL under control record LCTL, SFLPAG(5) per page, SFLSIZ(30), ROLLUP/ROLLDOWN paging). The operator keys a boat number; the program CHAINs HBBOAT for the boat name, scans HBSLIPAG for that boat's agreement (header agreement number and live balance), then loads the boat's ledger rows from HBCHGLF (keyed by BOATNO) into the subfile in posting order. The subfile is cleared and reloaded each pass, so a fresh inquiry always reflects current data.

Customer Ledger - HARBOR/i Boat number :B00001 Boat name . :SEA SPIRIT Agreement . :AG000001 Balance . . : 137.50 Chg# Date T Amount Balance Memo R0000109 20260901 R 400.00 400.00 SLIP RENT CHARGE U0000002 20260910 U 112.50 512.50 FUEL DOCK CHARGE P0000100 20260910 P 400.00 112.50 PAYMENT APPLIED L0000109 20260930 L 25.00 137.50 LATE FEE ASSESSED F3=Exit Roll=Page Enter=Inquire

Subfile columns (LSFL)

FieldType (DDS)Shows
SCHGNO8A outputCharge number (ledger row key).
SCDT10A outputTransaction date.
SCTYPE1A outputType: R rent, U fuel, S service, L late fee, P payment.
SCAMT12A outputTransaction amount (%EDITC 'K').
SCBAL12A outputAgreement balance AFTER this row (CBALAFT).
SCMEMO30A outputFree-text memo.
Honest access-path note. HBSLIPAG's own key is AGRNO, not BOATNO, so finding a boat's agreement for the header is a filtered full scan (SETLL *LOVAL + sequential READ, iter when BOATNO ≠ wboat) rather than a keyed read — the same honest choice the sibling storage-app's SVLEDGQ makes. The subfile load itself does use a keyed access path (HBCHGLF by BOATNO).

C. Batch Jobs & the Periodic Cycles ↑ top

HARBOR/i's processing runs as three CL-driven cycles rather than one nightly monolith: a daily cycle (fuel receiving + fuel sale posting + service posting + cash application), a monthly cycle (slip rent-roll → AR aging → late fees → GL post → aging report), and a period cycle (transient-slip checkout billing). Each CL simply ADDLIBLE HARBOR then CALLs its member programs in order. None takes parameters.

-- run the three cycles (bare, parameterless CALLs / SBMJOBs)
CALL PGM(HARBOR/HBDAILY)
CALL PGM(HARBOR/HBMONTH)
CALL PGM(HARBOR/HBPERIOD)
-- or scheduled:
SBMJOB CMD(CALL PGM(HARBOR/HBDAILY)) JOB(HBDAILY)

C.1 Full batch program set

ProgramCyclePurposeReads / writesDSPLY summary
HBFUELRCVDailyReceive fuel deliveries; raise QOH; replace standing cost.HBFUELRC (RPOST) → HBFUEL.FQOH/FCOST.HBFUELRCV RECEIVED=n SKIP=n
HBFUELPOSTDailyPost fuel sale tickets: relieve inventory; charge charged sales to the ledger.HBFSALE (FPOST) → HBFUEL.FQOH, HBCHG('U'), HBSLIPAG.RBAL.HBFUELPOST POSTED=n SKIP=n
HBWOPOSTDailyPost completed service work orders to the ledger.HBWO (WSTAT='C', WPOST) → HBCHG('S'), HBSLIPAG.RBAL.HBWOPOST POSTED=n SKIP=n
HBCASHAPDailyApply cash receipts oldest-due-first; write payment rows.HBRCPT (RCSTAT='U') → HBCHG('P')/COPEN, HBSLIPAG.RBAL.HBCASHAP APPLIED=n SKIP=n
HBRENTRLMonthlyRent-roll: charge active M/S agreements their locked rate.HBSLIPAG → HBCHG('R'), HBSLIPAG.RBAL.HBRENTRL BILLED=n SKIP=n
HBAGEMonthlyAge each active agreement on its oldest open charge.HBSLIPAG + HBCHG → HBDELQ.DDAYS (upsert).HBAGE CURRENT=n LATE=n
HBLATEFEEMonthlyCharge one flat late fee past the grace period.HBDELQ (DDAYS>grace) → HBCHG('L'), HBSLIPAG.RBAL.HBLATEFEE CHARGED=n SKIP=n
HBGLPSTMonthlySum the ledger since last post; write balanced DR/CR rows.HBCHG → HBGLDIST (SQL).HBGLPST BATCH=nnnnnn ROWS=n
HBAGERPTMonthlyCOBOL control report: reconcile the receivables sub-ledger.HBCHG (sequential).HBAGERPT BILLED/OPEN/CHARGES/STILLOPEN
HBTRNBILLPeriodBill checked-out transient stays per night; terminate agreement.HBSLIPAG (T, RCKOUTDT set) → HBCHG('R'), RSTAT='T'.HBTRNBILL BILLED=n SKIP=n

C.2 Daily / monthly / period detail

Daily — HBDAILY (HBFUELRCV, HBFUELPOST, HBWOPOST, HBCASHAP)

The daily cycle moves money and inventory captured during the day: (1) HBFUELRCV raises HBFUEL.FQOH by each unposted delivery's RQTY and replaces the standing cost; (2) HBFUELPOST relieves FQOH by each unposted sale's FQTY and, for a FPAY='A' (charged) sale, writes a 'U' ledger debit and raises the agreement balance — a FPAY='C' (cash) sale relieves inventory only; (3) HBWOPOST posts each complete-and-unposted work order as an 'S' debit; (4) HBCASHAP walks each unapplied receipt against its agreement's open charges oldest-due-first.

Expected DSPLY (the hb_cycle oracle: 1 delivery, 2 sales, 1 WO, 2 receipts):
  HBFUELRCV RECEIVED=    1 SKIP=    0   MAR1 gas 800.0 -> 1000.0 (+200.0)
  HBFUELPOST POSTED=    2 SKIP=    0    1000.0 -> 920.0 (-50 cash -30 charged)
  HBWOPOST POSTED=    1 SKIP=    0      AG000002 +75.00 service
  HBCASHAP APPLIED=    2 SKIP=    0     RC000001 full, RC000002 part
Oldest-due-first without a due-date access path. HBCASHAP and HBAGE want an agreement's open charges in due-date order, but HBCHG's own key is CHGNO. Because a logical file with an alternate key is read/update-only here (a WRITE through it is refused) and a program cannot open a PF and an LF sharing one record format at once, both programs scan HBCHG (SETLL *LOVAL + sequential READ) to find the current agreement's oldest open CDUEDT and settle it one charge at a time — the same honest choice storage-app's SVCASHAP/SVAGE make. A receipt larger than what is owed applies only what is owed; the remainder stays unapplied on the receipt (RCSTAT='P').

Monthly — HBMONTH (HBRENTRL, HBAGE, HBLATEFEE, HBGLPST, HBAGERPT)

(1) HBRENTRL writes one 'R' slip-rent charge per active M/S agreement for its locked SLRATE and raises the balance by exactly that amount — transient (T) agreements are skipped. (2) HBAGE finds each active agreement's oldest open charge and stamps HBDELQ.DDAYS = days past due on the 30/360 serial convention. (3) HBLATEFEE charges a flat 25.00 fee to any agreement whose DDAYS > 10 (the grace period), once per period. (4) HBGLPST sums the ledger by type and posts balanced GL rows. (5) HBAGERPT (COBOL) prints/DSPLYs the receivables control totals.

Expected DSPLY (the hb_cycle oracle):
  HBRENTRL BILLED=    4 SKIP=    1   4 monthly/seasonal billed, 1 transient skipped
  HBAGE CURRENT=    1 LATE=    4     only the un-charged transient is current
  HBLATEFEE CHARGED=    4 SKIP=    1
  HBGLPST BATCH=202609 ROWS=n       DR total = CR total

The GL post writes a balanced set for the movement since the last close: DR 1200-AR against CR 4100-RENT / 4300-FUEL / 4400-SVC / 4200-FEES for the billed charges, and DR 1000-CASH / CR 1200-AR for cash applied. In the seeded run the credit totals reconcile to slip-rent 4585.00, fuel 112.50, service 75.00, late fees 100.00 (4×25.00), and cash 500.00 — and total debits equal total credits.

Period — HBPERIOD (HBTRNBILL)

For every active transient (T) agreement whose RCKOUTDT is set (checkout recorded) and not yet billed, HBTRNBILL computes the number of nights between RSTRTDT and RCKOUTDT on the 30/360 serial convention (minimum one night), charges nights × SLRATE as an 'R' ledger row, raises the balance, and terminates the agreement (RSTAT='T'). An agreement not yet checked out (RCKOUTDT=0) is simply skipped until it is.

Expected DSPLY (AG000005 checks in 09-10, out 09-13 = 3 nights @ 45.00):
  HBTRNBILL BILLED=    1 SKIP=    4   3 x 45.00 = 135.00, RSTAT -> T

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library HARBOR, grounded in the DDS in src/sources.mjs. Dates are stored as signed 8S0 in YYYYMMDD form; money is packed DECIMAL; fuel quantities are packed one-decimal gallons; fuel cost/price are packed three-decimal per-gallon.

HBMAR — Marina/basin master (K MARCD)

FieldTypeMeaning
MARCD4AMarina code (key), e.g. MAR1.
MARNM30AMarina name.
MARCITY / MARST20A / 2ACity / state-or-region code.
MARSTAT1AMarina status (A active).

HBSZCL — Slip size-class rate table (K SZCLCD)

FieldTypeMeaning
SZCLCD2ASize-class code (key): S/M/L.
SZDESC20ADescription, e.g. "MEDIUM SLIP TO 35FT".
SZMORATE9P2Standard MONTHLY rate.
SZSSNRATE9P2Standard SEASONAL (May–Sep lump) rate.
SZTRNRATE9P2Standard TRANSIENT day-rate.

Seeded classes: S 250/1100/45, M 400/1800/65, L 650/2900/95. These are the standard rates a new rental picks up at signing; the agreement then carries its own locked SLRATE, so a later table change never reprices an existing renter.

HBSLIP — Slip master (K MARCD, SLIPNO)

FieldTypeMeaning
MARCD / SLIPNO4A / 6AMarina + slip number (key).
SZCLCD2ASize class of this slip.
SSHORE1AShore power Y/N (tracked; standard, no premium).
SSTAT1AV vacant, O occupied, M maintenance.

Alternate access path HBSLPLF keys the same records by (MARCD, SSTAT, SZCLCD) for the availability inquiry without a scan.

HBBOAT — Boat/owner registry (K BOATNO)

FieldTypeMeaning
BOATNO6ABoat number (key), e.g. B00001.
BNAME30ABoat name.
OWNNM / OADDR / OPHONE30A / 30A / 12AOwner name / address / phone.
BLOA5P1Length overall, feet (one decimal).
BSTAT1AA active, X removed.

HBSLIPAG — Slip rental agreement (K AGRNO)

FieldTypeMeaning
AGRNO8AAgreement number (key), e.g. AG000001.
MARCD / SLIPNO / BOATNO4A / 6A / 6AThe slip and boat this agreement covers.
RTYPE1AM monthly, S seasonal, T transient.
RSTRTDT / RCKOUTDT8S0 / 8S0Start date / checkout date (0 until checked out).
SLRATE9P2Rate LOCKED at signing (monthly amount / season lump / day-rate).
RBAL11P2Live ledger balance (rises via charge, falls via cash — the invariant).
RSTAT1AA active, T terminated (checked out).
RLASTCH8S0Date of last charge posted to this agreement.

HBCHG — Customer ledger / charge journal (K CHGNO)

FieldTypeMeaning
CHGNO8ACharge number (key); for posting programs it is DERIVED so re-runs collide.
AGRNO / BOATNO8A / 6AOwning agreement / boat.
CDT8S0Transaction date.
CTYPE1AR slip rent, U fuel, S service, L late fee, P payment (credit).
CAMT11P2Amount (always POSITIVE; sign implied by CTYPE).
CDUEDT8S0Due date for a debit (0 for a payment row).
COPEN11P2Open-item remaining unpaid on THIS debit (drives cash application).
CSTAT1AO open / P fully paid (blank on a payment row).
CBALAFT11P2Agreement balance immediately AFTER this row (point-in-time snapshot).
CMEMO30AFree-text detail.

Two alternate access paths: HBCHGLF (BOATNO, CHGNO) for the ledger subfile, and HBCHGDLF (AGRNO, CDUEDT, CHGNO) for the oldest-due-first walk aging/cash want.

HBRCPT — Cash receipts (K RCPTNO)

FieldTypeMeaning
RCPTNO8AReceipt number (key).
AGRNO / BOATNO8A / 6AAgreement / boat the receipt is for.
RCDT8S0Receipt date.
RCAMT11P2Amount received.
RCAPPL11P2Amount actually applied against open charges.
RCSTAT1AU unapplied, P part-applied, A fully applied.

HBDELQ — AR aging / late-fee detail (K AGRNO)

FieldTypeMeaning
AGRNO / BOATNO8A / 6AAgreement / boat (one row per agreement, upserted in place).
DDAYS5S0Days past due on the OLDEST unpaid charge at the last aging run.
DFEEDT8S0When the late fee was first assessed for the current delinquency.
DFEETOT11P2Late fees accumulated on this agreement.

HBFUEL — Fuel-dock inventory (K MARCD, FGRADE)

FieldTypeMeaning
MARCD / FGRADE4A / 1AMarina + grade (G gasoline, D diesel) (key).
FQOH11P1Quantity on hand, gallons (the conservation invariant).
FCOST / FPRICE7P3 / 7P3Standing cost / retail price per gallon.

HBFSALE — Fuel sale ticket (K SALENO)

FieldTypeMeaning
SALENO8ASale number (key).
MARCD / AGRNO / BOATNO4A / 8A / 6AMarina / (charged) agreement / boat.
SDT / FGRADE8S0 / 1ASale date / fuel grade.
FQTY / FAMT11P1 / 11P2Gallons sold / extended amount at price.
FPAY1AC cash (no ledger charge) / A charged to agreement.
FPOST1AN unposted / Y posted (the idempotency guard).

HBFUELRC — Fuel delivery/receipt (K RCPTNO)

FieldTypeMeaning
RCPTNO8ADelivery receipt number (key).
MARCD / FGRADE4A / 1AMarina / grade delivered.
RDT / RQTY / RCOST8S0 / 11P1 / 7P3Delivery date / gallons / cost per gallon.
RPOST1AN unposted / Y posted (guard).

HBWO — Service work order (K WONO)

FieldTypeMeaning
WONO8AWork-order number (key).
AGRNO / BOATNO8A / 6AAgreement / boat.
WDT / WDESC / WAMT8S0 / 30A / 11P2Date / description / amount.
WSTAT1AO open / C complete (chargeable).
WPOST1AN unposted / Y posted (guard).

HBGLDIST — GL distribution (SQL table, PK GLSEQ)

FieldTypeMeaning
GLSEQDECIMAL(8,0)Sequence (PK); HBGLPST continues it from MAX(GLSEQ).
GLBATCHDECIMAL(6,0)Batch (the close year-month, e.g. 202609).
ACCTCHAR(9)Account: 1000-CASH, 1200-AR, 4100-RENT, 4200-FEES, 4300-FUEL, 4400-SVC.
DRCRCHAR(1)D debit / C credit.
AMT / GLREF / GLDTDECIMAL(11,2) / CHAR(8) / DECIMAL(8,0)Amount / reference / date. Index HBGLDACC on (ACCT, DRCR).

Relationships

E. Operations Runbook ↑ top

E.1 Setup & seed (first time)

  1. Ensure the job's library list includes HARBOR (ADDLIBLE HARBOR).
  2. CALL HARBOR/HBSETUP — deletes any prior files, then CRTPF/CRTLF the database, CRTDSPF/CRTPRTF the screens/report, RUNSQLSTM the GL table + index, and CRTBNDRPG/CRTBNDCBL every program. It ends with the "HARBOR setup complete." message.
  3. CALL HARBOR/HBREFLD — seeds 2 marinas, 3 size classes, 8 slips, 5 boats, 5 rental agreements and 4 fuel-inventory rows. Confirm the DSPLY: HBREFLD MAR=2 SZCL=3 SLIP=8 BOAT=5 AGR=5 FUEL=4.

E.2 Day-in-the-life

  1. Capture the day's fuel deliveries (HBFUELRC, RPOST='N'), fuel sale tickets (HBFSALE, FPOST='N'), completed work orders (HBWO, WSTAT='C', WPOST='N') and cash receipts (HBRCPT, RCSTAT='U') — captured at the dock/desk during the day.
  2. Submit the daily cycle: SBMJOB CMD(CALL PGM(HARBOR/HBDAILY)).
  3. Post-check each DSPLY summary line (below).
  4. Handle inquiries interactively through CALL HARBOR/HBMENU (availability, ledger) as needed.
  5. When a transient boat checks out, set its HBSLIPAG.RCKOUTDT and run CALL HARBOR/HBPERIOD to bill the stay.

Post-checks after the daily cycle:

E.3 Month-end close

  1. Confirm the month's daily cycles have all run (fuel, service and cash are posted).
  2. Submit the monthly cycle: SBMJOB CMD(CALL PGM(HARBOR/HBMONTH)).
  3. Confirm the DSPLY chain: HBRENTRL BILLED=, HBAGE CURRENT=/LATE=, HBLATEFEE CHARGED=, HBGLPST BATCH=/ROWS=, and the HBAGERPT control totals.
  4. Reconcile the GL — total debits must equal total credits:
SELECT DRCR, SUM(AMT) FROM HARBOR.HBGLDIST GROUP BY DRCR;
-- and by account:
SELECT ACCT, DRCR, SUM(AMT) FROM HARBOR.HBGLDIST GROUP BY ACCT, DRCR ORDER BY ACCT;

Reconciling figures (the same ones the hb_cycle simulation checks against a hand-derived JS oracle):

Aging control: HBAGERPT (COBOL) DSPLYs BILLED (sum of debit charge amounts), OPEN (sum still unpaid), CHARGES (debit row count) and STILLOPEN (count of open debits) so the receivables sub-ledger can be reconciled against the ledger.

E.4 Failure & re-run rules

Every posting batch is safely re-runnable; each DSPLYs a POSTED/BILLED/APPLIED= plus a SKIP= count, and on a clean re-run the posted count drops to 0 while everything is skipped.

SituationBehaviourAction
Daily fuel/service/cash re-runPosted rows carry FPOST/RPOST/WPOST='Y' and receipts RCSTAT≠'U'; the derived payment key already exists.Safe: HBDAILY re-runs with everything skipped. Idempotent.
Rent-roll re-run, same periodCharge number is derived from (agreement, period); the same key already exists.Safe no-op — the agreement is skipped, no double charge. A NEW period (different WPERIOD) bills again.
Aging re-runHBAGE upserts one HBDELQ row per agreement (CHAIN then UPDATE-or-WRITE).Safe: recomputes cleanly, no duplicate rows.
Late-fee re-run, same periodFee charge number is derived from (agreement, period).Safe no-op — never double-fees within a period.
GL post re-runHBGLPST subtracts what is already posted per account before writing (watermark).Safe: a second run posts only new movement; the first month's figures are never re-posted.
Transient billing re-runCharge key is derived from the agreement; a billed stay already exists and the agreement is now RSTAT='T'.Safe: skipped, never double-billed.
Over-payment on a receiptOnly what is owed is applied; the remainder stays unapplied.Receipt shows RCSTAT='P'; the surplus is never lost. Apply it later against new charges.
Because every money movement is journaled to HBCHG with a post-transaction balance snapshot (CBALAFT) and an open-item remaining (COPEN), any cycle's effect is fully reconstructable for reconciliation and recovery.

F. Developer Reference ↑ top

The programs are held as JS string constants in marina-app/src/sources.mjs and loaded into library HARBOR's source physical files by src/seed.mjs (seedHarbor()), mirroring the emulator's ibmi/samples.js DEMOLIB convention. The RPG is mostly fixed-form C-specs with /free blocks; two programs (HBGLPST embedded SQL, and the interactive drivers) lean more on free-form.

F.1 Batch programs

HBREFLD (RPG) — reference seed
Output-only over 6 files; writes the marinas, size classes, slips, boats, agreements and fuel inventory, then DSPLYs the seed counts.
HBRENTRL (RPG) — monthly/seasonal rent-roll
Reads HBSLIPAG; for each active M/S agreement builds WCHGNO = 'R' + %subst(agr:4:5) + period, guards with CHAIN(EN) + IF *IN95=*OFF, then writes a 'R' charge and raises RBAL. Transient agreements are skipped.
HBTRNBILL (RPG) — transient checkout billing
For active T agreements with RCKOUTDT≠0: computes nights on the 30/360 serial (min 1), charges nights×SLRATE, sets RSTAT='T'. Guarded by a derived 'T' charge key.
HBFUELPOST (SQLRPGLE-free/RPG) — fuel sale posting
For each FPOST='N' sale: CHAIN(E) HBFUEL and relieve FQOH -= FQTY; for FPAY='A', write a 'U' ledger debit and raise RBAL; stamp FPOST='Y'. Its idempotency guard uses free-form CHAIN + %FOUND() (not a fixed-form indicator) — see F.4.
HBFUELRCV (RPG) — fuel delivery receiving
For each RPOST='N' delivery: FQOH += RQTY, replace FCOST (simple replace-cost, not weighted-average), stamp RPOST='Y'.
HBWOPOST (RPG) — work-order posting
For each complete-and-unposted work order: write an 'S' debit and raise RBAL. Work variables are prefixed WK* deliberately — see F.3.
HBCASHAP (RPG) — cash application
For each RCSTAT='U' receipt, walks the agreement's open charges oldest-due-first by scanning HBCHG, settling COPEN one charge at a time until the receipt is exhausted; reduces RBAL by the amount applied; writes a 'P' payment row keyed by a derived receipt key; stamps RCSTAT='A'/'P'.
HBAGE (RPG) — AR aging
For each active agreement, finds the oldest open charge (scan) and stamps HBDELQ.DDAYS = run-date minus due-date on the 30/360 serial. Upserts the HBDELQ row.
HBLATEFEE (RPG) — late-fee assessment
For each HBDELQ row with DDAYS>10 (grace), charges one flat 25.00 'L' fee (derived-key guarded) and raises RBAL.
HBGLPST (SQLRPGLE) — GL post
Accumulates HBCHG by CTYPE, reads the already-posted totals per account (the watermark), subtracts them, and INSERTs balanced DR/CR rows into HBGLDIST continuing GLSEQ from MAX(GLSEQ). Only a row whose insert returned SQLCOD=0 is counted.
HBAGERPT (ILE COBOL) — AR aging control report
Sequentially reads HBCHG, sums debit charge amounts and open amounts, counts charges and still-open debits; DSPLYs BILLED / OPEN / CHARGES / STILLOPEN.

F.2 Interactive programs

HBMENU (RPG) — menu driver
EXFMT HBMENU loop; option 1 CALL 'HBAVAILQ', option 2 CALL 'HBLEDGQ', anything else sets "Invalid option."; *IN03 (F3) exits.
HBAVAILQ (RPG) — slip availability inquiry
CHAIN(E) HBSZCL for the rate tiers, then READE HBSLIP by marina tallying vacant/occupied/maintenance for the keyed class; renders via %EDITC.
HBLEDGQ (RPG) — customer ledger subfile
Subfile LSFL/LCTL over HBLEDGD (SFILE(LSFL:RRN)). Clears the subfile (*IN31WRITE LCTL) each pass, CHAINs HBBOAT, scans HBSLIPAG for the header, then loads HBCHGLF rows by boat.

F.3 Key patterns & conventions

F.4 Platform finding PG-HB-001 (honest)

Finding. During the build of HARBOR/i, the fixed-form RPG CHAIN(xN) FILE key nn not-found resulting indicator was found to unreliably report FOUND for a key proven absent from the file, while the free-form %FOUND() built-in on the identical operation is reliable. The behavior is 100% reproducible in a minimal single-file repro (a freshly created UNIQUE-keyed PF with zero records: WKEY CHAIN(EN) TESTPFR nn then IF *INnn=*ON prints "FOUND" when the file is empty), yet the exact trigger could not be reduced below "a hand-written program of this shape" — the sibling storage-app's real SVCASHAP, using the identical idiom, correctly reports NOT FOUND on an empty file. Logged as Confirmed (behavior) / Suspected (exact trigger) in marina-app/FINDINGS.md; locus ibmi/rpg.js emitChain()'s indicator-assignment codegen path (the %FOUND() path is unaffected).

Where it bit HARBOR/i and the fix. In HBFUELPOST, the charged-fuel-sale idempotency guard WCHGNO CHAIN(EN) HBCHGR ... 95 reported FOUND on the first run against an empty HBCHG, so the 'U' fuel debit was silently never written and the boat's balance was never raised for the charge — while inventory relief and the "POSTED=n" counter were still correct, making the bug easy to miss. The fix, in HARBOR/i's own source (not a platform workaround that hides the finding), switched that guard to the free-form CHAIN + %FOUND() form, which is reliable in every repro. See S.HBFUELPOST, the block commented PG-HB-001.

The other four programs using the same fixed-form CHAIN(EN) ... nn idiom (HBRENTRL, HBTRNBILL, HBCASHAP, HBLATEFEE) all pass their own idempotency tests in test/hb_cycle.mjs (correctly NOT FOUND on first run, FOUND on re-run), so this is not a blanket "never use CHAIN(EN)" situation — it appears to depend on some program-shape/size property not fully characterized. Those four were left as-is (proven working by their passing tests) rather than speculatively rewritten.

G. Glossary ↑ top

Agreement (slip rental agreement, HBSLIPAG)
One boat-to-slip rental, carrying a billing cadence (monthly / seasonal / transient), a rate locked at signing (SLRATE) and a live ledger balance (RBAL).
Aging (AR aging)
Measuring how overdue an agreement's oldest unpaid charge is. HARBOR/i stamps days-past-due into HBDELQ.DDAYS on the 30/360 serial convention.
Charge / ledger row (HBCHG)
One debit (rent, fuel, service, late fee) or credit (payment) row. A debit is its own open item with a remaining unpaid amount (COPEN) and a status (O/P).
Conservation invariant (fuel)
Fuel on hand (FQOH) falls only via a sale and rises only via a delivery, each by exactly the ticket quantity.
Derived charge key
A ledger charge number computed from stable inputs (agreement + period, or a source ticket/receipt number) so a same-period re-run collides on the key and is refused — the idempotency guard.
Grace period
Days past due before a late fee applies. HARBOR/i uses 10 days; anything more overdue earns one flat 25.00 fee per period.
Idempotent
Safe to run again with the same result. HARBOR/i's posting batches are idempotent via posted-flags, derived keys, or (for the GL) a watermark subtraction.
Ledger invariant (balance)
An agreement balance rises only via a charge and falls only via cash application, by exactly the transaction amount.
Locked rate (SLRATE)
The rate an agreement carries from signing, independent of later changes to the standard HBSZCL rate table, so an existing renter is never silently repriced.
Oldest-due-first (open-item application)
Cash is applied to an agreement's open charges in due-date order, settling each fully before the next. A surplus stays unapplied on the receipt (RCSTAT='P').
Open item
A debit charge that still has an unpaid remainder (COPEN>0, CSTAT='O'); cash application and aging both walk open items.
SBMJOB
Submit Job — the IBM i command that queues a program to run as a batch job (e.g. SBMJOB CMD(CALL PGM(HARBOR/HBDAILY))).
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). HBLEDGQ's ledger list is a subfile with roll-up/roll-down paging.
Transient
A short-stay boat billed per night at the locked day-rate, charged at checkout by HBTRNBILL, which then terminates the agreement (RSTAT='T').
Watermark (GL post)
Posting only the ledger movement since the last close by subtracting the already-posted per-account totals, so a second run never re-posts the first period's figures.
30/360 serial
A day-count convention treating every month as 30 days: a date becomes ((yy*12)+mm)*30+dd, and day counts are differences of these serials.