UNPKG

n8n-nodes-myobv2

Version:

Comprehensive n8n node for MYOB Business API - Sales Orders, Customers, Items, Invoices, and more

1,132 lines (1,131 loc) 75.4 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', name: 'myob', icon: 'file:myob.png', group: ['output'], version: 1, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], description: 'Interact with MYOB Business API - Sales Orders, Customers, Items, Invoices, and more', defaults: { name: 'MYOB' }, credentials: [ { name: 'myobOAuth2Api', required: true }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, required: true, default: 'salesOrder', options: [ { name: 'Sales Order', value: 'salesOrder', }, { name: 'Customer', value: 'customer', }, { name: 'Item', value: 'item', }, { name: 'Invoice', value: 'invoice', }, ], }, // Sales Order Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, required: true, displayOptions: { show: { resource: ['salesOrder'], }, }, default: 'getUidByPurchaseOrder', options: [ { name: 'Get UID by Purchase Order Number', value: 'getUidByPurchaseOrder', description: 'Retrieve Sales Order UID by Purchase Order Number (like Shopify order number)', }, { name: 'Update Order', value: 'updateOrder', description: 'Update a MYOB Sales Order with Shopify data (line items, shipping, customer info)', }, { name: 'Get', value: 'get', description: 'Get a sales order by UID', }, { name: 'List', value: 'list', description: 'List all sales orders', }, { name: 'Search', value: 'search', description: 'Search sales orders with advanced filters', }, ], }, // Customer Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, required: true, displayOptions: { show: { resource: ['customer'], }, }, default: 'get', options: [ { name: 'Get', value: 'get', description: 'Get a customer by UID', }, { name: 'List', value: 'list', description: 'List all customers', } ], }, // Item Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, required: true, displayOptions: { show: { resource: ['item'], }, }, default: 'get', options: [ { name: 'Get', value: 'get', description: 'Get an item by UID or SKU', }, { name: 'List', value: 'list', description: 'List all inventory items', } ], }, // Invoice Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, required: true, displayOptions: { show: { resource: ['invoice'], }, }, default: 'get', options: [ { name: 'Get', value: 'get', description: 'Get an invoice by UID', }, { name: 'List', value: 'list', description: 'List all invoices', } ], }, // ===== SALES ORDER FIELDS ===== // Sales Order Get UID by Purchase Order Number Field { displayName: 'Purchase Order Number', name: 'purchaseOrderNumberSearch', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['getUidByPurchaseOrder'], }, }, description: 'The Purchase Order Number to search for (e.g., Shopify order number). Will automatically remove # prefix if present.', placeholder: '#1234 or 1234', }, // Sales Order Update Fields { displayName: 'MYOB Order UID', name: 'updateOrderUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'The unique ID of the MYOB Sales Order to update', placeholder: 'e.g., 12345678-1234-1234-1234-123456789012', }, { displayName: 'Shopify Update Data', name: 'shopifyUpdateData', type: 'json', default: '{}', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'The Shopify line items array from the order data (e.g., from webhook line_items field)', placeholder: 'Use expression like {{ $json.line_items }} or paste Shopify line_items array', }, { displayName: 'Default Product UID', name: 'defaultProductUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Default MYOB Product UID to use when no SKU match is found in line items', placeholder: 'e.g., 12345678-1234-1234-1234-123456789012', }, { displayName: 'Shipping SKU', name: 'shippingSku', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'SKU for shipping line item (e.g., P&P1, P&P2) - optional, will not add shipping line if empty', placeholder: 'e.g., P&P1', }, { displayName: 'Shipping Description', name: 'shippingDescription', type: 'string', default: 'Shipping', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Description for the shipping line item', placeholder: 'e.g., Standard Shipping, Express Shipping', }, { displayName: 'Shipping Price', name: 'shippingPrice', type: 'number', default: 0, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Price for the shipping line item (will be added as separate line if Shipping Item UID is provided)', placeholder: 'e.g., 12.00', }, { displayName: 'Freight Tax Code UID', name: 'freightTaxCodeUid', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Tax Code UID for freight/shipping charges (optional - uses GST if not provided)', placeholder: 'e.g., 12345678-1234-1234-1234-123456789012', }, { displayName: 'Purchase Order Number', name: 'updatePurchaseOrderNumber', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Purchase Order Number (optional - leave empty to keep existing value)', placeholder: 'e.g., #1234 or 1234', }, { displayName: 'Comment', name: 'updateComment', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Comment for the sales order (optional - leave empty to keep existing value)', placeholder: 'e.g., Special delivery instructions', }, { displayName: 'Journal Memo', name: 'updateJournalMemo', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Internal journal memo (optional - leave empty to keep existing value)', placeholder: 'e.g., Updated from Shopify order', }, { displayName: 'Default Tax Code UID', name: 'defaultTaxCodeUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Default Tax Code UID to use for all line items (required - usually your GST/Sales Tax code UID)', placeholder: 'e.g., 12345678-1234-1234-1234-123456789012', }, { displayName: 'Default Location UID', name: 'defaultLocationUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['updateOrder'], }, }, description: 'Default Location UID for inventoried items (required - usually your main warehouse/store location UID)', placeholder: 'e.g., 12345678-1234-1234-1234-123456789012', }, // Sales Order Get Fields { displayName: 'Sales Order UID', name: 'salesOrderUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['get'], }, }, description: 'The unique ID of the sales order to retrieve', }, // Sales Order Create Fields { displayName: 'Customer UID', name: 'customerUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'The unique ID of the customer (GUID format)', }, { displayName: 'Tax Inclusive', name: 'isTaxInclusive', type: 'boolean', default: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Whether prices include tax', }, { displayName: 'Journal Memo', name: 'journalMemo', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Internal memo for the journal entry (optional)', }, { displayName: 'Comment', name: 'comment', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Comment for the sales order (optional)', }, { displayName: 'Purchase Order Number', name: 'purchaseOrderNumber', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Purchase order reference number (optional)', }, { displayName: 'Shipping Address Line 1', name: 'shippingLine1', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'First line of shipping address (optional)', }, { displayName: 'Shipping Address Line 2', name: 'shippingLine2', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Second line of shipping address (optional)', }, { displayName: 'Shipping Address Line 3', name: 'shippingLine3', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Third line of shipping address (optional)', }, { displayName: 'Shipping Address Line 4', name: 'shippingLine4', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Fourth line of shipping address (optional)', }, { displayName: 'Shipping Address Line 5', name: 'shippingLine5', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Fifth line of shipping address (optional)', }, { displayName: 'Shipping SKU', name: 'shippingSku', type: 'string', default: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, 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, displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, 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: '', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, description: 'Description for shipping line item (optional - uses MYOB item default if not provided)', }, { displayName: 'Input Method', name: 'inputMethod', type: 'options', default: 'ui', displayOptions: { show: { resource: ['salesOrder'], operation: ['create'], }, }, 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: { resource: ['salesOrder'], operation: ['create'], 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: { resource: ['salesOrder'], operation: ['create'], 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: { resource: ['salesOrder'], operation: ['create'], 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: { resource: ['salesOrder'], operation: ['create'], 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 }}', }, // ===== CUSTOMER FIELDS ===== { displayName: 'Customer UID', name: 'customerUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['customer'], operation: ['get'], }, }, description: 'The unique ID of the customer to retrieve', }, // Customer Create Fields { displayName: 'Customer Name', name: 'customerName', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['customer'], operation: ['create'], }, }, description: 'The name of the customer', }, { displayName: 'Customer Number', name: 'customerNumber', type: 'string', default: '', displayOptions: { show: { resource: ['customer'], operation: ['create'], }, }, description: 'The customer number/code (optional - MYOB will auto-generate if not provided)', }, { displayName: 'Email', name: 'customerEmail', type: 'string', default: '', displayOptions: { show: { resource: ['customer'], operation: ['create'], }, }, description: 'Customer email address (optional)', }, { displayName: 'Phone', name: 'customerPhone', type: 'string', default: '', displayOptions: { show: { resource: ['customer'], operation: ['create'], }, }, description: 'Customer phone number (optional)', }, // ===== ITEM FIELDS ===== { displayName: 'Item UID', name: 'itemUid', type: 'string', default: '', displayOptions: { show: { resource: ['item'], operation: ['get'], }, }, description: 'The unique ID of the item to retrieve (leave empty to search by SKU)', }, { displayName: 'Item SKU', name: 'itemSku', type: 'string', default: '', displayOptions: { show: { resource: ['item'], operation: ['get'], }, }, description: 'The SKU/Item Number to search for (used when Item UID is empty)', }, // Item Create Fields { displayName: 'Item Name', name: 'itemName', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['item'], operation: ['create'], }, }, description: 'The name of the inventory item', }, { displayName: 'Item SKU/Number', name: 'itemNumber', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['item'], operation: ['create'], }, }, description: 'The SKU/Item Number', }, { displayName: 'Item Description', name: 'itemDescription', type: 'string', default: '', displayOptions: { show: { resource: ['item'], operation: ['create'], }, }, description: 'Description of the inventory item (optional)', }, { displayName: 'Base Selling Price', name: 'baseSellingPrice', type: 'number', default: 0, displayOptions: { show: { resource: ['item'], operation: ['create'], }, }, description: 'The base selling price for the item (optional)', }, // ===== INVOICE FIELDS ===== { displayName: 'Invoice UID', name: 'invoiceUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['invoice'], operation: ['get'], }, }, description: 'The unique ID of the invoice to retrieve', }, { displayName: 'Sales Order UID', name: 'salesOrderUid', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['invoice'], operation: ['createFromSalesOrder'], }, }, description: 'The unique ID of the sales order to convert to invoice', }, // ===== LIST OPERATIONS FIELDS ===== { displayName: 'Limit', name: 'limit', type: 'number', default: 100, displayOptions: { show: { operation: ['list', 'search'], }, }, description: 'Maximum number of records to return (default: 100)', }, { displayName: 'Filter', name: 'filter', type: 'string', default: '', displayOptions: { show: { operation: ['list'], }, }, description: 'OData filter expression (optional). Example: IsActive eq true', placeholder: 'IsActive eq true', }, // ===== SALES ORDER SEARCH FIELDS ===== { displayName: 'Search Filter', name: 'searchFilter', type: 'options', default: 'customer', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], }, }, description: 'Type of search to perform', options: [ { name: 'By Customer Name', value: 'customer', description: 'Search by customer name (contains)', }, { name: 'By Purchase Order Number', value: 'purchaseOrder', description: 'Search by purchase order number (exact match)', }, { name: 'By Date Range', value: 'dateRange', description: 'Search by order date range', }, { name: 'By Status', value: 'status', description: 'Search by order status', }, { name: 'By Comment', value: 'comment', description: 'Search by comment content (contains)', }, { name: 'Custom Filter', value: 'custom', description: 'Provide your own OData filter expression', }, ], }, { displayName: 'Customer Name', name: 'searchCustomerName', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['customer'], }, }, description: 'Customer name to search for (partial match)', placeholder: 'e.g., John Smith', }, { displayName: 'Purchase Order Number', name: 'searchPurchaseOrder', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['purchaseOrder'], }, }, description: 'Purchase order number to search for (exact match)', placeholder: 'e.g., #1234 or 1234', }, { displayName: 'Start Date', name: 'searchStartDate', type: 'dateTime', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['dateRange'], }, }, description: 'Start date for date range search', }, { displayName: 'End Date', name: 'searchEndDate', type: 'dateTime', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['dateRange'], }, }, description: 'End date for date range search', }, { displayName: 'Order Status', name: 'searchStatus', type: 'options', default: 'Open', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['status'], }, }, description: 'Status of the sales orders to search for', options: [ { name: 'Open', value: 'Open' }, { name: 'Converting', value: 'Converting' }, { name: 'BackOrder', value: 'BackOrder' }, { name: 'Closed', value: 'Closed' }, ], }, { displayName: 'Comment Text', name: 'searchComment', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['comment'], }, }, description: 'Text to search for in order comments (partial match)', placeholder: 'e.g., urgent', }, { displayName: 'Custom Filter Expression', name: 'searchCustomFilter', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['salesOrder'], operation: ['search'], searchFilter: ['custom'], }, }, description: 'Custom OData filter expression', placeholder: 'e.g., TotalAmount gt 100 and Status eq \'Open\'', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; if (items.length === 0) { throw new Error('No input data received'); } for (let i = 0; i < items.length; i++) { try { // Get credentials to build the base URL const credentials = await this.getCredentials('myobOAuth2Api'); const baseUrl = `https://api.myob.com/accountright/${credentials.companyFileGuid}`; const resource = this.getNodeParameter('resource', i); const operation = this.getNodeParameter('operation', i); let result = {}; // Handle different resources and operations inline if (resource === 'salesOrder') { if (operation === 'getUidByPurchaseOrder') { // Retrieve Sales Order UID by Purchase Order Number - matches your Deluge function const purchaseOrderNumber = this.getNodeParameter('purchaseOrderNumberSearch', i); // Check if the input is valid - convert to string first const orderNumberStr = String(purchaseOrderNumber || ''); if (!orderNumberStr || orderNumberStr.trim() === '') { throw new Error('Purchase Order Number parameter cannot be empty.'); } // STEP 1: Format the order number for the API filter query (e.g., '1234') // Clean the number by removing the "#" prefix and create the search criteria const cleanOrderNumber = orderNumberStr.replace(/^#/, '').trim(); const searchCriteria = `'${cleanOrderNumber}'`; // STEP 2: Build the API URL with the filter const filterUrl = `/Sale/Order/Item?$filter=CustomerPurchaseOrderNumber eq ${encodeURIComponent(searchCriteria)}`; // STEP 3: Make the GET request to MYOB const response = await MyobApi_request_1.myobRequest.call(this, 'GET', filterUrl, {}, {}, baseUrl); // STEP 4: Process the response let myobOrderUID = ''; let foundOrder = null; let searchResults = { searchedFor: cleanOrderNumber, found: false, uid: '', message: '', fullResponse: response }; // The API returns an object like {"Items": [...]}. Check if response and "Items" key exist. if (response !== null && response.Items !== null && response.Items !== undefined) { const itemsList = response.Items; // Check if the list is not empty (i.e., an order was found) if (itemsList.length > 0) { // Get the first item from the list (there should only be one) foundOrder = itemsList[0]; myobOrderUID = foundOrder.UID; searchResults = { searchedFor: cleanOrderNumber, found: true, uid: myobOrderUID, message: `SUCCESS: Found MYOB Order UID: ${myobOrderUID}`, salesOrder: foundOrder, totalResults: itemsList.length }; } else { // This is not an error, it just means no match was found. searchResults.message = `INFO: No matching order found in MYOB for Purchase Order ${cleanOrderNumber}`; } } else { searchResults.message = `ERROR: The API response from MYOB was invalid or empty. Response: ${JSON.stringify(response)}`; } result = searchResults; } else if (operation === 'updateOrder') { // Update MYOB Order - handles Shopify line items data and adds separate shipping line const updateOrderUid = this.getNodeParameter('updateOrderUid', i); const shopifyUpdateDataRaw = this.getNodeParameter('shopifyUpdateData', i); const defaultProductUid = this.getNodeParameter('defaultProductUid', i); const shippingSku = this.getNodeParameter('shippingSku', i); const shippingDescription = this.getNodeParameter('shippingDescription', i); const shippingPrice = this.getNodeParameter('shippingPrice', i); const freightTaxCodeUid = this.getNodeParameter('freightTaxCodeUid', i); const updatePurchaseOrderNumber = this.getNodeParameter('updatePurchaseOrderNumber', i); const updateComment = this.getNodeParameter('updateComment', i); const updateJournalMemo = this.getNodeParameter('updateJournalMemo', i); const defaultTaxCodeUid = this.getNodeParameter('defaultTaxCodeUid', i); const defaultLocationUid = this.getNodeParameter('defaultLocationUid', i); // Validate the new required fields if (!defaultTaxCodeUid) { throw new Error('Default Tax Code UID is required'); } if (!defaultLocationUid) { throw new Error('Default Location UID is required'); } // Validate required fields if (!updateOrderUid) { throw new Error('MYOB Order UID is required'); } if (!defaultProductUid) { throw new Error('Default Product UID is required'); } // Parse the Shopify line items data (expecting array of line items) let shopifyLineItems; if (typeof shopifyUpdateDataRaw === 'string') { try { shopifyLineItems = JSON.parse(shopifyUpdateDataRaw); } catch (error) { throw new Error('Invalid JSON in Shopify Update Data'); } } else { shopifyLineItems = shopifyUpdateDataRaw; } if (!Array.isArray(shopifyLineItems)) { throw new Error('Shopify Update Data must be an array of line items'); } // STEP 1: Retrieve the existing MYOB order (like your Deluge script) const existingOrder = await MyobApi_request_1.myobRequest.call(this, 'GET', `/Sale/Order/Item/${updateOrderUid}`, {}, {}, baseUrl); if (!existingOrder) { throw new Error(`MYOB Order with UID ${updateOrderUid} not found`); } // STEP 2: Preserve existing order data (RowVersion, ShipToAddress, Comment) for concurrency and data integrity const preservedRowVersion = existingOrder.RowVersion; const preservedShipToAddress = existingOrder.ShipToAddress; const preservedComment = existingOrder.Comment; // STEP 3: Process Shopify line items with SKU lookups (like your original node) const processedLines = []; for (const lineItem of shopifyLineItems) { // Handle the new Shopify line item structure const lineItemSku = lineItem.sku || '';