VETCARE/i — Veterinary Clinic Management

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

VETCARE/i is a veterinary-clinic management application: owners and their animals (patients), the visits those animals make, the services and vaccinations performed on a visit priced off a shared fee schedule, invoices raised one-per-closed-visit, payments applied against invoices, vaccination due-date RECALL tracking that drives a weekly reminder run, and a monthly AR-aging / GL / close cycle. The spine of the system is the visit: an animal's visit accumulates service and vaccine lines priced off VCSVC, closes (rolling its lines into a billed total), and becomes one invoice; a payment posts against that invoice and moves the owner's AR balance; a vaccine line administered on a visit generates or refreshes that animal's next-due recall row. Every posting program is deliberately re-runnable off a ledger keyed by a stable business key. This manual is the reference for the operator who runs the online screens and the periodic batch cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (vet-app/src/sources.mjs, src/seed.mjs, and the test/vc_*.mjs drivers). Everything runs in library VETCARE.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

VETCARE/i runs the operational and billing life of a veterinary clinic:

A.2 The billing invariants

Every program in the application obeys one small set of billing identities, stated at the top of the source and asserted by the test drivers:

These are not decorative: the daily suite checks that invoice line amounts sum exactly to the invoice total, and that a rejected overpayment never drives a balance negative.

A.3 Component & flow

  REFERENCE            ONLINE                 BATCH (periodic)
  ---------            ------                 ----------------
  VCREFLD (seed)       VCMENU (5250)          DAILY  VCDAILY
    owners               opt 1 -> VCPATIQ       VCVSTCLS  visit close (roll-up)
    animals              opt 2 -> VCVLIQ        VCINVGEN  invoice generation
    providers                                   VCRECGEN  recall due-date gen
    fee schedule                                VCPYPOST  payment posting
                                              WEEKLY VCWEEK
                                                VCRECRUN  recall reminder run
                                              MONTHLY VCMONTH
                                                VCARAGE   AR aging
                                                VCGLDST   GL distribution (SQL)
                                                VCMCLOSE  close summary (COBOL)

  VCVISIT --close--> VSBILL --invoice--> VCINV --pay--> IBAL --> owner AR
     |                                     |
     +--> VCVSTL (lines) --copy-down--> VCINVL (invoice lines, verbatim)
     +--> vaccine line --> VCRECALL (RCDUE = VSDT + SVRECDY) --> VCRCWRK (weekly)

  Every posting program writes VCLEDG (durable audit + re-runnability guard).

A single event — say a visit being invoiced — flows: VCVSTCLS rolls the visit's lines into VSBILL and sets VSTAT='C'VCINVGEN raises VCINV/VCINVL, sets VSTAT='I' and carries the invoice number back → VCRECGEN reads the invoiced visit's vaccine lines and generates/refreshes VCRECALL → each step first CHAINs its stable ledger key in VCLEDG and refuses to double-post if the row is already there.

A.4 Object inventory

ObjectTypeRole
VCOWNRPFOwner / client master (spine OWNID).
VCANMLPFAnimal / patient master (spine ANMID, owner FK).
VCANMLOLFAnimals keyed by OWNER (patients-by-owner path).
VCPROVPFProvider / staff (vet, technician).
VCSVCPFFee schedule — services AND vaccines share one list.
VCVISITPFVisit header (the spine of the app).
VCVISITOLFVisits keyed by ANIMAL (patient-history path).
VCVSTLPFVisit lines (composite key VISITNO, VLINE).
VCRECALLPFVaccination recall (one row per ANMID, SVCCD).
VCRCWRKPFRecall reminder worklist (weekly output).
VCINVPFInvoice header (one per closed visit).
VCINVOLFInvoices keyed by OWNER (AR-aging path).
VCINVLPFInvoice lines (verbatim copy-down of visit lines).
VCPYMTPFPayment / receipt applied to an invoice.
VCARPFAR aging snapshot (monthly, per owner/run).
VCLEDGPFDurable audit trail + re-runnability ledger.
VCGLDISTSQL tableGL distribution the monthly close posts into.
VCPATD / VCVLD / VCMENUDDSPFPatient inquiry / visit-subfile inquiry / menu.
VCINVPPRTFClient-invoice / statement printer file.
VCREFLDRPGReference-data seeder.
VCVSTCLS / VCINVGEN / VCRECGEN / VCPYPOSTRPGDaily posting programs.
VCRECRUNRPGWeekly recall reminder run.
VCARAGERPGMonthly AR aging.
VCGLDSTSQLRPGLEMonthly GL distribution (embedded SQL).
VCPATIQ / VCVLIQ / VCMENURPGInteractive inquiry / menu programs.
VCMCLOSEILE COBOLMonthly close-summary report.
VCSETUPCLCreate every object, compile every program.
VCDAILY / VCWEEK / VCMONTHCLThe three job cycles.

The full catalogue is 13 PFs, 2 LFs, 3 DSPFs, 1 PRTF and 1 SQL table (+ its index), driven by 11 RPG programs, 1 ILE COBOL program and 4 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

VETCARE/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 through the VCMENU menu driver for the interactive programs, 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 VETCARE — the tested jobs run with LIBL = QSYS QGPL VETCARE QTEMP and CURLIB = VETCARE.

