Documentation / Procure-to-Pay

Procure-to-Pay (M5)

From vendor master to the final payment, procurement in ApexLedger is one auditable pipeline: PO → approval → receiving → inspection → 3-way match → payment. Every state change is a guarded RPC, and goods never touch stock un-inspected.

Actors: FINANCE_OPERATOR (creates PO), the dynamic approval role, WAREHOUSE_OPERATOR (receives), finance (bill + payment).

1. Vendor master

Procurement → Vendors: add each vendor with payment terms (days) and NPWP. The terms drive every bill's due date later — set them right at creation.

2. Purchase order

Create the PO with lines (item, qty, unit price). Status: DRAFT — nothing is committed, no stock is promised.

3. Submit — the dynamic approval engine

fn_submit_purchase_order reads the approval_thresholds table (ordered by min_amount DESC) and stamps the required role onto the PO:

  • small values may auto-approve,
  • large values can require DIREKSI (board level).

The requirement is data, not code — your finance team tunes thresholds without touching a migration. Status → PENDING_APPROVAL.

4. Approve

A user at or above the stamped role approves. A weaker role gets INSUFFICIENT_APPROVAL_AUTHORITY straight from the RPC — the UI hides nothing, the engine enforces everything. Status → APPROVED.

5. Receive goods — nothing enters stock yet

The WAREHOUSE_OPERATOR records a Goods Received Note (GRN) per PO. Stock is not booked at this point — the quantity sits as PUTG (Pending Untested Goods). Status → PARTIALLY_RECEIVED / RECEIVED.

This is deliberate: un-inspected goods are not inventory. A damaged shipment never contaminates your on-hand quantities or costs.

6. Inspect (PUTG)

Per GRN line, enter accepted vs rejected quantities:

  • Accepted — flows into stock at PO price via fn_receive_stock; the GL posts Dr Inventory / Cr GR-IR clearing.
  • Rejected — never enters stock.

GRN status → PASSED / PARTIAL / REJECTED.

7. AP bill

Finance creates a bill from a PASSED/PARTIAL GRN — one bill per GRN. Due date = bill date + the vendor's payment terms from step 1.

8. The 3-way match

fn_match_and_approve_ap_bill compares three documents:

Document Field
Bill qty, price
GRN accepted qty
PO price (±2% tolerance)
  • Mismatch → status DISPUTED with a machine-written reason, no GL posting. The bill does not become payables until a human resolves it.
  • Match → GL posts Dr GR-IR + Dr PPN Masukan + price variance / Cr AP.

9. Pay

fn_record_ap_payment posts Dr AP / Cr cash-bank and allocates FIFO by due date — oldest bills get the money first. Bill → PAID.

Side flows

Flow What happens
Purchase return debit note; stock out at cost basis; Dr AP / Cr Inventory / Cr PPN
Landed cost capitalize freight/customs into item cost, allocated by qty / value / weight

Guards recap

Guard Fires when
PO_INVALID_STATUS receiving before approval
RECEIPT_EXCEEDS_ORDER over-receipt
GRN_NOT_INSPECTED billing an un-inspected GRN
status guards double inspection, double billing

What's next

  • Order-to-Cash — the mirror flow on the sales side.
  • Month-End Close — where these invoices land at close.
  • Deep-dive: docs/USER_FLOWS.md §4 in the repository.