PROCURE/i — Purchasing & Accounts Payable

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

PROCURE/i is a purchase-to-pay application: a vendor master, purchase-order raising and approval, goods receipt, the classic three-way match (PO vs receipt vs invoice), invoice registration with hold/release, a weekly payment run that captures early-settlement discount, remittance advice, AP ageing, a received-not-invoiced accrual, an expense/AP GL distribution, and an annual vendor spend / 1099-style summary with accrual reversal. It is a classic mixed-language IBM i estate: fixed-form and free-form RPG for the batch posting logic, one SQLRPGLE program for the GL post, one ILE COBOL control report, DDS physical/logical/display/printer files, a SQL table, and CL drivers for setup and the four job cycles. This manual is the reference for the operator who runs the online inquiry screens and the periodic batch cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (ap-app/src/sources.mjs, src/seed.mjs, and the test/ap_build.mjs, ap_daily.mjs, ap_cycles.mjs and ap_interactive.mjs drivers). Everything lives in library PROCURE.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

PROCURE/i runs the purchase-to-pay cycle end to end:

A.2 The AP ledger is the spine: SUM(LAMT) IS the control balance

The application has no single “business-rules layer” the way a SQL-PL app does; the logic lives in the RPG posting programs. What ties them together is one durable file, APLEDG, that every posting program contributes to. Its design carries the two invariants the whole app is built on:

Two further invariants ride on top: the payment invariant PNET = PGROSS − PDISC on every cheque, and the “discount is income” rule — the vendor balance falls by the full gross settled, never by the net cash, because the captured discount is discount-received income, not a reduction of what was owed.

A.3 Component & flow

  REFERENCE / ONLINE            BATCH (four CL-driven cycles)
  -----------------            ----------------------------
  APREFLD  (seed data)         DAILY  APDAILY:
  APMENU (5250)                  APPOAPP  re-total + approve POs
    1 -> APVENDIQ  (vendor)      APRCPOST goods-receipt posting  --> APLEDG 'R'
    2 -> APPOIQ    (PO+subfile)  APMATCH  3-way match (hold/approve)
                                 APINVREG invoice registration   --> APLEDG 'I'  (+VBAL)
                                 APHOLDRP hold/release report
                                 APPOCLS  close fully-invoiced POs

                               WEEKLY APWEEK:
                                 APPAYRUN payment run + discount  --> APLEDG 'P'  (-VBAL)
                                 APREMIT  remittance advice (PRTF)

     APVEND  APPOH  APPOL       MONTHLY APMONTH:
       |  APRCPT APINVH APINVL    APAGERP  AP ageing snapshot    --> APAGE
       |  APPAY                   APACCR   GRNI accrual          --> APLEDG 'A'
       v                          APGLPST  expense/AP GL post    --> APGLDIST (SQL)
     APLEDG  <--- every           APAPRPT  AP control report (COBOL, reads APLEDG)
     posting program writes
       |                        ANNUAL APYEAR:
       +--> APAPRPT proves        APSPEND  vendor spend + 1099   --> APSPND
            SUM(LAMT) balances     APREVACC accrual reversal      --> APLEDG 'V'
                                   APAPRPT  AP control report

A single event — say a goods receipt — flows: an entered APRCPT row is read by APRCPOST → its quantity is added into the matching APPOL line (capped at ordered) → a receipt-valued APLEDG row is written under a stable key → the receipt flips to posted. Downstream, an invoice for those goods must pass APMATCH before it can ever reach APPAYRUN.

A.4 Object inventory

ObjectTypeRole
APVENDPFVendor master (terms, AP balance, 1099 flag).
APPOHPFPurchase-order header.
APPOLPFPurchase-order lines (composite key PONO+POLINE).
APPOLLFLFPO lines keyed by ITEMNO (alternate path).
APRCPTPFGoods receipts.
APINVHPFInvoice header.
APINVLFLFInvoices keyed by DUEDT (payment-run path; fmt APINVDR).
APINVLPFInvoice lines (tie to PO line via IPOLINE).
APPAYPFPayment header (one per cheque).
APLEDGPFThe AP ledger — the durable audit spine.
APAGEPFAP ageing snapshot (per run + vendor).
APSPNDPFVendor spend analysis (per year + vendor).
APGLDISTSQL tableExpense/AP GL distribution (+ index APGLDACC).
APVENDD / APPOD / APMENUDDSPFVendor inquiry / PO+subfile inquiry / menu.
APREMPPRTFRemittance advice / report layout.
APREFLDRPGReference-data seeder (vendors, POs, PO lines).
APPOAPP APRCPOST APMATCH APINVREG APHOLDRP APPOCLSRPGThe six daily posting programs.
APPAYRUN APREMITRPGWeekly payment run + remittance.
APAGERP APACCRRPGMonthly ageing + GRNI accrual.
APGLPSTSQLRPGLEMonthly expense/AP GL post (embedded SQL).
APSPEND APREVACCRPGAnnual spend analysis + accrual reversal.
APVENDIQ APPOIQ APMENURPGInteractive vendor / PO inquiry + menu.
APAPRPTILE COBOLAP control report over the ledger.
APSETUP APDAILY APWEEK APMONTH APYEARCLObject build + the four job cycles.

