Fix NetSuite Invoice Not Based on Fulfillments
A billing schedule invoices the whole order even when you ship in stages, billing for product not yet shipped. Trigger invoicing from item fulfillment instead.

On this page
Your sales order has ten line items. You ship five today and five next week. Your billing schedule generates one invoice for all ten items, and the customer calls asking why they're being billed for product they haven't received.
This is the exact problem when billing schedules don't respect partial fulfillments. The solution is to flip your invoicing trigger from the sales order to the item fulfillment record itself.
Why Billing Schedules Fail for Partial Shipments
Billing schedules work well for subscription-style revenue where the full order ships once. They fail when you ship in stages because the schedule generates invoices against the entire sales order, not against what actually left your warehouse.
NetSuite's native behavior compounds this. When you create an item fulfillment, the system tracks shipped quantities against the sales order line. The invoice, however, pulls whatever quantity remains unbilled at the time you generate it. If you haven't shipped everything, you risk invoicing for unshipped items unless you've configured the system correctly.
The fix starts with one setting. The Sales Orders and Cash Sales Guide covers the standard invoicing flow, but the preference below is what changes the behavior for partial shipments.
Turn Off Invoice in Advance of Fulfillment
Go to Setup > Accounting > Accounting Preferences > Order Management. Find the checkbox labeled Invoice in Advance of Fulfillment. Turn it off.
This setting controls whether NetSuite allows invoicing for quantities that haven't shipped. When disabled, any invoice you create from a sales order only includes quantities that have been fulfilled but not yet invoiced.
From an audit perspective, this is the control that keeps your revenue recognition aligned with your actual shipments. Leave it on, and you can create invoices for items sitting in your warehouse. Your COGS posts when the fulfillment happens, but the revenue books on the invoice date. For partial shipments across month-end, that mismatch lands revenue and COGS in different periods.
Your auditors will thank you for keeping this off.
The Native Bill Button on Item Fulfillments
Before you build any automation, check what you already have. Open an item fulfillment record. Look in the top-right corner for the Bill button.
This button transforms the item fulfillment into an invoice. Because the fulfillment record only contains the shipped quantities, the invoice reflects exactly what shipped on that record. No more, no less.
For low-volume operations, this button solves the problem with zero customization. Your team clicks Bill when a fulfillment ships, and NetSuite creates the invoice from that fulfillment's lines and quantities.
The challenge comes when you ship dozens or hundreds of fulfillments daily. Clicking a button per record becomes the bottleneck. That's where automation enters.
Automate Invoice Creation with a User Event Script
A user event script on the item fulfillment record can generate the invoice the moment the fulfillment status changes to Shipped. This eliminates the manual step while preserving the fulfillment-based billing logic.
Here's a working SuiteScript 2.1 example that does exactly this:
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record', 'N/log'],
(record, log) => {
function afterSubmit(context) {
if (context.type !== context.UserEventType.EDIT &&
context.type !== context.UserEventType.CREATE) {
return;
}
const fulfillmentRecord = context.currentRecord;
const status = fulfillmentRecord.getValue({
fieldId: 'status'
});
// Only trigger when status is Shipped (status code C)
if (status !== 'C') {
return;
}
// Check if an invoice already exists for this fulfillment
const linkedInvoice = fulfillmentRecord.getValue({
fieldId: 'custbody_linked_invoice'
});
if (linkedInvoice) {
log.debug('Invoice Already Exists',
`Fulfillment ${fulfillmentRecord.id} already has invoice ${linkedInvoice}`);
return;
}
try {
const invoice = record.transform({
fromType: record.Type.ITEM_FULFILLMENT,
fromId: fulfillmentRecord.id,
toType: record.Type.INVOICE,
isDynamic: true
});
const invoiceId = invoice.save({
enableSourcing: true,
ignoreMandatoryFields: true
});
// Store the invoice ID back on the fulfillment
fulfillmentRecord.setValue({
fieldId: 'custbody_linked_invoice',
value: invoiceId
});
fulfillmentRecord.save();
log.audit('Invoice Created',
`Created invoice ${invoiceId} from fulfillment ${fulfillmentRecord.id}`);
} catch (error) {
log.error('Invoice Creation Failed',
`Error creating invoice for fulfillment ${fulfillmentRecord.id}: ${error.message}`);
}
}
return {
afterSubmit: afterSubmit
};
}
);The record.transform API handles the conversion from item fulfillment to invoice. NetSuite's Revenue and Expense Recognition Guide explains how these source documents flow into revenue arrangements, which is why keeping the invoice tied to the actual fulfillment matters for recognition timing.
Create the Linked Invoice Custom Field
The script references a custom field called custbody_linked_invoice. You need to create this field on the item fulfillment record.
Go to Customization > Lists, Records, & Fields > Transaction Body Fields > New.
Configure the field:
| Setting | Value |
|---|---|
| Label | Linked Invoice |
| ID | custbody_linked_invoice |
| Applies To | Item Fulfillment |
| Type | List/Record |
| List/Record | Transaction |
| Store Value | Checked |
| Display Type | Normal |
This field serves as your guardrail. The script checks it before creating an invoice. If the field already contains an invoice ID, the script skips creation. This prevents duplicate invoices if someone moves a fulfillment from Shipped back to Picked, then ships it again.
Handle the Reversal Edge Case
Moving a fulfillment backward in status is the scenario that breaks naive automation. If you ship a fulfillment, the script creates an invoice, and then someone reverses the status to correct a packing error, the script could fire again when the status returns to Shipped.
The custbody_linked_invoice field prevents this. Once populated, the script never creates a second invoice for that fulfillment.
If you need to void the linked invoice and re-invoice, clear the custom field manually first. Then re-ship the fulfillment. The script will create a fresh invoice.
Alternative: Scheduled Script with a Saved Search
The user event script works well for real-time invoicing. If you prefer to batch invoices at the end of each day, use a scheduled script combined with a saved search.
Create a transaction search with these criteria:
- Type is Item Fulfillment
- Status is Shipped
- Main Line is No
- Date is on or after today
Add a formula field to the results that checks whether a linked invoice exists. Your scheduled script runs this search, transforms each fulfillment into an invoice, and updates the custom field.
This approach gives you a nightly billing run. Your team reviews the invoice batch each morning instead of monitoring every fulfillment in real time.
What About the "Bill" Button on Sales Orders?
You'll still see Bill on the sales order. That button creates an invoice from the sales order, not from a specific fulfillment. With Invoice in Advance of Fulfillment turned off, the invoice only includes shipped quantities.
The problem: if you have two fulfillments on different days, clicking Bill on the sales order after the second shipment creates one invoice for both. If you need separate invoices per fulfillment, always generate invoices from the fulfillment record, not the sales order.
This is the core distinction. The fulfillment is your billing source of truth. The sales order is your order source of truth. Keep them separate.
Test Before You Deploy
Deploy the user event script to Testing first. Create a test sales order with two line items. Fulfill one line, mark it shipped, and confirm the script creates an invoice for that single line. Then fulfill the second line and confirm a separate invoice appears.
Check that the custbody_linked_invoice field populates on both fulfillments. Try reversing one fulfillment status and re-shipping it. Confirm no duplicate invoice appears.
Once your test passes, deploy to production. You'll have automated invoice creation from item fulfillments, with separate invoices for each shipment and guardrails against duplicates.
If you're managing a high volume of fulfillments and need to audit which ones still lack invoices, a simple saved search on the custbody_linked_invoice field being empty will show you the gap. That search alone can save your billing team hours each week. For teams running this process at scale, the NetSuite Connector documentation also covers how fulfillment data syncs with external systems, which matters when your invoicing workflow feeds into ecommerce or ERP integrations.


