UNPKG

n8n-nodes-myob

Version:

Simple n8n node to create sales orders in MYOB Business API with multiple line items

675 lines (674 loc) 40 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Myob = void 0; const MyobApi_request_1 = require("../transport/MyobApi.request"); class Myob { constructor() { this.description = { displayName: 'MYOB Sales Order', name: 'myob', icon: 'file:myob.png', group: ['output'], version: 1, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], description: 'Create sales orders in MYOB Business API', defaults: { name: 'MYOB Sales Order' }, credentials: [ { name: 'myobOAuth2Api', required: true }, ], properties: [ { displayName: 'Customer UID', name: 'customerUid', type: 'string', default: '', required: true, description: 'The unique ID of the customer (GUID format)', }, { displayName: 'Order Date', name: 'orderDate', type: 'string', default: '', required: false, description: 'Sales order date in ISO format (e.g., 2025-08-24T04:32:01+10:00). If not provided, uses current date in n8n timezone.', placeholder: '2025-08-24T04:32:01+10:00', }, { displayName: 'Tax Inclusive', name: 'isTaxInclusive', type: 'boolean', default: true, description: 'Whether prices include tax', }, { displayName: 'Journal Memo', name: 'journalMemo', type: 'string', default: '', description: 'Internal memo for the journal entry (optional)', }, { displayName: 'Comment', name: 'comment', type: 'string', default: '', description: 'Comment for the sales order (optional)', }, { displayName: 'Purchase Order Number', name: 'purchaseOrderNumber', type: 'string', default: '', description: 'Purchase order reference number (optional)', }, { displayName: 'Shipping Address Line 1', name: 'shippingLine1', type: 'string', default: '', description: 'First line of shipping address (optional)', }, { displayName: 'Shipping Address Line 2', name: 'shippingLine2', type: 'string', default: '', description: 'Second line of shipping address (optional)', }, { displayName: 'Shipping Address Line 3', name: 'shippingLine3', type: 'string', default: '', description: 'Third line of shipping address (optional)', }, { displayName: 'Shipping Address Line 4', name: 'shippingLine4', type: 'string', default: '', description: 'Fourth line of shipping address (optional)', }, { displayName: 'Shipping Address Line 5', name: 'shippingLine5', type: 'string', default: '', description: 'Fifth line of shipping address (optional)', }, { displayName: 'Shipping SKU', name: 'shippingSku', type: 'string', default: '', description: 'SKU for shipping line item (e.g., P&P1, P&P2) - will be added as last line item if provided', }, { displayName: 'Shipping Price', name: 'shippingPrice', type: 'number', default: 0, description: 'Price for shipping line item - will be added as last line item if Shipping SKU is provided', }, { displayName: 'Shipping Description', name: 'shippingDescription', type: 'string', default: '', description: 'Description for shipping line item (optional - uses MYOB item default if not provided)', }, { displayName: 'Input Method', name: 'inputMethod', type: 'options', default: 'ui', description: 'How to specify the line items', options: [ { name: 'UI Form', value: 'ui', description: 'Add line items using the form below' }, { name: 'JSON Array', value: 'json', description: 'Provide line items as a JSON array (for dynamic data)' }, ], }, { displayName: 'Line Items', name: 'lineItems', type: 'fixedCollection', typeOptions: { multipleValues: true }, default: [{}], required: true, displayOptions: { show: { inputMethod: ['ui'] } }, description: 'Line items for the sales order', options: [ { name: 'item', displayName: 'Line Item', values: [ { displayName: 'SKU (Item Number)', name: 'sku', type: 'string', default: '', required: true, description: 'The SKU/Item Number - will automatically lookup the MYOB Item UID' }, { displayName: 'Quantity', name: 'quantity', type: 'number', default: 1, required: true }, { displayName: 'Unit Price', name: 'unitPrice', type: 'number', default: 0, required: true }, { displayName: 'Tax Code UID', name: 'taxCodeUid', type: 'string', default: '', description: 'The unique ID of the tax code (optional - uses item default if not specified)' }, { displayName: 'Description', name: 'description', type: 'string', default: '', description: 'Optional description override (optional - uses item default if not specified)' }, ], }, ], }, { displayName: 'Default SKU', name: 'defaultSku', type: 'string', default: 'DEFAULT-ITEM', required: true, displayOptions: { show: { inputMethod: ['json'] } }, description: 'Default SKU to use when line items have no SKU, empty SKU, or when SKU lookup fails', }, { displayName: 'Default Tax Code UID', name: 'defaultTaxCodeUid', type: 'string', default: '', displayOptions: { show: { inputMethod: ['json'] } }, description: 'Default Tax Code UID to use for all line items (optional - uses item default if not specified)', }, { displayName: 'Line Items Data', name: 'lineItemsData', type: 'json', default: '[]', required: true, displayOptions: { show: { inputMethod: ['json'] } }, description: 'Raw line items array from Shopify webhook or other source. Will extract sku, quantity, price automatically', placeholder: 'Paste Shopify line_items array or use expression like {{ $json.line_items }}', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; // Process all input data as a single sales order if (items.length === 0) { throw new Error('No input data received'); } // Use the first item for parameters, ignore additional items to prevent multiple executions const i = 0; try { // Get credentials to build the base URL const credentials = await this.getCredentials('myobOAuth2Api'); const baseUrl = `https://api.myob.com/accountright/${credentials.companyFileGuid}`; const customerUid = this.getNodeParameter('customerUid', i); const orderDate = this.getNodeParameter('orderDate', i); const isTaxInclusive = this.getNodeParameter('isTaxInclusive', i); const journalMemo = this.getNodeParameter('journalMemo', i); const comment = this.getNodeParameter('comment', i); const purchaseOrderNumber = this.getNodeParameter('purchaseOrderNumber', i); const shippingLine1 = this.getNodeParameter('shippingLine1', i); const shippingLine2 = this.getNodeParameter('shippingLine2', i); const shippingLine3 = this.getNodeParameter('shippingLine3', i); const shippingLine4 = this.getNodeParameter('shippingLine4', i); const shippingLine5 = this.getNodeParameter('shippingLine5', i); const shippingSku = this.getNodeParameter('shippingSku', i); const shippingPrice = this.getNodeParameter('shippingPrice', i); const shippingDescription = this.getNodeParameter('shippingDescription', i); const inputMethod = this.getNodeParameter('inputMethod', i); // Clean up purchase order number by removing # symbol const cleanedPurchaseOrderNumber = purchaseOrderNumber ? purchaseOrderNumber.replace(/^#/, '').trim() : ''; // Validate required fields if (!customerUid) { throw new Error('Customer UID is required'); } // Get line items based on input method let lineItemsArray = []; let defaultSku = ''; let defaultTaxCodeUid = ''; if (inputMethod === 'json') { const rawLineItemsData = this.getNodeParameter('lineItemsData', i); defaultSku = this.getNodeParameter('defaultSku', i); defaultTaxCodeUid = this.getNodeParameter('defaultTaxCodeUid', i); try { let rawData; // Handle both string (JSON) and already-parsed object/array data if (typeof rawLineItemsData === 'string') { rawData = JSON.parse(rawLineItemsData); } else if (Array.isArray(rawLineItemsData)) { rawData = rawLineItemsData; } else if (typeof rawLineItemsData === 'object' && rawLineItemsData !== null) { // If it's an object but not an array, it might be wrapped rawData = rawLineItemsData; } else { throw new Error('Line Items Data must be a JSON string or array'); } if (!Array.isArray(rawData)) { throw new Error('Line Items Data must be an array'); } // Transform Shopify/raw data to standardized format lineItemsArray = rawData.map((shopifyItem) => { // Preserve original SKU value (including null) for proper fallback logic // Don't substitute defaultSku here - let the processing logic handle fallbacks const itemSku = (shopifyItem.sku && shopifyItem.sku.trim() !== '') ? shopifyItem.sku.trim() : null; // Use current_quantity if available, fallback to quantity, then to 1 const itemQuantity = shopifyItem.current_quantity || shopifyItem.quantity || 1; // Parse price from string to number const itemPrice = parseFloat(shopifyItem.price || shopifyItem.unit_price || '0'); // Calculate discount amount from Shopify discount_allocations let discountAmount = 0; if (shopifyItem.discount_allocations && Array.isArray(shopifyItem.discount_allocations)) { discountAmount = shopifyItem.discount_allocations.reduce((total, discount) => { const discountValue = parseFloat(discount.amount || '0'); return total + discountValue; }, 0); } else if (shopifyItem.total_discount) { // Fallback to total_discount if discount_allocations not available discountAmount = parseFloat(shopifyItem.total_discount || '0'); } // Build description from available fields - prioritize 'name' for Shopify line items let itemDescription = shopifyItem.name || shopifyItem.title || ''; if (shopifyItem.variant_title && shopifyItem.variant_title.trim() !== '') { itemDescription = `${itemDescription} - ${shopifyItem.variant_title}`.trim(); } return { sku: itemSku, quantity: itemQuantity, unitPrice: itemPrice, discountAmount: discountAmount, taxCodeUid: defaultTaxCodeUid, description: itemDescription || shopifyItem.description || '' }; }); } catch (error) { throw new Error(`Invalid Line Items Data: ${error.message}`); } } else { const lineItemsData = this.getNodeParameter('lineItems', i); if (!lineItemsData || !lineItemsData.item || lineItemsData.item.length === 0) { throw new Error('At least one line item is required'); } lineItemsArray = lineItemsData.item; } if (lineItemsArray.length === 0) { throw new Error('At least one line item is required'); } // Build lines array with SKU lookup const lines = []; for (const item of lineItemsArray) { let itemUid = ''; let itemDescription = item.description || ''; // Transformed description from Shopify data // Look up Item UID from SKU with fallback to default SKU let actualSku = item.sku; if (item.sku && item.sku.trim() !== '') { try { const skuEncoded = encodeURIComponent(`'${item.sku}'`); const lookupUrl = `/Inventory/Item?$filter=Number eq ${skuEncoded}`; const lookupResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', lookupUrl, {}, {}, baseUrl); if (lookupResponse && lookupResponse.Items && lookupResponse.Items.length > 0) { itemUid = lookupResponse.Items[0].UID; // Always use MYOB description when SKU matches (prioritize over Shopify) // Prioritize 'Description' field over 'Name' field from MYOB API if (lookupResponse.Items[0].Description && lookupResponse.Items[0].Description.trim() !== '') { itemDescription = lookupResponse.Items[0].Description; console.log(`Using MYOB Description for SKU '${item.sku}': '${itemDescription}'`); } else if (lookupResponse.Items[0].Name && lookupResponse.Items[0].Name.trim() !== '') { itemDescription = lookupResponse.Items[0].Name; console.log(`Using MYOB Name (fallback) for SKU '${item.sku}': '${itemDescription}'`); } else if (!itemDescription || itemDescription.trim() === '') { // Only fallback to empty if both MYOB Description, Name, and Shopify descriptions are empty itemDescription = ''; console.log(`No MYOB Description or Name found for SKU '${item.sku}', using Shopify description: '${item.description || ''}'`); } } else { // SKU not found, fallback to default SKU console.log(`SKU '${item.sku}' not found in MYOB, falling back to default SKU '${defaultSku}'`); actualSku = defaultSku; if (defaultSku && defaultSku.trim() !== '') { const defaultSkuEncoded = encodeURIComponent(`'${defaultSku}'`); const defaultLookupUrl = `/Inventory/Item?$filter=Number eq ${defaultSkuEncoded}`; const defaultLookupResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', defaultLookupUrl, {}, {}, baseUrl); if (defaultLookupResponse && defaultLookupResponse.Items && defaultLookupResponse.Items.length > 0) { itemUid = defaultLookupResponse.Items[0].UID; // Keep Shopify description when using fallback SKU (only use MYOB for exact SKU matches) // If Shopify description is empty, only then use the MYOB default SKU description as fallback if (!itemDescription || itemDescription.trim() === '') { if (defaultLookupResponse.Items[0].Description && defaultLookupResponse.Items[0].Description.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Description; console.log(`No Shopify description, using default SKU MYOB Description: '${itemDescription}'`); } else if (defaultLookupResponse.Items[0].Name && defaultLookupResponse.Items[0].Name.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Name; console.log(`No Shopify description, using default SKU MYOB Name: '${itemDescription}'`); } } else { console.log(`Using Shopify description for fallback SKU: '${itemDescription}'`); } } else { throw new Error(`Default SKU '${defaultSku}' not found in MYOB`); } } else { throw new Error(`SKU '${item.sku}' not found in MYOB and no default SKU provided`); } } } catch (error) { // If it's a network/API error (not SKU not found), still try default SKU if (error.message.includes('not found in MYOB')) { throw error; // Re-throw our custom not found errors } else { console.log(`Failed to lookup SKU '${item.sku}': ${error.message}, falling back to default SKU '${defaultSku}'`); actualSku = defaultSku; if (defaultSku && defaultSku.trim() !== '') { const defaultSkuEncoded = encodeURIComponent(`'${defaultSku}'`); const defaultLookupUrl = `/Inventory/Item?$filter=Number eq ${defaultSkuEncoded}`; const defaultLookupResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', defaultLookupUrl, {}, {}, baseUrl); if (defaultLookupResponse && defaultLookupResponse.Items && defaultLookupResponse.Items.length > 0) { itemUid = defaultLookupResponse.Items[0].UID; // Keep Shopify description when using fallback SKU (only use MYOB for exact SKU matches) // If Shopify description is empty, only then use the MYOB default SKU description as fallback if (!itemDescription || itemDescription.trim() === '') { if (defaultLookupResponse.Items[0].Description && defaultLookupResponse.Items[0].Description.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Description; console.log(`No Shopify description, using default SKU MYOB Description: '${itemDescription}'`); } else if (defaultLookupResponse.Items[0].Name && defaultLookupResponse.Items[0].Name.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Name; console.log(`No Shopify description, using default SKU MYOB Name: '${itemDescription}'`); } } else { console.log(`Using Shopify description for fallback SKU: '${itemDescription}'`); } } else { throw new Error(`Default SKU '${defaultSku}' not found in MYOB`); } } else { throw new Error(`Failed to lookup SKU '${item.sku}': ${error.message}, and no default SKU provided`); } } } } else { // No SKU provided, use default SKU console.log(`No SKU provided for item, using default SKU '${defaultSku}'`); actualSku = defaultSku; if (defaultSku && defaultSku.trim() !== '') { const defaultSkuEncoded = encodeURIComponent(`'${defaultSku}'`); const defaultLookupUrl = `/Inventory/Item?$filter=Number eq ${defaultSkuEncoded}`; const defaultLookupResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', defaultLookupUrl, {}, {}, baseUrl); if (defaultLookupResponse && defaultLookupResponse.Items && defaultLookupResponse.Items.length > 0) { itemUid = defaultLookupResponse.Items[0].UID; // When no SKU provided, preserve Shopify description (actual product info) // Only use default SKU for accounting/pricing, keep meaningful product description // Always prioritize Shopify description over MYOB when using fallback SKU if (itemDescription && itemDescription.trim() !== '') { console.log(`Using Shopify description for empty SKU: '${itemDescription}'`); } else { // Only use MYOB description if Shopify description is truly empty if (defaultLookupResponse.Items[0].Description && defaultLookupResponse.Items[0].Description.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Description; console.log(`No Shopify description (empty SKU), using default SKU MYOB Description: '${itemDescription}'`); } else if (defaultLookupResponse.Items[0].Name && defaultLookupResponse.Items[0].Name.trim() !== '') { itemDescription = defaultLookupResponse.Items[0].Name; console.log(`No Shopify description (empty SKU), using default SKU MYOB Name: '${itemDescription}'`); } } } else { throw new Error(`Default SKU '${defaultSku}' not found in MYOB`); } } else { throw new Error('No SKU provided and no default SKU available'); } } const lineItem = { Item: { UID: itemUid }, ShipQuantity: item.quantity, UnitPrice: item.unitPrice, }; // Add discount percentage if present (from Shopify discounts) if (item.discountAmount && item.discountAmount > 0 && item.unitPrice > 0) { // Convert discount amount to percentage based on total line value (unit price × quantity) const totalLineValue = item.unitPrice * item.quantity; const discountPercent = (item.discountAmount / totalLineValue) * 100; lineItem.DiscountPercent = Math.round(discountPercent * 100) / 100; // Round to 2 decimal places console.log(`Converted discount amount ${item.discountAmount} to percentage: ${lineItem.DiscountPercent}% (unit price: ${item.unitPrice}, quantity: ${item.quantity}, total line value: ${totalLineValue})`); } // Add TaxCode if provided if (item.taxCodeUid && item.taxCodeUid.trim() !== '') { lineItem.TaxCode = { UID: item.taxCodeUid }; } // Add description if available if (itemDescription && itemDescription.trim() !== '') { lineItem.Description = itemDescription; } lines.push(lineItem); } // Add shipping line item if provided if (shippingSku && shippingSku.trim() !== '') { let shippingItemUid = ''; let finalShippingDescription = shippingDescription || ''; // Start with manual description as fallback try { const skuEncoded = encodeURIComponent(`'${shippingSku}'`); const lookupUrl = `/Inventory/Item?$filter=Number eq ${skuEncoded}`; const lookupResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', lookupUrl, {}, {}, baseUrl); if (lookupResponse && lookupResponse.Items && lookupResponse.Items.length > 0) { shippingItemUid = lookupResponse.Items[0].UID; // Always use MYOB description when shipping SKU matches (consistent with line items) // Prioritize 'Description' field over 'Name' field for shipping items if (lookupResponse.Items[0].Description && lookupResponse.Items[0].Description.trim() !== '') { finalShippingDescription = lookupResponse.Items[0].Description; console.log(`✅ USING MYOB Description for shipping SKU '${shippingSku}': '${finalShippingDescription}'`); } else if (lookupResponse.Items[0].Name && lookupResponse.Items[0].Name.trim() !== '') { // Fallback to Name field if Description is empty finalShippingDescription = lookupResponse.Items[0].Name; console.log(`✅ USING MYOB Name (fallback) for shipping SKU '${shippingSku}': '${finalShippingDescription}'`); } else { // Only use manual description if both MYOB Description and Name are empty console.log(`❌ No MYOB Description or Name found for shipping SKU '${shippingSku}', using manual description: '${shippingDescription || ''}'`); // finalShippingDescription already set to shippingDescription at initialization } const shippingLineItem = { Item: { UID: shippingItemUid }, ShipQuantity: 1, UnitPrice: shippingPrice, }; // Add default tax code if provided if (defaultTaxCodeUid && defaultTaxCodeUid.trim() !== '') { shippingLineItem.TaxCode = { UID: defaultTaxCodeUid }; } // Add description if available if (finalShippingDescription && finalShippingDescription.trim() !== '') { shippingLineItem.Description = finalShippingDescription; } lines.push(shippingLineItem); } else { throw new Error(`Shipping SKU '${shippingSku}' not found in MYOB`); } } catch (error) { throw new Error(`Failed to lookup Shipping SKU '${shippingSku}': ${error.message}`); } } // Build shipping address from lines (similar to your example) let shippingAddress = ''; const newline = '\r\n'; // Add each line to the shipping address, appending newline after each if (shippingLine1 && shippingLine1.trim() !== '') { shippingAddress = shippingLine1; } if (shippingLine2 && shippingLine2.trim() !== '') { shippingAddress = shippingAddress + (shippingAddress ? newline : '') + shippingLine2; } if (shippingLine3 && shippingLine3.trim() !== '') { shippingAddress = shippingAddress + (shippingAddress ? newline : '') + shippingLine3; } if (shippingLine4 && shippingLine4.trim() !== '') { shippingAddress = shippingAddress + (shippingAddress ? newline : '') + shippingLine4; } if (shippingLine5 && shippingLine5.trim() !== '') { shippingAddress = shippingAddress + (shippingAddress ? newline : '') + shippingLine5; } // Build the payload according to MYOB Business API structure // Handle order date - use provided date or fallback to current date in n8n timezone let formattedDate; if (orderDate && orderDate.trim() !== '') { // Use provided order date (should be in format like 2025-08-24T04:32:01+10:00) // Extract the local date part and preserve it, don't convert to UTC try { const providedDate = new Date(orderDate.trim()); if (isNaN(providedDate.getTime())) { throw new Error(`Invalid order date format: ${orderDate}`); } // Extract the date/time parts from the original input to preserve local date // Parse the input to get the local date components const inputDateMatch = orderDate.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/); if (inputDateMatch) { // Use the local date and time from input, format as ISO for MYOB const [, year, month, day, hour, minute, second] = inputDateMatch; formattedDate = `${year}-${month}-${day}T${hour}:${minute}:${second}.000Z`; } else { // Fallback: extract date from the Date object but keep it as local date const year = providedDate.getFullYear(); const month = String(providedDate.getMonth() + 1).padStart(2, '0'); const day = String(providedDate.getDate()).padStart(2, '0'); const hour = String(providedDate.getHours()).padStart(2, '0'); const minute = String(providedDate.getMinutes()).padStart(2, '0'); const second = String(providedDate.getSeconds()).padStart(2, '0'); formattedDate = `${year}-${month}-${day}T${hour}:${minute}:${second}.000Z`; } console.log(`Using provided order date: ${orderDate} -> ${formattedDate} (preserved local date/time)`); } catch (error) { throw new Error(`Invalid order date format: ${orderDate}. Expected format: 2025-08-24T04:32:01+10:00`); } } else { // Fallback to current date in configured n8n timezone const timezone = this.getTimezone(); const currentDate = new Date(); // Create a date that represents today in the configured timezone const localDate = new Date(currentDate.toLocaleString("en-US", { timeZone: timezone })); formattedDate = localDate.toISOString().split('T')[0] + 'T00:00:00.000Z'; console.log(`Using current date in timezone ${timezone}: ${formattedDate}`); } const payload = { OrderType: 'Item', Customer: { UID: customerUid }, Date: formattedDate, IsTaxInclusive: isTaxInclusive, Lines: lines, }; // Add optional fields if provided if (journalMemo && journalMemo.trim() !== '') { payload.JournalMemo = journalMemo; } if (comment && comment.trim() !== '') { payload.Comment = comment; } if (cleanedPurchaseOrderNumber && cleanedPurchaseOrderNumber.trim() !== '') { payload.CustomerPurchaseOrderNumber = cleanedPurchaseOrderNumber; } if (shippingAddress && shippingAddress.trim() !== '') { payload.ShipToAddress = shippingAddress; } // Step 1: Create the sales order const createResponse = await MyobApi_request_1.myobRequest.call(this, 'POST', '/Sale/Order/Item', payload, {}, baseUrl); // Check if creation was successful let creationSuccessful = false; if (createResponse && typeof createResponse === 'object' && 'statusCode' in createResponse) { creationSuccessful = createResponse.statusCode >= 200 && createResponse.statusCode < 300; } else { // Assume success if we got any response without error creationSuccessful = true; } if (!creationSuccessful) { throw new Error('Failed to create sales order in MYOB'); } // Step 2: Look up the created sales order by purchase order number if (cleanedPurchaseOrderNumber && cleanedPurchaseOrderNumber.trim() !== '') { try { const searchCriteria = `'${cleanedPurchaseOrderNumber}'`; const lookupUrl = `/Sale/Order/Item?$filter=CustomerPurchaseOrderNumber eq ${encodeURIComponent(searchCriteria)}`; const salesOrderResponse = await MyobApi_request_1.myobRequest.call(this, 'GET', lookupUrl, {}, {}, baseUrl); if (salesOrderResponse && salesOrderResponse.Items && salesOrderResponse.Items.length > 0) { // Return the found sales order data returnData.push({ json: salesOrderResponse.Items[0] // Return the first matching sales order }); } else { // Sales order created but not found in lookup (might be timing issue) returnData.push({ json: { success: true, message: 'Sales order created successfully but not found in immediate lookup', purchaseOrderNumber: cleanedPurchaseOrderNumber, note: 'The sales order may take a moment to be available for search. Try searching manually in MYOB.' } }); } } catch (lookupError) { // Sales order created but lookup failed returnData.push({ json: { success: true, message: 'Sales order created successfully but lookup failed', purchaseOrderNumber: cleanedPurchaseOrderNumber, lookupError: lookupError.message, note: 'The sales order was created in MYOB but could not be retrieved immediately.' } }); } } else { // No purchase order number to search by returnData.push({ json: { success: true, message: 'Sales order created successfully', note: 'No purchase order number provided, so the created sales order cannot be retrieved automatically.' } }); } } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message || error.toString() } }); } else { throw error; } } return this.prepareOutputData(returnData); } } exports.Myob = Myob;