STORVAULT/i — Self-Storage Facility Management

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

STORVAULT/i runs a self-storage operator's back office: a facility/unit inventory, tenant rental agreements each carrying a rate locked at signing, a monthly rent-roll that bills the book, daily cash-receipt application against an open-item tenant ledger, delinquency aging, flat late fees, and the lien / overlock / auction ladder a storage operator runs against a non-paying tenant — closing into a balanced GL distribution. The business logic lives in fixed-form and free-form RPG (with one COBOL control report and an SQLRPGLE GL post); CL drives the two job cycles. This manual is the reference for the operator who runs the online inquiry screens and the daily / monthly cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (storage-app/src/sources.mjs, src/seed.mjs, and the test/sv_build.mjs / sv_cycle.mjs / sv_interactive.mjs drivers). Everything runs in library STORVLT.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

STORVAULT/i services the full operating life of a self-storage book:

A.2 Layering & the ledger invariant

STORVAULT/i is a classic keyed-file RPG/COBOL/CL application — there is no SQL-PL logic layer; the business rules live in the programs themselves, over DDS physical and logical files, with one SQL table (the GL distribution) reached through embedded SQL from the SQLRPGLE GL post. Two design rules govern the whole app:

Each program is idempotent by a stable derived key: the rent charge, the late fee, and the applied-payment ledger rows are keyed on a value derived from (agreement/receipt + period), so re-running a cycle in the same period regenerates the same key and is refused rather than double-posting. The GL post uses the complementary watermark-since-last-post discipline.

A.3 Component & flow

  ONLINE (5250)              DAILY                 MONTHLY (SVMONTH chain)
  ------------              -----                 -----------------------
  SVMENU                   SVDAILY:              SVRENTRL  (rent-roll charge)
    1 -> SVAVAILQ            SVCASHAP              SVAGE     (delinquency aging)
    2 -> SVLEDGQ            (cash application)     SVLATEFEE (flat late fee)
                                                  SVLIENADV (lien/auction ladder)
                                                  SVGLPST   (GL distribution post)
                                                  SVDELRPT  (COBOL control report)
       \                        |                        /
        \                       v                       /
         +----------->  SVCHG  (tenant ledger: R / F / P rows, each debit an OPEN ITEM)
                          |  \
                          |   +--> SVRENT  (agreement: RBAL, RSTAT, RLNSTAT ladder rung)
                          |   +--> SVDELQ  (aging + ladder rung dates, one row/agreement)
                          |   +--> SVRCPT  (cash receipts, applied/part-applied)
                          +------> SVGLDIST (SQL GL distribution)   SVUNIT (status flips on lien/auction)

A monthly close flows: SVRENTRL bills the book (charge rows into SVCHG, balances up on SVRENT) → SVAGE stamps days-past-due on SVDELQ from the oldest open charge → SVLATEFEE fees the past-grace agreements → SVLIENADV advances the ladder and flips unit status → SVGLPST posts the balanced GL → SVDELRPT prints the receivables control totals. The daily cycle is just SVCASHAP.

A.4 Object inventory

ObjectTypeRole
SVFACPFFacility master (one row per site).
SVSZCLPFUnit size-class rate card (standard rate + climate premium).
SVUNITPFUnit master; USTAT drives availability & overlock.
SVUNTLFLFUnits keyed (FACCD, USTAT, SZCLCD) for availability.
SVTENPFTenant master.
SVRENTPFRental agreement (RBAL, RSTAT, RLNSTAT ladder rung).
SVCHGPFTenant ledger / open-item journal (R/F/P rows).
SVCHGLFLFLedger keyed (TENNO, CHGNO) for the ledger subfile.
SVCHGDLFLFLedger keyed (AGRNO, CDUEDT, CHGNO) — oldest-due-first.
SVRCPTPFCash receipts (applied / part-applied).
SVDELQPFDelinquency + lien-ladder detail (one row/agreement).
SVGLDISTSQL tableGL distribution (balanced DR/CR rows).
SVAVAILD / SVLEDGD / SVMENUDDSPFAvailability, tenant-ledger subfile, menu.
SVDELQPPRTFDelinquency / lien-ladder printer report.
SVREFLDRPGReference-data seeder (facilities/classes/units/tenants/agreements).
SVRENTRLRPGMonthly rent-roll charge.
SVCASHAPRPGDaily cash application.
SVAGERPGMonthly delinquency aging.
SVLATEFEERPGMonthly flat late-fee assessment.
SVLIENADVRPGMonthly lien/overlock/auction ladder.
SVGLPSTSQLRPGLEMonthly GL distribution post.
SVAVAILQ / SVLEDGQ / SVMENURPGAvailability inquiry / ledger inquiry / menu driver.
SVDELRPTCOBOLReceivables / open-item control report.
SVSETUP / SVDAILY / SVMONTHCLBuild; the daily and monthly job cycles.

11 physical/logical files + 1 SQL table, 3 display files + 1 printer file, driven by 10 RPG programs, 1 SQLRPGLE, 1 COBOL program and 3 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

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

To do thisType on the command line
Open the operator main menuCALL STORVLT/SVMENU
Go straight to the unit availability inquiryCALL STORVLT/SVAVAILQ
Go straight to the tenant ledger inquiryCALL STORVLT/SVLEDGQ
Run the daily cash-application cycleCALL STORVLT/SVDAILY (or SBMJOB it)
Run the full monthly cycleCALL STORVLT/SVMONTH
Build/rebuild every object & programCALL STORVLT/SVSETUP

Only SVMENU, SVAVAILQ and SVLEDGQ are interactive (WORKSTN); the batch cycles run to completion and DSPLY a one-line result per step. The batch programs take no CALL parameters — their run date, due date and period are baked-in D-spec constants (see the note in C), so a scheduled submission is a bare CALL.

B.2 The menu & inquiry screens

SVMENU / SVMENUD — main menu

SVMENU is a simple EXFMT loop that reads a one-character option and CALLs the chosen inquiry program, returning to the menu when the inquiry exits. Option 1SVAVAILQ; option 2SVLEDGQ; any other non-blank option redisplays Invalid option.; F3 exits.

STORVAULT/i Main Menu 1. Unit Availability Inquiry 2. Tenant Ledger Inquiry Option . . . . : _ F3=Exit Enter=Select

SVAVAILQ / SVAVAILD — unit availability inquiry (plain screen)

A non-subfile inquiry keyed by facility + size class. The operator keys a facility code and a size class; SVAVAILQ chains SVSZCL for the description, standard rate and climate premium, then walks every unit at that facility (READE over SVUNIT by FACCD) tallying vacant / occupied / locked counts for that class. A size class not on the rate card shows Size class not found.

Unit Availability - STORVAULT/i Facility code: FAC1 Size class . : 10X10 Description : MEDIUM UNIT 10X10 Standard rate: 95.00 Climate prem.: 15.00 Vacant units .: 1 Occupied . . .: 1 Locked/lien . : 0 Size class found. F3=Exit Enter=Inquire

SVLEDGQ / SVLEDGD — tenant ledger inquiry (subfile)

The app's subfile screen (DDS record LSFL under control record LCTL, SFLPAG(5) per page, SFLSIZ(30), ROLLUP/ROLLDOWN paging, SFLEND(*MORE)). The operator keys a tenant number; the program clears the subfile, loads the header (name, agreement, live balance, lien status) and every ledger row for that tenant (via SVCHGLF, keyed by TENNO, CHGNO), then displays. A tenant number that does not exist shows Tenant not found. and correctly clears the subfile.

Tenant Ledger - STORVAULT/i Tenant number: T00002 Name . . . . . : BRACKEN MOTORS Agreement . . : AG000002 Balance . . . : 90.00 Lien status . : L1 Chg# Date T Amount Balance Memo R000209 20260901 R 35.00 35.00 MONTHLY RENT CHARGE F000209 20260930 F 20.00 90.00 LATE FEE ASSESSED Tenant found. F3=Exit Roll=Page Enter=Inquire
The subfile-clear on a re-display is real: SVLEDGD's conditioning-indicator lines (N31 SFLDSP, N31 SFLDSPCTL, 31 SFLCLR, 90 SFLEND) are written with the exact column alignment the DDS parser requires, so a "tenant not found" re-display clears the previous tenant's rows (0 rows) rather than leaving them on screen. See F.4 for the platform finding behind this.

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

Honest statement: STORVAULT/i does not model a four-eyes maker–checker / separate-authorization workflow, and the two online screens are inquiry-only — they read and display; they post nothing. All money movement happens in the batch cycles. There is no "one user posts, a second user approves" step in the code. The manual documents the control model the application does have:

