LEXTIME/i — Law-Firm Time, Billing & Trust Accounting

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

LEXTIME/i is a law-firm practice-management application: clients and their legal matters, timekeeper time and expense capture with a daily edit, a billing run that sweeps unbilled work-in-progress (WIP) into invoices — applying a per-matter write-down to fees, billing expenses at cost, applying client trust (IOLTA) funds, and posting the remainder to accounts receivable — and a monthly close (AR aging, trust reconciliation, a balanced GL feed and a trial balance). The whole app is written in RPG (fixed and free form), ILE COBOL, CL and DDS over DB2 for i physical/logical files, with one SQL table for the GL distribution. The strict invariant it enforces everywhere is that a client's trust balance may never go negative. This manual is the reference for the operator who runs the three job cycles and the two online inquiry screens, and for the developer maintaining the application. It is grounded entirely in the committed source (legal-app/src/sources.mjs, src/seed.mjs, the DDS/RPG/COBOL/CL members it loads into library LEXTIME, FINDINGS.md, and the test/lt_build.mjs / lt_daily.mjs / lt_cycles.mjs drivers).

Contents

A. Overview & Architecture ↑ top

A.1 What it does

LEXTIME/i tracks the full life of billable legal work, from a timekeeper's logged hour to cash and the general ledger:

A.2 The WIP-to-cash pipeline

The application is a single, auditable pipeline. A timekeeper's hour becomes cash and a GL entry in a fixed sequence, each step validated and each money movement journaled:

  time/expense  --edit-->  validated WIP  --bill-->  invoice  --trust-->  AR  --close-->  GL feed
   (E rows)               (V rows, rated)          (LTBILH/D)  (applied)  (LTAR)  (LTGLFEED)

The billing model is deliberately hand-derivable — every figure below is stated in the test oracles to the exact cent:

time fee (line)    = HOURS x BILLRATE           (the timekeeper's own rate)
  time fees (matter) = SUM(time fee lines) over the matter's 'V' time WIP
  write-down amount  = time fees x WDPCT          (matter's write-down %, 7P4)
  billed fees        = time fees - write-down amount
  expenses (matter)  = SUM(expense 'V' WIP)       (AT COST, no write-down)
  invoice total      = billed fees + expenses
  trust applied      = MIN(invoice total, available trust balance)
  AR posted          = invoice total - trust applied
  trust balance after= trust balance before - trust applied   (must stay >= 0)

The write-down is genuinely per-matter, not a hidden floor: a matter with WDPCT=0.0000 bills billed-fees = time-fees exactly. Trust application is optional: a client with no trust account bills the full invoice straight to AR. And trust is capped at the balance, not the invoice, so a client whose funds fall short posts the remainder to AR while the balance lands at exactly zero — never negative.

A.3 Component & flow

  REFERENCE / SEED       ONLINE                 BATCH (three cycles)
  ----------------       ------                 --------------------
  LTREFLD (clients,      LTMENU (5250)          LXDAILY   -> LTTIMLD  (raw capture)
    timekeepers,           opt 1 -> LTMTRIQ       (daily)     LTWIPED  (edit/validate)
    matters)               opt 2 -> LTWIPIQ                   LTWIPRP  (reject report)
  LTTRDEP (trust                                LXBILL    -> LTBILL   (WIP -> invoice)
    deposits)                                     (billing)   LTBILPRT (invoice register)
  LTTIMLD (raw time                                          LTGLRPT  (trial balance, COBOL)
    & expense cards)                            LXMONTH   -> LTARAGE  (AR aging)
                                                  (monthly)   LTTRREC  (trust reconciliation)
                          \                    /              LTGLDST  (GL feed, SQLRPGLE)
                           \                  /               LTGLRPT  (trial balance, COBOL)
                            v                v
     library LEXTIME: LTMATR/LTTIME/LTEXP/LTTRUST/LTTRLDG/LTBILH/LTBILD/LTAR + logicals
            |
            v
     LTGLFEED (SQL GL distribution table) <-- the monthly close posts balanced DR/CR pairs

A single event — say a day's billing — flows: the daily cycle has already left validated ('V') WIP on LTTIME/LTEXPLTBILL scans the active matters, sums each matter's WIP, computes the invoice, applies trust from LTTRUST (writing an APP row to the trust ledger and reducing the balance), posts the AR remainder to LTAR, writes the invoice header/detail (LTBILH/LTBILD) and flips each swept WIP row to billed ('B').

A.4 Object inventory

ObjectTypeRole
LTCLNTPFClient master.
LTTKPRPFTimekeeper master (bill rate + cost rate).
LTMATRPFMatter master (client + write-down %).
LTTIMEPFTime entry WIP capture.
LTEXPPFExpense entry WIP capture.
LTTRUSTPFTrust (IOLTA) account — running balance per client.
LTTRLDGPFTrust ledger — durable deposit/application audit trail.
LTBILH / LTBILDPFInvoice header / detail lines.
LTARPFAR ledger — durable posting audit trail.
LTMATLF / LTTIMLF / LTEXPLF / LTTRLLF / LTBILLFLF (5)Alternate keyed access paths (client / status).
LTGLFEEDSQL tableGL distribution the monthly close posts into.
LTMATRD / LTWIPD / LTMENUDDSPFMatter-inquiry / WIP-subfile / menu display files.
LTBILPPRTFClient invoice-register printer file.
LTREFLD / LTTRDEP / LTTIMLDRPGReference-data / trust-deposit / raw-card seeders.
LTWIPED / LTWIPRPRPGDaily edit / reject-report drivers.
LTBILL / LTBILPRTRPGBilling engine / invoice-register print.
LTARAGE / LTTRRECRPGAR aging / trust reconciliation.
LTGLDSTSQLRPGLEGL distribution into LTGLFEED.
LTGLRPTILE COBOLGL trial balance over the invoice headers.
LTMTRIQ / LTWIPIQ / LTMENURPGMatter-inquiry / WIP-inquiry / menu programs.
LTSETUPCLCreate every object & compile every program.
LXDAILY / LXBILL / LXMONTHCLThe three job cycles.

The full catalogue is 10 physical files + 5 logical files, 1 SQL table, 3 display files and 1 printer file, driven by 13 RPG programs, 1 ILE COBOL program and 4 CL programs. Sections D and F expand each. Everything lives in library LEXTIME.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

LEXTIME/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). 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 LEXTIME — the tested jobs run with LIBL = QSYS QGPL LEXTIME QTEMP and CURLIB = LEXTIME (each CL cycle also issues its own ADDLIBLE LIB(LEXTIME) defensively).

To do thisType on the command line
Open the main menu (routes to the inquiry screens)CALL LEXTIME/LTMENU
Matter inquiry directlyCALL LEXTIME/LTMTRIQ
WIP subfile inquiry directlyCALL LEXTIME/LTWIPIQ
Run the daily capture/edit cycleCALL LEXTIME/LXDAILY (or SBMJOB it)
Run the billing cycleCALL LEXTIME/LXBILL
Run the monthly close cycleCALL LEXTIME/LXMONTH
One-time: build/compile the whole appCALL LEXTIME/LTSETUP

The batch cycles take no CALL parameters and drive fixed processing dates baked into the programs (e.g. the billing run dates invoices 20260814, the AR-aging report ages as of 20260913), matching the app's hand-checkable test convention. Only LTMENU, LTMTRIQ and LTWIPIQ are interactive; the batch programs run to completion and DSPLY a one-line result per step to the job log.

B.2 The main menu & inquiry screens

Three display files drive the online surface: LTMENUD (the menu), LTMATRD (a plain matter-inquiry screen) and LTWIPD (a WIP subfile). All are read-only inquiry screens — LEXTIME/i does not capture time or post payments interactively; capture is the batch daily cycle's job (section C).

The main menu (LTMENU / LTMENUD)

LEXTIME/i Main Menu 1. Matter Inquiry 2. WIP Inquiry Option . . . . : _ F3=Exit Enter=Select

Key 1 to CALL 'LTMTRIQ', 2 to CALL 'LTWIPIQ'; any other non-blank option redisplays with Invalid option. in the message line. F3 (DDS CA03 → indicator *IN03) exits.

Matter inquiry (LTMTRIQ / LTMATRD)

A plain (non-subfile) screen. Key a matter number; the program CHAINs LTMATR, shows the client name (from LTCLNT), the description, the write-down policy and status, and a derived unbilled WIP total — the sum of validated ('V') time fees plus validated expenses for that matter, read over the status logicals LTTIMLF/LTEXPLF on the 'V'+matter key. Note this WIP figure is the raw pre-write-down value (write-down is a billing-time discount, not part of what sits in WIP).

Matter Inquiry - LEXTIME/i Matter number: M00001 Client . . . . : HARLOW & VANCE MFG Description . : SUPPLY CONTRACT DISPUTE Write-down % : .1000 Status . . . . : A Unbilled WIP : 5150.00 Matter found. F3=Exit Enter=Inquire

An unknown matter blanks the fields and shows Matter not found.

WIP subfile inquiry (LTWIPIQ / LTWIPD)

The app's subfile screen (DDS record WSFL under control record WCTL, SFLPAG(5) per page / SFLSIZ(20), ROLLUP/ROLLDOWN paging). Key a matter number; the program clears and reloads the subfile with one row per validated ('V') WIP entry — time lines (with the timekeeper name, hours and fee) then expense lines (description and amount) — and totals the time fees and expenses in the header. Because it reads the same 'V' status key the billing run uses, the subfile always reflects the current unbilled WIP, never a stale billed line.

WIP Inquiry - LEXTIME/i Matter: M00001 Time fees . . : 4900.00 Expenses . . . : 250.00 Seq T Description Hours Amount 1 T AUGUSTINE PELLERIN 6.00 2700.00 2 T ISADORA WHITCOMBE 8.00 2200.00 3 X FILING FEE 250.00 WIP loaded. F3=Exit Roll=Page Enter=Inquire

Subfile columns (WSFL)

FieldType (DDS)Shows
WSEQ4Y,0 outputLine sequence within the load (RRN).
WTYP1A outputLine type: T time, X expense.
WDESC20A outputTimekeeper name (T) or expense description (X).
WHRS10A outputHours (time lines only; blank for expenses).
WAMT13A outputLine amount (fee or expense, edited).
The subfile clear (SFLCLR via indicator *IN31) is written after the EXFMT returns the newly-keyed matter number and immediately before the reload, and the READE cursor over each status logical is re-established with a SETLL after the timekeeper CHAIN moves it — standard subfile-refresh and cursor-repositioning discipline. Operationally the screen behaves as described.

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

Honest statement: LEXTIME/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user enters time, a second user approves the bill" gate in the code: the daily edit validates cards automatically, and the billing run sweeps and bills WIP in one flow. The manual documents the control model the application does have:

In sum, the control posture is dual-ledger audit + running/re-derived balance reconciliation + status gating + a strict never-negative trust invariant, all in the data-and-batch layer, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

LEXTIME/i runs as three CL-driven cycles, each doing real, distinct work: a daily cycle (LXDAILY — time/expense capture + edit + reject report), a billing cycle (LXBILL — WIP→invoice, trust application, AR posting, register print, trial balance) and a monthly cycle (LXMONTH — AR aging, trust reconciliation, GL distribution, trial balance). Each CL is a bare sequence of CALL PGM(...) steps with a closing SNDPGMMSG banner; a scheduled submission is a bare SBMJOB CMD(CALL ...). Before the very first run, LTSETUP creates every object and compiles every program, and the seed programs (LTREFLD, LTTRDEP) load reference data and initial trust deposits.

-- one-time build of the whole application
CALL PGM(LEXTIME/LTSETUP)
CALL PGM(LEXTIME/LTREFLD)      clients, timekeepers, matters
CALL PGM(LEXTIME/LTTRDEP)      initial trust deposits (C00001 1000.00, C00002 300.00)

-- then the three cycles, in order
SBMJOB CMD(CALL PGM(LEXTIME/LXDAILY)) JOB(LXDAILY)
SBMJOB CMD(CALL PGM(LEXTIME/LXBILL))  JOB(LXBILL)
SBMJOB CMD(CALL PGM(LEXTIME/LXMONTH)) JOB(LXMONTH)

C.1 Full batch program set

Cycle / CLProgramPurposeReadsWrites / DSPLY
LXDAILY LTTIMLDLoad the day's raw time & expense cards (incl. deliberate exceptions). — (host-coded cards) LTTIME/LTEXP 'E' rows; TCARDS=10 XCARDS=5 SKIP=0.
LTWIPEDDaily edit: validate or reject each 'E' entry, rate the clean ones. LTTIME/LTEXP/LTMATR/LTTKPR 'E'→'V' (rated) or 'R' (+reason); TOK=5 TREJ=5 XOK=3 XREJ=2.
LTWIPRPReject exception report over the status logicals. LTTIMLF/LTEXPLF TREJ=5 E1=1 E2=1 E3=1 / E4=1 E5=1 XREJ=2.
LXBILL LTBILLBilling engine: WIP→invoice, trust applied, AR posted. base PFs + LTMATR/LTTKPR/LTTRUST LTBILH/LTBILD/LTAR/LTTRLDG; balance reduced; BILLED=4 SKIP=1.
LTBILPRTPrint the client invoice register (PRTF, page overflow). LTBILH/LTBILD/LTCLNT spooled register; INVS=4 BILLED=7303.
LTGLRPTGL trial balance over the invoice headers (ILE COBOL). LTBILH TOTDIFF 0.00, ARDIFF 0.00.
LXMONTH LTARAGEAR aging: bucket each open invoice's outstanding balance by age. LTBILH INVS / CUR / B31 / B61 / B90 / TOT.
LTTRRECTrust reconciliation: re-sum the ledger vs. the running balance. LTTRUST/LTTRLLF CLIENTS / BREAKS / NEG + total trust liability.
LTGLDSTGL distribution: balanced DR/CR pairs into the SQL feed (SQLRPGLE). LTBILH LTGLFEED rows; BATCH / ROWS / TOTAL.
LTGLRPTTrial balance again, closing the month. LTBILH TOTDIFF 0.00, ARDIFF 0.00.

C.2 Daily / billing / monthly detail

Daily — LXDAILY

LTTIMLD writes 10 raw time cards and 5 raw expense cards (all status 'E', unrated), including deliberate bad cards. LTWIPED then walks every 'E' row: for a time entry it CHAINs the matter (E1 if missing, E2 if not active), then the timekeeper (E5 if missing or terminated, else defaulting TRATE from BILLRATE), then checks hours (E3 over the 24-hour ceiling, E4 zero-or-negative); a clean entry is rated TFEE = THOURS × TRATE (half-adjusted to exact cents) and flipped to 'V'. Expenses take the same E1/E2 matter checks (E4/E5 don't apply) and flip to 'V' at cost. LTWIPRP reports the rejects over the status logicals.

Expected DSPLY:
  LTTIMLD TCARDS=10 XCARDS=5 SKIP=0
  LTWIPED TOK=5 TREJ=5 XOK=3 XREJ=2   5 clean time (fees 7337.50), 3 clean exp (510.50)
  LTWIPRP TREJ=5 E1=1 E2=1 E3=1
  LTWIPRP E4=1 E5=1 XREJ=2            one reject of each kind, both bad expenses
The clean fees are hand-derivable: 6.00×450 + 8.00×275 + 3.50×125 + 4.00×275 + 2.00×450 = 2700.00 + 2200.00 + 437.50 + 1100.00 + 900.00 = 7337.50; clean expenses 250.00 + 85.50 + 175.00 = 510.50.

Billing — LXBILL

LTBILL scans all active matters; for each one carrying validated WIP it sums time fees and expenses, applies the matter write-down to fees only, computes the invoice total, applies MIN(total, trust balance) from LTTRUST (writing an APP ledger row and reducing the balance, never below zero), posts the AR remainder to LTAR, writes the invoice header and detail lines (one summarised T line per timekeeper, the X expense lines, then an informational W write-down line when the write-down is positive), and flips each swept WIP row to 'B'. Matters bill in matter-number order, so a client's earlier matter drains the trust first. LTBILPRT prints the register; LTGLRPT proves the trial balance.

Expected DSPLY / hand-derived invoices:
  LTBILL BILLED=4 SKIP=1              M00005 closed, no WIP -> skipped
  M00001  fees 4900.00  wd 490.00 (10%)  billed 4410.00  exp 250.00  TOTAL 4660.00
          trust MIN(4660,1000)=1000.00   AR 3660.00   balance after 0.00
  M00002  fees 437.50   wd 0.00          billed 437.50   exp 0.00     TOTAL 437.50
          trust MIN(437.50,0)=0.00 (drained by M00001)  AR 437.50 in full
  M00003  fees 1100.00  wd 55.00  (5%)   billed 1045.00  exp 85.50    TOTAL 1130.50
          trust MIN(1130.50,300)=300.00 (partial, capped at balance)  AR 830.50
  M00004  fees 900.00   wd 0.00          billed 900.00   exp 175.00   TOTAL 1075.00
          no trust account -> trust 0.00  AR 1075.00 in full
  TOTALS  billed 7303.00  trust applied 1300.00  AR posted 6003.00  (7303 - 1300 = 6003)

Monthly — LXMONTH

LTARAGE buckets each open invoice's outstanding balance (BARAMT - BPAIDAMT) by age from BILDT to a fixed as-of date 20260913, using a 30-day-month julian approximation (CURRENT 0–30, B31 31–60, B61 61–90, B90 over 90). LTTRREC re-sums the trust ledger per client and asserts it matches TRBAL with zero breaks and zero negatives. LTGLDST sums the invoice headers and posts balanced DR/CR pairs into LTGLFEED. LTGLRPT closes with the trial balance.

Expected DSPLY (with one injected 74-day-old 500.00 invoice for aging):
  LTARAGE INVS=5  CUR=6003.00 ...      the 4 real invoices are exactly on the 30-day edge
  LTARAGE ... B61=500.00 ...           the injected aged invoice
  LTTRREC CLIENTS=2 BREAKS=0 NEG=0
  LTTRREC TOTAL TRUST LIABILITY=0.00   both accounts fully drained, strict zero
  LTGLRPT TOTDIFF 0.00 / ARDIFF 0.00   every figure reconciles

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library LEXTIME, described in DDS in src/sources.mjs. Dates are stored as signed numeric YYYYMMDD (8S 0); money is packed 11P 2; rates and the write-down percentage are packed 7P 4 (so 450.0000 is a rate and 0.1000 is 10%). Business keys are the letter+5-digit spine convention (C00001, T00001, M00001).

LTCLNT — Client master (key CLNO, UNIQUE)

FieldTypeMeaning
CLNO6AClient number (key), e.g. C00001.
CNAME30AClient name.
CADDR30AAddress.
CTERMS2S 0Payment terms (days).
CSTAT1AA active, C closed (no new matters; existing may still bill).
CSTARTDT8S 0Client start date.

LTTKPR — Timekeeper master (key TKNO, UNIQUE)

FieldTypeMeaning
TKNO6ATimekeeper number (key), e.g. T00001.
TKNAME30ATimekeeper name.
TKCLASS1AP partner, A associate, L paralegal.
BILLRATE7P 4Hourly rate charged to the client.
COSTRATE7P 4Internal cost/hour (separate figure, for realization/margin reporting).
TKSTAT1AA active, T terminated. (Deliberately named TKSTAT, not TSTAT — see the field-name note below.)
HIREDT8S 0Hire date.

Seeded set: T00001 partner 450.0000/180.0000, T00002 associate 275.0000/110.0000, T00003 paralegal 125.0000/45.0000, T00004 associate 300.0000/120.0000 (terminated).

Why TKSTAT and not TSTAT: LTTIME already carries a field named TSTAT (time-entry status). This emulator's RPG compiler shares one flat global-storage slot per field name across every externally-described file a program opens, with no compile-time conflict check (documented as FINDING 1's field-name class in the app's own notes). A program that opened both LTTIME and LTTKPR would silently corrupt whichever TSTAT it didn't mean; naming the timekeeper status TKSTAT avoids the collision by construction.

LTMATR — Matter master (key MTNO, UNIQUE)

FieldTypeMeaning
MTNO6AMatter number (key), e.g. M00001.
CLNO6AOwning client.
MTDESC30AMatter description.
WDPCT7P 4Write-down % applied to time fees at billing (0.1000 = 10%).
MOPENDT / MCLOSDT8S 0Opened / closed dates.
MSTAT1AA active, C closed (closed rejects new capture; existing WIP may still bill).

Seeded: M00001→C00001 (10%), M00002→C00001 (0%), M00003→C00002 (5%), M00004→C00003 (0%), M00005→C00002 (closed).

LTTIME — Time entry / WIP (key TENTNO, UNIQUE)

FieldTypeMeaning
TENTNO8S 0Time-entry number (key).
MTNO / TKNO6A / 6AMatter / timekeeper.
TWKDT8S 0Work date.
THOURS5P 2Hours logged.
TRATE7P 4Rate (defaulted from the timekeeper's BILLRATE at edit time).
TFEE11P 2Line fee = THOURS × TRATE (set by the edit; zero until validated).
TSTAT1AE entered, V validated (unbilled WIP), R rejected, B billed.
TERRCD2AReason code on a rejected entry (E1–E5).

LTEXP — Expense entry / WIP (key XENTNO, UNIQUE)

FieldTypeMeaning
XENTNO8S 0Expense-entry number (key).
MTNO6AMatter.
XWKDT8S 0Date incurred.
XDESC20ADescription (e.g. FILING FEE).
XAMT11P 2Cost (billed at cost, no write-down).
XSTAT / XERRCD1A / 2AStatus (E/V/R/B) / reason code (E1/E2 only).

LTTRUST — Trust (IOLTA) account (key CLNO, UNIQUE)

FieldTypeMeaning
CLNO6AClient (key) — one trust account per client.
TRBAL11P 2Current running trust balance; must never go negative.
TROPENDT8S 0Account open date.

LTTRLDG — Trust ledger (key TRSEQ, UNIQUE)

FieldTypeMeaning
TRSEQ8S 0Stable business key derived from client digits + a sequence offset (idempotency guard).
CLNO / MTNO6A / 6AClient / matter (matter blank on a deposit).
TRTYP3ADEP deposit, APP applied against an invoice.
TRAMT11P 2Amount.
TRDT / TRMEMO8S 0 / 25ADate / free-text memo.

LTBILH — Invoice header (key BILNO, UNIQUE)

FieldTypeMeaning
BILNO8S 0Invoice number (key) — a stable key = the matter's 5 digits (one bill per matter per run).
MTNO / CLNO6A / 6AMatter / client.
BFEES11P 2Billed fees (time fees NET of write-down).
BWDOWN11P 2Write-down amount taken off gross time fees (informational).
BEXP11P 2Expenses at cost.
BTOT11P 2Invoice total = BFEES + BEXP.
BTRUST11P 2Trust funds applied to this invoice.
BARAMT11P 2Posted to AR = BTOT − BTRUST.
BILDT / BDUEDT8S 0Invoice / due dates.
BPAIDAMT11P 2Amount paid (aging uses BARAMT − BPAIDAMT).
BSTAT1AO open, P paid.

LTBILD — Invoice detail lines (key BILNO, LSEQ, UNIQUE)

FieldTypeMeaning
BILNO / LSEQ8S 0 / 3S 0Invoice + line sequence (key).
LTYPE1AT time line (one per timekeeper), X expense line, W write-down (informational).
LDESC20ALine description (timekeeper name / expense text / "WRITE-DOWN TIME FEES").
LHRS / LRATE5P 2 / 7P 4Hours / rate on a time line (rate carries WDPCT on the W line).
LAMT11P 2Line amount.

LTAR — AR ledger (key ARSEQ, UNIQUE)

FieldTypeMeaning
ARSEQ8S 0Stable business key (= 20000000 + BILNO) — idempotency guard.
CLNO / MTNO6A / 6AClient / matter.
ARTYP4AINVC (invoice posted).
ARAMT11P 2Net-of-trust AR amount.
ARDT / ARMEMO8S 0 / 25ADate / memo.

LTGLFEED — GL distribution (SQL table, PK GLSEQ)

FieldTypeMeaning
GLSEQDECIMAL(8,0)Sequence (PK) — the idempotency guard (a re-run's INSERT collides).
GLBATCHDECIMAL(6,0)Batch (period), e.g. 202608.
ACCT / DRCRCHAR(9) / CHAR(1)GL account / D debit or C credit.
AMTDECIMAL(13,2)Amount.
GLREF / GLDTCHAR(8) / DECIMAL(8,0)Reference (BILLING/WRITEDWN/TRUSTAPP) / date. Index LTGLFACC on (ACCT, DRCR).

Logical files & relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. Confirm the job's library list includes LEXTIME (each CL also issues its own ADDLIBLE).
  2. First-time only: build the app and seed it — CALL LEXTIME/LTSETUP, then CALL LEXTIME/LTREFLD (masters), then CALL LEXTIME/LTTRDEP (trust deposits).
  3. Run the daily capture/edit cycle: SBMJOB CMD(CALL PGM(LEXTIME/LXDAILY)).
  4. Post-check the daily run (see below), then handle any interactive inquiries through CALL LEXTIME/LTMENU.

Post-checks after the daily cycle:

E.2 Billing & month-end close

  1. Confirm the daily cycle has left validated ('V') WIP on file.
  2. Run the billing cycle: SBMJOB CMD(CALL PGM(LEXTIME/LXBILL)). Confirm LTBILL BILLED=4 SKIP=1 and review the spooled invoice register (INVS=4 BILLED=7303).
  3. Reconcile the billing figures (the same ones the volume simulation checks to the cent against an independent JS oracle):
SELECT MTNO, BFEES, BWDOWN, BEXP, BTOT, BTRUST, BARAMT
  FROM LEXTIME/LTBILH ORDER BY MTNO;
  1. Run the monthly close: SBMJOB CMD(CALL PGM(LEXTIME/LXMONTH)). Confirm LTTRREC CLIENTS=2 BREAKS=0 NEG=0 and TOTAL TRUST LIABILITY=0.00, the aging buckets, and LTGLRPT TOTDIFF 0.00 / ARDIFF 0.00.
  2. Review the balanced GL feed:
SELECT ACCT, DRCR, SUM(AMT) AS T FROM LEXTIME.LTGLFEED
  GROUP BY ACCT, DRCR ORDER BY ACCT;

Total debits must equal total credits. The BILLING leg debits 1200-AR for the invoice total and credits 4100-FEES (net billed fees) + 4200-EXP (expenses); the WRITEDWN memo pair (4900-WDWN / 4950-WDOF) self-balances; the TRUSTAPP leg moves 2300-TRLB to reduce 1200-AR by the trust applied.

E.3 Failure & re-run rules

Each program DSPLYs its result to the job log. Every cycle in LEXTIME/i is designed to be safely re-runnable through a stable-key idempotency guard — a re-run regenerates the same key and the duplicate write/insert is refused rather than double-posting.

SituationBehaviourAction
Daily edit re-runOnly 'E' rows are touched; validated/rejected rows are left alone.Safe no-op: LTWIPED TOK=0 TREJ=0 XOK=0 XREJ=0. Idempotent.
Raw-card load re-runThe sequential TENTNO/XENTNO counter has advanced past the seeded range.Nothing new captured: TCARDS=0 XCARDS=0 SKIP=15. Idempotent.
Billing re-runEvery swept entry is now 'B', so no matter has any 'V' WIP left.LTBILL BILLED=0 SKIP=5: no bill, no AR, no trust movement happens twice. Idempotent off the WIP gate.
Trust deposit re-runLTTRUST is UNIQUE-keyed on CLNO; the CHAIN finds the account exists.Skips re-depositing (SKIP counter increments). Idempotent.
GL distribution re-runGLSEQ is the SQL PRIMARY KEY; the re-run's INSERTs collide.The feed is left exactly as it was (not doubled). Idempotent per batch.
A card rejected by the editSet to 'R' with a reason code (E1–E5); no rate, no fee.Correct the master data (matter/timekeeper) or the card, reload as a fresh 'E' entry, re-run the edit. Rejected rows never bill.
A trust break at reconciliationLTTRREC DSPLYs a BREAK line and bumps BREAKS.Investigate: the ledger-derived balance and TRBAL disagree, meaning a balance was moved outside a ledgered posting. A healthy month shows BREAKS=0 NEG=0.
Because every trust movement is journaled to LTTRLDG and every AR posting to LTAR, and the running balance is re-derived monthly from the ledger by LTTRREC, any billing run's effect on client funds is fully reconstructable and independently verifiable after the fact.

F. Developer Reference ↑ top

The program surface, from src/sources.mjs. All objects are in library LEXTIME. The source is held as JS string constants (column-exact fixed-form RPG via the C()/D() helpers, plus free-form /free blocks) and loaded into library LEXTIME's source physical files by seedLextime() in src/seed.mjs — the same DEMOLIB convention as ibmi/samples.js.

F.1 Programs (13 RPG + 1 COBOL + 4 CL)

LTREFLD (RPG)
Seeds 4 clients (3 active + 1 closed), 4 timekeepers (3 active + 1 terminated) and 5 matters (4 active + 1 closed).
LTTRDEP (RPG)
Seeds initial trust deposits: C00001 1000.00, C00002 300.00; writes both the LTTRUST running-balance row and a DEP LTTRLDG row. Idempotent (CHAIN on the UNIQUE CLNO key).
LTTIMLD (RPG)
Loads 10 raw time cards + 5 raw expense cards (all 'E'), including the deliberate exception cards. Idempotent (sequential-counter key guard).
LTWIPED (RPG)
The daily edit. Walks each 'E' entry, applies E1–E5 checks, rates a clean time entry TFEE = THOURS × TRATE (half-adjusted) from the timekeeper's bill rate, flips to 'V' or 'R'. Re-CHAINs the entry after the master lookups moved the cursor. Idempotent.
LTWIPRP (RPG)
Reject exception report over the LTTIMLF/LTEXPLF status logicals (read-only), breaking rejects out by reason code.
LTBILL (RPG)
The billing engine. Per active matter: sum 'V' WIP, write-down on fees, expenses at cost, invoice total, MIN(total, balance) trust applied (never negative), AR remainder; writes header/detail, ledger rows, flips WIP to 'B'. Scans the base PFs for the sweep, deliberately not the status logicals — see F.4. Idempotent off the WIP gate.
LTBILPRT (RPG)
Prints the client invoice register to the LTBILP PRTF with page overflow (OFLIND(*IN90)): a matter block per invoice (header, itemised lines via a partial-key READE on BILNO, AR-posted net), then register totals.
LTARAGE (RPG)
Monthly AR aging. Buckets each open invoice's BARAMT − BPAIDAMT by age (30-day-month julian day-count) from BILDT to the fixed as-of date 20260913: CURRENT / B31 / B61 / B90.
LTTRREC (RPG)
Monthly trust reconciliation. Per client with a trust account, independently re-sums LTTRLDG (DEP adds, APP subtracts) over LTTRLLF and asserts it equals TRBAL to the cent; counts breaks and negatives; totals the trust liability.
LTGLDST (SQLRPGLE)
Monthly GL distribution. Sums the invoice headers and writes balanced DR/CR pairs into LTGLFEED with free-form exec sql insert. Idempotent (GLSEQ PK).
LTGLRPT (ILE COBOL)
GL trial balance over LTBILH: proves invoice total = billed fees + expenses (TOTDIFF) and AR posted = invoice total − trust applied (ARDIFF), both exactly zero.
LTMTRIQ / LTWIPIQ / LTMENU (RPG)
The three interactive programs (section B): plain matter inquiry, WIP subfile inquiry, and the menu that routes to them.
LTSETUP / LXDAILY / LXBILL / LXMONTH (CL)
The build/compile program and the three job cycles (section C).

F.2 The billing arithmetic (worked example)

Every figure below is hand-derivable and asserted to the cent by test/lt_cycles.mjs.

MatterTime feesWDPCTWrite-downBilled feesExpensesTotalTrustAR
M000014900.000.1000490.004410.00250.004660.001000.003660.00
M00002437.500.00000.00437.500.00437.500.00437.50
M000031100.000.050055.001045.0085.501130.50300.00830.50
M00004900.000.00000.00900.00175.001075.000.001075.00
TOTAL7337.50545.006792.50510.507303.001300.006003.00

Note how the invariants show up in the totals: billed fees + expenses = total (6792.50 + 510.50 = 7303.00), total − trust = AR (7303.00 − 1300.00 = 6003.00), the write-down is per-matter (M00002/M00004 take none), and trust is capped at the balance (M00003 applies only 300.00 of a 1130.50 invoice), leaving both trust accounts at exactly 0.00.

F.3 The GL journal (double-entry)

LTGLDST posts three self-consistent legs, so the batch balances (DR = CR):

ReferenceDebitCreditBasis
BILLING1200-AR4100-FEES + 4200-EXPAR debited for invoice total; fee revenue at NET billed fees, expense revenue at cost. AR = FEES + EXP by construction, so this leg alone balances.
WRITEDWN4900-WDWN4950-WDOFInformational memo pair recording the realization loss; never touches AR or revenue (the write-down happened before billing, not as a credit note).
TRUSTAPP2300-TRLB1200-ARClient funds held (trust liability) move to reduce AR by the trust applied.

With the test's injected 500.00 orphan invoice included, the feed nets to: 1200-AR debit 7803.00 = 4100-FEES 7292.50 + 4200-EXP 510.50; the 4900/4950 memo pair 545.00; and 2300-TRLB debit = 1200-AR credit = 1300.00 (trust applied).

F.4 A platform finding — the cross-file-instance stale logical index

What LTBILL does and why. The billing engine deliberately opens only the base physical files (LTTIME/LTEXP) for its WIP sweep, never their status logicals (LTTIMLF/LTEXPLF), scanning SETLL *LOVAL / READ sequentially and filtering MTNO/TSTAT in RPG logic. This is a defensive redesign around a confirmed platform bug, logged as FINDING 1 in legal-app/FINDINGS.md.

The bug (confirmed). A logical file's keyed index does not observe an UPDATE posted through a separate, concurrently-open file instance over the same base PF / record format in the same job. The earlier version of LTBILL swept each matter's unbilled ('V') WIP via the status logicals, and for each row read through the logical it CHAIN-and-UPDATE-ed the same record through the base PF to flip it to 'B', then re-SETLLed the logical to reposition. The logical's index never saw the 'V''B' flip, so the next READE returned the same record (with its old key values) forever — an infinite READE loop looping on TENTNO 90000001 (matter M00001, timekeeper T00001). Left running, the 3-digit line-sequence counter WSEQ would eventually overflow past 999 and raise a second, misleading SNX0103 Receiver too small symptom that masked the true infinite loop.

Class & severity. This is the same class as the freight application's NA-02 finding (cross-file-instance index staleness). Severity High: it silently hangs the calling program with no error or escape message. On real IBM i, DB2/400 keeps a logical file's keyed index transactionally consistent with its base PF within a job across separate opens (single-job read-your-writes), so this specific shape either can't arise or the index would be maintained transactionally.

The fix (app-level, not a platform workaround). LTBILL was rewritten to scan the base PFs sequentially and filter in RPG — the same defensive shape staffing-app's SPCALC already uses (it opens only the base time PF, never its status logical, for exactly this reason). The status logicals remain in the app, used correctly (read-only, no same-run cross-instance UPDATE) by LTWIPRP, LTMTRIQ and LTWIPIQ, and the build battle still proves they are genuine, distinct keyed access paths. The platform gap itself remains present for any other program that would combine a keyed-LF sweep with a same-run UPDATE through a sibling file instance.

F.5 Reason-code & status table

Edit reason codes (TERRCD / XERRCD)

CodeApplies toMeaning
E1time + expenseNo such matter on the master.
E2time + expenseMatter is not active (closed).
E3time onlyHours exceed the 24.00 one-day ceiling.
E4time onlyHours are zero or negative.
E5time onlyNo such timekeeper, or timekeeper not active (terminated).

Status values

FieldValues
TSTAT / XSTATE entered, V validated (WIP), R rejected, B billed.
MSTAT / CSTATA active, C closed.
TKSTATA active, T terminated.
BSTATO open, P paid.
TRTYP / ARTYPDEP / APP (trust); INVC (AR).
LTYPET time, X expense, W write-down.

G. Glossary ↑ top

AR aging
Classifying each open invoice's outstanding balance by how long it has been unpaid (CURRENT / 31-60 / 61-90 / over 90 days). LTARAGE uses a 30-day-month julian approximation from BILDT to a fixed as-of date.
Billing run
The LXBILL cycle that sweeps validated WIP into invoices (write-down on fees, expenses at cost), applies trust funds and posts the remainder to AR.
Idempotent
Safe to run again with the same result. Every LEXTIME/i cycle is idempotent via a stable-key guard: the edit only touches 'E' rows, the card load's counter has advanced, billing's WIP gate is already 'B', the trust deposit's CLNO key exists, and the GL feed's GLSEQ PK collides.
IOLTA / trust account
Client funds a firm holds on the client's behalf (a liability, not the firm's revenue). Here one LTTRUST balance per client, drawn down by billing, and it may never go negative.
Matter
A legal engagement (case/file) for one client. Time and expenses are logged against a matter; a matter carries its own write-down percentage.
Realization / write-down
A discount applied to time fees before billing (the firm "realizes" less than its standard rates). WDPCT is the matter's write-down percentage; the amount is informational-memo'd, never a credit note against a receivable.
SBMJOB
Submit Job — the IBM i command that queues a program to run as a batch job (e.g. SBMJOB CMD(CALL PGM(LEXTIME/LXBILL))).
Stable business key
A key derived deterministically from the business data (here from client/matter digits + an offset) so a re-run regenerates the same key and the duplicate write is refused — the app's idempotency mechanism.
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). LTWIPIQ's WIP list is a subfile.
Timekeeper
A person who logs billable time — partner, associate or paralegal — each with their own BILLRATE (charged to the client) and COSTRATE (internal cost).
Trial balance
The COBOL LTGLRPT report proving invoice total = billed fees + expenses (TOTDIFF 0.00) and AR posted = invoice total − trust applied (ARDIFF 0.00).
Trust reconciliation
The monthly LTTRREC check that independently re-derives each client's trust balance from the ledger and asserts it equals the running TRBAL to the cent, with no negatives.
WIP — Work in Progress
Validated but unbilled time and expense entries (status 'V'), waiting for the billing run to sweep them into an invoice.
Write-down line (W)
An informational invoice-detail line recording the write-down amount taken off gross time fees; it does not change what the client is billed (that is already net in BFEES).