Ask most people to sketch Voyager's procure-to-pay process and they'll draw a straight line: requisition, then purchase order, then receiving, then invoice, then payment. It reads cleanly on a whiteboard. It's also not how the data is actually structured — and building a report or a reconciliation query on the straight-line assumption produces a query that silently drops half the payables in the system.
The real architecture is a hub-and-spoke around the payable, not a chain. Here's what that means and why it matters for anyone querying the module.
The one reliable edge
Start from the one link in the whole structure that always holds: every posted AP invoice becomes a payable. That's the reliable, unconditional edge in the entire procure-to-pay picture. Everything else — the requisition stage, the purchase order stage, the receiving stage — is optional, and whether a given payable passed through any of them depends entirely on how that specific client's procurement is configured.
That's the "hub" — the payable sits at the center, and the upstream stages are spokes that may or may not be attached to any given payable. Some payables have a full requisition-to-payment trail behind them. Others were entered as a direct AP invoice with no purchase order in sight. Others still originate somewhere else entirely.
The spokes, and why each one is conditional
Requisition → purchase order. A PO line can carry a link back to the requisition line that generated it — but plenty of installs skip requisitioning entirely and cut purchase orders directly. Don't assume a requisition exists just because a PO does; check for the link rather than joining on the assumption it's always populated.
Purchase order → receiving. Receiving is a client configuration choice, not a universal step. Where it's used, receipt status lives directly on the PO line — a received-quantity field measured against the ordered quantity, with a receipt date — rather than in a dedicated receiving-header table. Where a client doesn't use receiving workflows at all, that comparison will simply show nothing received, by design, not by error.
Purchase order → invoice. This is the join most first drafts get wrong: it happens at the line level, not the header. An invoice's distribution line carries the actual pointer to the specific PO line it's paying against. The invoice header's own PO reference field is commonly unpopulated or unreliable — don't rely on it. Match line to line.
Invoice → payable. This is the hub edge — the one that's always there for a posted invoice.
Payable, without an invoice at all. A payable doesn't require an invoice register entry to exist. Direct AP entries post straight to a payable with no invoice register row behind them.
Payable, from construction. Projects and construction workflows have their own path in: payment certificates on a job-cost commitment create payables through a separate construction-commitment layer, bridging committed spend to actual payables and payments outside the standard invoice-register flow. A construction invoice can flow through both paths at once, which is worth knowing before you try to classify a payable's origin as one or the other.
What this means for classifying where a payable came from
Because payables can originate from the invoice register, from the construction-commitment layer, from both at once, or from neither, a query that tries to answer "where did this payable come from" needs to check multiple paths rather than assume a single upstream source. In practice that means reverse-checking a payable against the invoice register link and the construction-commitment link separately, and treating anything that matches neither as standalone, direct AP — not as a data-quality problem.
The header/detail trap sitting inside the invoice register itself
Even within the one reliable spoke — the invoice register — there's a grain trap worth knowing before you write anything that sums money. Invoice register headers carry the gross total; the distribution lines carry net-of-tax amounts with per-line tax fields. Reconciling the two means adding the line-level tax back to the net lines, not assuming the header total simply equals the sum of the line amounts. And because payable and payment amounts are recorded at invoice-header grain, they repeat identically across every PO line a single invoice happens to cover — summed carelessly across a line-level join, that inflates real dollar totals fast.
Why the shape matters more than any one table name
The specific tables involved — the requisition tables, the PO header and line tables, the invoice register header and detail, the payable and disbursement tables, the construction-commitment bridge — matter less on their own than understanding that they don't form a chain. A query built assuming linearity will inner-join its way into dropping every standalone payable, every direct AP entry, and every construction-originated payable from the result set, without throwing an error. It'll just quietly return less than the true picture.
That's the trap generic "Yardi procure-to-pay" content doesn't cover, because describing the module conceptually doesn't require getting the join structure right. Getting the join structure right requires having actually run the queries against a live schema.
Where this knowledge lives
PropETL's SQL Assistant carries the procure-to-pay architecture as execution-verified schema knowledge, alongside the invoice-register traps, MM2 purchasing conventions, and construction-commitment bridge — plus ready-made ySQL patterns for a full procure-to-pay trace, a three-way-match exception report, and an AP position summary by vendor. Ask it for the join path between any two tables in the chain and it walks the actual relationships rather than guessing from table names.
Start the free 7-day trial — no credit card — and see the hub-and-spoke shape mapped against your own module list.
