REALTY/i is a property-management application: a property/unit inventory, tenant and
lease master, a monthly rent-roll charge run, daily cash-receipt application against the tenant ledger,
a period AR ageing run with late-fee assessment, a lease-expiry sweep, an owner-distribution
calculation, and a balanced GL feed with an AR control report. It is built as a classic IBM i
application — RPG and ILE COBOL over keyed physical/logical files, DDS display and printer files,
an SQL GL distribution table, and CL job cycles — and the money-moving programs enforce a single
hard invariant: a lease's AR balance moves only by the charge run (up) and cash application
(down). This manual is the reference for the operator who runs the online screens and the periodic
cycles, and for the developer maintaining the application. It is grounded entirely in the committed
source (property-app/src/sources.mjs, src/seed.mjs, and the
test/rl_build.mjs / test/rl_cycle.mjs drivers). Everything runs in library
REALTY.
REALTY/i services the full month of a small property book — two properties (Riverside Court, Oakwood Plaza), seven units, five tenants, five active leases, two owners in the seeded book:
LSTAT drives the lease lifecycle (new → active → expired / terminated).LTERMS days) has been breached — 5% of contracted rent.LEND has
passed and vacate its unit; flag a unit whose lease is within 30 days of expiry as
notice-to-vacate.DISTGROSS = DISTFEE + DISTNET).The application is a conventional IBM i keyed-file design with one SQL edge:
RLSETUP from DDS source in REALTY/QDDSSRC. The tenant ledger
(RLLEDG) is the receivables sub-ledger; the lease master (RLLEASE) carries
the running AR balance LBAL.RLARRPT) is hand-written ILE COBOL over the ledger's keyed side.REALTY.RLGLDIST) created by RUNSQLSTM; the GL post
(RLGLPST) is SQLRPGLE that reads the RPG history file with native I/O and writes the
balanced GL rows with embedded EXEC SQL.RLDAILY, RLMONTH,
RLPERIOD — each ADDLIBLE REALTY then CALL the
programs of that cycle in order and send a completion banner.The design deliberately mirrors the sibling order-app (ORDERIS/i): the ledger/receipt/balance model
follows OEAROP/OECASH/OECUST, and the durable history file RLHIST mirrors OESHIS — one
accumulating audit trail every posting program contributes to, with disjoint numeric key ranges per
program derived from a stable business key, which is what makes every cycle idempotent (Section F.2).
ONLINE BATCH CYCLES (CL-driven)
------ ------------------------
RLMENU (5250 menu) RLDAILY -> RLCASHAP (cash application)
opt 1 -> RLUNTIQ RLMONTH -> RLCHRUN (rent + CAM charge run)
opt 2 -> RLLEDIQ RLPERIOD -> RLLATE (ageing + late fees)
-> RLRENEW (lease-expiry sweep)
(inquiry only) -> RLDISTRB (owner distribution)
-> RLGLPST (balanced GL post)
-> RLARRPT (AR control report)
\ /
\ /
v v
RLLEASE (LBAL: AR balance) <-- raised ONLY by RLCHRUN/RLLATE, lowered ONLY by RLCASHAP
| \
| +--> RLLEDG (one row per charge: rent 'R' / CAM 'C' / late fee 'L')
| +--> RLHIST (durable audit trail; every posting program appends)
+------> RLDIST (owner distribution) RLGLDIST (SQL GL distribution)
A single business event — say the monthly charge run — flows: RLMONTH calls
RLCHRUN → RLCHRUN reads each active lease, writes a rent and a CAM row to
RLLEDG, appends charge rows to RLHIST, and adds the total to
RLLEASE.LBAL. The seeded charge period is 2026-09-01 and the period-run date is
2026-09-25 (constants inside the programs; see Section C).
| Object | Type | Role |
|---|---|---|
| RLPROP | PF | Property master (building/complex). |
| RLUNIT | PF | Unit master (composite key PROPID+UNITNO). |
| RLUNTLF | LF | Units keyed by PROPID alone (rent-roll-by-property path). |
| RLTEN | PF | Tenant master. |
| RLLEASE | PF | Lease master (carries LBAL, the AR balance). |
| RLLSULF | LF | Leases keyed by PROPID+UNITNO (unit-inquiry path). |
| RLLEDG | PF | Tenant AR open-item ledger (the receivables sub-ledger). |
| RLLEDLF | LF | Ledger keyed by CHGDUE (due-date ageing path). |
| RLRCPT | PF | Cash receipts. |
| RLOWN | PF | Owner master (mgmt-fee & distribution %). |
| RLDIST | PF | Owner-distribution history. |
| RLHIST | PF | Durable audit trail (all posting programs append). |
| RLGLDIST | SQL table | GL distribution (balanced DR/CR). |
| RLUNTD / RLLEDD / RLMENUD | DSPF | Unit inquiry / ledger subfile / main menu. |
| RLAGEP | PRTF | AR ageing / owner report printer file. |
| RLREFLD | RPGLE | Seed reference data (properties/units/tenants/leases/owners). |
| RLCHRUN | RPGLE | Monthly rent-roll charge run. |
| RLCASHAP | RPGLE | Daily cash-receipt application. |
| RLLATE | RPGLE | Period AR ageing + late-fee assessment. |
| RLDISTRB | RPGLE | Period owner distribution. |
| RLGLPST | SQLRPGLE | Period GL post (balanced DR/CR). |
| RLRENEW | RPGLE | Period lease-expiry sweep. |
| RLUNTIQ / RLLEDIQ / RLMENU | RPGLE | Unit inquiry / ledger inquiry / menu drivers. |
| RLARRPT | CBLLE | AR control report (ILE COBOL). |
| RLSETUP | CLP | Create every object & compile every program. |
| RLDAILY / RLMONTH / RLPERIOD | CLP | The three job cycles. |
The full catalogue is 9 PFs + 3 LFs + 1 SQL table + 3 DSPF + 1 PRTF, driven by 10 RPG programs, 1 ILE
COBOL program and 4 CL programs, all seeded into library REALTY by
seedRealty(). Sections D and F expand each.
REALTY/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 REALTY — the tested jobs run with
LIBL = QSYS QGPL REALTY QTEMP and CURLIB = REALTY. The online surface is
inquiry-only (a main menu that dispatches to two inquiry programs); every money-moving action is a
batch program driven by a CL cycle.
| To do this | Type on the command line |
|---|---|
| Open the main menu (unit inquiry / ledger inquiry) | CALL REALTY/RLMENU |
| Open the unit / lease inquiry directly | CALL REALTY/RLUNTIQ |
| Open the tenant-ledger subfile inquiry directly | CALL REALTY/RLLEDIQ |
| Run the daily cash cycle | CALL REALTY/RLDAILY (or SBMJOB it) |
| Run the monthly rent-roll cycle | CALL REALTY/RLMONTH |
| Run the period-end cycle | CALL REALTY/RLPERIOD |
| (Re)build every object & program | CALL REALTY/RLSETUP |
| Seed the reference data | CALL REALTY/RLREFLD |
The batch drivers take no CALL parameters. Rather than a control-row date, each posting program
carries its processing date as a program constant in its source (the charge run posts for
WPERDT = 20260901; the period programs run for WRUNDT = 20260925). A production
reschedule for a new period is therefore a source-constant change and recompile, not a data update
— an honest reflection of how this teaching application is written.
RLMENU (DDS RLMENUD) is a plain menu: option 1 calls RLUNTIQ,
option 2 calls RLLEDIQ, any other non-blank option shows Invalid option.;
F3 exits.
A non-subfile screen. The operator keys a property and unit; the program CHAINs the unit master, then
walks the unit's leases via the RLLSULF (PROPID+UNITNO) access path to find the current
active lease, chains the tenant for the name, and displays the derived total monthly charge
(rent + CAM) and the lease's AR balance.
| Field | Type (DDS) | Shows |
|---|---|---|
| IPROP / IUNIT | 6A / 4A input | Property and unit keyed by the operator. |
| DDESC / DUSTAT | 20A / 1A output | Unit description and status (V/O/N). |
| DLEASE / DTEN | 8A / 30A output | Current active lease number and tenant name. |
| DRENT / DCAM / DTOT | 14A output | Contracted rent, CAM, and their sum (%editc 'K'). |
| DBAL | 14A output | Lease AR balance (LBAL). |
| DMSG | 50A output | Result line (unit not found / no current lease / found). |
The app's subfile screen (DDS RLLEDD: record LSFL under control record
LCTL, SFLPAG(5) / SFLSIZ(20), ROLLUP/ROLLDOWN
paging). The operator keys a lease number; the program chains the lease and tenant, then walks the
lease's ledger via RLLEDG's own primary key (which starts with LEASENO),
loading one subfile row per charge. The subfile is cleared and reloaded on every pass, and
SFLDSP is driven from *IN31 so the list displays only once at least one row has
loaded — the same subfile-refresh discipline as the sibling order-app's OEORDIQ.
| Field | Type (DDS) | Shows |
|---|---|---|
| ILEASE | 8A input | Lease number keyed by the operator. |
| DLTEN / DLBAL | 30A / 14A output | Tenant name and lease AR balance. |
| SSEQ / STYPE | 5S / 1A output | Charge sequence and type (R/C/L). |
| SDESC | 25A output | Charge description (MONTHLY RENT / CAM CHARGE / LATE FEE ASSESSED). |
| SAMT / SOPEN | 12A output | Charge amount as raised / outstanding open amount. |
| SAGE | 1A output | Ageing bucket stamped by RLLATE (0/1/2/3). |
Honest statement: REALTY/i does not model a four-eyes maker–checker / separate-authorization workflow, and the online screens post nothing at all. There is no "one user posts, a second approves" step in the code. The control model the application does have is enforced in the batch/data layer:
HTYPE='R'), a CAM charge ('C'), a cash application
('P', a negative amount), a late fee ('L'), and an owner distribution
('D'). Every key is unique. This is the after-the-fact accountability trail, and the
cycle test asserts it carries all five types with distinct keys.LBAL is moved only by the charge run and
late-fee run (up) and cash application (down), each by exactly the charge/fee/payment amount. The
tests assert this to the penny (Section F.2).CHAIN(EN)
existence check before posting — so a re-run of the same period regenerates the same key and
is refused. The GL post instead posts only the incremental movement since the last close.LSTAT='A' (active) lease is charged or fee'd;
only an RCSTAT='U' (unapplied) receipt is applied; only an OSTAT='A'
owner is distributed to; an owner who collected nothing is skipped entirely (no zero row).DISTGROSS = DISTFEE + DISTNET, and every GL post writes DR = CR exactly.In sum, the control posture is durable audit + a strict AR balance invariant + per-run idempotency + status gating + balanced double-entry, all in the batch and data layers, rather than an online segregation-of-duties approval workflow.
REALTY/i's servicing runs as three CL-driven cycles rather than one monolithic nightly job: a
daily cycle (cash application), a monthly cycle (the rent-roll charge run), and a
period cycle (ageing + late fees → lease-expiry sweep → owner distribution → GL post
→ AR control report). Each CL ADDLIBLE REALTY, CALLs its programs in a fixed
order, and sends a completion banner. All batch programs take no CALL parameters and carry their
processing date as a program constant.
-- the three cycles are bare parameterless submissions SBMJOB CMD(CALL PGM(REALTY/RLMONTH)) JOB(RLMONTH) rent-roll charge run SBMJOB CMD(CALL PGM(REALTY/RLDAILY)) JOB(RLDAILY) cash-receipt application SBMJOB CMD(CALL PGM(REALTY/RLPERIOD)) JOB(RLPERIOD) ageing/renewal/distribution/GL/AR
| Program | Cycle | Purpose | Reads / writes | DSPLY line |
|---|---|---|---|---|
| RLREFLD | setup | Seed reference data. | Writes RLPROP/RLUNIT/RLTEN/RLLEASE/RLOWN. | RLREFLD PROP=2 UNIT=7 TEN=5 LEASE=5 OWN=2 |
| RLCHRUN | RLMONTH | Rent-roll charge run: rent + CAM per active lease. | Reads RLLEASE; writes RLLEDG (R/C), RLHIST; raises RLLEASE.LBAL. | RLCHRUN CHARGED=n SKIP=n |
| RLCASHAP | RLDAILY | Apply each unapplied receipt to its named charge. | Reads RLRCPT; updates RLLEDG open/status, RLRCPT status; lowers RLLEASE.LBAL; writes RLHIST (P). | RLCASHAP APPLIED=n SKIP=n |
| RLLATE | RLPERIOD | Age every open charge; assess ≤1 late fee per lease past grace. | Updates RLLEDG.CHGAGE; writes late-fee RLLEDG (L) + RLHIST (L); raises RLLEASE.LBAL. | RLLATE B0=n B1=n B2=n B3=n FEES=n |
| RLRENEW | RLPERIOD | Expire past-LEND active leases; flag near-expiry units on notice. | Updates RLLEASE.LSTAT (→X), RLUNIT.USTAT (→V / N). | RLRENEW EXPIRED=n NOTICE=n SAME=n |
| RLDISTRB | RLPERIOD | Per owner: distribute cash collected, less mgmt fee. | Reads RLOWN, sums RLHIST 'P' rows for the owner's property; writes RLDIST, RLHIST (D). | RLDISTRB DIST=n SKIP=n |
| RLGLPST | RLPERIOD | Post balanced DR/CR into the SQL GL distribution. | Reads RLHIST; EXEC SQL INSERT into RLGLDIST. |
RLGLPST BATCH=202609 ROWS=n |
| RLARRPT | RLPERIOD | AR control report over the ledger (ILE COBOL). | Reads RLLEDG sequentially. | RLARRPT RAISED/OPEN/ITEMS/OVERDUE |
For every LSTAT='A' lease, RLCHRUN posts a rent charge (CHGTYPE='R',
MONTHLY RENT) and a CAM charge ('C', CAM CHARGE) to RLLEDG
for period 20260901 (due 20260901), each with its own ledger sequence and its own
RLHIST row, then adds LRENT + LCAM to the lease's LBAL. It finds the
next ledger sequence with a SETLL/READE partial-key walk of the lease's existing
charges. Idempotent: the history key is 10000000 + leaseDigits×10 + type (rent 0, CAM 1),
so a same-period re-run is refused by the CHAIN(EN) guard.
Expected DSPLY (5 active leases, first run): RLCHRUN CHARGED= 5 SKIP= 0 AR invariant after the run (LBAL raised by rent+CAM): L0000001 -> 895.00 L0000002 -> 1160.00 L0000003 -> 1160.00 L0000004 -> 2350.00 L0000005 -> 1710.00 (10 ledger rows total)
RLCASHAP reads every RCSTAT='U' receipt, CHAINs the ledger charge it names by
the composite key (LEASENO,RCCHGSQ), and applies only what is owed:
wtake = min(RCAMT, CHGOPEN). It reduces CHGOPEN (marking the charge
CHGSTAT='P' when fully applied), lowers the lease's LBAL by exactly
wtake, writes a negative RLHIST 'P' row, and stamps the receipt
'A' (fully) or 'P' (part). Idempotent: history key
20000000 + receiptDigits.
Seeded receipts (RCDT 20260910): RC000001 pays L1 rent 850.00 full, RC000002 pays L1 CAM 45.00 full, RC000003 pays 500.00 of L2 rent (1100 open). RLCASHAP APPLIED= 3 SKIP= 0 L0000001 -> 0.00 (895.00 fully settled) L0000002 -> 660.00 (1160.00 - 500.00) L0000002 rent charge open falls 1100.00 -> 600.00; L1 rent+CAM open 0, status P
Run date 20260925 throughout. Days overdue use a 30/360 date serial:
((yyyy×12 + mm)×30 + dd), so charges due 20260901 are 24 days overdue.
CHGAGE. Pass 2 walks each active lease's own open
rent/CAM charges (a late fee never triggers another) and, if any is past that lease's
LTERMS-day grace, assesses one late fee of LRENT × 5% — a new
'L' ledger row plus an RLHIST 'L' row, raising LBAL. Fully
settled charges (open 0) are skipped by both passes. Idempotent: history key
30000000 + leaseDigits.LEND has passed the run date, vacate the
unit (USTAT='V') and expire the lease (LSTAT='X'); if within 30 days of
expiry, flag an occupied unit as notice-to-vacate (USTAT='N') without touching the
lease. The lease's LSTAT='X' is set only after the unit re-CHAIN, so the
in-memory status is not overwritten by a disk reload.RLHIST 'P' rows, negated, for leases on that owner's property), deduct the property's
OMGMTPCT management fee, and write a balanced RLDIST row. An owner who
collected nothing is skipped entirely. Idempotent: history key 40000000 + ownerDigits.1400-TAR (receivable) = rent + CAM + late fees, and CR 4100-RENT /
4200-CAM / 4300-LATE revenue lines, so DR = CR. Posts only the movement
since the last close (it subtracts what is already debited to 1400-TAR), so a second run
adds no rows.CHGAMT), OPEN (sum CHGOPEN), ITEMS (row count), and OVERDUE (rows with
CHGAGE > 0).Expected period DSPLY (after the monthly + daily runs above): RLLATE B0= 0 B1= 8 B2= 0 B3= 0 FEES= 4 8 open charges aged, 4 late fees (L2..L5) RLRENEW EXPIRED= 1 NOTICE= 0 SAME= ... L0000002 (LEND 20260831) expires; unit 0102 -> V RLDISTRB DIST= 1 SKIP= 1 Riverside distributes; Oakwood collected nothing RLGLPST BATCH=202609 ROWS= 4 DR receivable + 3 CR revenue lines RLARRPT ITEMS 00014 10 charges + 4 late fees
RLLATE (age + fee) →
RLRENEW (expiry) → RLDISTRB (distribution) → RLGLPST
(GL) → RLARRPT (report). Late fees must exist before the GL post totals the
late-fee revenue and before the AR report counts them; distribution reads the collected-cash
history that daily cash application produced.All files are in library REALTY, grounded in the DDS members in sources.mjs.
Dates are stored as signed integers in YYYYMMDD form (8S 0); money is packed
decimal 11P 2; percentages (fees) are packed 5P 2. Key fields are noted per file.
| Field | Type | Meaning |
|---|---|---|
| PROPID | 6A | Property id (key), e.g. P00001. |
| PNAME / PADDR / PCITY | 30A/30A/20A | Name, address, city. |
| PUNITS | 5S 0 | Unit count (informational). |
| PSTAT | 1A | Property status (A active). |
| Field | Type | Meaning |
|---|---|---|
| PROPID / UNITNO | 6A / 4A | Owning property + unit number (composite key). |
| UDESC | 20A | Unit description (e.g. ONE BED FLAT, RETAIL UNIT A). |
| USQFT | 6S 0 | Square footage. |
| URENT / UCAM | 11P 2 | STANDING (list) monthly rent / CAM — the lease may carry different contracted figures. |
| USTAT | 1A | V vacant, O occupied, N notice-to-vacate. |
| Field | Type | Meaning |
|---|---|---|
| TENNO | 6A | Tenant number (key), e.g. T00001. |
| TNAME / TPHONE | 30A / 14A | Tenant name and phone. |
| TSTAT | 1A | Tenant status (A active). |
| Field | Type | Meaning |
|---|---|---|
| LEASENO | 8A | Lease number (key), e.g. L0000001. |
| PROPID / UNITNO / TENNO | 6A/4A/6A | The property, unit and tenant this lease is for. |
| LSTART / LEND | 8S 0 | Lease start / end dates (YYYYMMDD). |
| LRENT / LCAM | 11P 2 | CONTRACTED monthly rent / CAM this lease actually posts. |
| LDEP | 11P 2 | Security deposit held at move-in (held, not AR). |
| LTERMS | 3S 0 | Grace-period days before a late fee applies. |
| LBAL | 11P 2 | Running AR balance — moved ONLY by charge/late-fee (up) and cash (down). |
| LSTAT | 1A | N new, A active, X expired, T terminated. |
| Field | Type | Meaning |
|---|---|---|
| LEASENO / CHGSEQ | 8A / 5S 0 | Lease + per-lease charge sequence (composite key). |
| CHGTYPE | 1A | R rent, C CAM, L late fee. |
| CHGDT / CHGDUE | 8S 0 | Charge date / due date (YYYYMMDD). |
| CHGAMT | 11P 2 | Charge as raised (never changed after posting). |
| CHGOPEN | 11P 2 | Outstanding amount (CHGAMT less cash applied). |
| CHGAGE | 1S 0 | Ageing bucket stamped by RLLATE: 0/1/2/3. |
| CHGDESC | 25A | Description (MONTHLY RENT / CAM CHARGE / LATE FEE ASSESSED). |
| CHGSTAT | 1A | O open, P part/fully-applied (set when CHGOPEN reaches 0). |
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8A | Receipt number (key), e.g. RC000001. |
| LEASENO / RCCHGSQ | 8A / 5S 0 | The lease + ledger charge sequence this receipt names. |
| RCDT | 8S 0 | Receipt date (YYYYMMDD). |
| RCAMT | 11P 2 | Amount received. |
| RCAPPL | 11P 2 | Amount actually applied to the open charge. |
| RCSTAT | 1A | U unapplied, A fully applied, P part-applied. |
| Field | Type | Meaning |
|---|---|---|
| OWNID | 6A | Owner id (key), e.g. O00001. |
| ONAME | 30A | Owner name. |
| PROPID | 6A | Property this owner owns. |
| ODISTPCT | 5P 2 | Owner's share of NET collected rent (%). |
| OMGMTPCT | 5P 2 | Management-fee % deducted before the owner's share. |
| OSTAT | 1A | Owner status (A active). |
| Field | Type | Meaning |
|---|---|---|
| OWNID / DISTSEQ | 6A / 5S 0 | Owner + per-owner distribution sequence (composite key). |
| PROPID / DISTDT | 6A / 8S 0 | Property and distribution run date. |
| DISTGROSS | 11P 2 | Cash actually collected for the property this period. |
| DISTFEE | 11P 2 | Management fee withheld. |
| DISTNET | 11P 2 | Distributed (DISTGROSS − DISTFEE) — the balanced figure. |
| Field | Type | Meaning |
|---|---|---|
| HSEQ | 8S 0 | History key (unique). Disjoint ranges: charge 10M, cash 20M, late fee 30M, distribution 40M — derived from a stable business key. |
| LEASENO / HITEM | 8A / 8A | Lease affected / item (lease or owner) reference. |
| HTYPE | 1A | R rent, C CAM, P payment, L late fee, D distribution. |
| HDT / HAMT | 8S 0 / 11P 2 | Date and amount (payment 'P' rows carry a negative amount). |
| HREF / HMEMO | 8A / 25A | Reference (receipt/property) and free-text memo. |
| Field | Type | Meaning |
|---|---|---|
| GLSEQ | DECIMAL(8,0) | GL sequence (PK). |
| GLBATCH | DECIMAL(6,0) | Post batch (e.g. 202609). |
| ACCT / DRCR | CHAR(9) / CHAR(1) | Account (1400-TAR, 4100-RENT, 4200-CAM, 4300-LATE) and D/C. |
| AMT | DECIMAL(11,2) | Posted amount. |
| GLREF / GLDT | CHAR(8) / DECIMAL(8,0) | Reference and post date. Index RLGLDACC on (ACCT, DRCR). |
| LF | Over | Keyed by | Why it exists |
|---|---|---|---|
| RLUNTLF | RLUNIT | PROPID | Read a whole property's units together (rent-roll by property) with a partial key. |
| RLLSULF | RLLEASE | PROPID, UNITNO | Find a unit's current/most-recent lease without scanning the file (unit inquiry). |
| RLLEDLF | RLLEDG | CHGDUE, LEASENO | Walk the ledger oldest-due-first for the ageing report without sorting. |
One-time (or on a rebuild): compile and stand up the application, then seed the reference book.
REALTY (LIBL = QSYS QGPL REALTY QTEMP, CURLIB = REALTY).CALL REALTY/RLSETUP — expect the REALTY setup complete. banner.CALL REALTY/RLREFLD — expect RLREFLD PROP=2 UNIT=7 TEN=5 LEASE=5 OWN=2.Then, for a normal period:
SBMJOB CMD(CALL PGM(REALTY/RLMONTH)). Check the
RLCHRUN CHARGED=n SKIP=n line and confirm each active lease's LBAL rose by
exactly its rent + CAM.RLRCPT as RCSTAT='U', then
SBMJOB CMD(CALL PGM(REALTY/RLDAILY)). Check RLCASHAP APPLIED=n SKIP=n and
confirm each paid lease's LBAL fell by exactly the applied amount.CALL REALTY/RLMENU → option 1 (unit/lease) or 2 (ledger)
to look up a unit's lease or a lease's open items.Post-checks after cash application:
APPLIED= equals the number of U receipts; SKIP= counts already-applied receipts and any whose named charge was not found.CHGOPEN=0, CHGSTAT='P'; its receipt is RCSTAT='A'.RCSTAT='P'.RLHIST 'P' row.SBMJOB CMD(CALL PGM(REALTY/RLPERIOD)). It runs, in order,
RLLATE → RLRENEW → RLDISTRB → RLGLPST
→ RLARRPT, then sends the period-end banner.SELECT ACCT, DRCR, SUM(AMT) FROM REALTY.RLGLDIST GROUP BY ACCT, DRCR;
-- proves DR total = CR total
SELECT SUM(CASE WHEN DRCR='D' THEN AMT END) AS DR,
SUM(CASE WHEN DRCR='C' THEN AMT END) AS CR FROM REALTY.RLGLDIST;
Reconciling figures (the same ones the cycle battle verifies against a hand-derived JS oracle, for the seeded book after the September charge run + the three seeded receipts):
55.00, L3
55.00, L4 110.00, L5 80.00 (each LRENT×5%).
L1, settled before close, earns none. Ageing puts all 8 remaining open charges in bucket 1.715.00, L3 1215.00, L4
2460.00, L5 1790.00; L1 0.00.20260831, past) expires
(LSTAT='X') and its unit P00001/0102 flips to vacant.895.00 + 500.00 = 1395.00; fee 139.50; net 1255.50
(DISTGROSS = DISTFEE + DISTNET). Oakwood (O00002) collected nothing and is skipped
(no row).1400-TAR = rent 6850.00 + CAM 425.00
+ late fees 300.00 = 7575.00; CR 4100-RENT 6850.00,
4200-CAM 425.00, 4300-LATE 300.00 — DR = CR exactly.RLARRPT ITEMS 00014 (10 rent/CAM charges + 4 late fees), with
RAISED and OPEN totals over the whole ledger.Each program DSPLYs a one-line result. A cycle can be re-submitted whole; the idempotency guards make every posting a no-op the second time.
| Situation | Behaviour | Action |
|---|---|---|
| Charge run re-run (same period) | History key 10M+leaseDigits×10+type already exists; the CHAIN(EN) guard refuses it (counted in SKIP). | Safe no-op. L0000001 stays 895.00, ledger stays 10 rows. Idempotent. |
| Cash application re-run | Receipt history key 20M+receiptDigits already present. | Safe no-op — APPLIED=0, no lease balance falls twice. |
| Cash names a missing charge | Ledger CHAIN fails (*IN91); the receipt is skipped, not applied. | Correct RCCHGSQ and re-run; the receipt is still U. |
| Late-fee run re-run (same run date) | Late-fee history key 30M+leaseDigits exists. | Safe no-op — no lease is fee'd twice. (Pass 1 re-ages harmlessly.) |
| Lease-expiry sweep re-run | An already-expired lease is LSTAT='X', no longer active, so it is not swept again. | Naturally idempotent for a fixed book. |
| Owner distribution re-run | Owner history key 40M+ownerDigits exists. | Safe no-op — no second RLDIST row. |
| GL post re-run | Incremental: it subtracts what is already debited to 1400-TAR, leaving nothing new. | Safe no-op — ROWS=0, DR still equals CR. |
RLHIST (charge, cash, late fee,
distribution) and every open item lives in RLLEDG, any cycle's effect is fully
reconstructable after the fact for reconciliation and recovery — and the AR balance invariant plus
the balanced GL/distribution give an independent cross-check on every figure.The complete program surface, from property-app/src/sources.mjs. All objects are in
library REALTY. The fixed-form RPG is generated with two column-exact helpers in
sources.mjs (C() for C-specs, D() for D-specs), mirroring the
ibmi/samples.js convention.
LBAL=0), 2 owners, then
DSPLYs the seed counts. The one-time source of the seeded book every test and figure in this manual
is grounded in.SETLL *LOVAL / READ); per active lease, finds the next
CHGSEQ by a partial-key SETLL/READE walk, writes a rent and a
CAM RLLEDG row and their RLHIST rows, then re-CHAINs the lease and adds
WRENT+WCAM to LBAL. Idempotency guard on history key
10000000 + wdig×10 + type.U receipt CHAINs the named (LEASENO,CHGSEQ) ledger charge via a
KLIST, applies min(RCAMT, CHGOPEN), updates the charge and receipt status,
lowers LBAL, writes a negative RLHIST 'P' row. Guard on
20000000 + receiptDigits.CHGAGE. Pass 2: per active lease, walk its open rent/CAM charges; if any exceeds the
lease's LTERMS grace, write one L charge of LRENT×5%,
an RLHIST 'L' row, and raise LBAL. Guard on
30000000 + leaseDigits.LEND < run date, vacate the unit (via a PROPID+UNITNO
KLIST CHAIN) and, after re-CHAINing the lease, set LSTAT='X'; if within 30
days of expiry, flag an occupied unit USTAT='N'. Counts EXPIRED / NOTICE / SAME.RLHIST 'P' rows whose lease belongs to the owner's
property (cash actually collected), deduct OMGMTPCT, write a balanced RLDIST
row (DISTGROSS = DISTFEE + DISTNET) and an RLHIST 'D' row. An owner with
zero collected is skipped. Guard on 40000000 + ownerDigits.RLHIST by type (R/C/L), reads the current 1400-TAR debit total
via EXEC SQL, and posts only the incremental movement: DR 1400-TAR, CR
4100-RENT/4200-CAM/4300-LATE, each via
EXEC SQL INSERT into REALTY.RLGLDIST. A refused insert is not counted as a
posted row.RLLEDIQ is the subfile program;
RLUNTIQ uses the RLLSULF access path to find a unit's active lease.READ of RLLEDG accumulating RAISED (LG-AMT), OPEN
(LG-OPEN), item count, and overdue count (LG-AGE > 0); proves the
receivables sub-ledger totals reconcile.RLSETUP DLTFs then CRTPF/CRTLF/CRTDSPF/CRTPRTF/RUNSQLSTM/CRTBNDRPG/CRTBNDCBL every
member. The three cycle CLs ADDLIBLE REALTY and CALL the cycle's programs in order.Two properties define the application's correctness and are asserted to the penny by
test/rl_cycle.mjs:
RLLEASE.LBAL starts at 0 and moves only by:
+ (rent + CAM) in RLCHRUN, + late fee in RLLATE, and
− amount applied in RLCASHAP. Nothing else touches it. The test drives a full
charge/cash/late-fee cycle and checks each lease's balance equals the hand-derived figure
(e.g. L0000002: 0 → 1160.00 → 660.00 → 715.00).RLHIST key from a stable business key in a program-disjoint numeric range and does a
CHAIN(EN) (existence-only) read before posting. A re-run regenerates the same key, the
CHAIN finds it, and the post is refused. The GL post is idempotent differently — it posts only
the movement since the last close, so a second run adds nothing.| Program | History key formula | Range |
|---|---|---|
| RLCHRUN | 10000000 + leaseDigits×10 + type (rent 0, CAM 1) | 10M |
| RLCASHAP | 20000000 + receiptDigits | 20M |
| RLLATE | 30000000 + leaseDigits | 30M |
| RLDISTRB | 40000000 + ownerDigits | 40M |
The three logical files are genuine alternate orderings, not decoration — the build test asserts each LF's leading key differs from its PF's:
RLUNTIQ find a unit's current lease directly.RLLATE deliberately does
not open RLLEDLF alongside RLLEDG: two files sharing one record format
cannot both be declared in the same program without a RENAME the emulator does not support, and the
lease-keyed walk RLLATE needs is already RLLEDG's own primary key (which starts with
LEASENO). This mirrors the sibling order-app's OEALLOC/OEINVGEN discipline. Several programs
also re-CHAIN a master record after intervening ledger/history writes, because those writes move the
file cursor; and RLRENEW sets LSTAT='X' only after the unit CHAIN, since
a CHAIN reloads the whole record from disk and would otherwise discard an in-memory field.YYYYMMDD integers are computed by
collapsing each to ((yyyy×12 + mm)×30 + dd) and subtracting — the same
convention as the order-app's OEAGE. RLLATE and RLRENEW both use it (run date 20260925 → charges
due 20260901 are 24 days overdue).RLHIST file with
native READ, then uses EXEC SQL SELECT ... INTO to read the current GL
debit total and EXEC SQL INSERT to write GL rows — the one program that spans both
the keyed-file and SQL worlds. It checks SQLCOD and does not count a refused insert.LEASENO+CHGSEQ, unit
PROPID+UNITNO) are built with KLIST/KFLD in the C-specs.RLLEASE.LBAL. Raised only by the charge run and late
fees, lowered only by cash application — the invariant the tests assert.CHGTYPE='C') with its own revenue account 4200-CAM.CHAIN(EN)
guard (and the GL post's incremental logic) makes every cycle re-runnable.CHGTYPE='L', revenue 4300-LATE).LEND and vacates its
unit, and flags near-expiry units notice-to-vacate.DISTFEE).CHGOPEN > 0). Cash application
reduces it; a fully-applied item is skipped by ageing and late fees.RLDIST row (DISTGROSS = DISTFEE + DISTNET).SBMJOB CMD(CALL PGM(REALTY/RLPERIOD))).