Matching multiple vendor bills to a single purchase order is a common task in many AP processes, so it’s best to know how to handle this situation in NetSuite. The good news is that Oracle has a bunch of options to help you streamline this process – you can use the NetSuite UI, the API, or SuiteScript.
In this guide, we’ll explore how to link a single purchase order to multiple vendor bills, using each of these methods. We’ll also discuss the limitations and benefits of using the APIs.
It’s important to note that the complexity of the NetSuite API can be overwhelming. It takes a lot of dedicated time (and effort) to set up an API integration and actually automate your NetSuite workflows.
That’s where Nanonets comes in, providing a plug-and-play integration with NetSuite that eliminates the hassle and confusion, making PO matching automation effortless.
Nanonets automates vendor bill entry into NetSuite, and sets up seamless 2-way and 3-way PO matching in less than 15 minutes!
But before we get too far ahead of ourselves, let’s first understand how to match multiple vendor bills to a PO with ease.
Matching a PO to Multiple Vendor Bills on the NetSuite UI
Using the NetSuite UI to match a single purchase order (PO) to multiple vendor bills is a straightforward task, ideal for smaller operations or businesses that don’t need automation. This method allows you to create bills for different line items on a PO or for partial quantities of items.
Step-by-Step Instructions:
- Access the Purchase Order:
- Navigate to Transactions > Purchases > Enter Purchase Orders to locate the PO you want to match.
- Bill the Purchase Order:
- Click on Bill on the top header row of the Purchase Order.
- Edit the Bill Line Details:
- By default all the lines of the PO will be added to the vendor bill. You can remove the lines that are not required, so that the PO is only partially billed for the required line items.
- In case you need to partially bill any line (for eg, if you haven’t received the entire quantity for that item) – you can edit the quantity billed at this stage.
- Repeat for Additional Vendor Bills:
- If the PO requires multiple bills, repeat the process, selecting the remaining items or quantities on the PO.
Note that the created vendor bills will NOT show up in the “Billing” subtab on the purchase order. They will be visible in related records only. This is because NetSuite only does a 1-time update using their ‘Transform’ function to convert a PO into a vendor bill.
Matching a PO to Multiple Vendor Bills Using the API
NetSuite provides both SOAP and REST APIs (these are 2 different API technologies that are commonly used by developers). While the SOAP API is older and more mature, it can also be slow in performance. The NetSuite REST API is more modern (it was introduced in 2019 by Oracle) but doesn’t have the extensive support and customisation capabilities that the SOAP API has.
💡
For this article, we’ll use the example of the SOAP API to do the PO matching with Vendor Bills.
There are 2 approaches you can take to link a purchase order to a vendor bill:
- Use the purchaseOrderList tag to reference a PO when creating the vendor bill – doing this will pull in ALL of the items and expenses present on that PO
- Use an Initialize request and select item and expense lines from existing POs – doing this has the advantage of being able to select specific lines from the PO. You can also use this to address the use case of multiple POs in a single vendor bill.
Step-by-Step Instructions:
- Retrieve the Purchase Order Record:
Retrieve the details of the purchase order using the get
operation. This provides access to the PO’s line items, quantities, and remaining quantities.
Payload:
<get>
<baseRef internalId="PO_ID" type="purchaseOrder"/>
</get>
- Create Vendor Bill for First Bill:
As we saw above, if you use approach 1 – use the add
operation to create a vendor bill for the first set of line items or quantities from the PO.
Payload:
<add xmlns="urn:messages_2017_1.platform.webservices.netsuite.com">
<record xsi:type="ns6:VendorBill" xmlns:ns6="urn:purchases_2017_1.transactions.webservices.netsuite.com">
<ns6:entity internalId="98" xsi:type="ns7:RecordRef" xmlns:ns7="urn:core_2017_1.platform.webservices.netsuite.com"/>
<ns6:purchaseOrderList xsi:type="ns8:RecordRefList" xmlns:ns8="urn:core_2017_1.platform.webservices.netsuite.com">
<ns8:recordRef internalId="2659" type="purchaseOrder" xsi:type="ns8:RecordRef"/>
<ns8:recordRef internalId="2661" type="purchaseOrder" xsi:type="ns8:RecordRef"/>
</ns6:purchaseOrderList>
</record>
</add>
If you’re using approach 2, the payload will be:
<initialize xmlns="urn:messages_2017_1.platform.webservices.netsuite.com">
<initializeRecord>
<ns7:type xmlns:ns7="urn:core_2017_1.platform.webservices.netsuite.com">vendorBill</ns7:type>
<ns8:referenceList xmlns:ns8="urn:core_2017_1.platform.webservices.netsuite.com">
<ns8:initializeRef internalId="2659" type="purchaseOrder"/>
<ns8:initializeRef internalId="2661" type="purchaseOrder"/>
</ns8:referenceList>
</initializeRecord>
</initialize>
3. Repeat for Additional Vendor Bills:
- Repeat the process to add more bills, adjusting the line items and quantities for each vendor bill.
SOAP API vs REST API: Key Differences
We’ll go into a little more detail since this is something that often comes up in NetSuite development – what is the difference, really, between these two types of APIs given by Oracle?
SOAP API:
- Authentication: SOAP uses Token-Based Authentication (TBA), which is OAuth1-based. Though older, it is well-supported in enterprise settings and relatively easy to configure.
- Metadata Handling: SOAP handles standard entities and fields well but struggles with custom fields without additional workarounds.
- Saved Searches and File Cabinet: SOAP provides built-in support for saved searches and file cabinet operations, making it the preferred choice for complex data retrieval tasks.
- Performance: SOAP is slow (but stable) and ideal for large, complex integrations, though the syntax is more verbose and challenging compared to REST.
💡
1. If you’re doing large-scale or complex integrations that will almost certainly involve non-standard operations, file movement and so on
2. For any self-serve deployments (since the documentation is extensive, and there is a history of successful integrations done by others)
NetSuite SOAP API Documentation Link
REST API:
- Authentication Flexibility: REST supports OAuth2 in addition to TBA. However, OAuth2 in NetSuite has some caveats, such as frequent token expirations for the Authorization Code Grant method.
- Metadata Handling: REST allows for more dynamic access to metadata, making it useful for environments where entity structures change frequently. REST also supports OpenAPI schemas, including for custom fields.
- SuiteQL Support: REST includes support for SuiteQL, which can significantly simplify complex queries by allowing SQL-like queries via the API.
- Limitations: REST is still relatively new in NetSuite, and many records are still in beta. Critical functions like saved searches and file cabinet access require workarounds, such as custom Restlets.
💡
1. For faster and small-scale deployments, where you are mostly working with standard objects (like customer, salesOrder, invoice and so on).
2. For cases where OAuth2 is mandated by the client’s authentication requirements
NetSuite REST API Documentation Link
Matching a PO to Vendor Bills Using SuiteScript
For companies that require a highly customized solution, SuiteScript provides a powerful JavaScript-based scripting platform to match purchase orders with multiple vendor bills. SuiteScript allows full control over workflows and is more useful in cases where customization is important.
Step-by-Step Instructions:
- Load the Purchase Order:
var poRecord = record.load({
type: record.Type.PURCHASE_ORDER,
id: 'PO_ID'
});
Use SuiteScript’s record.load
method to access the purchase order.
2. Create the Vendor Bill:
define(['N/record', 'N/search'], function(record, search) {
function createVendorBillFromPO(poId) {
try {
// Load the Purchase Order record
var purchaseOrder = record.load({
type: record.Type.PURCHASE_ORDER,
id: poId
});
// Create a new Vendor Bill
var vendorBill = record.create({
type: record.Type.VENDOR_BILL,
isDynamic: true
});
// Set the Vendor and other basic details from the PO
vendorBill.setValue({
fieldId: 'entity',
value: purchaseOrder.getValue('entity') // Vendor from PO
});
// Copy over any other header-level fields from the PO if necessary
// (e.g. terms, subsidiary, etc.)
vendorBill.setValue({
fieldId: 'terms',
value: purchaseOrder.getValue('terms')
});
// Add PO line items to the Vendor Bill
var lineCount = purchaseOrder.getLineCount({ sublistId: 'item' });
for (var i = 0; i < lineCount; i++) {
var orderdoc = purchaseOrder.getSublistValue({
sublistId: 'item',
fieldId: 'orderdoc',
line: i
});
var orderline = purchaseOrder.getSublistValue({
sublistId: 'item',
fieldId: 'orderline',
line: i
});
// Select the current line in the Vendor Bill
vendorBill.selectNewLine({ sublistId: 'item' });
// Set the necessary fields (orderdoc and orderline are important for matching)
vendorBill.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'orderdoc',
value: orderdoc // PO Internal ID
});
vendorBill.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'orderline',
value: orderline // PO line number
});
// Set the item, quantity, and rate (copying from PO)
vendorBill.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'item',
value: purchaseOrder.getSublistValue({
sublistId: 'item',
fieldId: 'item',
line: i
})
});
vendorBill.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'quantity',
value: purchaseOrder.getSublistValue({
sublistId: 'item',
fieldId: 'quantity',
line: i
})
});
vendorBill.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'rate',
value: purchaseOrder.getSublistValue({
sublistId: 'item',
fieldId: 'rate',
line: i
})
});
// Commit the line to the Vendor Bill
vendorBill.commitLine({ sublistId: 'item' });
}
// Save the Vendor Bill
var vendorBillId = vendorBill.save();
log.debug('Vendor Bill created', 'Vendor Bill ID: ' + vendorBillId);
return vendorBillId;
} catch (e) {
log.error('Error creating Vendor Bill', e);
throw e;
}
}
return {
createVendorBillFromPO: createVendorBillFromPO
};
});
Create a vendor bill record using record.create
. The above code copies over all the lines from the PO to the vendor bill – if needed, you can select the appropriate line items from the PO and map them to the vendor bill. This allows you to match each PO line item to the vendor bill line item.
3. Repeat for Multiple Bills:
Use this logic to create additional vendor bills by selecting remaining PO lines in subsequent script executions.
Advantages of SuiteScript:
- Full Customization: SuiteScript offers total control over the process, making it perfect for unique billing workflows.
- Extensibility: You can easily build on the script to handle other custom needs like validation, logging, or error handling.
Common Errors and Troubleshooting
- The PO is fully billed and received, but still shows Pending Bill
- This is likely because the vendor bill has been created as a standalone bill (i.e., a bill not linked to a PO). You can verify this by checking the Related Records sub-tab on the bill for the PO link (or check the journal entry – a standalone bill will be tagged to Inventory, while a PO-linked bill will be tagged to Accruals).
- I want to link a vendor bill where received quantity is more than the original PO
- In this case, it’s usually best to complete the receiving and billing, keeping that information as accurate as possible, and then running PVBV (Post Vendor Bill Variances) on the PO lines to find and adjust the errors to Accrued Purchases and your GL.
- I want to match multiple POs to a single bill
- In some cases you might want to match more than one PO to a single vendor bill (if, for example, you received a combined invoice for many POs on the same vendor). This can be addressed using the SOAP API as described above, using the Initialize method.
Using end-to-end Workflow Automation for PO Matching
If you’re using NetSuite to run your finances and CRM operations, it’s quite likely you’re already running a production deployment of NetSuite, and all your workflows are set up and being used daily.
In such a scenario, it might be easier to work with an integration partner who can migrate your processes and automate a production environment, without you having to get into the messy details of API integrations and SuiteQL queries.
💡
– More than 80% time savings on PO matching
– Inbuilt data validation and formatting for NetSuite
– High accuracy, trainable AI
This is what full-scale NetSuite automation on Nanonets looks like:
Interested in learning more? A short 15-minute intro call with an automation expert is the best way to get started.
Conclusion
Matching a purchase order to multiple vendor bills in NetSuite can be handled in several ways, depending on your business setup. You can go for something that is more manual and gives you more control, or you can fully automate it using the API (with significantly higher effort and some coding involved).
By combining the best of both worlds using a no-code solution like Nanonets, you can confidently manage vendor item codes, streamline data entry, and reduce manual errors, saving valuable time for your procurement and finance teams.
References: