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.
STORVAULT/i services the full operating life of a self-storage book:
V vacant / O occupied / L locked / D
disabled) drives the everyday "have we got a 10x10 free?" inquiry.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:
SVCHG holds
one row per money event — a rent charge (R), a late fee (F), or an
applied payment (P). Every debit row is also its own open item
(COPEN = what remains unpaid on that charge, CSTAT O/P), so cash can be
applied and aging reasoned oldest-due-first, charge by charge.SVRENT.RBAL is the live balance; it
rises only via a charge (rent-roll or late fee) and falls only via cash application.
Every charge/payment test in sv_cycle.mjs asserts this invariant against a hand-derived
oracle — e.g. a rent-roll raises a balance by exactly the locked rate, a payment lowers it by
exactly the amount applied.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.
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.
| Object | Type | Role |
|---|---|---|
| SVFAC | PF | Facility master (one row per site). |
| SVSZCL | PF | Unit size-class rate card (standard rate + climate premium). |
| SVUNIT | PF | Unit master; USTAT drives availability & overlock. |
| SVUNTLF | LF | Units keyed (FACCD, USTAT, SZCLCD) for availability. |
| SVTEN | PF | Tenant master. |
| SVRENT | PF | Rental agreement (RBAL, RSTAT, RLNSTAT ladder rung). |
| SVCHG | PF | Tenant ledger / open-item journal (R/F/P rows). |
| SVCHGLF | LF | Ledger keyed (TENNO, CHGNO) for the ledger subfile. |
| SVCHGDLF | LF | Ledger keyed (AGRNO, CDUEDT, CHGNO) — oldest-due-first. |
| SVRCPT | PF | Cash receipts (applied / part-applied). |
| SVDELQ | PF | Delinquency + lien-ladder detail (one row/agreement). |
| SVGLDIST | SQL table | GL distribution (balanced DR/CR rows). |
| SVAVAILD / SVLEDGD / SVMENUD | DSPF | Availability, tenant-ledger subfile, menu. |
| SVDELQP | PRTF | Delinquency / lien-ladder printer report. |
| SVREFLD | RPG | Reference-data seeder (facilities/classes/units/tenants/agreements). |
| SVRENTRL | RPG | Monthly rent-roll charge. |
| SVCASHAP | RPG | Daily cash application. |
| SVAGE | RPG | Monthly delinquency aging. |
| SVLATEFEE | RPG | Monthly flat late-fee assessment. |
| SVLIENADV | RPG | Monthly lien/overlock/auction ladder. |
| SVGLPST | SQLRPGLE | Monthly GL distribution post. |
| SVAVAILQ / SVLEDGQ / SVMENU | RPG | Availability inquiry / ledger inquiry / menu driver. |
| SVDELRPT | COBOL | Receivables / open-item control report. |
| SVSETUP / SVDAILY / SVMONTH | CL | Build; 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.
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 this | Type on the command line |
|---|---|
| Open the operator main menu | CALL STORVLT/SVMENU |
| Go straight to the unit availability inquiry | CALL STORVLT/SVAVAILQ |
| Go straight to the tenant ledger inquiry | CALL STORVLT/SVLEDGQ |
| Run the daily cash-application cycle | CALL STORVLT/SVDAILY (or SBMJOB it) |
| Run the full monthly cycle | CALL STORVLT/SVMONTH |
| Build/rebuild every object & program | CALL 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.
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 1 →
SVAVAILQ; option 2 → SVLEDGQ; any other non-blank option
redisplays Invalid option.; F3 exits.
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.
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.
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.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:
CBALAFT) and a
memo, so any agreement's history is fully reconstructable. The ledger is never summarised away.RBAL can only rise via a charge and only fall via cash
application; no program edits a balance any other way, and the cash-application step floors it at
zero rather than letting an over-application drive it negative.SVLIENADV never steps a ladder rung backward and stamps
each rung-reached date only once. A filed lien is a real-world legal act; a later part-payment that
lowers arrears does not silently retreat an already-reached rung.USTAT='L') fires only at
L2+ (45 days), never at the pre-lien L1 rung; an auctioned unit is
repossessed back to V vacant. The receivables control report (SVDELRPT)
independently re-sums the ledger open items so the sub-ledger totals can be reconciled.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.
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.
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)).| Program | Purpose | Reads / writes | DSPLY result | Cycle |
|---|---|---|---|---|
| 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 |
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,
CSTAT→P 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)
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.SVDELQ.DDAYS; a fully-paid agreement ages
back to 0. Creates the SVDELQ row the first time an agreement is seen.20.00 fee to any agreement whose DDAYS
exceeds the 10-day grace, once per period (derived key 'F'+last-5+period), raising
RBAL.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).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
SVAGE must run
after SVRENTRL (so this period's charge exists to age) and before SVLATEFEE
and SVLIENADV (both read SVDELQ.DDAYS that SVAGE stamps).
SVGLPST runs after all money movement so it captures the full period; SVDELRPT
runs last as a read-only control total. SVMONTH encodes exactly this order.SVDAILY(s), then SVMONTH.DDAYS; only SVAGE writes it.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.
| Field | Type | Meaning |
|---|---|---|
| FACCD | 4A K | Facility code (e.g. FAC1, FAC2). |
| FACNM | 30A | Facility name. |
| FACCITY | 20A | City. |
| FACST | 2A | State/region code. |
| FACSTAT | 1A | Facility status (A active). |
| Field | Type | Meaning |
|---|---|---|
| SZCLCD | 5A K | Size-class code (05X05, 10X10, 10X20). |
| SZDESC | 20A | Description (e.g. MEDIUM UNIT 10X10). |
| SZRATE | 9P 2 | Standard monthly rate a new rental picks up at signing. |
| SZCLPRM | 9P 2 | Climate-control premium for the class. |
SVRENT.RRATE), so editing this card never reprices a sitting
tenant.| Field | Type | Meaning |
|---|---|---|
| FACCD | 4A K | Facility the unit belongs to. |
| UNITNO | 7A K | Unit number (e.g. U000001). 7 characters — see F.4 for the width bug corrected during build. |
| SZCLCD | 5A | Size class of the unit. |
| UCLIMATE | 1A | Y/N — climate-controlled (earns the premium). |
| USTAT | 1A | Status: V vacant, O occupied, L locked (overlock/lien), D disabled. |
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.
| Field | Type | Meaning |
|---|---|---|
| TENNO | 6A K | Tenant number (T00001 …). |
| TNAME | 30A | Tenant name. |
| TADDR | 30A | Address. |
| TPHONE | 12A | Phone. |
| TSTAT | 1A | A active, X closed-out (auctioned/vacated). |
| Field | Type | Meaning |
|---|---|---|
| AGRNO | 8A K | Agreement number (AG000001 …). One row per unit rental. |
| FACCD | 4A | Facility of the rented unit. |
| UNITNO | 7A | Rented unit. |
| TENNO | 6A | Tenant. |
| RSTRTDT | 8S 0 | Agreement start date (YYYYMMDD). |
| RRATE | 9P 2 | Monthly rate locked at signing — independent of later SVSZCL changes. |
| RBAL | 11P 2 | Live ledger balance. Invariant: rises only via a charge, falls only via cash application. |
| RSTAT | 1A | A active, T terminated (vacated, current), S sold/auctioned out. |
| RLNSTAT | 2A | Current lien-ladder rung: N, L1, L2, L4, S (mirrors SVDELQ.DLADDER). |
| RLASTCH | 8S 0 | Date of the last rent charge posted (stamped by SVRENTRL). |
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.
| Field | Type | Meaning |
|---|---|---|
| CHGNO | 8A K | Ledger row key. Derived & stable for idempotency (see F.2): R/F + last-5-of-agreement + period, or P + last-5-of-receipt + 00. |
| AGRNO | 8A | Agreement. |
| TENNO | 6A | Tenant. |
| CDT | 8S 0 | Transaction date. |
| CTYPE | 1A | R rent charge, F late fee, P payment (credit). |
| CAMT | 11P 2 | Amount (always positive; sign implied by CTYPE). |
| CDUEDT | 8S 0 | Due date for a debit row; 0 on a payment row. |
| COPEN | 11P 2 | Unpaid remainder on this charge (debit rows only). |
| CSTAT | 1A | O open / P fully paid (debit rows); blank on a payment row. |
| CBALAFT | 11P 2 | Agreement balance snapshot immediately after this row posted. |
| CMEMO | 30A | Free-text memo (MONTHLY RENT CHARGE, LATE FEE ASSESSED, PAYMENT APPLIED). |
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.
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8A K | Receipt number (RC000001 …). |
| AGRNO | 8A | Agreement the receipt pays. |
| TENNO | 6A | Tenant. |
| RCDT | 8S 0 | Receipt date. |
| RCAMT | 11P 2 | Amount received. |
| RCAPPL | 11P 2 | Amount actually applied to open charges (≤ RCAMT). |
| RCSTAT | 1A | U unapplied, A fully applied, P part-applied. |
One row per agreement, updated in place (not an accumulating history — SVCHG already
carries the auditable trail).
| Field | Type | Meaning |
|---|---|---|
| AGRNO | 8A K | Agreement. |
| TENNO | 6A | Tenant. |
| DDAYS | 5S 0 | Days past due on the oldest unpaid charge, as of the last aging run. |
| DLADDER | 2A | Current ladder rung (mirrors SVRENT.RLNSTAT). |
| DL1DT–DL4DT | 8S 0 | Date each rung (L1/L2/L3/L4) was first reached — stamped once, for idempotency. |
| DFEETOT | 11P 2 | Late-fee total accumulator field. |
| Column | Type | Meaning |
|---|---|---|
| GLSEQ | DECIMAL(8,0) PK | Post sequence — continues from the max already posted (the watermark). |
| GLBATCH | DECIMAL(6,0) | Close batch (202609 for the tested book). |
| ACCT | CHAR(9) | Account: 1000-CASH, 1200-AR, 4100-RENT, 4200-FEES. |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(11,2) | Row amount. |
| GLREF | CHAR(8) | Reference (LEDGER). |
| GLDT | DECIMAL(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.
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.
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.The daily cycle is a single program wrapped by SVDAILY: apply the day's cash receipts.
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.CALL STORVLT/SVDAILY (or SBMJOB CMD(CALL PGM(STORVLT/SVDAILY))).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.
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)
These are the exact end-state numbers sv_cycle.mjs asserts. Use them to reconcile a real
run of the same seed data.
| Agreement | Rate | Rent | Cash | Late fee | Final RBAL | Ladder / status |
|---|---|---|---|---|---|---|
| AG000001 | 95.00 | +95.00 | −95.00 | — | 0.00 | N / current |
| AG000002 | 35.00 | +70.00* | — | +20.00 | 0.00** | S / sold, terminated |
| AG000003 | 95.00 | +95.00 | −40.00 | +20.00 | 75.00 | N (only 15 days past due) |
| AG000004 | 190.00 | +380.00* | — | +20.00 | 400.00 | L1 / 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 | DDAYS | GL account (DRCR) | Amount | |
|---|---|---|---|---|
| AG000001 | 0 (settled) | 1200-AR (D) | 700.00 | |
| AG000002 | 95 | 4100-RENT (C) | 640.00 | |
| AG000003 | 15 | 4200-FEES (C) | 60.00 | |
| AG000004 | 35 | 1000-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).
SVRENTRL reports its agreements as SKIPped and leaves balances
unchanged; a second SVCASHAP reports APPLIED=0; a second
SVGLPST posts nothing (watermark). The whole SVMONTH chain is re-runnable as
a unit — the cycle test asserts AG000003 stays at 75.00 after a second
full chain.SVMONTH from the top: completed
steps skip, the failed step and those after it complete. There is no separate "restart from step N"
control.SVAGE must run after SVRENTRL (so
this period's charge exists to age) and before SVLATEFEE/SVLIENADV (both read
the DDAYS it stamps). SVGLPST runs after all money movement;
SVDELRPT runs last, read-only. SVMONTH encodes exactly this order — do
not call the monthly programs individually out of sequence.D-spec run/due/period constants must be
changed and the programs recompiled (there is no control-row date). See the note in section C.MONMSG MSGID(SFF0000) so a
benign "already exists / not found" does not abort the job; genuine program failures still surface in
the joblog. Read the joblog, not just the banner, when reconciling.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.
| Program | Type | Files opened | What it does |
|---|---|---|---|
| SVREFLD | RPGLE | SVFAC/SVSZCL/SVUNIT/SVTEN/SVRENT (O) | Seeds reference data: 2 facilities, 3 size classes, 8 units, 4 tenants, 4 agreements. |
| SVRENTRL | RPGLE | SVRENT (UF), SVCHG (UF A) | Rent-roll: one R charge per active agreement at the locked rate; raises RBAL. |
| SVCASHAP | RPGLE | SVRCPT (UF), SVCHG (UF A), SVRENT (UF) | Cash application oldest-due-first; writes a P row; lowers RBAL. |
| SVAGE | RPGLE | SVRENT (IF), SVCHGDLF (IF), SVDELQ (UF A) | Ages each agreement on its oldest open charge; upserts SVDELQ. |
| SVLATEFEE | RPGLE | SVDELQ (IF), SVRENT (UF), SVCHG (UF A) | One flat 20.00 fee per agreement past the 10-day grace, once per period. |
| SVLIENADV | RPGLE | SVDELQ (UF), SVRENT (UF), SVUNIT (UF) | Advances the monotonic ladder by DDAYS; flips unit status; terminates on sale. |
| SVGLPST | SQLRPGLE | SVCHG (IF); EXEC SQL on SVGLDIST | Watermark GL post: sums the ledger by type, subtracts what is posted, inserts balanced DR/CR rows. |
| SVAVAILQ | RPGLE | SVAVAILD (WORKSTN), SVSZCL (IF), SVUNIT (IF) | Availability inquiry: rate/premium + vacant/occupied/locked counts for a class. |
| SVLEDGQ | RPGLE | SVLEDGD (WORKSTN SFILE), SVTEN/SVRENT (IF), SVCHGLF (IF) | Tenant ledger subfile inquiry. |
| SVMENU | RPGLE | SVMENUD (WORKSTN) | Menu driver: option 1→SVAVAILQ, 2→SVLEDGQ. |
| SVDELRPT | CBLLE | SVCHG (sequential, indexed) | COBOL receivables control report: re-sums ledger open items to reconcile. |
'R' + %subst(wagr:4:5) + wperiod (last 5 of the agreement + 2-digit period); the late fee
is the same with 'F'; the payment row is 'P' + %subst(wrcpt:4:5) + '00'. Each
program does a CHAIN(EN) on the derived key first and LEAVESR/skips if the row
already exists — the guard against double-billing, double-fee'ing or double-applying.SVGLPST reads
coalesce(max(GLSEQ),0) to continue the sequence, and per-account
coalesce(sum(AMT),0) of what is already posted, subtracting it before inserting — so
a second month posts only the new movement. A refused insert (sqlcod <> 0) is not
counted as a posted row.SVCASHAP scans SVCHG (keyed CHGNO) for an agreement's
oldest open CDUEDT; SVLEDGQ scans SVRENT (keyed
AGRNO) filtered by TENNO. The seed data is small; this is the honest, simplest
correct choice.SVLIENADV's RANK subroutine maps
N/L1/L2/L4/S to 0..4; a computed rung lower than the current one is clamped up
(wnew = wcur) so the ladder never retreats. Each rung-reached date
(DL1DT…DL4DT) is stamped only when still zero.RBAL is raised only in
SVRENTRL/SVLATEFEE (by the charge amount) and lowered only in
SVCASHAP (by the amount applied, floored at zero). No program edits it any other way.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 20260915 →
729645; difference 15 days — matching AG000003's
DDAYS=15.
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.
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.
A so the token lands at columns 8–10.WRITE through
SVCHGDLF is refused (ibmi/rpg.js SNF2112). New ledger rows go through the
base SVCHG PF; the oldest-open walk is a sequential scan of the base PF.SVCHGR) makes an unqualified CHAIN/READE
resolve to the first-declared file, silently. SVCASHAP was corrected to not
dual-open and to update each row in place immediately after the READE that found it.6A, but the seeded unit numbers are 7 characters
(U000001…), so every FAC1 unit after the first collided on a truncated key. Fixed by
widening UNITNO to 7A in the DDS and the matching WUNIT RPG
field.APPLIED while no ledger row or balance actually changed.SETLL/READE on
SVRENTR using a tenant number, but SVRENT is keyed by AGRNO;
fixed to a full sequential scan filtered by TENNO.RRATE), the live
balance (RBAL), the lifecycle status (RSTAT) and the current ladder rung
(RLNSTAT).((YYYY×12)+MM)×30+DD), because no date builtin is available. See F.3.SVRENT.RBAL rises only via a charge (rent-roll or late fee) and falls only
via cash application — asserted by every charge/payment test.SVSZCL.SZCLPRM) a climate-controlled unit (UCLIMATE='Y')
earns over the standard size-class rate.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.N current → L1 pre-lien (30d) → L2 lien filed + overlock
(45d) → L4 auction pending (60d) → S sold (90d). Monotonic — it
never retreats.SVCASHAP APPLIED=2 SKIP=0) — the operator's confirmation a step ran and the figure to
reconcile.20.00.SVMONTH chain.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.USTAT='L'), fired at the
L2+ rung (45 days), never at the pre-lien L1 rung.SBMJOB CMD(CALL PGM(STORVLT/SVMONTH))). The batch programs take no parameters.SVLEDGD is the app's subfile
(the tenant ledger inquiry), paged with ROLLUP/ROLLDOWN.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.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.