The catalogue is 12 PF/LF files, 1 SQL table + 1 index, 3 DSPF, 1 PRTF, 17 RPG programs (one of them SQLRPGLE), 1 ILE COBOL program and 5 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

PROCURE/i has no CICS transaction identifiers. On IBM i each program is reached by name from a 5250 command-entry line (interactive programs) or from a JOBQ / scheduler (batch). Before invoking anything the job's library list must include PROCURE — the tested jobs run with LIBL = QSYS QGPL PROCURE QTEMP and CURLIB = PROCURE. Object creation is done once by the setup CL, which itself does ADDLIBLE LIB(PROCURE).

To do thisType on the command line
Build every object & compile every program (once)CALL PROCURE/APSETUP
Load the reference data (vendors, POs, PO lines)CALL PROCURE/APREFLD
Open the operator menu (routes to the two inquiries)CALL PROCURE/APMENU
Open the vendor inquiry directlyCALL PROCURE/APVENDIQ
Open the PO inquiry (with a line subfile) directlyCALL PROCURE/APPOIQ
Run the daily / weekly / monthly / annual cycleCALL PROCURE/APDAILYAPWEEK / APMONTH / APYEAR (or SBMJOB them)

The batch cycles take no CALL parameters: each is a CL program that simply calls its posting programs in order, so a scheduled submission is a bare CALL of the cycle. The three interactive programs are inquiry-only (see B.2); all posting happens in batch.

Honest note on processing dates. Unlike some SteelFrame X apps, PROCURE/i has no single control-table row holding the processing date. The batch programs carry their key dates as literals in the source — the payment run uses WRUNDT = 20260810 and pay-through WTHRU = 20260910; the ageing uses WASOF = 20260831, run 202608; the accrual/GL post stamp 20260831 / period 202608; the annual programs use year 2026. Advancing the business date is therefore a source change and recompile, not a data update. This is called out plainly so operators do not go looking for a control file that does not exist.

B.2 The menu & the two inquiry screens

APMENU — the operator menu (APMENUD)

APMENU presents a two-option menu and routes by program-to-program CALL: option 1 calls APVENDIQ, option 2 calls APPOIQ; any other non-blank option shows Invalid option. on the menu itself. F3 ends the program; each inquiry's F3 returns to the menu.

PROCURE/i Main Menu 1. Vendor Inquiry 2. Purchase Order Inquiry Option . . . . : _ F3=Exit Enter=Select

APVENDIQ — vendor inquiry (APVENDD, plain screen)

A non-subfile screen: key a vendor number, press Enter, and it CHAINs APVEND and renders the master record plus the derived terms. A miss clears the detail fields and shows Vendor not found. rather than leaving stale values on screen. The AP balance shown is the live VBAL the daily cycle posted — not a re-derivation.

Vendor Inquiry - PROCURE/i Vendor number: V00001 Name . . . . . : ACME INDUSTRIAL SUPPLY Terms . . . . : 2/10 N30 Discount % . .: 2.00 Disc days . . : 10 Net days . . . : 30 AP balance . . : 2,250.00 1099 vendor . : N Vendor found. F3=Exit Enter=Inquire
Field (DDS)Shows
IVENDInput: vendor number to CHAIN.
DNAME / DTERMSVendor name; terms code (e.g. 2/10 N30).
DDISC / DDDAYS / DNDAYSDiscount % (rendered 2.00), discount days, net days.
DBALAP balance, edited with thousands separators (%EDITC 'K', e.g. 2,250.00).
D1099 / DMSG1099 flag; status/found message line.

APPOIQ — purchase-order inquiry (APPOD, subfile screen)

The app's subfile screen. Key a PO number; the header (APPOH) renders vendor, re-derived order total and status, and a subfile (DDS record PSFL under control PCTL, SFLPAG(5) / SFLSIZ(20), ROLLUP/ROLLDOWN) loads the order's lines showing ordered / received / invoiced quantities and line status. Enquiring a second PO clears and reloads the subfile, so the row count tracks the new order (a one-line PO shows one row); an unknown PO shows Purchase order not found. with an empty subfile.