In sum, the control posture is open-item auditability + a strict balance invariant + derived-key idempotency + a monotonic ladder, all enforced in the programs, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

STORVAULT/i runs as two cycles: a daily cycle (cash application) and a monthly cycle (rent-roll → aging → late fees → lien ladder → GL post → control report). Both are CL wrappers — SVDAILY calls the one daily program; SVMONTH chains the six monthly programs in order. Each RPG driver DSPLYs a one-line result.

How the run date is supplied. Unlike some sibling apps, STORVAULT/i's batch programs do not read a control-row date; there is no ASOFDATE table. Each program's run date, charge due date, and billing period are baked-in D-spec constants (SVRENTRL: WRUNDT 20260901, WDUE 20260915, WPERIOD '09'; the monthly aging/fee/ladder programs: WRUNDT 20260930; late fee WDUE 20261005). To process a different period, those constants are changed and the programs recompiled — the tested book is the September 2026 close. A scheduled submission is therefore a bare SBMJOB CMD(CALL PGM(STORVLT/SVMONTH)).

C.1 Full batch program set

ProgramPurposeReads / writesDSPLY resultCycle
SVCASHAP Apply each unapplied receipt against its agreement's open charges, oldest-due-first. Reads SVRCPT/SVCHG; updates SVCHG.COPEN/CSTAT, SVRENT.RBAL, SVRCPT.RCSTAT/RCAPPL; writes a P ledger row. SVCASHAP APPLIED=n SKIP=n Daily
SVRENTRL Post one rent charge at the locked rate for every active agreement; raise its balance. Reads/updates SVRENT.RBAL/RLASTCH; writes an R ledger row per agreement. SVRENTRL BILLED=n SKIP=n Monthly
SVAGE Age each active agreement on its oldest open charge; upsert its SVDELQ row. Reads SVRENT/SVCHG; writes/updates SVDELQ.DDAYS. SVAGE CURRENT=n LATE=n Monthly
SVLATEFEE Assess one flat 20.00 fee per agreement past the 10-day grace, once per period. Reads SVDELQ; updates SVRENT.RBAL; writes an F ledger row. SVLATEFEE CHARGED=n SKIP=n Monthly
SVLIENADV Advance each agreement's lien/overlock/auction rung by days past due; flip unit status. Reads/updates SVDELQ; updates SVRENT.RLNSTAT/RSTAT/RBAL, SVUNIT.USTAT. SVLIENADV ADVANCED=n AUCTIONED=n SAME=n Monthly
SVGLPST Post balanced DR/CR GL rows for the ledger movement since the last close. Reads SVCHG; EXEC SQL insert into SVGLDIST (watermark). SVGLPST BATCH=nnnnnn ROWS=n Monthly
SVDELRPT COBOL receivables control report: re-sum the ledger open items to reconcile. Reads SVCHG sequentially; prints totals. SVDELRPT BILLED / OPEN / CHARGES / STILLOPEN Monthly

C.2 Daily / monthly detail

Daily — SVDAILY (→ SVCASHAP)

For each receipt still unapplied (RCSTAT='U'), SVCASHAP walks the agreement's open debit rows oldest-due-first, settling one charge at a time (COPEN down, CSTATP at zero) until the receipt is exhausted or the account is settled; it lowers RBAL by exactly the amount applied (floored at zero), writes one P payment ledger row, and stamps the receipt A fully-applied or P part-applied. An overpayment applies only what is owed; the remainder stays unapplied on the receipt.