To do thisType on the command line
Open the operator menu (routes to the inquiry screens)CALL VETCARE/VCMENU
Open the patient inquiry screen directlyCALL VETCARE/VCPATIQ
Open the visit-line subfile inquiry directlyCALL VETCARE/VCVLIQ
Run the daily visit/billing cycleCALL VETCARE/VCDAILY (or SBMJOB it)
Run the weekly recall reminder cycleCALL VETCARE/VCWEEK
Run the monthly close cycleCALL VETCARE/VCMONTH
Build/rebuild every object & programCALL VETCARE/VCSETUP, then CALL VETCARE/VCREFLD

The batch cycles and their component programs take no CALL parameters: each is a bare CALL, so a scheduled submission is trivial. Only the three inquiry programs (VCPATIQ, VCVLIQ, VCMENU) are interactive; the batch programs run to completion and DSPLY a one-line result per step.

Honest statement on data entry. The interactive surface is inquiry only. There is no on-line screen in the shipped application to create a visit, key a line, or enter a payment; VCPATIQ and VCVLIQ display an animal or a visit but do not update anything. In the tests, visits, lines and payments are written directly to the files (VCVISIT, VCVSTL, VCPYMT) before the daily cycle rolls them forward. Operationally, transaction rows arrive from whatever feeds these files (a data-entry front end, a load, or STRSQL); the batch programs then close, invoice, recall and post them.

B.2 The menu & inquiry screens

VCMENU / VCMENUD — the operator menu

A plain menu screen. Key an option in MOPT and press Enter: 1 program-to-program CALLs VCPATIQ, 2 CALLs VCVLIQ; any other non-blank value shows Invalid option. and leaves the menu usable. F3 ends the program.

VETCARE/i Main Menu 1. Patient Inquiry 2. Visit Inquiry Option . . . . : _ F3=Exit Enter=Select

VCPATIQ / VCPATD — patient inquiry (plain screen)

Key an animal id in IANM and press Enter. The program CHAINs the animal, resolves the owner name through the animal-to-owner lookup, and renders two derived figures computed on the fly, not stored: the animal's next-due vaccine (the earliest RCDUE among that animal's non-done open recalls) and the owner's live AR balance (the sum of that owner's open invoice balances, walked through VCINVO). An unknown animal reports Animal not found. and clears every detail field. F3 exits.

Patient Inquiry - VETCARE/i Animal id: AN00001 Name . . . . . : BISCUIT Species . : CAN Breed . . . . : LABRADOR RETRIEVER Sex/Fixed : M/FX Owner . . . . : MARISOL DE LA FUENTE Status . . . . : A Next vaccine . : VACRAB Due . . . : 20261166 AR balance . . : 93.00 Animal found. F3=Exit Enter=Inquire
The AR balance is keyed by owner, not animal: two pets of the same owner show the same owner-level outstanding total. The next-vaccine field is genuinely blank for an animal with no open recalls — it is re-derived per inquiry, never carried over from a previous animal.

VCVLIQ / VCVLD — visit inquiry (subfile screen)

The app's subfile screen. Key a visit number in IVIS and press Enter; the header renders the animal, visit date, status (O/C/I), billed total and invoice number, and the subfile (DDS record LSFL under control record LCTL, SFLPAG(5)/SFLSIZ(20)) loads the visit's service/vaccine lines. It follows the classic subfile discipline: clear (SFLCLR under indicator 31), load for the key just typed, then display — so a miss shows an empty subfile and Visit not found. rather than stale rows. Roll Up/Roll Down page the subfile; F3 exits.

Visit Inquiry - VETCARE/i Visit number: VIS000301 Animal . . . . : AN00001 Visit date . . : 20260801 Status . . : I Billed total . : 93.00 Invoice . : INV000301 Lin Svc Description Qty Amount 1 EXAM01 1.0 65.00 2 VACRAB 1.0 28.00 Visit found. F3=Exit Roll=Page Enter=Inquire
The subfile shows the service code and priced amount for each line; the description column is blank in the subfile itself (the program loads SDESC as blanks) — the fee-schedule description is filled in only on the invoice lines (VCINVL.ILDESC) and the printed statement. This is an honest note about the shipped screen, not a defect.

VCINVP — the printed client invoice

A printer file (not a screen): the client invoice / statement. It carries an owner/invoice/animal header block (INVOWN), one detail line per invoice line (INVDTL: service code, description, quantity, amount), and a totals block (INVTOT: invoice total and balance due). It is the printable, fixed record of a raised invoice.

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

Honest statement: VETCARE/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user posts, a second user approves" step: the daily cycle closes, invoices, recalls and posts in one flow, and the interactive screens are inquiry-only. The control model the application does have is enforced in the posting programs and the ledger:

In sum, the control posture is a durable ledger + state gating + a cap-and-reject rule on money, all enforced in the batch layer, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