Purchase Order Inquiry - PROCURE/i PO number: PO000001 Vendor . . . . : V00001 Order total . : 2,325.00 Status . . . . : A Lin Item Ordered Received Invoiced S 1 RM100001 100.00 100.00 100.00 I 2 RM100002 40.00 40.00 40.00 I 3 RM100003 10.00 0.00 0.00 O Purchase order found. F3=Exit Roll=Page Enter=Inquire
Field (DDS)Shows
IPONOInput: PO number to CHAIN.
DPVEND / DPTOT / DPSTATHeader vendor, order total (edited), status.
SLINE / SITEMSubfile: line number, item.
SORD / SRCV / SINVSubfile: ordered / received / invoiced quantity (edited).
SSTATSubfile: line status (O open, R received, I invoiced, C closed).

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

Honest statement: PROCURE/i does not model a true four-eyes maker–checker / separate-authorization workflow. The PO approval sweep APPOAPP approves automatically for any active vendor; the manual band POAPLM='M' that an over-10000.00 order receives is a label recording that it exceeded the auto limit, not a hold-for-a-second-approver gate — the order is approved in the same pass. The screens are inquiry-only and post nothing. The manual documents the control model the application does have, and it is a strong one, enforced in the posting programs:

In sum, the control posture is match-gating + receipt-bounding + a signed audit ledger + hard idempotency, all enforced in the posting programs, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

PROCURE/i runs as four CL-driven cycles: a daily purchase-to-pay cycle, a weekly payment cycle, a monthly close and an annual roll. Each CL simply ADDLIBLEs PROCURE and calls its posting programs in a fixed order, then sends a completion message. No cycle takes parameters (see the B.1 note on how dates are set).

-- one-time build, then load reference data
CALL PROCURE/APSETUP
CALL PROCURE/APREFLD

-- then submit a (parameterless) cycle
SBMJOB CMD(CALL PGM(PROCURE/APDAILY)) JOB(APDAILY)

C.1 Full batch program set

ProgramCyclePurposeReadsWrites / result
APREFLDsetupSeed 4 vendors, 4 POs, 7 PO lines.APVEND/APPOH/APPOL; DSPLY VENDORS=4 POS=4 POLINES=7.
APPOAPPdailyRe-total entered POs & approve (active vendor only).APPOH/APPOL/APVENDPOTOT/POSTAT/POAPLM; DSPLY APPROVED=/SKIP=.
APRCPOSTdailyPost entered receipts into PO lines (capped, cancel-aware).APRCPT/APPOL/APPOHRCVQTY, receipt→P, ledger 'R'; DSPLY POSTED=/SKIP=/CAPPED=.
APMATCHdaily3-way match; approve (O) or hold (H) each entered invoice.APINVH/APINVL/APPOLISTAT/IHOLDCD, INVQTY on clean match; DSPLY MATCHED=/HELD=/SKIP=.
APINVREGdailyRegister matched invoices; raise VBAL; stamp discount date.APINVH/APVENDVBAL/VYTD, DISCDT, ledger 'I' (+); DSPLY REGISTERED=/SKIP=.
APHOLDRPdailyReport held invoices split by hold code.APINVH/APVENDDSPLY HELD=/QTY=/PRC=/NOPO=/AMT=.
APPOCLSdailyClose a PO whose every line is invoiced.APPOH/APPOLPOSTAT→C; DSPLY CLOSED=/SKIP=.
APPAYRUNweeklyPay open invoices due by pay-through; capture discount.APINVLF/APINVH/APVENDAPPAY row, VBAL−gross, ledger 'P' (−); DSPLY PAID=/GROSS=/DISC=/SKIP=.
APREMITweeklyPrint remittance advice (PRTF, page overflow).APPAY/APVEND/APINVHSpooled advice; DSPLY LINES=/CASH=.
APAGERPmonthlyAge every unpaid invoice into current/1-30/31-60/61+ buckets.APVEND/APINVHAPAGE row per vendor; DSPLY RUN=/VENDORS=/SKIP=.
APACCRmonthlyAccrue received-not-invoiced (received>invoiced) at PO price.APPOL/APPOHLedger 'A'; DSPLY ACCRUED=/AMT=/SKIP=.
APGLPSTmonthlyPost expense/AP GL distribution from the ledger (SQLRPGLE).APLEDG + APGLDISTAPGLDIST DR/CR rows; DSPLY PER=/ROWS=.
APAPRPTmonthly/annualAP control report: proves SUM(LAMT) balances (ILE COBOL).APLEDGDSPLY INVOICED/PAID/DISCOUNT/CONTROL/ROWS.
APSPENDannualVendor spend + 1099-style summary from the ledger.APVEND/APLEDGAPSPND row per vendor; DSPLY YEAR=/VENDORS=/R1099=/SKIP=.
APREVACCannualReverse every GRNI accrual with an equal-and-opposite row.APLEDGLedger 'V' (−); DSPLY REVERSED=/AMT=/SKIP=.

