NetSuite Integration
Automatically create NetSuite records from processed documents
NetSuite Integration
The Email Ingestion service can automatically create records in NetSuite from extracted document data.
Overview
When a document is processed, the system can:
- Resolve entities: Match extracted names to NetSuite records
- Map fields: Convert extracted data to NetSuite field format
- Create records: Generate vendor bills, invoices, POs, or expense reports
- Attach files: Upload the original document to File Cabinet
flowchart LR
A[Extracted Data] --> B[Entity Resolution]
B --> C[Field Mapping]
C --> D[Record Creation]
D --> E[File Attachment]
E --> F[NetSuite Record]Prerequisites
NetSuite Connection
Before using NetSuite integration:
- Connect your NetSuite account: Go to Settings > Integrations > NetSuite
- Grant API access: Ensure your NetSuite user has API permissions
- Configure subsidiary: Select default subsidiary for records
- Set default accounts: Configure expense accounts, etc.
Required Permissions
Your NetSuite integration user needs:
| Permission | For |
|---|---|
| Vendor Bills | Creating vendor bills |
| Invoices | Creating customer invoices |
| Purchase Orders | Creating POs |
| Expense Reports | Creating expense reports |
| File Cabinet | Uploading document attachments |
| SuiteQL | Entity lookups |
Entity Resolution
How It Works
The Entity Resolver matches extracted names to NetSuite internal IDs:
// Resolve a vendor by name
const result = await entityResolver.resolveVendor(
"Acme Corporation", // Extracted vendor name
orgId,
netsuiteConnectionId
);
// Result
{
success: true,
internalId: "12345",
name: "Acme Corp",
entityType: "vendor",
confidence: 0.95,
matchMethod: "fuzzy" // 'exact' | 'fuzzy' | 'alias'
}Match Methods
| Method | Description | Confidence |
|---|---|---|
exact | Perfect string match | 1.0 |
fuzzy | Levenshtein distance match | 0.7 - 0.99 |
alias | Matched via configured alias | 0.9 |
default | Fallback/default entity used | 0.5 |
Fuzzy Matching
The system uses Levenshtein distance for fuzzy matching:
// Extracted: "ACME CORP"
// NetSuite records:
// - "Acme Corporation" (similarity: 0.75)
// - "Acme Corp" (similarity: 0.95) <- Selected
// - "Acme Inc" (similarity: 0.60)String normalization:
- Lowercase conversion
- Removal of special characters
- Whitespace normalization
- Common abbreviation expansion (Corp -> Corporation)
Configuring Aliases
Set up vendor/customer aliases for better matching:
// In NetSuite or via API
{
entityType: "vendor",
internalId: "12345",
aliases: [
"ACME",
"ACME CORPORATION",
"ACME CORP.",
"Acme Ltd"
]
}Entity Types
| Type | NetSuite Record | Used For |
|---|---|---|
vendor | Vendor | Vendor bills |
customer | Customer | Customer invoices |
item | Item | Line items |
employee | Employee | Expense reports |
account | Account | Expense categories |
Record Creation
Vendor Bill
Created from supplier invoices:
const result = await recordCreation.createVendorBill({
vendorId: "12345", // Resolved vendor internal ID
tranDate: "2024-11-23", // Invoice date
dueDate: "2024-12-23", // Payment due date
tranId: "INV-001", // External reference
memo: "From email ingestion",
lineItems: [
{
item: "54321", // Resolved item internal ID
description: "Consulting Services",
quantity: 10,
rate: 150.00,
amount: 1500.00,
department: "100", // Optional
class: "200" // Optional
}
],
// Original document
attachmentFileId: "doc_abc123"
}, {
connectionId: "conn_xyz",
isDraft: false, // Create as pending approval
uploadToFileCabinet: true
});Response:
{
success: true,
recordType: "vendorbill",
internalId: "98765",
externalId: "adteco-inv-001",
transactionNumber: "BILL-0042",
fileCabinetId: "file_123"
}Customer Invoice
Created from outbound invoices:
const result = await recordCreation.createCustomerInvoice({
customerId: "67890",
tranDate: "2024-11-23",
tranId: "INV-2024-001",
lineItems: [
{
item: "54321",
description: "Software License",
quantity: 1,
rate: 5000.00,
amount: 5000.00
}
],
terms: "30", // Net 30
memo: "Annual license renewal"
}, {
connectionId: "conn_xyz",
isDraft: true
});Purchase Order
Created from PO documents:
const result = await recordCreation.createPurchaseOrder({
vendorId: "12345",
tranDate: "2024-11-23",
tranId: "PO-2024-001",
expectedReceiptDate: "2024-12-01",
shipTo: "Main Warehouse",
lineItems: [
{
item: "11111",
description: "Office Supplies",
quantity: 100,
rate: 5.00,
amount: 500.00
}
]
}, {
connectionId: "conn_xyz"
});Expense Report
Created from expense receipts:
const result = await recordCreation.createExpenseReport({
employeeId: "999",
tranDate: "2024-11-23",
memo: "Business trip to NYC",
expenseItems: [
{
date: "2024-11-20",
category: "travel",
account: "6010", // Travel expense account
amount: 450.00,
memo: "Flight LAX-JFK",
isBillable: false,
attachmentId: "file_001"
},
{
date: "2024-11-21",
category: "meals",
account: "6020",
amount: 75.00,
memo: "Client dinner"
}
]
}, {
connectionId: "conn_xyz",
isDraft: false
});Field Mapping
Default Mappings
Extracted fields are automatically mapped:
| Extracted Field | NetSuite Field | Record Types |
|---|---|---|
vendorName | entity (resolved) | Vendor Bill, PO |
customerName | entity (resolved) | Invoice |
invoiceNumber | tranid | All |
invoiceDate | trandate | All |
dueDate | duedate | Vendor Bill, Invoice |
total | total (calculated) | All |
lineItems[].description | item.description | All |
lineItems[].quantity | item.quantity | All |
lineItems[].rate | item.rate | All |
Custom Field Mapping
Configure custom field mappings:
{
netsuiteConnectionId: "conn_xyz",
customMappings: [
{
extractedField: "projectCode",
netsuiteField: "custbody_project",
recordTypes: ["vendorbill", "purchaseorder"]
},
{
extractedField: "costCenter",
netsuiteField: "class",
transform: "lookup", // Lookup internal ID
recordTypes: ["vendorbill"]
}
]
}Value Transformations
| Transform | Description | Example |
|---|---|---|
direct | Copy value directly | Name -> Name |
lookup | Find NetSuite internal ID | "Sales" -> "12" |
format | Apply format pattern | "2024-11-23" -> "11/23/2024" |
calculate | Compute from other fields | quantity * rate |
default | Use default if empty | null -> "1" |
File Cabinet Integration
Uploading Documents
Original documents are uploaded to NetSuite File Cabinet:
// Configuration
{
fileCabinetFolder: "/Documents/Invoices", // Target folder
filenamePattern: "{docType}_{vendorName}_{date}_{id}",
allowedTypes: ["application/pdf", "image/png", "image/jpeg"]
}Generated filename: VendorBill_AcmeCorp_2024-11-23_inv001.pdf
Attaching to Records
Documents are automatically attached to created records:
// After record creation
{
recordType: "vendorbill",
internalId: "98765",
attachments: [
{
fileId: "file_123",
filename: "invoice.pdf",
fileCabinetPath: "/Documents/Invoices/2024/11/"
}
]
}Error Handling
Entity Not Found
When a vendor/customer/item cannot be resolved:
{
success: false,
error: {
code: "ENTITY_NOT_FOUND",
message: "Vendor 'Unknown Corp' not found in NetSuite",
entityType: "vendor",
searchTerm: "Unknown Corp",
suggestions: [
{ internalId: "123", name: "Unknown Corporation", confidence: 0.85 },
{ internalId: "456", name: "Unknowncorp LLC", confidence: 0.72 }
]
},
action: "REQUIRES_REVIEW"
}Resolution options:
- Select from suggestions
- Create new entity
- Map to existing entity
- Skip this field
Validation Errors
NetSuite field validation failures:
{
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Required field 'account' missing on line 2",
field: "lineItems[1].account",
recordType: "vendorbill"
}
}Connection Errors
Authentication or API issues:
{
success: false,
error: {
code: "CONNECTION_ERROR",
message: "NetSuite authentication failed",
details: "Invalid credentials or token expired"
},
action: "RECONNECT_REQUIRED"
}Approval Workflows
Draft Mode
Create records as drafts for review:
const result = await recordCreation.createVendorBill(data, {
connectionId: "conn_xyz",
isDraft: true // Creates in draft status
});Pending Approval
Integrate with NetSuite approval workflows:
{
// Configuration
approvalSettings: {
vendorBill: {
// Auto-approve under $1000
autoApproveThreshold: 1000,
// Require approval for specific vendors
alwaysRequireApproval: ["vendor_789"],
// Default approver
defaultApprover: "employee_123"
}
}
}Monitoring & Reporting
Sync Status
Track NetSuite sync status:
| Status | Description |
|---|---|
pending | Waiting to sync |
syncing | In progress |
synced | Successfully created |
failed | Sync failed |
review | Needs manual review |
Sync History
View sync history for each document:
GET /api/documents/{id}/netsuite-sync
{
documentId: "doc_123",
syncAttempts: [
{
timestamp: "2024-11-23T10:30:00Z",
status: "failed",
error: "Vendor not found"
},
{
timestamp: "2024-11-23T10:35:00Z",
status: "synced",
netsuiteId: "98765",
recordType: "vendorbill"
}
]
}Retry Failed Syncs
Retry failed NetSuite syncs:
POST /api/documents/{id}/netsuite-sync/retry
{
// Optional: override entity mappings
overrides: {
vendorId: "12345" // Force specific vendor
}
}Best Practices
Entity Management
- Maintain NetSuite data: Keep vendor/customer names consistent
- Use aliases: Configure aliases for known variations
- Regular cleanup: Merge duplicate entities
Error Prevention
- Required fields: Ensure all required NetSuite fields are mapped
- Test connections: Regularly test NetSuite connectivity
- Monitor failures: Set up alerts for sync failures
Performance
- Batch processing: Large documents processed off-peak
- Rate limiting: Respect NetSuite API limits
- Caching: Entity lookups are cached