VETCARE/i's work runs as three periodic cycles, each a CL driver that CALLs its component programs in a fixed order: a daily cycle (VCDAILY: close → invoice → recall → pay), a weekly cycle (VCWEEK: the recall reminder run), and a monthly cycle (VCMONTH: AR aging → GL distribution → close summary). All programs take no CALL parameters. Where a program needs a "run date" it currently carries it as a literal in the source (e.g. VCRECRUN uses run date 20260810 / run number 202632; VCARAGE uses 20260831 / 202608; VCGLDST uses batch 202608) — a deliberately simple, deterministic model for the emulator, not a control row. This is the SBMJOB idiom: a scheduled/JOBQ submission is a bare CALL.

-- submit the (parameterless) daily cycle as a batch job
SBMJOB CMD(CALL PGM(VETCARE/VCDAILY)) JOB(VCDAILY)
-- or run a single step interactively
CALL VETCARE/VCVSTCLS

C.1 Full batch program set

ProgramCyclePurposeReadsWritesDSPLY result
VCVSTCLSDaily 1 Close every open visit with ≥1 line; roll lines into VSBILL. VCVISIT, VCVSTLVCVISIT (VSTAT='C'), VCLEDG ('V') CLOSED=n NOLINES=n SKIP=n
VCINVGENDaily 2 Raise one invoice per closed visit; copy lines down verbatim. VCVISIT, VCVSTL, VCANML, VCSVCVCINV, VCINVL, VCVISIT (VSTAT='I'), VCLEDG ('I') GENERATED=n SKIP=n
VCRECGENDaily 3 Generate/roll-forward the recall for each vaccine line of an invoiced visit. VCVISIT, VCVSTL, VCSVCVCRECALL, VCLEDG ('R') GENERATED=n SKIP=n
VCPYPOSTDaily 4 Apply unposted payments; cap overpayments; mark invoice paid at zero. VCPYMT, VCINVVCINV (IBAL/ISTAT), VCPYMT (PYSTAT), VCLEDG ('P') POSTED=n REJECTED=n SKIP=n
VCRECRUNWeekly Sweep open recalls due/overdue at the run date; write worklist; mark reminded. VCRECALLVCRCWRK, VCRECALL (RCSTAT='R') REMINDED=n SKIP=n
VCARAGEMonthly 1 Age each owner's open-invoice balances into current/30/60/90 buckets. VCINVVCAR, VCLEDG ('A') OWNERS=n TOTAL=nnn
VCGLDSTMonthly 2 Post balanced DR/CR pairs for revenue and cash movement (embedded SQL). VCINV, VCPYMT, VCGLDISTVCGLDIST BATCH=nnn ROWS=n REVENUE=nnn
VCMCLOSEMonthly 3 ILE COBOL close summary: counts/totals by invoice status, grand outstanding. VCINV(report only) BILLED / OUTSTAND / INVOICES / PAID / OPEN

C.2 Daily / weekly / monthly detail

Daily — VCDAILY (VCVSTCLS → VCINVGEN → VCRECGEN → VCPYPOST)

(1) VCVSTCLS reads every visit; for each VSTAT='O' visit it sums the visit's lines (VLAMT) into VSBILL and sets VSTAT='C'. A lineless visit cannot close (counted NOLINES). (2) VCINVGEN turns each closed visit into one invoice: the invoice number is derived from the visit ('INV'+%subst(visit:4:6)), the total is copied from VSBILL, the owner is pulled off the animal master, every visit line is copied down verbatim into an invoice line with the fee-schedule description, and the visit moves to VSTAT='I' carrying INVNO back. (3) VCRECGEN reads each invoiced visit's vaccine lines (SVTYPE='V', SVRECDY>0) and, per (ANMID, SVCCD), either rolls the existing recall forward or opens a new one with RCDUE = visit date + SVRECDY. (4) VCPYPOST applies each unposted payment, capping at the invoice balance.

Expected DSPLY (4 lined visits + 1 empty; 3 vaccine lines; 3 payments, 1 overpay):
  VCVSTCLS CLOSED=4 NOLINES=1 SKIP=0
  VCINVGEN GENERATED=4 SKIP=0
  VCRECGEN GENERATED=3 SKIP=...      2x VACRAB rolled to one row + 1x VACBOR; BLOOD1 none
  VCPYPOST POSTED=2 REJECTED=1 SKIP=0 two paid off, one overpayment rejected
Recall roll-forward is the key behaviour: two rabies doses for the same animal keep one (AN00001, VACRAB) row, its RCLASTDT and RCDUE advanced to the later visit (the daily test seeds visit 1 on 20260801 → recall due 20261166, then visit 2 on 20260805 rolls the same row to due 20261170, then a third dose on 20260901 rolls it to 20261266 — still one row). A bordetella line uses its own 180-day interval (20260802 + 180 = 20260982), proving the interval comes from the fee-schedule row, not a constant.

Weekly — VCWEEK (VCRECRUN)

VCRECRUN sweeps every open recall (RCSTAT='O') and, for those due at or before the run date (RCDUE ≤ run date, including overdue), writes a VCRCWRK worklist row and flips the recall to reminded (RCSTAT='R') so it is not re-flagged next week. A recall strictly past the run date is flagged overdue (WKOVER='Y'); one due exactly today is WKOVER='N' (due today is not yet overdue). Recalls not yet due, and already-reminded ones, are skipped.