C.2 Daily / weekly / monthly / annual detail

Daily — APDAILY (APPOAPP → APRCPOST → APMATCH → APINVREG → APHOLDRP → APPOCLS)

The purchase-to-pay chain. Approve orders, post receipts, match invoices, register the clean ones, report the held ones, close finished orders. Worked against the seeded reference data plus the receipts and invoices the tests plant, the expected results are hand-derivable:

Expected DSPLY (reference data + the day's receipts/invoices):
  APPOAPP APPROVED=3 SKIP=1     3 active-vendor POs; V00004 on hold -> skipped
  APRCPOST POSTED=4 SKIP=0      four receipts posted to the right (PO,line)
  APMATCH MATCHED=1 HELD=3      1 clean (O); 3 held Q/P/N
  APINVREG REGISTERED=1         only the matched invoice registers; VBAL +2250.00
  APHOLDRP HELD=3 ... AMT=20137.50
  APPOCLS CLOSED=0              PO000001 line 3 never invoiced -> stays open
The match blocks over-billing. IN000002 invoices 200 against only 120 received → hold Q; IN000003 bills 19500.0000 against a PO price of 18000.0000 → hold P; IN000004 names a non-existent PO line → hold N. None register, so their vendors' balances stay 0.00, and none can reach the payment run.

Weekly — APWEEK (APPAYRUN → APREMIT)

APPAYRUN walks the due-date logical APINVLF and settles every 'O' invoice due on or before the pay-through date. If the run date is on or before the invoice discount date it captures VDISCPC% of the total, else it pays gross. One APPAY cheque per invoice with PNET = PGROSS − PDISC; the vendor balance falls by the full gross.

Expected DSPLY (2 open invoices; 1 held is skipped):
  APPAYRUN PAID=2 GROSS=3450.00 DISC=24.00
                               1200.00 paid inside 2/10 window -> 24.00 discount, 1176.00 cash
                               2250.00 past its window -> gross, 0.00 discount
  APREMIT LINES=2 CASH=3426.00 total net cash = 1176.00 + 2250.00

Monthly — APMONTH (APAGERP → APACCR → APGLPST → APAPRPT)

Age the unpaid invoices, accrue received-not-invoiced goods, post the GL distribution, and prove the control balance. Ageing buckets by whole days past due using exact civil-days arithmetic (F.3); the GRNI accrual is (RCVQTY−INVQTY)×POPRICE per PO line; the GL post writes balanced DR/CR pairs and posts only the movement since the last close.

Expected DSPLY / GL (as of 20260831, period 202608):
  APAGERP RUN=202608 VENDORS=4     the one open (held) 600.00 invoice ages to the 1-30 bucket
  APACCR  ACCRUED=2 AMT=18360.00   360.00 (120x3) + 18000.00 (1x18000)
  APGLPST PER=202608 ROWS=...      5100-EXP DR 21810.00; 1010-CSH CR 3426.00; 4900-DSC CR 24.00; 2100-AP both sides 3450.00
  APAPRPT CONTROL ...              COBOL re-proves SUM(LAMT)

Annual — APYEAR (APSPEND → APREVACC → APAPRPT)

Summarise each vendor's year from the ledger (gross invoiced, discount taken, net cash, invoice count, 1099 flag), then reverse the GRNI accruals so they do not double-count into the new year.

Expected DSPLY (year 2026):
  APSPEND YEAR=2026 VENDORS=4 R1099=1  V00001 gross 3450.00 / disc 24.00 / net 3426.00; V00003 flagged
  APREVACC REVERSED=2 AMT=18360.00     two 'V' rows totalling -18360.00; accrual nets to 0

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library PROCURE, grounded in src/sources.mjs. Dates are stored as signed 8S0 integers in YYYYMMDD form (or YYYYMM / YYYY for periods); money is packed decimal; unit prices are 11P4 (four decimals); the discount percent is 5P2.

APVEND — Vendor master (key VENDNO)

FieldTypeMeaning
VENDNO6AVendor number (key), e.g. V00001.
VNAME30AVendor name.
VTERMS10ATerms code text, e.g. 2/10 N30 (display only).
VDISCPC5P2Early-settlement discount percent (2.00 = 2%).
VDISCDY3S0Days within which the discount applies.
VNETDY3S0Net days (informational).
VBAL13P2Open AP balance: +invoice total on registration, −gross on payment.
VYTD13P2Year-to-date invoiced.
V10991AY = 1099-reportable vendor.
VSTAT1AA active, H hold (approval skips it).

Seeded: V00001 ACME 2/10 N30; V00002 BOREAL net 30 (no discount); V00003 CALDER 1/15 N45 (1099); V00004 DELTA net 60 (on hold).

APPOH — Purchase-order header (key PONO)

FieldTypeMeaning
PONO8APO number (key), e.g. PO000001.
VENDNO6AOwning vendor.
PODT / PONEED8S0Order date / needed-by date.
POTOT13P2Extended order value, re-derived from the lines by the approval sweep.
POSTAT1AE entered, A approved, R received, C closed, X cancelled.
POAPLM1AApproval band: A auto (≤10000), M manual band (>10000). Label, not a hold.
POBUYER6ABuyer code.

APPOL — Purchase-order lines (composite key PONO, POLINE)

FieldTypeMeaning
PONO / POLINE8A / 3S0PO + line number (composite key, stable order).
ITEMNO / PLDESC8A / 25AItem number; line description.
POQTY9P2Ordered quantity.
POPRICE11P4Unit price to four decimals (the match's price test).
RCVQTY9P2Quantity goods receipt has booked (capped at POQTY).
INVQTY9P2Quantity invoicing has consumed (only on a clean match).
PLACCT9AExpense account code.
PLSTAT1AO open, R received, I invoiced, C closed.

APPOLLF is a non-unique logical over these lines keyed by ITEMNO then PONO — “which orders is this part on?” as a keyed read rather than a scan.

APRCPT — Goods receipts (key RCPTNO)

FieldTypeMeaning
RCPTNO8AReceipt number (key).
PONO / RLINE8A / 3S0The PO line this delivery is against.
RQTY / RDT9P2 / 8S0Delivered quantity; receipt date.
RSTAT1AE entered (not posted), P posted. The flip to P is part of the idempotency guard.

APINVH — Invoice header (key INVNO)

FieldTypeMeaning
INVNO8AInvoice number (key).
VENDNO / PONO6A / 8AVendor; the PO being billed.
INVDT / DUEDT / DISCDT8S0Invoice date; due date (drives payment selection); discount date (stamped at registration).
INVTOT13P2Invoice total.
IDISCA / IPAID11P2 / 13P2Discount captured; net cash paid.
ISTAT1AE entered, H held, O open/approved, P paid, X cancelled.
IHOLDCD1AHold reason: Q qty over receipt, P price variance, N no matching PO.
IPAYNO8APayment/cheque number once settled.

APINVLF is a logical over the invoices keyed by DUEDT then INVNO, record format APINVDR (renamed so the payment run can open both this LF for the due-date read and the base APINVH for the keyed write-back).

APINVL — Invoice lines (composite key INVNO, ILINE)

FieldTypeMeaning
INVNO / ILINE8A / 3S0Invoice + line number (composite key).
IPOLINE3S0The PO line this line claims to bill (the match join).
IQTY / IPRICE9P2 / 11P4Billed quantity; billed unit price (compared to POPRICE).
IAMT13P2Extended line value.
ILSTAT1ALine status.

APPAY — Payment header (key PAYNO)

FieldTypeMeaning
PAYNO8ACheque number (key), derived from the invoice for stability.
VENDNO / PAYDT6A / 8S0Vendor; payment date.
PGROSS13P2Gross settled (the invoice total).
PDISC11P2Early-settlement discount captured.
PNET13P2Cash paid — the invariant is PNET = PGROSS − PDISC.
PCNT / PSTAT3S0 / 1AInvoice count on the cheque; status.

APLEDG — AP ledger (key LSEQ) — the audit spine

FieldTypeMeaning
LSEQ8S0Ledger key (unique), derived from a stable business key in a per-program range (F.2).
VENDNO / LTYPE6A / 1AVendor; class: R receipt, I invoice, P payment, A accrual, V reversal.
LDT8S0Posting date.
LAMT13P2Signed amount: +invoice, −payment, +accrual, −reversal. SUM(LAMT) = AP control balance.
LDISC11P2Discount on a payment row.
LREF / LMEMO8A / 25ASource reference (receipt/invoice/PO); memo text.

APAGE — AP ageing snapshot (composite key AGRUN, VENDNO)

FieldTypeMeaning
AGRUN / VENDNO6S0 / 6AAgeing run period (YYYYMM) + vendor (composite key; re-run refused).
AGCURR / AG30 / AG60 / AG9013P2Current, 1-30, 31-60, 61+ days-past-due buckets.
AGTOT13P2Vendor total across buckets.

APSPND — Vendor spend analysis (composite key SPYR, VENDNO)

FieldTypeMeaning
SPYR / VENDNO4S0 / 6AYear + vendor (composite key; re-run refused).
SPGROSS / SPDISC / SPNET13P2 / 11P2 / 13P2Gross invoiced; discount taken; net cash paid.
SPINVS / SP10995S0 / 1AInvoice count; 1099-reportable flag carried from the vendor.

APGLDIST — Expense/AP GL distribution (SQL table, PK GLSEQ)

FieldTypeMeaning
GLSEQDECIMAL(8,0)GL sequence (PK); continues across runs so months do not collide.
GLPERDECIMAL(6,0)Period YYYYMM.
ACCT / DRCRCHAR(9) / CHAR(1)Account (5100-EXP, 2100-AP, 1010-CSH, 4900-DSC, 2150-GRN); D/C.
AMTDECIMAL(13,2)Posting amount.
GLREF / GLDTCHAR(8) / DECIMAL(8,0)Posting group (INVOICES/PAYMENTS/ACCRUAL); post date.

Index APGLDACC is over (ACCT, DRCR).

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. First time only: CALL PROCURE/APSETUP (build objects & compile), then CALL PROCURE/APREFLD (load reference vendors/POs/lines). Confirm the setup-complete banner and APREFLD VENDORS=4 POS=4 POLINES=7.
  2. Enter the day's goods receipts into APRCPT (RSTAT='E') and invoices into APINVH/APINVL (ISTAT='E'), by whatever data-entry path your site uses.
  3. Submit the daily cycle: SBMJOB CMD(CALL PGM(PROCURE/APDAILY)).
  4. Post-check the daily run (below).
  5. On a payment day, submit APWEEK; check PAID=/GROSS=/DISC= and the remittance CASH=, and confirm the spooled advice.
  6. Answer vendor/PO questions interactively with CALL PROCURE/APMENU (inquiry-only).

Pre-checks: confirm the job's library list includes PROCURE; confirm the entered receipts/invoices carry 'E' status so the posting programs pick them up.

Post-checks after the daily cycle:

E.2 Period close

  1. Weekly: run every due payment run so open invoices are settled before the month is aged and posted.
  2. Month-end: submit APMONTH. Confirm APAGERP wrote one row per vendor, APACCR accrued the received-not-invoiced lines, and APGLPST posted balanced DR/CR rows for the period.
  3. Year-end: submit APYEAR. Confirm the spend analysis and that every accrual was reversed (the accrual class nets to zero).
  4. Review and reconcile (below).

Reconciling figures (the same ones the volume/battle suites check against hand-derived oracles):

-- reconcile the AP control balance and the GL balance
STRSQL
  SELECT LTYPE, SUM(LAMT) FROM PROCURE.APLEDG GROUP BY LTYPE;
  SELECT DRCR, SUM(AMT)  FROM PROCURE.APGLDIST GROUP BY DRCR;

E.3 Failure & re-run rules

Each program DSPLYs a result line naming its counts (F.5). The overriding rule is that every posting program is idempotent by a stable ledger/status key, so a partial or repeated run is safe.

SituationBehaviourAction
Daily cycle fails partwayOnly-entered guards (POSTAT/RSTAT/ISTAT='E') plus stable ledger keys.Re-submit APDAILY: done work is skipped, the rest completes. Balances do not double.
Re-run receipt postingRSTAT='P' and the 1000xxxx ledger key already exist.Safe no-op: POSTED=0; RCVQTY does not grow.
Re-run the matchOnly ISTAT='E' invoices are considered.Safe no-op: MATCHED=0 HELD=0.
Re-run invoice registration2000xxxx ledger key already exists.Safe no-op: REGISTERED=0; VBAL does not double.
Re-run the payment run3000xxxx ledger key already exists.Safe no-op: PAID=0; no second cheque; balance does not go negative. This is the app's most important guard.
Re-run ageing / accrual / spend / reversalComposite-key or 4000/5000xxxx ledger guards already satisfied.Safe no-op (VENDORS=0 / ACCRUED=0 / REVERSED=0); totals unchanged.
GL post re-run in a new monthPosts only the movement since the last close; GLSEQ continues, only SQLCOD=0 rows counted.Re-runnable; a refused insert is not reported as posted (a historical bug, now guarded).
Held invoice needs payingA 'H' invoice is never selected by the payment run.Correct the receipt/price/PO, set it back to 'E' and re-match so it becomes 'O'.
Because every money movement is a signed, keyed row in APLEDG with a memo, any cycle's effect is fully reconstructable after the fact — and SUM(LAMT) is always a one-query proof that the AP sub-ledger balances.

F. Developer Reference ↑ top

The program surface, from ap-app/src/sources.mjs. RPG programs are built column-exact fixed-form C/D specs interleaved with /free blocks (the same helper convention as ibmi/samples.js). All objects are in library PROCURE.

F.1 Program surface

APREFLD — reference seeder (RPG)
Writes 4 vendors, 4 POs and 7 PO lines with hand-computable extended values, so every downstream oracle is derivable from this one program. POs seed with POTOT=0; the approval sweep re-derives the total.
APPOAPP — PO approval sweep (RPG, daily)
For each POSTAT='E' order: CHAINs the vendor (skip unless VSTAT='A'), re-derives POTOT from the lines (Σ POQTY×POPRICE, excluding cancelled lines), sets band A/M at the 10000.00 threshold, flips to 'A'. Idempotent by status.
APRCPOST — goods-receipt posting (RPG, daily)
Composite KLIST CHAIN on (PONO,RLINE). Caps the delivery at the ordered quantity, refuses a delivery against a cancelled PO, adds into RCVQTY, flips the line to R when fully received, writes a ledger 'R' row, flips the receipt to P. Idempotent by key 10000000+receipt digits.
APMATCH — three-way match (RPG, daily)
Two passes: pass 1 tests every invoice line (PO line exists? qty within receipt? price = PO price?), consuming nothing; a failure sets the hold code (N/Q/P), holds the invoice and returns. Only a clean invoice runs pass 2, consuming INVQTY and approving to 'O'. Idempotent (only 'E' considered).
APINVREG — invoice registration (RPG, daily)
For each 'O' invoice not yet on the ledger: raises VBAL/VYTD by the total, computes the discount date by exact civil-days arithmetic (F.3), writes a positive ledger 'I' row, stamps DISCDT. Idempotent by key 20000000+invoice.
APHOLDRP — hold/release report (RPG, daily)
Counts held invoices split by hold code and totals their value. Read-only report.
APPOCLS — PO close (RPG, daily)
Closes an A/R order whose every line is 'I'; leaves it if any line is still open. Idempotent by status.
APPAYRUN — payment run (RPG, weekly)
Walks APINVLF in due-date order; pays each 'O' invoice due by the pay-through date. Captures VDISCPC% if the run date is on or before the discount date. Writes one APPAY cheque (PNET=PGROSS−PDISC), relieves VBAL by the full gross, writes a negative ledger 'P' row. Idempotent by key 30000000+invoice.
APREMIT — remittance advice (RPG, weekly)
Prints one vendor block per payment to the APREMP PRTF with page overflow (OFLIND(*IN90)) and a cash total. Printer fields are R-prefixed to avoid a namespace collision with the APPAY record format.
APAGERP — AP ageing (RPG, monthly)
Buckets every unpaid (O/H) invoice by whole days past due at the as-of date (exact civil-days) into current/1-30/31-60/61+. One row per vendor. Idempotent by composite (AGRUN,VENDNO). Named APAGERP so it does not collide with the APAGE file.
APACCR — GRNI accrual (RPG, monthly)
For each PO line with RCVQTY>INVQTY: accrues the difference at PO price as a ledger 'A' row. Idempotent by key 40000000+PO digits×10+line.
APGLPST — expense/AP GL post (SQLRPGLE, monthly)
Accumulates the ledger by class, then posts only the movement since the last close (reads back what each group already contributed) as balanced DR/CR rows into APGLDIST. Continues GLSEQ; counts only SQLCOD=0 inserts.
APSPEND — vendor spend + 1099 (RPG, annual)
Per vendor from the ledger: gross invoiced + count ('I'), discount + net cash ('P'), and the carried 1099 flag. One row per (year,vendor). Idempotent by composite key.
APREVACC — accrual reversal (RPG, annual)
Reverses each 'A' row with an equal-and-opposite 'V' row at key (accrual key + 10000000). Collects the accruals in one pass and writes only into the disjoint 50000000+ range so the new rows are not read back. Idempotent by that key.
APVENDIQ / APPOIQ / APMENU — interactive (RPG)
Vendor CHAIN inquiry; PO header + line subfile (clear/reload discipline); menu with option routing by program CALL. Inquiry-only.
APAPRPT — AP control report (ILE COBOL)
Sequentially reads APLEDG, accumulating INVOICED, PAID, DISCOUNT and the CONTROL total (SUM(LG-AMT)), and DISPLAYs them — the independent proof that the ledger balances.

F.2 The ledger-key idempotency scheme

Every posting program derives its LSEQ from a stable business key in a disjoint range, and guards each post with a CHAIN(EN) that skips when the row already exists. This is what makes each cycle re-runnable in place.

ProgramClassKey formulaRange
APRCPOSTR10000000 + receipt digits1000xxxx
APINVREGI20000000 + invoice digits2000xxxx
APPAYRUNP30000000 + invoice digits3000xxxx
APACCRA40000000 + PO digits×10 + line4000xxxx
APREVACCVaccrual key + 10000000 (= 50000000 + PO×10 + line)5000xxxx

The composite-key report files (APAGE, APSPND) use the same idea with a KLIST guard on their period+vendor key instead of a synthetic LSEQ.

F.3 Date arithmetic

The engine's %DATE builtin works on ISO text, not on an 8S0 YYYYMMDD number, so PROCURE/i does its own exact civil-days conversion (Howard Hinnant days_from_civil / civil_from_days) in DSER (date→serial) and DADD (serial→date) subroutines. This matters in two places: APINVREG adds the discount days to the invoice date to compute DISCDT (a plain integer add of 10 to 20260825 would produce the non-existent 20260835, corrupting the payment run's discount test), and APAGERP measures whole days past due. Both are exact for every Gregorian date with no 30-day-month approximation.

F.4 DDS & engine accommodations

F.5 DSPLY result-line table

ProgramResult line
APREFLDAPREFLD VENDORS=n POS=n POLINES=n
APPOAPPAPPOAPP APPROVED=n SKIP=n
APRCPOSTAPRCPOST POSTED=n SKIP=n CAPPED=n
APMATCHAPMATCH MATCHED=n HELD=n SKIP=n
APINVREGAPINVREG REGISTERED=n SKIP=n
APHOLDRPAPHOLDRP HELD=n QTY=n PRC=n NOPO=n AMT=n
APPOCLSAPPOCLS CLOSED=n SKIP=n
APPAYRUNAPPAYRUN PAID=n GROSS=n DISC=n SKIP=n
APREMITAPREMIT LINES=n CASH=n
APAGERPAPAGERP RUN=yyyymm VENDORS=n SKIP=n
APACCRAPACCR ACCRUED=n AMT=n SKIP=n
APGLPSTAPGLPST PER=yyyymm ROWS=n
APSPENDAPSPEND YEAR=yyyy VENDORS=n R1099=n SKIP=n
APREVACCAPREVACC REVERSED=n AMT=n SKIP=n
APAPRPTAPAPRPT INVOICED/PAID/DISCOUNT/CONTROL/ROWS (COBOL)

G. Glossary ↑ top

Approval band (POAPLM)
The label recording how a PO was approved: A auto (at or under 10000.00), M manual band (over it). It is descriptive — PROCURE/i approves both in one sweep; there is no second-approver hold.
AP control balance
SUM(LAMT) over the AP ledger — the running accounts-payable liability. Invoices add, payments subtract; the COBOL report APAPRPT re-proves it.
Discount is income
An early-settlement discount reduces the cash sent, not the obligation: the vendor balance falls by the full gross and the captured discount posts as discount-received (4900-DSC) income.
GRNI accrual (received-not-invoiced)
Goods received but not yet billed: (RCVQTY−INVQTY)×POPRICE per PO line, accrued at month-end (APACCR) and reversed at year-end (APREVACC).
Hold code (IHOLDCD)
Why the three-way match rejected an invoice: Q quantity over receipt, P price variance, N no matching PO line. A held invoice consumes nothing and is never paid.
Idempotent
Safe to run again with the same result. Every PROCURE/i posting program is idempotent by a stable ledger/status key, so re-running a cycle never doubles a posting (F.2).
Ledger (APLEDG)
The durable, signed, keyed audit trail every posting program contributes to; the app's spine and the source of the control balance.
Payment invariant
PNET = PGROSS − PDISC on every cheque — the discount is exactly the gap between gross owed and net cash.
SBMJOB
Submit Job — queues a program to run as a batch job, e.g. SBMJOB CMD(CALL PGM(PROCURE/APDAILY)).
SQLRPGLE / EXEC SQL
RPG with embedded SQL. Only APGLPST uses it here, to read/write the APGLDIST GL table; the rest of the app is native record-level I/O.
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). APPOIQ's order-line list is a subfile; it is cleared and reloaded on each enquiry.
Terms (2/10 net 30)
Payment terms carried as three vendor numbers — discount percent (VDISCPC), discount days (VDISCDY), net days (VNETDY) — so the payment run computes settlement without a terms-table lookup.
Three-way match
Reconciling a supplier invoice against the purchase order and the goods receipt (quantity within receipt, price equal to PO price, PO line exists) before it is approved for payment (APMATCH). PROCURE/i's real payment-control gate.