Expected DSPLY (RC000001 pays AG000001 in full 95.00; RC000002 pays 40.00 of AG000003's 95.00):
  SVCASHAP APPLIED=2 SKIP=0
  -> AG000001 RBAL 95.00 -> 0.00, its R charge COPEN 0 / CSTAT P
  -> AG000003 RBAL 95.00 -> 55.00, its R charge COPEN 55.00 / CSTAT O (part paid)

Monthly — SVMONTH (six-step chain)

  1. SVRENTRL bills every active agreement one rent charge at the locked RRATE, due 20260915, and raises RBAL by that amount. Charge number is derived 'R'+last-5-of-agreement+period, so a same-period re-run is refused.
  2. SVAGE finds each active agreement's oldest open charge and computes days-past-due at the run date on the 30/360 convention (F.3), stamping SVDELQ.DDAYS; a fully-paid agreement ages back to 0. Creates the SVDELQ row the first time an agreement is seen.
  3. SVLATEFEE charges a flat 20.00 fee to any agreement whose DDAYS exceeds the 10-day grace, once per period (derived key 'F'+last-5+period), raising RBAL.
  4. SVLIENADV advances the ladder by DDAYS: 0-29 current, 30-44 L1 pre-lien, 45-59 L2 lien filed + overlock, 60-89 L4 auction pending, 90+ S sold. It stamps the rung dates once, flips SVUNIT.USTAT to L at L2+ and back to V on auction, and at sale terminates the agreement (RSTAT='S', balance written off to 0.00).
  5. SVGLPST sums the ledger by type, subtracts what is already posted, and inserts balanced DR/CR rows (DR AR / CR rent / CR fees for billings; DR cash / CR AR for collections).
  6. SVDELRPT (COBOL) reads the whole ledger and prints billed, open, charge-count and still-open-count totals to reconcile the receivables sub-ledger.
Expected DSPLY (seeded Sept-2026 book, per sv_cycle.mjs oracle):
  SVRENTRL BILLED=4 SKIP=0      4 active agreements charged
  SVAGE CURRENT=1 LATE=3        AG000001 current; AG000002/003/004 past due
  SVLATEFEE CHARGED=3 SKIP=0    3 fees (settled AG000001 earns none)
  SVLIENADV ADVANCED=2 AUCTIONED=1 SAME=2
  SVGLPST BATCH=202609 ROWS=5   DR AR / CR rent / CR fees / DR cash / CR AR

C.3 Ordering & dependencies

D. Data Files (Data Dictionary) ↑ top

Eleven DDS physical/logical files plus one SQL table hold the whole book. Field names, lengths and types below are taken directly from the DDS in storage-app/src/sources.mjs. Type notation: nA = character, nP d = packed decimal with d fractional digits, nS d = zoned decimal. A trailing K row marks a key field. All monetary amounts are stored positive; a ledger row's sign is implied by its transaction type.

D.1 Master & inventory files

SVFAC — facility master (key FACCD)

FieldTypeMeaning
FACCD4A KFacility code (e.g. FAC1, FAC2).
FACNM30AFacility name.
FACCITY20ACity.
FACST2AState/region code.
FACSTAT1AFacility status (A active).

SVSZCL — unit size-class rate card (key SZCLCD)

FieldTypeMeaning
SZCLCD5A KSize-class code (05X05, 10X10, 10X20).
SZDESC20ADescription (e.g. MEDIUM UNIT 10X10).
SZRATE9P 2Standard monthly rate a new rental picks up at signing.
SZCLPRM9P 2Climate-control premium for the class.
The rate card is the standard price at signing only. Each agreement then carries its own locked rate (SVRENT.RRATE), so editing this card never reprices a sitting tenant.

SVUNIT — unit master (composite key FACCD + UNITNO)

FieldTypeMeaning
FACCD4A KFacility the unit belongs to.
UNITNO7A KUnit number (e.g. U000001). 7 characters — see F.4 for the width bug corrected during build.
SZCLCD5ASize class of the unit.
UCLIMATE1AY/N — climate-controlled (earns the premium).
USTAT1AStatus: V vacant, O occupied, L locked (overlock/lien), D disabled.

SVUNTLF — unit availability access path (LF over SVUNIT)

Same SVUNITR record format, keyed (FACCD, USTAT, SZCLCD) so the availability inquiry can find units of a class at a facility by status without a scan.

SVTEN — tenant master (key TENNO)

FieldTypeMeaning
TENNO6A KTenant number (T00001 …).
TNAME30ATenant name.
TADDR30AAddress.
TPHONE12APhone.
TSTAT1AA active, X closed-out (auctioned/vacated).

D.2 Agreement, ledger & receipts

SVRENT — rental agreement (key AGRNO)

FieldTypeMeaning
AGRNO8A KAgreement number (AG000001 …). One row per unit rental.
FACCD4AFacility of the rented unit.
UNITNO7ARented unit.
TENNO6ATenant.
RSTRTDT8S 0Agreement start date (YYYYMMDD).
RRATE9P 2Monthly rate locked at signing — independent of later SVSZCL changes.
RBAL11P 2Live ledger balance. Invariant: rises only via a charge, falls only via cash application.
RSTAT1AA active, T terminated (vacated, current), S sold/auctioned out.
RLNSTAT2ACurrent lien-ladder rung: N, L1, L2, L4, S (mirrors SVDELQ.DLADDER).
RLASTCH8S 0Date of the last rent charge posted (stamped by SVRENTRL).

SVCHG — tenant ledger / open-item journal (key CHGNO)

One row per money event. A debit row (R/F) is also its own open item; a credit row (P) carries blank/zero open-item fields.

FieldTypeMeaning
CHGNO8A KLedger row key. Derived & stable for idempotency (see F.2): R/F + last-5-of-agreement + period, or P + last-5-of-receipt + 00.
AGRNO8AAgreement.
TENNO6ATenant.
CDT8S 0Transaction date.
CTYPE1AR rent charge, F late fee, P payment (credit).
CAMT11P 2Amount (always positive; sign implied by CTYPE).
CDUEDT8S 0Due date for a debit row; 0 on a payment row.
COPEN11P 2Unpaid remainder on this charge (debit rows only).
CSTAT1AO open / P fully paid (debit rows); blank on a payment row.
CBALAFT11P 2Agreement balance snapshot immediately after this row posted.
CMEMO30AFree-text memo (MONTHLY RENT CHARGE, LATE FEE ASSESSED, PAYMENT APPLIED).

SVCHGLF / SVCHGDLF — ledger access paths (LFs over SVCHG)

SVCHGLF keys the ledger (TENNO, CHGNO) for the tenant-ledger subfile; SVCHGDLF keys it (AGRNO, CDUEDT, CHGNO) for an oldest-due-first walk. Both are read/update-only alternate access paths — a WRITE through them is refused by the platform (see F.4), so all new ledger rows are written through the base SVCHG PF.

SVRCPT — cash receipts (key RCPTNO)

FieldTypeMeaning
RCPTNO8A KReceipt number (RC000001 …).
AGRNO8AAgreement the receipt pays.
TENNO6ATenant.
RCDT8S 0Receipt date.
RCAMT11P 2Amount received.
RCAPPL11P 2Amount actually applied to open charges (≤ RCAMT).
RCSTAT1AU unapplied, A fully applied, P part-applied.

SVDELQ — delinquency / lien-ladder detail (key AGRNO)

One row per agreement, updated in place (not an accumulating history — SVCHG already carries the auditable trail).

FieldTypeMeaning
AGRNO8A KAgreement.
TENNO6ATenant.
DDAYS5S 0Days past due on the oldest unpaid charge, as of the last aging run.
DLADDER2ACurrent ladder rung (mirrors SVRENT.RLNSTAT).
DL1DT–DL4DT8S 0Date each rung (L1/L2/L3/L4) was first reached — stamped once, for idempotency.
DFEETOT11P 2Late-fee total accumulator field.

SVGLDIST — GL distribution (SQL table, PK GLSEQ)

ColumnTypeMeaning
GLSEQDECIMAL(8,0) PKPost sequence — continues from the max already posted (the watermark).
GLBATCHDECIMAL(6,0)Close batch (202609 for the tested book).
ACCTCHAR(9)Account: 1000-CASH, 1200-AR, 4100-RENT, 4200-FEES.
DRCRCHAR(1)D debit / C credit.
AMTDECIMAL(11,2)Row amount.
GLREFCHAR(8)Reference (LEDGER).
GLDTDECIMAL(8,0)Post date (20260930).

Index SVGLDACC on (ACCT, DRCR) supports the watermark SUM queries SVGLPST runs to find what is already posted per account.

E. Operations Runbook ↑ top

This section is the operator's procedural reference: the daily and monthly sequences, the exact figures the tested September 2026 book produces (so a real run can be reconciled against a known-good baseline), and the failure / re-run rules. Every figure below is the hand-derived oracle from test/sv_cycle.mjs — the same numbers the automated cycle battle asserts against the live engine.

Prerequisite (once per environment). Before any cycle can run, the objects and programs must exist and the reference data must be loaded: CALL STORVLT/SVSETUP then CALL STORVLT/SVREFLD. SVSETUP is a full drop-and-recreate; it ends with the banner STORVAULT setup complete. and SVREFLD reports SVREFLD FAC=2 UNIT=8 TEN=4 AGR=4.

E.1 Day-in-the-life (the daily cycle)

The daily cycle is a single program wrapped by SVDAILY: apply the day's cash receipts.

  1. Ensure the day's receipts have been loaded into SVRCPT with RCSTAT='U' (unapplied). In the tested book two receipts are staged: RC000001 pays AG000001's full 95.00; RC000002 pays 40.00 of AG000003's 95.00.
  2. CALL STORVLT/SVDAILY (or SBMJOB CMD(CALL PGM(STORVLT/SVDAILY))).
  3. Read the one-line result. Expected on the tested book:
SVCASHAP APPLIED=2 SKIP=0
  AG000001  RBAL 95.00 -> 0.00   its R charge COPEN 0.00 / CSTAT P  (fully paid)
  AG000003  RBAL 95.00 -> 55.00  its R charge COPEN 55.00 / CSTAT O (part paid)
  a P (payment) ledger row is written for each receipt; receipt stamped A / P

Run the day's SVDAILY(s) before the monthly close, so receipts settled today reduce the open items that aging and late fees key off. A receipt for more than is owed applies only what is owed; the remainder stays unapplied (RCSTAT='P') and is never lost.

E.2 Month-end close (the monthly cycle)

SVMONTH chains the six monthly programs in the fixed, load-bearing order. Run it once per period: CALL STORVLT/SVMONTH. Each step DSPLYs one line. The expected results on the tested September 2026 book (with the daily cash above applied, and two backdated charges injected by the test to exercise the full ladder in one pass) are:

SVRENTRL BILLED=4 SKIP=0       4 active agreements charged at their locked rate
SVAGE    CURRENT=1 LATE=3       AG000001 current; AG000002/003/004 past due
SVLATEFEE CHARGED=3 SKIP=0      3 flat 20.00 fees (settled AG000001 earns none)
SVLIENADV ADVANCED=2 AUCTIONED=1 SAME=2
SVGLPST  BATCH=202609 ROWS=5    DR AR / CR rent / CR fees / DR cash / CR AR
SVDELRPT BILLED / OPEN / CHARGES / STILLOPEN  (COBOL control totals)

Reconciling figures — the tested September 2026 book

These are the exact end-state numbers sv_cycle.mjs asserts. Use them to reconcile a real run of the same seed data.

Agreement balances after the full close

AgreementRateRentCashLate feeFinal RBALLadder / status
AG00000195.00+95.00−95.000.00N / current
AG00000235.00+70.00*+20.000.00**S / sold, terminated
AG00000395.00+95.00−40.00+20.0075.00N (only 15 days past due)
AG000004190.00+380.00*+20.00400.00L1 / pre-lien

* AG000002 and AG000004 each carry a second backdated open charge the cycle test injects (95 days and 35 days past due respectively) to drive the ladder in one pass; their "rent" column reflects both charges. ** AG000002 reaches the top rung (95 days → sold at auction): the agreement terminates (RSTAT='S'), its balance is written off to 0.00, and its unit U000004 is repossessed back to V vacant. The mid-ladder L1 agreement's unit is not overlocked — overlock starts at L2/45 days.

Aging (SVDELQ.DDAYS) & the GL distribution

AgingDDAYSGL account (DRCR)Amount
AG0000010 (settled)1200-AR (D)700.00
AG000002954100-RENT (C)640.00
AG000003154200-FEES (C)60.00
AG000004351000-CASH (D)135.00
1200-AR (C)135.00

The GL is balanced: debits 700.00 + 135.00 = 835.00 equal credits 640.00 + 60.00 + 135.00 = 835.00. Rent revenue 640.00 = the four regular charges (95+35+95+190 = 415.00) plus the two backdated charges (35 + 190 = 225.00); fees 60.00 = three flat 20.00 fees; cash 135.00 = the two receipts applied (95.00 + 40.00). The AR debit 700.00 = rent + fees billed this close (640.00 + 60.00).

E.3 Failure & re-run rules

F. Developer Reference ↑ top

STORVAULT/i is a keyed-file RPG/COBOL/CL application over DDS files with one SQL table. The source is held as string constants in storage-app/src/sources.mjs and loaded into library STORVLT's source physical files by seedStorvlt() in src/seed.mjs. This section is for the developer maintaining or extending it.

F.1 Programs

ProgramTypeFiles openedWhat it does
SVREFLDRPGLESVFAC/SVSZCL/SVUNIT/SVTEN/SVRENT (O)Seeds reference data: 2 facilities, 3 size classes, 8 units, 4 tenants, 4 agreements.
SVRENTRLRPGLESVRENT (UF), SVCHG (UF A)Rent-roll: one R charge per active agreement at the locked rate; raises RBAL.
SVCASHAPRPGLESVRCPT (UF), SVCHG (UF A), SVRENT (UF)Cash application oldest-due-first; writes a P row; lowers RBAL.
SVAGERPGLESVRENT (IF), SVCHGDLF (IF), SVDELQ (UF A)Ages each agreement on its oldest open charge; upserts SVDELQ.
SVLATEFEERPGLESVDELQ (IF), SVRENT (UF), SVCHG (UF A)One flat 20.00 fee per agreement past the 10-day grace, once per period.
SVLIENADVRPGLESVDELQ (UF), SVRENT (UF), SVUNIT (UF)Advances the monotonic ladder by DDAYS; flips unit status; terminates on sale.
SVGLPSTSQLRPGLESVCHG (IF); EXEC SQL on SVGLDISTWatermark GL post: sums the ledger by type, subtracts what is posted, inserts balanced DR/CR rows.
SVAVAILQRPGLESVAVAILD (WORKSTN), SVSZCL (IF), SVUNIT (IF)Availability inquiry: rate/premium + vacant/occupied/locked counts for a class.
SVLEDGQRPGLESVLEDGD (WORKSTN SFILE), SVTEN/SVRENT (IF), SVCHGLF (IF)Tenant ledger subfile inquiry.
SVMENURPGLESVMENUD (WORKSTN)Menu driver: option 1→SVAVAILQ, 2→SVLEDGQ.
SVDELRPTCBLLESVCHG (sequential, indexed)COBOL receivables control report: re-sums ledger open items to reconcile.

F.2 Key idioms & derived keys

F.3 The 30/360 aging convention

SVAGE has no date-arithmetic builtin available, so it computes days-past-due on a 30/360 serial: a date YYYYMMDD becomes ((YYYY×12) + MM)×30 + DD, and days past due is the run-date serial minus the oldest-open-charge due-date serial (floored at zero). This is the same convention the cycle test's days360/fromDays360 helpers use to derive its oracle, so oracle and engine never disagree on the arithmetic. Worked example from the tested book: run date 20260930 → serial ((2026×12)+9)×30 + 30 = 729660; a charge due 20260915729645; difference 15 days — matching AG000003's DDAYS=15.

F.4 Platform notes

The application was built against the SteelFrame X emulator engine (ibmi/**) without modifying the engine; the findings are recorded in storage-app/FINDINGS.md. Three items are worth a maintainer's attention.

PG-STORVLT-001 — DDS conditioning-indicator parser is column-position-sensitive (platform finding)

Honest platform finding, confirmed with a minimal repro. The DDS parser (ibmi/dds.js, function lineConds) reads a conditioning indicator from a fixed 3-character window at source columns 8–10 / 11–13 / 14–16, matched against /^[N ]\s?\d\d?$/. Because that regex's \d\d? also accepts a single digit, a token shifted even one column is not rejected — it is silently split. Writing a line with two spaces after the A :

     A  N31                                 SFLDSP     <- MISALIGNED (two spaces)

puts N31 at columns 9–11, so the first window reads ' N3' (indicator 3) and the trailing 1 falls into the next window as a second, spurious indicator 1. The keyword ends up conditioned on {3, 1} instead of {31} — with no compile error, no runtime error, and a fully-compiling DDS object. The conditioned keyword simply never fires as written. Correct alignment (one space) lands the token at columns 8–10 and parses to the intended single indicator {31}:

     A N31                                  SFLDSP     <- CORRECT (one space)
     A N31                                  SFLDSPCTL
     A  31                                  SFLCLR
     A  90                                  SFLEND(*MORE)

The concrete symptom in this app: on the tenant ledger subfile, SFLCLR is conditioned on indicator 31 to clear the subfile on a re-display. With the misaligned form it silently arms against indicator 3 (never set for that purpose), so a "tenant not found" re-display leaves the previous tenant's rows on screen. S.SVLEDGD is written with the correct one-space alignment from the start, so the subfile correctly clears to 0 rows — the behavior test/sv_interactive.mjs verifies. Note (per FINDINGS.md) the sibling order-app's OEORDD carries the identical two-space misalignment and its author attributed the symptom to an inherent "2-digit indicator" limitation — one level too high; the true root cause is this column-alignment sensitivity, since a correctly-aligned N31/31 does condition correctly.

Expected fix (engine, out of scope here): a misaligned/split conditioning token should be a hard parse error (unrecognized indicator field), never a silent partial match that yields a different but valid-looking indicator set. Until then, keep every conditioning indicator on one space after the A so the token lands at columns 8–10.

Two confirmed platform rules the app works with (not bugs)

Own-app bugs found and fixed during build (audit trail)

G. Glossary ↑ top

Agreement (rental agreement, SVRENT)
One row per unit rental, carrying the rate locked at signing (RRATE), the live balance (RBAL), the lifecycle status (RSTAT) and the current ladder rung (RLNSTAT).
Aging (30/360)
Computing days-past-due on the oldest open charge using a 30-day-month serial (((YYYY×12)+MM)×30+DD), because no date builtin is available. See F.3.
Balance invariant
The rule that SVRENT.RBAL rises only via a charge (rent-roll or late fee) and falls only via cash application — asserted by every charge/payment test.
Cash application (SVCASHAP)
Applying an unapplied receipt against an agreement's open charges oldest-due-first, one charge at a time; an overpayment applies only what is owed and leaves the remainder unapplied.
Climate premium
The extra monthly amount (SVSZCL.SZCLPRM) a climate-controlled unit (UCLIMATE='Y') earns over the standard size-class rate.
Derived key / idempotency
A ledger row's CHGNO is derived from (agreement/receipt + period), so re-running a cycle in the same period regenerates the same key and is refused rather than double-posting. See F.2.
Delinquency ladder (lien / overlock / auction)
The escalation an operator runs against a non-paying tenant by days past due: N current → L1 pre-lien (30d) → L2 lien filed + overlock (45d) → L4 auction pending (60d) → S sold (90d). Monotonic — it never retreats.
DSPLY result
The one-line summary each batch program writes to the joblog (e.g. SVCASHAP APPLIED=2 SKIP=0) — the operator's confirmation a step ran and the figure to reconcile.
GL distribution (SVGLDIST)
The SQL table of balanced debit/credit rows (AR / rent revenue / late-fee revenue / cash) the monthly close posts into; posted on a watermark so a re-run never double-posts.
Grace period
Days past due before a late fee is assessed. Here 10 days; the flat fee is 20.00.
Idempotent
Safe to run again with the same result. Every STORVAULT/i batch step is idempotent within a period (derived-key or watermark guarded); recovery from a partial failure is to re-run the whole SVMONTH chain.
Ledger / open item (SVCHG)
The tenant ledger: one row per money event (R rent charge, F late fee, P payment). Every debit row is also its own open item (COPEN/CSTAT), so cash and aging reason charge by charge. An audit trail, never summarised away.
Locked rate (RRATE)
The rate captured on the agreement at signing, independent of later size-class rate-card changes — so editing the card never reprices a sitting tenant.
Overlock
Physically locking a unit against a delinquent tenant (USTAT='L'), fired at the L2+ rung (45 days), never at the pre-lien L1 rung.
Rent-roll (SVRENTRL)
The monthly step that posts one rent charge at the locked rate for every active agreement and raises its balance.
SBMJOB
Submit Job — the IBM i command that queues a program as a batch job (e.g. SBMJOB CMD(CALL PGM(STORVLT/SVMONTH))). The batch programs take no parameters.
Subfile (SFL / SFLCTL)
A 5250 display construct listing many rows on one screen. SVLEDGD is the app's subfile (the tenant ledger inquiry), paged with ROLLUP/ROLLDOWN.
Watermark (GL post)
Posting only the ledger movement since the last close — continuing GLSEQ from the max already posted and subtracting the per-account amounts already posted — so a second month's run never re-posts month one's figures.
Write-off (auction)
At the top ladder rung (S, sold at auction) the agreement terminates (RSTAT='S'), its balance is written off to 0.00, and the unit is repossessed back to V vacant. Auction-proceeds settlement is a real-world manual process outside this system's scope.