Expected DSPLY (run date 20260810; overdue + due-today + not-due + already-reminded):
  VCRECRUN REMINDED=2 SKIP=2         overdue(Y) + due-today(N) reminded; 2 skipped

Monthly — VCMONTH (VCARAGE → VCGLDST → VCMCLOSE)

(1) VCARAGE walks every open invoice (ISTAT='O'), buckets its balance by age against the run date using 30/360 day arithmetic (see F.4) into current (0–29), 30–59, 60–89 and 90+, and writes one VCAR row per owner with a balance. Paid invoices contribute nothing. (2) VCGLDST totals invoiced revenue over all invoices and applied cash over payments, then posts only the movement since the last close as two balanced pairs: DR 1200-ARSVC / CR 4100-SVCREV for revenue, DR 1010-CASH / CR 1200-ARSVC for cash collected. (3) VCMCLOSE (ILE COBOL) reports billed, outstanding, and invoice counts by status.

Expected DSPLY (2 owners aged; 5 invoices incl. one paid; one applied payment):
  VCARAGE OWNERS=2 TOTAL=425.00      OW00001 225.00 + OW00002 200.00
  VCGLDST BATCH=202608 ROWS=4 REVENUE=485.00  2 balanced pairs; DR/CR each 485.00 & 60.00
  VCMCLOSE BILLED   485.00           grand billed over all invoices
  VCMCLOSE OUTSTAND 425.00           total open balances
The figures above are the exact hand-derived oracles the test drivers assert (the billed/outstanding totals shift when the drivers add a further 40.00 invoice — e.g. the monthly test ends at BILLED 525.00 / OUTSTAND 465.00 after seeding INV000006). The aging arithmetic is 30/360 on plain YYYYMMDD integers precisely because a naive subtraction across a month boundary would be wildly wrong (20260831 − 20260731 "=" 100, not 31).

C.3 Ordering & dependencies

D. Data Files (Data Dictionary) ↑ top

Every field below is taken verbatim from the DDS/DDL in vet-app/src/sources.mjs. Numeric types follow the DDS convention: nP m = packed decimal, n digits, m decimal places; nS m = zoned decimal; nA = character; nY = numeric-display. Dates are plain YYYYMMDD integers (see F.4). All physical files are UNIQUE-keyed; the two logical files are non-unique by design.

D.1 Reference & party masters

VCOWNR — owner / client master

FieldTypeMeaning
OWNID7AOwner id — the spine key (e.g. OW00001).
OWNAME30AOwner name.
OWADDR30AAddress.
OWPHONE12APhone.
OWEMAIL30AEmail.
OWSTAT1AStatus: A=active, I=inactive.

Key: OWNID.

VCANML — animal / patient master

FieldTypeMeaning
ANMID7AAnimal id — the patient spine key (e.g. AN00001).
OWNID7AOwner FK (one owner keeps many pets).
ANNAME20AAnimal name.
ANSPEC3ASpecies: CAN/FEL/AVI/EXO/EQU
ANBREED20ABreed.
ANSEX1AM/F.
ANFIXED1AY=spayed/neutered, N=intact.
ANDOB8S 0Birth date YYYYMMDD (0 if unknown).
ANWGT5P 1Weight.
ANSTAT1AA=active, D=deceased (retained so the owner keeps history).

Key: ANMID. Logical VCANMLO re-keys the same record on (OWNID, ANMID) — the patients-by-owner path.

VCPROV — provider / staff

FieldTypeMeaning
PROVID6AProvider id (e.g. DR0001, TC0001).
PVNAME30AProvider name.
PVTYPE1AV=veterinarian, T=technician.
PVLIC12ALicence number.
PVSTAT1AA=active, I=inactive.

Key: PROVID.

VCSVC — fee schedule (services AND vaccines)

FieldTypeMeaning
SVCCD6AService / vaccine code (e.g. EXAM01, VACRAB).
SVDESC30ADescription (copied down onto invoice lines).
SVTYPE1AS=service, V=vaccine.
SVPRICE9P 2Unit price.
SVRECDY4S 0Recall interval in days for a vaccine (e.g. 365 rabies, 180 bordetella); 0 for a plain service, which never recalls.
SVSTAT1AStatus A/I.

Key: SVCCD.

D.2 The visit spine & recall

VCVISIT — visit header (the spine)

FieldTypeMeaning
VISITNO9AVisit number (e.g. VIS000301).
ANMID7AAnimal FK.
PROVID6AAttending provider.
VSDT8S 0Visit date YYYYMMDD.
VSREASN30AReason for visit.
VSBILL9P 2Rolled-up billed total (written by VCVSTCLS at close).
VSTAT1AWorkflow: O=open, C=closed, I=invoiced.
INVNO9AInvoice number carried back after invoicing.

Key: VISITNO. Logical VCVISITO re-keys on (ANMID, VISITNO) — the patient-history path both the patient inquiry and the recall run walk.

VCVSTL — visit lines

FieldTypeMeaning
VISITNO9AVisit FK (composite key part 1).
VLINE3S 0Line number (composite key part 2).
SVCCD6AFee-schedule code performed.
VLQTY5P 1Quantity.
VLAMT9P 2Priced extension = SVPRICE × VLQTY as of the line.
VLSTAT1ALine status.

