💼 Business Context
Accounts Payable teams often spend hours manually verifying supplier invoices against Purchase Orders and Item Receipts — a process known as invoice matching.
In NetSuite, this ensures you only pay for goods and services that were actually ordered and received.
- 2-Way Match: Compares the vendor invoice to the Purchase Order (PO) — checking quantities, prices, and amounts.
- 3-Way Match: Adds a goods receipt (Item Receipt) to confirm that billed quantities were received before approval.
When performed manually, this is slow and prone to errors. By automating it with SuiteScript 2.x, you can validate invoices instantly, auto-approve matches, and route exceptions for human review.
⚙️ Why Automate Invoice Matching
| Benefit | Description |
|---|---|
| Accuracy & Fraud Prevention | Reduces human error and detects overbilling or duplicate invoices before payment. |
| Efficiency & Cost Savings | Auto-approved invoices speed up the payables cycle, cutting processing costs. |
| Policy Compliance | Enforces tolerance rules ( e.g., ±3 % variance ) and ensures 3-way match for invoices > $5 000. |
| Audit Trail & Visibility | Logs all match results, variances, and approval decisions for audit readiness. |
🧩 Solution Architecture & Workflow
1️⃣ Invoice Entry:
A Vendor Bill enters NetSuite (manually, via Bill Capture OCR, or through integration) and references a Purchase Order.
2️⃣ Trigger Match Logic:
A User Event or Scheduled/Map-Reduce Script runs to locate the related PO and Item Receipts.
3️⃣ Perform Comparisons:
- Check invoice quantities ≤ ordered & received quantities.
- Validate unit rates and amount totals.
- Apply configured tolerances (e.g., ±5 %).
4️⃣ Take Action:
- ✅ Within tolerance → auto-approve, set Match Status = Matched or change status to Approved.
- 🚫 Exceeds tolerance → set Match Status = Exception, email AP manager, hold for review.
5️⃣ Re-check Pending Invoices:
A daily scheduled script re-evaluates invoices awaiting receipts — once items are received, the script can auto-approve them.
6️⃣ Exception Handling:
Unmatched invoices remain pending until reviewed or adjusted, ensuring no payment is released without validation.
🧠 SuiteScript 2.x Modules Used
| Module | Purpose |
|---|---|
N/record | Load POs, Vendor Bills, and Item Receipts; update approval fields. |
N/search | Find receipts, existing bills, and variance data efficiently. |
N/runtime | Read script parameters (e.g., tolerance %) and execution context. |
N/email | Notify AP teams of exceptions or auto-approvals. |
N/log / N/error | Capture debug and error details for troubleshooting and audit trails. |
🧱 Implementation Strategy
1️⃣ User Event Script (AfterSubmit)
Runs immediately when a Vendor Bill is created or edited.
Ideal for real-time feedback — auto-approves or flags the bill instantly.
Tip: Keep runtime light (< 5 seconds). Use one search per PO, not per line.
2️⃣ Scheduled Script
Processes bills in bulk (e.g., nightly) for performance or re-check scenarios.
Searches for Pending Approval bills and re-matches them.
3️⃣ Map/Reduce Script
Best for high volume.
Automatically yields and runs in parallel for hundreds of invoices, ensuring scalability and governance compliance.
🧩 Sample Code Snippet (Pseudo)
/**
* @NApiVersion 2.x
* @NScriptType UserEventScript
* @Description Automated 2-way / 3-way invoice matching
*/
define(['N/record', 'N/search', 'N/runtime', 'N/email'], (record, search, runtime, email) => {
function afterSubmit(context) {
if (context.type !== context.UserEventType.CREATE && context.type !== context.UserEventType.EDIT) return;
const bill = record.load({ type: record.Type.VENDOR_BILL, id: context.newRecord.id });
const poId = bill.getValue('purchaseorder');
if (!poId) return;
const po = record.load({ type: record.Type.PURCHASE_ORDER, id: poId });
const qtyTolerance = parseFloat(runtime.getCurrentScript().getParameter({ name: 'custscript_qty_tolerance' })) || 0;
const rateTolerance = parseFloat(runtime.getCurrentScript().getParameter({ name: 'custscript_rate_tolerance' })) || 0;
let hasException = false;
let messages = [];
const lineCount = bill.getLineCount({ sublistId: 'item' });
for (let i = 0; i < lineCount; i++) {
const qtyBill = parseFloat(bill.getSublistValue({ sublistId: 'item', fieldId: 'quantity', line: i })) || 0;
const qtyOrdered = parseFloat(po.getSublistValue({ sublistId: 'item', fieldId: 'quantity', line: i })) || 0;
if (qtyBill > qtyOrdered + qtyTolerance) {
hasException = true;
messages.push(`Line ${i + 1}: Qty ${qtyBill} > PO Qty ${qtyOrdered}`);
}
}
record.submitFields({
type: record.Type.VENDOR_BILL,
id: bill.id,
values: {
custbody_match_status: hasException ? 'EXCEPTION' : 'MATCHED'
}
});
if (hasException) {
email.send({
author: -5,
recipients: ['ap@company.com'],
subject: `Invoice Exception – Bill ${bill.getValue('tranid')}`,
body: messages.join('\n')
});
}
}
return { afterSubmit };
});
⚡ Edge Cases Handled
- Minor Variance: Within tolerance → auto-approve.
- Partial Receipt: Re-check daily until received quantities match.
- Over-billing / Unauthorized Item: Flag and hold payment.
- Duplicate Invoices: Detect same vendor + invoice number.
- PO-less Invoices: Skip auto-approval and route for manual review.
- Multi-PO Invoices: Supported via custom logic or RESTlet integration.
🧮 Integration & Extensions
| Integration | Purpose |
|---|---|
| NetSuite Bill Capture | OCRs supplier invoices → feeds data to matching script. |
| Third-Party AP Automation | Platforms like Stampli or Tipalti handle OCR + push to NetSuite; SuiteScript validates. |
| RESTlet Integration | External systems can POST invoice data and trigger matching on creation. |
🧰 Deployment & Best Practices
- ✅ Test in Sandbox with real PO/Bill data.
- 🕐 Schedule batch scripts off-peak hours.
- ⚠️ Enable “Enforce Unique Reference” to prevent duplicates.
- 🔄 Use Match Status custom field for workflow control instead of directly setting Approved.
- 🧩 Phase Deployment: start in “log-only” mode to monitor behavior before auto-approval.
- 📧 Email alerts for exceptions and include PO + Vendor details for AP visibility.
🧩 Summary
Automating 2-way and 3-way invoice matching in NetSuite using SuiteScript 2.x transforms Accounts Payable from a manual control task to a streamlined, auditable process.
Matched invoices move straight to payment; exceptions are flagged instantly for review.
The result: faster AP cycles, fewer errors, and stronger financial controls.
Leave a Reply