Key: (VISITNO, VLINE).

VCRECALL — vaccination recall

FieldTypeMeaning
ANMID7AAnimal (composite key part 1).
SVCCD6AVaccine code (composite key part 2) — one row per pair.
RCDUE8S 0Next-due date = visit date + SVRECDY.
RCLASTDT8S 0Date of the last dose (the visit date that generated/rolled this row).
RCSTAT1AO=open (not yet reminded), R=reminded, D=done.

Key: (ANMID, SVCCD). A second dose of the same vaccine rolls this one row forward rather than adding a new one.

VCRCWRK — recall reminder worklist (weekly output)

FieldTypeMeaning
WKSEQ8S 0Stable per-(animal, vaccine, run) sequence key.
ANMID7AAnimal.
SVCCD6AVaccine.
WKRUN6S 0Run number (e.g. 202632).
WKDUE8S 0The recall due date at run time.
WKOVER1AY=overdue (due < run date), N=due today.
WKSTAT1AWorklist status.

Key: WKSEQ.

D.3 Billing, AR, GL & the ledger

VCINV — invoice header (one per closed visit)

FieldTypeMeaning
INVNO9AInvoice number = 'INV'+%subst(visit:4:6).
VISITNO9ASource visit.
OWNID7AOwner (pulled off the animal master).
ANMID7AAnimal.
IDT8S 0Invoice date (= visit date).
ITOTAL9P 2Invoice total (= the visit's VSBILL).
IBAL9P 2Live balance, decremented by payments.
ISTAT1AO=open (owed), P=paid in full, V=void.

Key: INVNO. Logical VCINVO re-keys on (OWNID, INVNO) — the AR-aging path.

VCINVL — invoice lines (verbatim copy-down)

FieldTypeMeaning
INVNO9AInvoice FK (composite key part 1).
ILINE3S 0Line number (composite key part 2).
SVCCD6AFee-schedule code.
ILDESC30AFee-schedule description snapshotted at generation (blank if the code is not found).
ILQTY5P 1Quantity.
ILAMT9P 2Line amount (copied from the visit line).

Key: (INVNO, ILINE). The copy-down makes the invoice a fixed, printable record even if the fee schedule later changes.

VCPYMT — payment / receipt

FieldTypeMeaning
PAYNO9APayment number (its own unique business key).
INVNO9AInvoice the payment is applied to.
OWNID7AOwner.
PYDT8S 0Payment date.
PYAMT9P 2Amount tendered.
PYMTHD2ACA=cash, CK=check, CC=card.
PYSTAT1AE=entered (unposted), A=applied, X=rejected (overpayment).

Key: PAYNO.

VCAR — AR aging snapshot (monthly, per owner/run)

FieldTypeMeaning
OWNID7AOwner (composite key part 1).
ARRUN6S 0Aging run (e.g. 202608) (composite key part 2).
ARCUR9P 2Current bucket (0–29 days).
AR309P 230–59 days.
AR609P 260–89 days.
AR909P 290+ days.
ARTOT9P 2Row total (= sum of the four buckets).

Key: (OWNID, ARRUN).

VCLEDG — durable audit trail + re-runnability ledger

FieldTypeMeaning
LSEQ8S 0Stable ledger key, disjoint numeric range per program (see F.3).
LTYPE1AProgram tag: V/I/R/P/W/A/G.
LKEY9AThe business key posted (visit, invoice, animal, payment, owner…).
LDT8S 0Effective date.
LAMT11P 2Primary amount (billed, applied, due date, aged total…).
LAMT211P 2Secondary amount (e.g. remaining balance after a payment).
LREF9ACross-reference (invoice, visit, owner…).
LMEMO30AFree-text memo (VISIT CLOSED, INVOICE GENERATED…).

Key: LSEQ.

VCGLDIST — GL distribution (SQL table)

ColumnTypeMeaning
GDSEQDECIMAL(8,0)Sequence (primary key; allocated from MAX(GDSEQ)).
GDBATCHDECIMAL(6,0)Close batch (e.g. 202608).
GDACCTCHAR(12)Account (1200-ARSVC, 4100-SVCREV, 1010-CASH).
GDDRCRCHAR(1)D=debit, C=credit.
GDAMTDECIMAL(9,2)Posted amount.
GDCATCHAR(3)Category (SVC, CSH).
GDREFCHAR(9)Reference (REVENUE, CASHRCPT).
GDDTDECIMAL(8,0)Posting date.

Primary key GDSEQ; index VCGLDACC on (GDACCT, GDDRCR) so the movement re-derivation (SUM(GDAMT) by account/side) is keyed.

E. Operations Runbook ↑ top

This runbook walks the three cycles as an operator runs them, and gives the exact reconciling figures the test drivers (test/vc_daily.mjs, vc_weekly.mjs, vc_monthly.mjs) assert. Those figures are the reconciliation oracle: if a run's DSPLY lines do not match them for the seeded data, something is wrong. The library list and one-time build are as in B.1: run CALL VETCARE/VCSETUP then CALL VETCARE/VCREFLD once before anything else.

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

Transaction rows (visits, lines, payments) arrive into VCVISIT/VCVSTL/ VCPYMT from whatever feeds them (there is no on-line data-entry screen — see B). Then run the daily cycle:

-- interactively
CALL VETCARE/VCDAILY
-- or as a batch job
SBMJOB CMD(CALL PGM(VETCARE/VCDAILY)) JOB(VCDAILY)

The daily driver runs VCVSTCLS → VCINVGEN → VCRECGEN → VCPYPOST in that fixed order (see C.3). For the seeded daily scenario — four visits with lines plus one empty visit; a rabies dose on two visits for the same animal plus one bordetella dose; three payments one of which overpays — the expected DSPLY is:

  VCVSTCLS CLOSED=4 NOLINES=1 SKIP=0
  VCINVGEN GENERATED=4 SKIP=0
  VCRECGEN GENERATED=3 SKIP=...
  VCPYPOST POSTED=2 REJECTED=1 SKIP=0

What each figure reconciles to (all hand-derived in vc_daily.mjs):

ObjectFigureWhy
INV000001total 93.00, balance 0.00 EXAM01 65.00 + VACRAB 28.00; PAY000001 pays 93.00 → paid off, ISTAT='P'.
INV000002total 28.00, balance 28.00 VACRAB 28.00; PAY000002 tenders 500.00 > balance — overpayment REJECTED, balance unchanged, payment marked X.
INV000003total 24.00, balance 0.00 VACBOR 24.00; PAY000003 pays 24.00 exactly.
INV000004total 145.00, balance 145.00 BLOOD1 145.00, no payment; contributes to owner AR.

The overpayment case is the load-bearing control: a payment larger than the balance is rejected outright (never a negative balance). The invoice-line copy-down invariant is asserted too — the invoice lines of INV000001 sum exactly to its total.

Recall roll-forward. Two rabies doses for one animal keep a single (AN00001, VACRAB) recall row, its RCDUE/RCLASTDT advanced to the later visit; the bordetella dose uses its own 180-day interval from VCSVC, not a constant. That is why VCRECGEN reports GENERATED=3, not one row per vaccine line.

E.2 The weekly reminder run

CALL VETCARE/VCWEEK runs the single program VCRECRUN against run date 20260810 / run number 202632 (literals in the source, see C). It sweeps open recalls (RCSTAT='O') due at or before the run date, writes a VCRCWRK worklist row per hit, and flips each hit to reminded (RCSTAT='R'). For the seeded weekly scenario — one overdue, one due-today, one not-yet-due, one already-reminded — the expected DSPLY is:

  VCRECRUN REMINDED=2 SKIP=2

The overdue recall (WKOVER='Y') and the due-today recall (WKOVER='N' — due today is not yet overdue) are reminded; the not-yet-due and the already-reminded rows are skipped. A re-run of the same week finds every worklist key already on file and reports REMINDED=0 (idempotent by the stable per-(animal, vaccine, run) key).

E.3 Month-end close

CALL VETCARE/VCMONTH runs VCARAGE → VCGLDST → VCMCLOSE against run month 202608 / run date 20260831. For the seeded monthly scenario — five invoices, four open across two owners and one (INV000005, 60.00) fully paid, plus the one applied 60.00 payment — the expected DSPLY is:

  VCARAGE OWNERS=2 TOTAL=425.00
  VCGLDST BATCH=202608 ROWS=4 REVENUE=485.00
  VCMCLOSE BILLED   485.00
  VCMCLOSE OUTSTAND 425.00

The reconciliation, straight from vc_monthly.mjs:

LineFigureDerivation
VCARAGE OWNERS2Only owners with an open balance get a VCAR row; the paid invoice is excluded from aging entirely.
VCARAGE TOTAL425.00 OW00001 225.00 (INV000001 100.00 current + INV000002 50.00 30–59 + INV000003 75.00 90+) + OW00002 200.00 (INV000004 60–89).
VCGLDST REVENUE485.00Sum of ITOTAL over all 5 invoices (100+50+75+200+60), incl. the paid one.
VCGLDST ROWS4Two balanced pairs: revenue DR 1200-ARSVC 485.00 / CR 4100-SVCREV 485.00, and cash DR 1010-CASH 60.00 / CR 1200-ARSVC 60.00.
VCMCLOSE BILLED485.00Grand billed (ITOTAL) over all invoices.
VCMCLOSE OUTSTAND425.00Total open balances (485.00 billed − 60.00 paid).
Movement-only GL & the second-run figures. VCGLDST re-derives what it has already posted from VCGLDIST and posts only the movement since. If the drivers then seed a further 40.00 open invoice (INV000006, OW00001) and re-run, the second GL run posts just that movement — ROWS=2 REVENUE=525.00 — and the COBOL close then reports BILLED 525.00 / OUTSTAND 465.00. A re-run with no new movement posts nothing. An aging re-run over data with no open invoices reports OWNERS=0.

E.4 Failure & re-run rules

F. Developer Reference ↑ top

All source is held as JS string constants in vet-app/src/sources.mjs and loaded into library VETCARE as source members by src/seed.mjs (seedVetcare()), mirroring the ibmi/samples.js DEMOLIB convention. The RPG is fixed-form C-specs and D-specs emitted by two column-exact helpers (C(), D()) interleaved with /free blocks; VCMCLOSE is hand-written ILE COBOL; the GL table is SQL DDL; the cycles and build are CL. VCSETUP creates every object (13 PFs, 2 LFs, 3 DSPFs, 1 PRTF, 1 SQL table + index) and compiles every program — note it uses CRTBNDRPG for the SQLRPGLE program VCGLDST (a platform quirk: CRTBNDRPG, never CRTSQLRPGI).

F.1 Batch programs

VCVSTCLS — visit close (RPG)

Files: VCVISIT (UF), VCVSTL (IF), VCLEDG (UF A). Reads every visit *LOVAL-forward; for a VSTAT='O' visit it sums the visit's VLAMT lines into WSUM, then re-CHAINs the header (the line READEs moved the cursor), sets VSBILL=WSUM, VSTAT='C', and writes the 'V' ledger row. A lineless visit is counted NOLINES and left open. Ledger key 10000000 + visit digits.

VCINVGEN — invoice generation (RPG)

Files add VCANML/VCSVC (IF) and VCINV/VCINVL (UF A). For each VSTAT='C' visit: derive WINV = 'INV'+%subst(visit:4:6), pull the owner off VCANML, write the VCINV header (ITOTAL=IBAL=VSBILL, ISTAT='O'), copy every visit line down into a VCINVL line snapshotting SVDESC (blank if the code is not found), then move the visit to VSTAT='I' carrying INVNO back and write the 'I' ledger row. Ledger key 20000000 + visit digits.

VCRECGEN — recall generation (RPG)

Files: VCVISIT/VCVSTL/VCSVC (IF), VCRECALL/ VCLEDG (UF A). For each VSTAT='I' visit it reads the visit's lines; a line whose VCSVC row is SVTYPE='V' with SVRECDY>0 yields WDUE = visit date + SVRECDY. It CHAINs the composite (ANMID, SVCCD) recall key: if present it rolls the row forward (RCLASTDT/RCDUE advanced), else it writes a new open recall. A SETGT restores the line cursor after the recall I/O. Ledger key 30000000 + (visit digits mod 100000)×10 + (VLINE mod 10), bounded to fit the 8S0 key.

VCPYPOST — payment posting (RPG)

Files: VCPYMT/VCINV (UF), VCLEDG (UF A). For each PYSTAT='E' payment it CHAINs the invoice, then caps the applied amount at the balance: WAMT > WBALWAPPLY=0 (rejected, PYSTAT='X', balance unchanged, counted REJECTED); otherwise it decrements IBAL, marks the payment applied (PYSTAT='A'), and moves the invoice to ISTAT='P' when the balance hits zero. The ledger row carries the applied amount (LAMT) and the remaining balance (LAMT2). Ledger key 40000000 + payment digits.

VCRECRUN — weekly reminder run (RPG)

Files: VCRECALL (UF), VCRCWRK (UF A). Run date/number are literals (20260810/202632). Skips non-open recalls and those with RCDUE > run date; for a hit it writes a VCRCWRK row (WKOVER='Y' if RCDUE < run date, else 'N') and flips the recall to RCSTAT='R'. The re-runnability guard is on the worklist file itself (key 50000000 + animal digits×1000 + vaccine digits), not VCLEDG — the only posting program that guards on its own output file.

VCARAGE — AR aging (RPG)

Files: VCINV (IF), VCAR/VCLEDG (UF A). Run month/date literals 202608/20260831. It sweeps VCINV keyed on INVNO and accumulates per-owner buckets, posting the previous owner's row via the POST subroutine on an owner break and once more at end. Each open invoice is aged in 30/360 days against the run date (see F.4) into current/30/60/90. Paid invoices contribute nothing. Ledger key 60000000 + owner digits×100 + (run − 202600).

Buffer-aliasing care. VCINV and VCAR share the field name OWNID (both are owner-keyed), so AGE1 captures the invoice's fields into work variables before calling POST — because POST WRITEs a VCAR record and would otherwise overwrite the shared OWNID buffer mid-owner. This is a deliberate, commented ordering in the source.

VCGLDST — GL distribution (SQLRPGLE)

Files: VCINV/VCPYMT (IF); embedded SQL against VETCARE.VCGLDIST. Batch literal 202608. It totals invoiced revenue (SUM(ITOTAL) over all invoices) and applied cash (PYSTAT='A' payments), re-derives what it has already posted (SELECT COALESCE(SUM(GDAMT),0) for the revenue-credit and cash-debit sides), and posts only the positive movement as two balanced pairs via the GDROW subroutine, allocating GDSEQ = MAX(GDSEQ)+1. Because it re-derives before posting, a re-run with no new movement writes nothing.

VCMCLOSE — close summary (ILE COBOL)

Sequentially reads the indexed VCINV file, accumulating count and INV-TOTAL, plus paid-count (P) and open-count/outstanding-balance (O), then DISPLAYs five VCMCLOSE lines: BILLED, OUTSTAND, INVOICES, PAID, OPEN. Report-only; it writes no files.

F.2 Interactive programs

VCPATIQ — patient inquiry (RPG + VCPATD)

Files: VCPATD (CF WORKSTN), VCANML/VCOWNR/VCRECALL/ VCINVO (IF). On an animal id it CHAINs the animal, resolves the owner name, then derives two live figures: the earliest RCDUE among the animal's non-done open recalls (walking VCRECALL by animal), and the owner's live AR balance = sum of open (ISTAT='O') invoice balances walked through VCINVO. Neither is stored. An unknown animal clears every field and shows Animal not found..

VCVLIQ — visit inquiry with a subfile (RPG + VCVLD)

Files: VCVLD (CF WORKSTN, SFILE(LSFL:RRN)), VCVISIT/ VCVSTL (IF). Classic subfile discipline: set *IN31 and WRITE the control record to clear (SFLCLR), zero the RRN, then on a found visit load the lines into LSFL and EXFMT the control record. It stays fixed-form around the SETLL/READE load loop on purpose so a free-form IF cannot let the loop run on a miss (which would show stale rows). The subfile leaves SDESC blank — descriptions live only on invoice lines and the printed statement.

VCMENU — menu driver (RPG + VCMENUD)

EXFMTs the menu; MOPT='1' program-CALLs 'VCPATIQ', '2' CALLs 'VCVLIQ', any other non-blank shows Invalid option.. F3 (*IN03) ends the loop.

F.3 The re-runnability ledger (key ranges)

Every posting program derives a stable guard key from a business key (never a per-run counter), CHAINs it with (EN) before doing any work, and refuses if the row is already present. The ranges are disjoint so the keys never collide across programs:

ProgramTagGuard fileKey formula
VCVSTCLSVVCLEDG10000000 + visit digits
VCINVGENIVCLEDG20000000 + visit digits
VCRECGENRVCLEDG30000000 + (visit digits mod 100000)×10 + (VLINE mod 10)
VCPYPOSTPVCLEDG40000000 + payment digits
VCRECRUNWVCRCWRK50000000 + animal digits×1000 + vaccine digits
VCARAGEAVCLEDG60000000 + owner digits×100 + (run − 202600)
VCGLDSTGVCGLDISTMAX(GDSEQ)+1; movement re-derived from the table before posting

Note VCRECRUN and VCGLDST guard on their own output file rather than VCLEDG: the weekly run cannot re-flag the same recall for the same run, and the GL feed cannot re-post the same movement.

F.4 Date arithmetic & GL feed

Every date in the app is a plain YYYYMMDD integer. Two conventions matter:

G. Glossary ↑ top

AR balance (owner)
The sum of an owner's open invoice balances. Derived live on the patient inquiry, and summed into buckets by the monthly aging run. Keyed by owner, not by animal — two pets of one owner show the same figure.
Copy-down (invoice lines)
At invoice generation each visit line is copied verbatim into an invoice line, snapshotting the fee-schedule description, so the invoice stays a fixed printable record even if the fee schedule later changes. Invariant: invoice total = sum of invoice lines, always.
Fee schedule (VCSVC)
One shared price list for both services (SVTYPE='S') and vaccines (SVTYPE='V'). A vaccine's SVRECDY drives its recall interval.
Idempotent / re-runnable
Safe to run again with the same result. Every posting program CHAINs a stable guard key with (EN) before working and skips anything already posted, so a re-run does not double-post.
Invoice balance (IBAL)
Invoice total minus the payments applied to that invoice, always. Never negative — an overpayment is rejected, not absorbed.
Ledger (VCLEDG)
The durable audit trail every posting program writes, and the file whose stable, disjoint-range key makes each program re-runnable. See F.3.
Overpayment cap
The VCPYPOST rule that a payment larger than the invoice balance is rejected outright (PYSTAT='X', balance unchanged) rather than driving the balance negative.
Patient / owner
An animal (VCANML, the patient) and its client (VCOWNR, the owner). One owner keeps many pets; a deceased patient is retained so the owner keeps history.
Recall (VCRECALL)
One row per (animal, vaccine) holding that animal's next-due date for that vaccine (RCDUE = visit date + SVRECDY). A repeat dose rolls the same row forward rather than duplicating it.
Recall roll-forward
The behaviour where a second dose of the same vaccine for the same animal advances the existing recall row's RCDUE/RCLASTDT instead of creating a new row — a due-date applied to a composite-key accumulator.
Reminder run (VCRECRUN)
The weekly sweep of open recalls due at or before the run date, writing a VCRCWRK worklist and marking each hit reminded so it is not re-flagged next week.
SBMJOB
Submit Job — the IBM i command that queues a program as a batch job (e.g. SBMJOB CMD(CALL PGM(VETCARE/VCDAILY))). The parameterless cycles make a scheduled submission trivial.
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). VCVLIQ's visit-line list is the app's subfile (SFLPAG(5)/SFLSIZ(20)).
Visit (VCVISIT)
The spine of the app: an animal's visit accumulates service/vaccine lines, closes (rolling them into VSBILL), and becomes exactly one invoice. Workflow OCI.
30/360
The day-count convention the AR aging uses: YYYY×360 + MM×30 + DD, so ages are meaningful across month boundaries (a naive YYYYMMDD subtraction is not).