From 5baa31b0532710c18fd05c8afcfb84e2463c8ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Sat, 13 Feb 2021 13:27:08 -0300 Subject: [PATCH] :sparkles: Add QuickBooks node (#1365) * Add OAuth2 credentials * Adjust credentials params * Add node to listing * Add initial node scaffolding * Remove unused credentials params * Add customer search with select statement * Add pagination to customer search * Add customer creation functionality * Add customer update functionality * Small formatting fix * Adjust property name casing for consistency * Adjust customer operations for consistency * Handle large QuickBooks listings * Add initial estimate resource description * Add estimate resource retrieval operations * Refactor customer billing address * Simplify customer billing address * Fix casing for customer additional fields * Adjust types to accommodate loadOptions functions * Add loadOptions for customers to estimate * Sort customer update fields alphabetically * Refactor estimate line into standalone file * Add stub for PDF retrieval operation * Add invoice resource description and execute branches * Implement estimate PDF download functionality * Place descriptions in their own dir * Add get and getAll for invoices * Add send functionality to invoices * Refactor handling of binary data * Add invoice voiding functionality * Add invoice deletion functionality * Refactor resources into subdirs and add interfaces * Add get and getAll for bill * Add payment description * Add get and getAll for payment * Make variables in endpoints consistent * Refactor interfaces for consistency * Add interface for item resource * Fill in fields for all resources * Minor fixes in defaults and descriptions * Refactor loader * Add all resources to execute function * Fix line property duplication * Add get and getAll for vendor * Optimize description imports * Add creation for customer and bill * Add update operation for bill * Refactor create and update for customer * Implement employee create and update * Implement create and update for estimate * Make address params more consistent * Add create and update to payment * Add item operations * Add create and delete operations for invoice * Refactor binary data handler * Refactor generic functions * Add create and update operations for vendor * Fix build * Fix total amount in bill:update * Fix balance in bill:update * Remove currency from bill:update * Implement reference retrieval in bill:update * Fix failing params in customer:update * Fix param in employee:update * Implement reference retrieval in estimate:update * Fix failing params in estimate:update * Fix failing params in invoice:update * Fix failing param in vendor:update * Implement reference retrieval in payment:update * Remove unused interfaces * Rename line property function * Remove hared directory * Refactor reference and sync token retrieval * Fix line structure in bill:create * Fix line structure in estimate:create * Flatten responses * Refactor line processing * Remove unused interfaces * Add endpoint documentation * Fix payment:void content type * Fix default for bill line item * Hide auth URI query parameters * Hide auth header parameter * Add switch for credentials environment * Adjust OAuth2 callback to accommodate realmId * Retrieve realmId from OAuth2 flow * :zap: Improvements * Reposition dividers * Add IDs to display names of reference fields * Add estimate:delete and bill:delete * Load items in lines for bill, estimate and invoice * Add filename for binary property in PDF download * :zap: Improvements * Adjust field description * Implement estimate:send * Adjust field description * Adjust custom field descriptions * Add missing period to description * :zap: Minor improvements on QuickBooks-Node * Add descriptions for bill * Add descriptions for customer * Add descriptions for employee * Add descriptions for estimate * Add descriptions for invoice * Add descriptions for payment * Add descriptions for vendor Co-authored-by: ricardo Co-authored-by: Jan Oberhauser --- packages/cli/src/Server.ts | 13 +- .../QuickBooksOAuth2Api.credentials.ts | 68 ++ .../nodes/QuickBooks/GenericFunctions.ts | 436 ++++++++ .../nodes/QuickBooks/QuickBooks.node.ts | 990 ++++++++++++++++++ .../Bill/BillAdditionalFieldsOptions.ts | 86 ++ .../descriptions/Bill/BillDescription.ts | 330 ++++++ .../CustomerAdditionalFieldsOptions.ts | 151 +++ .../Customer/CustomerDescription.ts | 222 ++++ .../EmployeeAdditionalFieldsOptions.ts | 91 ++ .../Employee/EmployeeDescription.ts | 236 +++++ .../EstimateAdditionalFieldsOptions.ts | 227 ++++ .../Estimate/EstimateDescription.ts | 425 ++++++++ .../Invoice/InvoiceAdditionalFieldsOptions.ts | 183 ++++ .../Invoice/InvoiceDescription.ts | 451 ++++++++ .../descriptions/Item/ItemDescription.ts | 129 +++ .../Payment/PaymentAdditionalFieldsOptions.ts | 9 + .../Payment/PaymentDescription.ts | 399 +++++++ .../descriptions/Shared.interface.ts | 48 + .../Vendor/VendorAdditionalFieldsOptions.ts | 117 +++ .../descriptions/Vendor/VendorDescription.ts | 222 ++++ .../nodes/QuickBooks/descriptions/index.ts | 8 + .../nodes/QuickBooks/quickbooks.svg | 1 + packages/nodes-base/package.json | 2 + 23 files changed, 4841 insertions(+), 3 deletions(-) create mode 100644 packages/nodes-base/credentials/QuickBooksOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Item/ItemDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Shared.interface.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorAdditionalFieldsOptions.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorDescription.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/descriptions/index.ts create mode 100644 packages/nodes-base/nodes/QuickBooks/quickbooks.svg diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a391c33126..c52e2f66bc 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -395,7 +395,8 @@ class App { })); //support application/x-www-form-urlencoded post data - this.app.use(bodyParser.urlencoded({ extended: false, + this.app.use(bodyParser.urlencoded({ + extended: false, verify: (req, res, buf) => { // @ts-ignore req.rawBody = buf; @@ -725,7 +726,7 @@ class App { // Make a copy of the object. If we don't do this, then when // The method below is called the properties are removed for good // This happens because nodes are returned as reference. - const nodeInfo: INodeTypeDescription = {...nodeData.description}; + const nodeInfo: INodeTypeDescription = { ...nodeData.description }; if (req.query.includeProperties !== 'true') { // @ts-ignore delete nodeInfo.properties; @@ -1310,6 +1311,8 @@ class App { // Verify and store app code. Generate access tokens and store for respective credential. this.app.get(`/${this.restEndpoint}/oauth2-credential/callback`, async (req: express.Request, res: express.Response) => { + + // realmId it's currently just use for the quickbook OAuth2 flow const { code, state: stateEncoded } = req.query; if (code === undefined || stateEncoded === undefined) { @@ -1384,6 +1387,10 @@ class App { const oauthToken = await oAuthObj.code.getToken(`${oAuth2Parameters.redirectUri}?${queryParameters}`, options); + if (Object.keys(req.query).length > 2) { + _.set(oauthToken.data, 'callbackQueryString', _.omit(req.query, 'state', 'code')); + } + if (oauthToken === undefined) { const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404); return ResponseHelper.sendErrorResponse(res, errorResponse); @@ -1510,7 +1517,7 @@ class App { } if (req.query.unflattedResponse === 'true') { - const fullExecutionData = ResponseHelper.unflattenExecutionData(result); + const fullExecutionData = ResponseHelper.unflattenExecutionData(result); return fullExecutionData as IExecutionResponse; } else { // Convert to response format in which the id is a string diff --git a/packages/nodes-base/credentials/QuickBooksOAuth2Api.credentials.ts b/packages/nodes-base/credentials/QuickBooksOAuth2Api.credentials.ts new file mode 100644 index 0000000000..f48109bb26 --- /dev/null +++ b/packages/nodes-base/credentials/QuickBooksOAuth2Api.credentials.ts @@ -0,0 +1,68 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +const scopes = [ + 'com.intuit.quickbooks.accounting', + 'com.intuit.quickbooks.payment', +]; + +// https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization + +export class QuickBooksOAuth2Api implements ICredentialType { + name = 'quickBooksOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'QuickBooks OAuth2 API'; + documentationUrl = 'quickbooks'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://appcenter.intuit.com/connect/oauth2', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: scopes.join(' '), + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'header', + }, + { + displayName: 'Environment', + name: 'environment', + type: 'options' as NodePropertyTypes, + default: 'production', + options: [ + { + name: 'Production', + value: 'production', + }, + { + name: 'Sandbox', + value: 'sandbox', + }, + ], + }, + ]; +} diff --git a/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts b/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts new file mode 100644 index 0000000000..9218f82bad --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts @@ -0,0 +1,436 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + CustomField, + GeneralAddress, + Ref, +} from './descriptions/Shared.interface'; + +import { + capitalCase, +} from 'change-case'; + +import { + pickBy, +} from 'lodash'; + +import { + OptionsWithUri, +} from 'request'; + +/** + * Make an authenticated API request to QuickBooks. + */ +export async function quickBooksApiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + qs: IDataObject, + body: IDataObject, + option: IDataObject = {}, +): Promise { // tslint:disable-line:no-any + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let isDownload = false; + + if (['estimate', 'invoice', 'payment'].includes(resource) && operation === 'get') { + isDownload = this.getNodeParameter('download', 0) as boolean; + } + + const productionUrl = 'https://quickbooks.api.intuit.com'; + const sandboxUrl = 'https://sandbox-quickbooks.api.intuit.com'; + + const credentials = this.getCredentials('quickBooksOAuth2Api') as IDataObject; + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + }, + method, + uri: `${credentials.environment === 'sandbox' ? sandboxUrl : productionUrl}${endpoint}`, + qs, + body, + json: !isDownload, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + if (Object.keys(option)) { + Object.assign(options, option); + } + + if (isDownload) { + options.headers!['Accept'] = 'application/pdf'; + } + + if (resource === 'invoice' && operation === 'send') { + options.headers!['Content-Type'] = 'application/octet-stream'; + } + + if ( + (resource === 'invoice' && (operation === 'void' || operation === 'delete')) || + (resource === 'payment' && (operation === 'void' || operation === 'delete')) + ) { + options.headers!['Content-Type'] = 'application/json'; + } + + try { + return await this.helpers.requestOAuth2!.call(this, 'quickBooksOAuth2Api', options); + } catch (error) { + + const errors = error.error.Fault.Error; + + if (errors && Array.isArray(errors)) { + const errorMessage = errors.map( + (e) => `QuickBooks error response [${e.code}]: ${e.Message} - Detail: ${e.Detail}`, + ).join('|'); + + throw new Error(errorMessage); + } + + throw error; + } +} + +/** + * Make an authenticated API request to QuickBooks and return all results. + */ +export async function quickBooksApiRequestAllItems( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + qs: IDataObject, + body: IDataObject, + resource: string, +): Promise { // tslint:disable-line:no-any + + let responseData; + let startPosition = 1; + const maxResults = 1000; + const returnData: IDataObject[] = []; + + const maxCount = await getCount.call(this, method, endpoint, qs); + + const originalQuery = qs.query; + + do { + qs.query = `${originalQuery} MAXRESULTS ${maxResults} STARTPOSITION ${startPosition}`; + responseData = await quickBooksApiRequest.call(this, method, endpoint, qs, body); + returnData.push(...responseData.QueryResponse[capitalCase(resource)]); + startPosition += maxResults; + + } while (maxCount > returnData.length); + + return returnData; +} + +async function getCount( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + qs: IDataObject, +): Promise { // tslint:disable-line:no-any + + const responseData = await quickBooksApiRequest.call(this, method, endpoint, qs, {}); + + return responseData.QueryResponse.totalCount; +} + +/** + * Handles a QuickBooks listing by returning all items or up to a limit. + */ +export async function handleListing( + this: IExecuteFunctions, + i: number, + endpoint: string, + resource: string, +): Promise { // tslint:disable-line:no-any + let responseData; + + const qs = { + query: `SELECT * FROM ${resource}`, + } as IDataObject; + + const returnAll = this.getNodeParameter('returnAll', i); + + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.query) { + qs.query += ` ${filters.query}`; + } + + if (returnAll) { + return await quickBooksApiRequestAllItems.call(this, 'GET', endpoint, qs, {}, resource); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.query += ` MAXRESULTS ${limit}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, qs, {}); + responseData = responseData.QueryResponse[capitalCase(resource)]; + return responseData; + } +} + +/** + * Get the SyncToken required for delete and void operations in QuickBooks. + */ +export async function getSyncToken( + this: IExecuteFunctions, + i: number, + companyId: string, + resource: string, +) { + const resourceId = this.getNodeParameter(`${resource}Id`, i); + const getEndpoint = `/v3/company/${companyId}/${resource}/${resourceId}`; + const propertyName = capitalCase(resource); + const { [propertyName]: { SyncToken } } = await quickBooksApiRequest.call(this, 'GET', getEndpoint, {}, {}); + + return SyncToken; +} + +/** + * Get the reference and SyncToken required for update operations in QuickBooks. + */ +export async function getRefAndSyncToken( + this: IExecuteFunctions, + i: number, + companyId: string, + resource: string, + ref: string, +) { + const resourceId = this.getNodeParameter(`${resource}Id`, i); + const endpoint = `/v3/company/${companyId}/${resource}/${resourceId}`; + const responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + + return { + ref: responseData[capitalCase(resource)][ref], + syncToken: responseData[capitalCase(resource)].SyncToken, + }; + +} + +/** + * Populate node items with binary data. + */ +export async function handleBinaryData( + this: IExecuteFunctions, + items: INodeExecutionData[], + i: number, + companyId: string, + resource: string, + resourceId: string, +) { + const binaryProperty = this.getNodeParameter('binaryProperty', i) as string; + const fileName = this.getNodeParameter('fileName', i) as string; + const endpoint = `/v3/company/${companyId}/${resource}/${resourceId}/pdf`; + const data = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}, { encoding: null }); + + items[i].binary = items[i].binary ?? {}; + items[i].binary![binaryProperty] = await this.helpers.prepareBinaryData(data); + items[i].binary![binaryProperty].fileName = fileName; + items[i].binary![binaryProperty].fileExtension = 'pdf'; + + return items; +} + +export async function loadResource( + this: ILoadOptionsFunctions, + resource: string, +) { + const returnData: INodePropertyOptions[] = []; + + const qs = { + query: `SELECT * FROM ${resource}`, + } as IDataObject; + + const { oauthTokenData: { callbackQueryString: { realmId } } } = this.getCredentials('quickBooksOAuth2Api') as { oauthTokenData: { callbackQueryString: { realmId: string } } }; + const endpoint = `/v3/company/${realmId}/query`; + + const resourceItems = await quickBooksApiRequestAllItems.call(this, 'GET', endpoint, qs, {}, resource); + + if (resource === 'preferences') { + const { SalesFormsPrefs: { CustomField } } = resourceItems[0]; + const customFields = CustomField[1].CustomField; + for (const customField of customFields) { + const length = customField.Name.length; + returnData.push({ + name: customField.StringValue, + value: customField.Name.charAt(length - 1), + }); + } + return returnData; + } + + resourceItems.forEach((resourceItem: { DisplayName: string, Name: string, Id: string }) => { + returnData.push({ + name: resourceItem.DisplayName || resourceItem.Name, + value: resourceItem.Id, + }); + }); + + return returnData; +} + +/** + * Populate the `Line` property in a request body. + */ +export function processLines( + this: IExecuteFunctions, + body: IDataObject, + lines: IDataObject[], + resource: string, +) { + + lines.forEach((line) => { + if (resource === 'bill') { + + if (line.DetailType === 'AccountBasedExpenseLineDetail') { + line.AccountBasedExpenseLineDetail = { + AccountRef: { + value: line.accountId, + }, + }; + delete line.accountId; + } else if (line.DetailType === 'ItemBasedExpenseLineDetail') { + line.ItemBasedExpenseLineDetail = { + ItemRef: { + value: line.itemId, + }, + }; + delete line.itemId; + } + + } else if (resource === 'estimate') { + if (line.DetailType === 'SalesItemLineDetail') { + line.SalesItemLineDetail = { + ItemRef: { + value: line.itemId, + }, + }; + delete line.itemId; + } + + } else if (resource === 'invoice') { + if (line.DetailType === 'SalesItemLineDetail') { + line.SalesItemLineDetail = { + ItemRef: { + value: line.itemId, + }, + }; + delete line.itemId; + } + } + + }); + + return lines; +} + +/** + * Populate update fields or additional fields into a request body. + */ +export function populateFields( + this: IExecuteFunctions, + body: IDataObject, + fields: IDataObject, + resource: string, +) { + + Object.entries(fields).forEach(([key, value]) => { + + if (resource === 'bill') { + + if (key.endsWith('Ref')) { + const { details } = value as { details: Ref }; + body[key] = { + name: details.name, + value: details.value, + }; + + } else { + body[key] = value; + } + + } else if (['customer', 'employee', 'vendor'].includes(resource)) { + + if (key === 'BillAddr') { + const { details } = value as { details: GeneralAddress }; + body.BillAddr = pickBy(details, detail => detail !== ''); + + } else if (key === 'PrimaryEmailAddr') { + body.PrimaryEmailAddr = { + Address: value, + }; + + } else if (key === 'PrimaryPhone') { + body.PrimaryPhone = { + FreeFormNumber: value, + }; + + } else { + body[key] = value; + } + + } else if (resource === 'estimate' || resource === 'invoice') { + + if (key === 'BillAddr' || key === 'ShipAddr') { + const { details } = value as { details: GeneralAddress }; + body[key] = pickBy(details, detail => detail !== ''); + + } else if (key === 'BillEmail') { + body.BillEmail = { + Address: value, + }; + + } else if (key === 'CustomFields') { + const { Field } = value as { Field: CustomField[] }; + body.CustomField = Field; + const length = (body.CustomField as CustomField[]).length; + for (let i = 0; i < length; i++) { + //@ts-ignore + body.CustomField[i]['Type'] = 'StringType'; + } + + } else if (key === 'CustomerMemo') { + body.CustomerMemo = { + value, + }; + + } else if (key.endsWith('Ref')) { + const { details } = value as { details: Ref }; + body[key] = { + name: details.name, + value: details.value, + }; + + } else if (key === 'TotalTax') { + body.TxnTaxDetail = { + TotalTax: value, + }; + + } else { + body[key] = value; + } + + } else if (resource === 'payment') { + body[key] = value; + } + }); + return body; +} diff --git a/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts b/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts new file mode 100644 index 0000000000..4fe8f983df --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts @@ -0,0 +1,990 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + billFields, + billOperations, + customerFields, + customerOperations, + employeeFields, + employeeOperations, + estimateFields, + estimateOperations, + invoiceFields, + invoiceOperations, + itemFields, + itemOperations, + paymentFields, + paymentOperations, + vendorFields, + vendorOperations, +} from './descriptions'; + +import { + getRefAndSyncToken, + getSyncToken, + handleBinaryData, + handleListing, + loadResource, + populateFields, + processLines, + quickBooksApiRequest, +} from './GenericFunctions'; + +import { + capitalCase, +} from 'change-case'; + +import { + isEmpty, +} from 'lodash'; + +export class QuickBooks implements INodeType { + description: INodeTypeDescription = { + displayName: 'QuickBooks', + name: 'quickbooks', + icon: 'file:quickbooks.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the QuickBooks API', + defaults: { + name: 'QuickBooks', + color: '#2CA01C', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'quickBooksOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Bill', + value: 'bill', + }, + { + name: 'Customer', + value: 'customer', + }, + { + name: 'Employee', + value: 'employee', + }, + { + name: 'Estimate', + value: 'estimate', + }, + { + name: 'Invoice', + value: 'invoice', + }, + { + name: 'Item', + value: 'item', + }, + { + name: 'Payment', + value: 'payment', + }, + { + name: 'Vendor', + value: 'vendor', + }, + ], + default: 'customer', + description: 'Resource to consume', + }, + ...billOperations, + ...billFields, + ...customerOperations, + ...customerFields, + ...employeeOperations, + ...employeeFields, + ...estimateOperations, + ...estimateFields, + ...invoiceOperations, + ...invoiceFields, + ...itemOperations, + ...itemFields, + ...paymentOperations, + ...paymentFields, + ...vendorOperations, + ...vendorFields, + ], + }; + + methods = { + loadOptions: { + async getCustomers(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'customer'); + }, + + async getCustomFields(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'preferences'); + }, + + async getItems(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'item'); + }, + + async getVendors(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'vendor'); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + const returnData: IDataObject[] = []; + + const { oauthTokenData } = this.getCredentials('quickBooksOAuth2Api') as IDataObject; + // @ts-ignore + const companyId = oauthTokenData.callbackQueryString.realmId; + + for (let i = 0; i < items.length; i++) { + + if (resource === 'bill') { + + // ********************************************************************* + // bill + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/estimate + + if (operation === 'create') { + + // ---------------------------------- + // bill: create + // ---------------------------------- + + const lines = this.getNodeParameter('Line', i) as IDataObject[]; + + if (!lines.length) { + throw new Error(`Please enter at least one line for the ${resource}.`); + } + + if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { + throw new Error('Please enter detail type, amount and description for every line.'); + } + + lines.forEach(line => { + if (line.DetailType === 'AccountBasedExpenseLineDetail' && line.accountId === undefined) { + throw new Error('Please enter an account ID for the associated line.'); + } else if (line.DetailType === 'ItemBasedExpenseLineDetail' && line.itemId === undefined) { + throw new Error('Please enter an item ID for the associated line.'); + } + }); + + let body = { + VendorRef: { + value: this.getNodeParameter('VendorRef', i), + }, + } as IDataObject; + + body.Line = processLines.call(this, body, lines, resource); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'delete') { + + // ---------------------------------- + // bill: delete + // ---------------------------------- + + const qs = { + operation: 'delete', + } as IDataObject; + + const body = { + Id: this.getNodeParameter('billId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // bill: get + // ---------------------------------- + + const billId = this.getNodeParameter('billId', i); + const endpoint = `/v3/company/${companyId}/${resource}/${billId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // bill: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'update') { + + // ---------------------------------- + // bill: update + // ---------------------------------- + + const { ref, syncToken } = await getRefAndSyncToken.call(this, i, companyId, resource, 'VendorRef'); + + let body = { + Id: this.getNodeParameter('billId', i), + SyncToken: syncToken, + sparse: true, + VendorRef: { + name: ref.name, + value: ref.value, + }, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'customer') { + + // ********************************************************************* + // customer + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/customer + + if (operation === 'create') { + + // ---------------------------------- + // customer: create + // ---------------------------------- + + let body = { + DisplayName: this.getNodeParameter('displayName', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // customer: get + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + const endpoint = `/v3/company/${companyId}/${resource}/${customerId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // customer: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'update') { + + // ---------------------------------- + // customer: update + // ---------------------------------- + + let body = { + Id: this.getNodeParameter('customerId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + sparse: true, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'employee') { + + // ********************************************************************* + // employee + // ********************************************************************* + + if (operation === 'create') { + + // ---------------------------------- + // employee: create + // ---------------------------------- + + let body = { + FamilyName: this.getNodeParameter('FamilyName', i), + GivenName: this.getNodeParameter('GivenName', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // employee: get + // ---------------------------------- + + const employeeId = this.getNodeParameter('employeeId', i); + const endpoint = `/v3/company/${companyId}/${resource}/${employeeId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // employee: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'update') { + + // ---------------------------------- + // employee: update + // ---------------------------------- + + let body = { + Id: this.getNodeParameter('employeeId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + sparse: true, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'estimate') { + + // ********************************************************************* + // estimate + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/estimate + + if (operation === 'create') { + + // ---------------------------------- + // estimate: create + // ---------------------------------- + + const lines = this.getNodeParameter('Line', i) as IDataObject[]; + + if (!lines.length) { + throw new Error(`Please enter at least one line for the ${resource}.`); + } + + if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { + throw new Error('Please enter detail type, amount and description for every line.'); + } + + lines.forEach(line => { + if (line.DetailType === 'SalesItemLineDetail' && line.itemId === undefined) { + throw new Error('Please enter an item ID for the associated line.'); + } + }); + + let body = { + CustomerRef: { + value: this.getNodeParameter('CustomerRef', i), + }, + } as IDataObject; + + body.Line = processLines.call(this, body, lines, resource); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'delete') { + + // ---------------------------------- + // estimate: delete + // ---------------------------------- + + const qs = { + operation: 'delete', + } as IDataObject; + + const body = { + Id: this.getNodeParameter('estimateId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // estimate: get + // ---------------------------------- + + const estimateId = this.getNodeParameter('estimateId', i) as string; + const download = this.getNodeParameter('download', i) as boolean; + + if (download) { + + responseData = await handleBinaryData.call(this, items, i, companyId, resource, estimateId); + + } else { + + const endpoint = `/v3/company/${companyId}/${resource}/${estimateId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (operation === 'getAll') { + + // ---------------------------------- + // estimate: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'send') { + + // ---------------------------------- + // estimate: send + // ---------------------------------- + + const estimateId = this.getNodeParameter('estimateId', i) as string; + + const qs = { + sendTo: this.getNodeParameter('email', i) as string, + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}/${estimateId}/send`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'update') { + + // ---------------------------------- + // estimate: update + // ---------------------------------- + + const { ref, syncToken } = await getRefAndSyncToken.call(this, i, companyId, resource, 'CustomerRef'); + + let body = { + Id: this.getNodeParameter('estimateId', i), + SyncToken: syncToken, + sparse: true, + CustomerRef: { + name: ref.name, + value: ref.value, + }, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'invoice') { + + // ********************************************************************* + // invoice + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/invoice + + if (operation === 'create') { + + // ---------------------------------- + // invoice: create + // ---------------------------------- + + const lines = this.getNodeParameter('Line', i) as IDataObject[]; + + if (!lines.length) { + throw new Error(`Please enter at least one line for the ${resource}.`); + } + + if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { + throw new Error('Please enter detail type, amount and description for every line.'); + } + + lines.forEach(line => { + if (line.DetailType === 'SalesItemLineDetail' && line.itemId === undefined) { + throw new Error('Please enter an item ID for the associated line.'); + } + }); + + let body = { + CustomerRef: { + value: this.getNodeParameter('CustomerRef', i), + }, + } as IDataObject; + + body.Line = processLines.call(this, body, lines, resource); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'delete') { + + // ---------------------------------- + // invoice: delete + // ---------------------------------- + + const qs = { + operation: 'delete', + } as IDataObject; + + const body = { + Id: this.getNodeParameter('invoiceId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // invoice: get + // ---------------------------------- + + const invoiceId = this.getNodeParameter('invoiceId', i) as string; + const download = this.getNodeParameter('download', i) as boolean; + + if (download) { + + responseData = await handleBinaryData.call(this, items, i, companyId, resource, invoiceId); + + } else { + + const endpoint = `/v3/company/${companyId}/${resource}/${invoiceId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (operation === 'getAll') { + + // ---------------------------------- + // invoice: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'send') { + + // ---------------------------------- + // invoice: send + // ---------------------------------- + + const invoiceId = this.getNodeParameter('invoiceId', i) as string; + + const qs = { + sendTo: this.getNodeParameter('email', i) as string, + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}/${invoiceId}/send`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'update') { + + // ---------------------------------- + // invoice: update + // ---------------------------------- + + const { ref, syncToken } = await getRefAndSyncToken.call(this, i, companyId, resource, 'CustomerRef'); + + let body = { + Id: this.getNodeParameter('invoiceId', i), + SyncToken: syncToken, + sparse: true, + CustomerRef: { + name: ref.name, + value: ref.value, + }, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'void') { + + // ---------------------------------- + // invoice: void + // ---------------------------------- + + const qs = { + Id: this.getNodeParameter('invoiceId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + operation: 'void', + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, {}); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'item') { + + // ********************************************************************* + // item + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/item + + if (operation === 'get') { + + // ---------------------------------- + // item: get + // ---------------------------------- + + const item = this.getNodeParameter('itemId', i); + const endpoint = `/v3/company/${companyId}/${resource}/${item}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // item: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } + + } else if (resource === 'payment') { + + // ********************************************************************* + // payment + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/payment + + if (operation === 'create') { + + // ---------------------------------- + // payment: create + // ---------------------------------- + + let body = { + CustomerRef: { + value: this.getNodeParameter('CustomerRef', i), + }, + TotalAmt: this.getNodeParameter('TotalAmt', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'delete') { + + // ---------------------------------- + // payment: delete + // ---------------------------------- + + const qs = { + operation: 'delete', + } as IDataObject; + + const body = { + Id: this.getNodeParameter('paymentId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // payment: get + // ---------------------------------- + + const paymentId = this.getNodeParameter('paymentId', i) as string; + const download = this.getNodeParameter('download', i) as boolean; + + if (download) { + + responseData = await handleBinaryData.call(this, items, i, companyId, resource, paymentId); + + } else { + + const endpoint = `/v3/company/${companyId}/${resource}/${paymentId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (operation === 'getAll') { + + // ---------------------------------- + // payment: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'send') { + + // ---------------------------------- + // payment: send + // ---------------------------------- + + const paymentId = this.getNodeParameter('paymentId', i) as string; + + const qs = { + sendTo: this.getNodeParameter('email', i) as string, + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}/${paymentId}/send`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'update') { + + // ---------------------------------- + // payment: update + // ---------------------------------- + + const { ref, syncToken } = await getRefAndSyncToken.call(this, i, companyId, resource, 'CustomerRef'); + + let body = { + Id: this.getNodeParameter('paymentId', i), + SyncToken: syncToken, + sparse: true, + CustomerRef: { + name: ref.name, + value: ref.value, + }, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'void') { + + // ---------------------------------- + // payment: void + // ---------------------------------- + + const qs = { + Id: this.getNodeParameter('paymentId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + operation: 'void', + } as IDataObject; + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, qs, {}); + responseData = responseData[capitalCase(resource)]; + + } + + } else if (resource === 'vendor') { + + // ********************************************************************* + // vendor + // ********************************************************************* + + // https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/vendor + + if (operation === 'create') { + + // ---------------------------------- + // vendor: create + // ---------------------------------- + + let body = { + DisplayName: this.getNodeParameter('displayName', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + body = populateFields.call(this, body, additionalFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'get') { + + // ---------------------------------- + // vendor: get + // ---------------------------------- + + const vendorId = this.getNodeParameter('vendorId', i); + const endpoint = `/v3/company/${companyId}/${resource}/${vendorId}`; + responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData[capitalCase(resource)]; + + } else if (operation === 'getAll') { + + // ---------------------------------- + // vendor: getAll + // ---------------------------------- + + const endpoint = `/v3/company/${companyId}/query`; + responseData = await handleListing.call(this, i, endpoint, resource); + + } else if (operation === 'update') { + + // ---------------------------------- + // vendor: update + // ---------------------------------- + + let body = { + Id: this.getNodeParameter('vendorId', i), + SyncToken: await getSyncToken.call(this, i, companyId, resource), + sparse: true, + } as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + body = populateFields.call(this, body, updateFields, resource); + + const endpoint = `/v3/company/${companyId}/${resource}`; + responseData = await quickBooksApiRequest.call(this, 'POST', endpoint, {}, body); + responseData = responseData[capitalCase(resource)]; + + } + + } + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + } + + const download = this.getNodeParameter('download', 0, false) as boolean; + + if (['invoice', 'estimate', 'payment'].includes(resource) && ['get'].includes(operation) && download) { + return this.prepareOutputData(responseData); + } else { + return [this.helpers.returnJsonArray(returnData)]; + } + } +} diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..5d872f6461 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillAdditionalFieldsOptions.ts @@ -0,0 +1,86 @@ +export const billAdditionalFieldsOptions = [ + { + displayName: 'Accounts Payable Account', + name: 'APAccountRef', + placeholder: 'Add APA Fields', + description: 'Accounts Payable account to which the bill will be credited.', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'ID', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Balance', + name: 'Balance', + description: 'The balance reflecting any payments made against the transaction.', + type: 'string', + default: '', + }, + { + displayName: 'Due Date', + name: 'DueDate', + description: 'Date when the payment of the transaction is due.', + type: 'dateTime', + default: '', + }, + { + displayName: 'Sales Term', + name: 'SalesTermRef', + description: 'Sales term associated with the transaction.', + placeholder: 'Add Sales Term Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'ID', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Total Amount', + name: 'TotalAmt', + description: 'Total amount of the transaction.', + type: 'number', + default: 0, + }, + { + displayName: 'Transaction Date', + name: 'TxnDate', + description: 'Date when the transaction occurred.', + type: 'dateTime', + default: '', + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillDescription.ts new file mode 100644 index 0000000000..9a1dcff0f9 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Bill/BillDescription.ts @@ -0,0 +1,330 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + billAdditionalFieldsOptions, +} from './BillAdditionalFieldsOptions'; + +export const billOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'bill', + ], + }, + }, + }, +] as INodeProperties[]; + +export const billFields = [ + // ---------------------------------- + // bill: create + // ---------------------------------- + { + displayName: 'For Vendor', + name: 'VendorRef', + type: 'options', + required: true, + description: 'The ID of the vendor who the bill is for.', + default: [], + typeOptions: { + loadOptionsMethod: 'getVendors', + }, + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Line', + name: 'Line', + type: 'collection', + placeholder: 'Add Line Item Property', + description: 'Individual line item of a transaction.', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Detail Type', + name: 'DetailType', + type: 'options', + default: 'ItemBasedExpenseLineDetail', + options: [ + { + name: 'Account-Based Expense Line Detail', + value: 'AccountBasedExpenseLineDetail', + }, + { + name: 'Item-Based Expense Line Detail', + value: 'ItemBasedExpenseLineDetail', + }, + ], + }, + { + displayName: 'Item', + name: 'itemId', + type: 'options', + default: [], + typeOptions: { + loadOptionsMethod: 'getItems', + }, + }, + { + displayName: 'Account ID', + name: 'accountId', + type: 'string', + default: '', + }, + { + displayName: 'Amount', + name: 'Amount', + description: 'Monetary amount of the line item.', + type: 'number', + default: 0, + }, + { + displayName: 'Description', + name: 'Description', + description: 'Textual description of the line item.', + type: 'string', + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + { + displayName: 'Position', + name: 'LineNum', + description: 'Position of the line item relative to others.', + type: 'number', + default: 1, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'create', + ], + }, + }, + options: billAdditionalFieldsOptions, + }, + + // ---------------------------------- + // bill: delete + // ---------------------------------- + { + displayName: 'Bill ID', + name: 'billId', + type: 'string', + required: true, + default: '', + description: 'The ID of the bill to delete.', + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // bill: get + // ---------------------------------- + { + displayName: 'Bill ID', + name: 'billId', + type: 'string', + required: true, + default: '', + description: 'The ID of the bill to retrieve.', + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // bill: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting bills. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // bill: update + // ---------------------------------- + { + displayName: 'Bill ID', + name: 'billId', + type: 'string', + required: true, + default: '', + description: 'The ID of the bill to update.', + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'bill', + ], + operation: [ + 'update', + ], + }, + }, + // filter out fields that cannot be updated + options: billAdditionalFieldsOptions.filter(property => property.name !== 'TotalAmt' && property.name !== 'Balance'), + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..db037f9ff5 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerAdditionalFieldsOptions.ts @@ -0,0 +1,151 @@ +export const customerAdditionalFieldsOptions = [ + { + displayName: 'Active', + name: 'Active', + description: 'Whether the customer is currently enabled for use by QuickBooks.', + type: 'boolean', + default: true, + }, + { + displayName: 'Balance', + name: 'Balance', + description: 'Open balance amount or amount unpaid by the customer.', + type: 'string', + default: '', + }, + { + displayName: 'Balance With Jobs', + name: 'BalanceWithJobs', + description: 'Cumulative open balance amount for the customer (or job) and all its sub-jobs.', + type: 'number', + default: 0, + }, + { + displayName: 'Billing Address', + name: 'BillAddr', + placeholder: 'Add Billing Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Bill With Parent', + name: 'BillWithParent', + description: 'Bill this customer together with its parent.', + type: 'boolean', + default: false, + }, + { + displayName: 'Company Name', + name: 'CompanyName', + type: 'string', + default: '', + }, + { + displayName: 'Family Name', + name: 'FamilyName', + type: 'string', + default: '', + }, + { + displayName: 'Fully Qualified Name', + name: 'FullyQualifiedName', + type: 'string', + default: '', + }, + { + displayName: 'Given Name', + name: 'GivenName', + type: 'string', + default: '', + }, + { + displayName: 'Preferred Delivery Method', + name: 'PreferredDeliveryMethod', + type: 'options', + default: 'Print', + options: [ + { + name: 'Print', + value: 'Print', + }, + { + name: 'Email', + value: 'Email', + }, + { + name: 'None', + value: 'None', + }, + ], + }, + { + displayName: 'Primary Email Address', + name: 'PrimaryEmailAddr', + type: 'string', + default: '', + }, + { + displayName: 'Primary Phone', + name: 'PrimaryPhone', + type: 'string', + default: '', + }, + { + displayName: 'Print-On-Check Name', + name: 'PrintOnCheckName', + description: 'Name of the customer as printed on a check.', + type: 'string', + default: '', + }, + { + displayName: 'Taxable', + name: 'Taxable', + description: 'Whether transactions for this customer are taxable.', + type: 'boolean', + default: false, + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerDescription.ts new file mode 100644 index 0000000000..f4735d2b55 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Customer/CustomerDescription.ts @@ -0,0 +1,222 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + customerAdditionalFieldsOptions, +} from './CustomerAdditionalFieldsOptions'; + +export const customerOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + }, +] as INodeProperties[]; + +export const customerFields = [ + // ---------------------------------- + // customer: create + // ---------------------------------- + { + displayName: 'Display Name', + name: 'displayName', + type: 'string', + required: true, + default: '', + description: 'The display name of the customer to create.', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + options: customerAdditionalFieldsOptions, + }, + + // ---------------------------------- + // customer: get + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'The ID of the customer to retrieve.', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // customer: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting customers. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // customer: update + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'The ID of the customer to update.', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + options: customerAdditionalFieldsOptions, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..e10304ad33 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeAdditionalFieldsOptions.ts @@ -0,0 +1,91 @@ +export const employeeAdditionalFieldsOptions = [ + { + displayName: 'Active', + name: 'Active', + description: 'Whether the employee is currently enabled for use by QuickBooks.', + type: 'boolean', + default: false, + }, + { + displayName: 'Billable Time', + name: 'BillableTime', + type: 'boolean', + default: false, + }, + { + displayName: 'Display Name', + name: 'DisplayName', + type: 'string', + default: '', + }, + { + displayName: 'Billing Address', + name: 'BillAddr', + placeholder: 'Add Billing Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Primary Phone', + name: 'PrimaryPhone', + type: 'string', + default: '', + }, + { + displayName: 'Print-On-Check Name', + name: 'PrintOnCheckName', + description: 'Name of the employee as printed on a check.', + type: 'string', + default: '', + }, + { + displayName: 'Social Security Number', + name: 'SSN', + type: 'string', + default: '', + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeDescription.ts new file mode 100644 index 0000000000..7e6b001aed --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Employee/EmployeeDescription.ts @@ -0,0 +1,236 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + employeeAdditionalFieldsOptions, +} from './EmployeeAdditionalFieldsOptions'; + +export const employeeOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'employee', + ], + }, + }, + }, +] as INodeProperties[]; + +export const employeeFields = [ + // ---------------------------------- + // employee: create + // ---------------------------------- + { + displayName: 'Family Name', + name: 'FamilyName', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Given Name', + name: 'GivenName', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'create', + ], + }, + }, + options: employeeAdditionalFieldsOptions, + }, + + // ---------------------------------- + // employee: get + // ---------------------------------- + { + displayName: 'Employee ID', + name: 'employeeId', + type: 'string', + required: true, + default: '', + description: 'The ID of the employee to retrieve.', + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // employee: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting employees. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // employee: update + // ---------------------------------- + { + displayName: 'Employee ID', + name: 'employeeId', + type: 'string', + required: true, + default: '', + description: 'The ID of the employee to update.', + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'employee', + ], + operation: [ + 'update', + ], + }, + }, + options: employeeAdditionalFieldsOptions, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..9532b7f442 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateAdditionalFieldsOptions.ts @@ -0,0 +1,227 @@ +export const estimateAdditionalFieldsOptions = [ + { + displayName: 'Apply Tax After Discount', + name: 'ApplyTaxAfterDiscount', + type: 'boolean', + default: false, + }, + { + displayName: 'Billing Address', + name: 'BillAddr', + placeholder: 'Add Billing Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Billing Email', + name: 'BillEmail', + description: 'E-mail address to which the estimate will be sent.', + type: 'string', + default: '', + }, + { + displayName: 'Custom Fields', + name: 'CustomFields', + placeholder: 'Add Custom Fields', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + displayName: 'Field', + name: 'Field', + values: [ + { + displayName: 'Field Definition ID', + name: 'DefinitionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomFields', + }, + default: '', + description: 'ID of the field to set.', + }, + { + displayName: 'Field Value', + name: 'StringValue', + type: 'string', + default: '', + description: 'Value of the field to set.', + }, + ], + }, + ], + }, + { + displayName: 'Customer Memo', + name: 'CustomerMemo', + description: 'User-entered message to the customer. This message is visible to end user on their transactions.', + type: 'string', + default: '', + }, + { + displayName: 'Document Number', + name: 'DocNumber', + description: 'Reference number for the transaction.', + type: 'string', + default: '', + }, + { + displayName: 'Email Status', + name: 'EmailStatus', + type: 'options', + default: 'NotSet', + options: [ + { + name: 'Not Set', + value: 'NotSet', + }, + { + name: 'Need To Send', + value: 'NeedToSend', + }, + { + name: 'Email Sent', + value: 'EmailSent', + }, + ], + }, + { + displayName: 'Print Status', + name: 'PrintStatus', + type: 'options', + default: 'NotSet', + options: [ + { + name: 'Not Set', + value: 'NotSet', + }, + { + name: 'Need To Print', + value: 'NeedToPrint', + }, + { + name: 'PrintComplete', + value: 'PrintComplete', + }, + ], + }, + { + displayName: 'Shipping Address', + name: 'ShipAddr', + placeholder: 'Add Shippping Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Total Amount', + name: 'TotalAmt', + description: 'Total amount of the transaction.', + type: 'number', + default: 0, + }, + { + displayName: 'Transaction Date', + name: 'TxnDate', + description: 'Date when the transaction occurred.', + type: 'dateTime', + default: '', + }, + { + displayName: 'Total Tax', + name: 'TotalTax', + description: 'Total amount of tax incurred.', + type: 'number', + default: 0, + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateDescription.ts new file mode 100644 index 0000000000..2768c675f0 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Estimate/EstimateDescription.ts @@ -0,0 +1,425 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + estimateAdditionalFieldsOptions, +} from './EstimateAdditionalFieldsOptions'; + +export const estimateOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Send', + value: 'send', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'estimate', + ], + }, + }, + }, +] as INodeProperties[]; + +export const estimateFields = [ + // ---------------------------------- + // estimate: create + // ---------------------------------- + { + displayName: 'For Customer', + name: 'CustomerRef', + type: 'options', + required: true, + description: 'The ID of the customer who the estimate is for.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCustomers', + }, + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Line', + name: 'Line', + type: 'collection', + placeholder: 'Add Line Item Property', + description: 'Individual line item of a transaction.', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Detail Type', + name: 'DetailType', + type: 'options', + default: 'SalesItemLineDetail', + options: [ + { + name: 'Sales Item Line Detail', + value: 'SalesItemLineDetail', + }, + ], + }, + { + displayName: 'Item', + name: 'itemId', + type: 'options', + default: [], + typeOptions: { + loadOptionsMethod: 'getItems', + }, + }, + { + displayName: 'Amount', + name: 'Amount', + description: 'Monetary amount of the line item.', + type: 'number', + default: 0, + }, + { + displayName: 'Description', + name: 'Description', + description: 'Textual description of the line item.', + type: 'string', + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + { + displayName: 'Position', + name: 'LineNum', + description: 'Position of the line item relative to others.', + type: 'number', + default: 1, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'create', + ], + }, + }, + options: estimateAdditionalFieldsOptions, + }, + + // ---------------------------------- + // estimate: delete + // ---------------------------------- + { + displayName: 'Estimate ID', + name: 'estimateId', + type: 'string', + required: true, + default: '', + description: 'The ID of the estimate to delete.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // estimate: get + // ---------------------------------- + { + displayName: 'Estimate ID', + name: 'estimateId', + type: 'string', + required: true, + default: '', + description: 'The ID of the estimate to retrieve.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Download', + name: 'download', + type: 'boolean', + required: true, + default: false, + description: 'Download the estimate as a PDF file.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Binary Property', + name: 'binaryProperty', + type: 'string', + required: true, + default: 'data', + description: 'Name of the binary property to which to write to.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + required: true, + default: '', + placeholder: 'data.pdf', + description: 'Name of the file that will be downloaded.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + + // ---------------------------------- + // estimate: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting estimates. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // estimate: send + // ---------------------------------- + { + displayName: 'Estimate ID', + name: 'estimateId', + type: 'string', + required: true, + default: '', + description: 'The ID of the estimate to send.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + description: 'The email of the recipient of the estimate.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'send', + ], + }, + }, + }, + + // ---------------------------------- + // estimate: update + // ---------------------------------- + { + displayName: 'Estimate ID', + name: 'estimateId', + type: 'string', + required: true, + default: '', + description: 'The ID of the estimate to update.', + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'estimate', + ], + operation: [ + 'update', + ], + }, + }, + // filter out fields that cannot be updated + options: estimateAdditionalFieldsOptions.filter(property => property.name !== 'TotalAmt' && property.name !== 'TotalTax'), + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..49a70e085e --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceAdditionalFieldsOptions.ts @@ -0,0 +1,183 @@ +export const invoiceAdditionalFieldsOptions = [ + { + displayName: 'Balance', + name: 'Balance', + description: 'The balance reflecting any payments made against the transaction.', + type: 'number', + default: 0, + }, + { + displayName: 'Billing Address', + name: 'BillAddr', + placeholder: 'Add Billing Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Billing Email', + name: 'BillEmail', + description: 'E-mail address to which the invoice will be sent.', + type: 'string', + default: '', + }, + { + displayName: 'Customer Memo', + name: 'CustomerMemo', + description: 'User-entered message to the customer. This message is visible to end user on their transactions.', + type: 'string', + default: '', + }, + { + displayName: 'Custom Fields', + name: 'CustomFields', + placeholder: 'Add Custom Fields', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + displayName: 'Field', + name: 'Field', + values: [ + { + displayName: 'Field Definition ID', + name: 'DefinitionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomFields', + }, + default: '', + description: 'ID of the field to set.', + }, + { + displayName: 'Field Value', + name: 'StringValue', + type: 'string', + default: '', + description: 'Value of the field to set.', + }, + ], + }, + ], + }, + { + displayName: 'Document Number', + name: 'DocNumber', + description: 'Reference number for the transaction.', + type: 'string', + default: '', + }, + { + displayName: 'Due Date', + name: 'DueDate', + description: 'Date when the payment of the transaction is due.', + type: 'dateTime', + default: '', + }, + { + displayName: 'Email Status', + name: 'EmailStatus', + type: 'options', + default: 'NotSet', + options: [ + { + name: 'Not Set', + value: 'NotSet', + }, + { + name: 'Need To Send', + value: 'NeedToSend', + }, + { + name: 'Email Sent', + value: 'EmailSent', + }, + ], + }, + { + displayName: 'Print Status', + name: 'PrintStatus', + type: 'options', + default: 'NotSet', + options: [ + { + name: 'Not Set', + value: 'NotSet', + }, + { + name: 'Need To Print', + value: 'NeedToPrint', + }, + { + name: 'PrintComplete', + value: 'PrintComplete', + }, + ], + }, + { + displayName: 'Shipping Address', + name: 'ShipAddr', + type: 'string', + default: '', + }, + { + displayName: 'Total Amount', + name: 'TotalAmt', + description: 'Total amount of the transaction.', + type: 'number', + default: 0, + }, + { + displayName: 'Transaction Date', + name: 'TxnDate', + description: 'Date when the transaction occurred.', + type: 'dateTime', + default: '', + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceDescription.ts new file mode 100644 index 0000000000..d9a4eee478 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Invoice/InvoiceDescription.ts @@ -0,0 +1,451 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + invoiceAdditionalFieldsOptions +} from './InvoiceAdditionalFieldsOptions'; + +export const invoiceOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Send', + value: 'send', + }, + { + name: 'Update', + value: 'update', + }, + { + name: 'Void', + value: 'void', + }, + ], + displayOptions: { + show: { + resource: [ + 'invoice', + ], + }, + }, + }, +] as INodeProperties[]; + +export const invoiceFields = [ + // ---------------------------------- + // invoice: create + // ---------------------------------- + { + displayName: 'For Customer', + name: 'CustomerRef', + type: 'options', + required: true, + description: 'The ID of the customer who the invoice is for.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCustomers', + }, + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Line', + name: 'Line', + type: 'collection', + placeholder: 'Add Line Item Property', + description: 'Individual line item of a transaction.', + typeOptions: { + multipleValues: true, + }, + default: {}, + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Detail Type', + name: 'DetailType', + type: 'options', + default: 'SalesItemLineDetail', + options: [ + { + name: 'Sales Item Line Detail', + value: 'SalesItemLineDetail', + }, + ], + }, + { + displayName: 'Item', + name: 'itemId', + type: 'options', + default: [], + typeOptions: { + loadOptionsMethod: 'getItems', + }, + }, + { + displayName: 'Amount', + name: 'Amount', + description: 'Monetary amount of the line item.', + type: 'number', + default: 0, + }, + { + displayName: 'Description', + name: 'Description', + description: 'Textual description of the line item.', + type: 'string', + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + { + displayName: 'Position', + name: 'LineNum', + description: 'Position of the line item relative to others.', + type: 'number', + default: 1, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'create', + ], + }, + }, + options: invoiceAdditionalFieldsOptions, + }, + + // ---------------------------------- + // invoice: delete + // ---------------------------------- + { + displayName: 'Invoice ID', + name: 'invoiceId', + type: 'string', + required: true, + default: '', + description: 'The ID of the invoice to delete.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // invoice: get + // ---------------------------------- + { + displayName: 'Invoice ID', + name: 'invoiceId', + type: 'string', + required: true, + default: '', + description: 'The ID of the invoice to retrieve.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Download', + name: 'download', + type: 'boolean', + required: true, + default: false, + description: 'Download the invoice as a PDF file.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Binary Property', + name: 'binaryProperty', + type: 'string', + required: true, + default: 'data', + description: 'Name of the binary property to which to write to.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + required: true, + default: '', + placeholder: 'data.pdf', + description: 'Name of the file that will be downloaded.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + + // ---------------------------------- + // invoice: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting invoices. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // invoice: send + // ---------------------------------- + { + displayName: 'Invoice ID', + name: 'invoiceId', + type: 'string', + required: true, + default: '', + description: 'The ID of the invoice to send.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + description: 'The email of the recipient of the invoice.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'send', + ], + }, + }, + }, + + // ---------------------------------- + // invoice: void + // ---------------------------------- + { + displayName: 'Invoice ID', + name: 'invoiceId', + type: 'string', + required: true, + default: '', + description: 'The ID of the invoice to void.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'void', + ], + }, + }, + }, + + // ---------------------------------- + // invoice: update + // ---------------------------------- + { + displayName: 'Invoice ID', + name: 'invoiceId', + type: 'string', + required: true, + default: '', + description: 'The ID of the invoice to update.', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'update', + ], + }, + }, + // filter out fields that cannot be updated + options: invoiceAdditionalFieldsOptions.filter(property => property.name !== 'TotalAmt' && property.name !== 'Balance'), + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Item/ItemDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Item/ItemDescription.ts new file mode 100644 index 0000000000..91a23cabe8 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Item/ItemDescription.ts @@ -0,0 +1,129 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const itemOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + displayOptions: { + show: { + resource: [ + 'item', + ], + }, + }, + }, +] as INodeProperties[]; + +export const itemFields = [ + // ---------------------------------- + // item: get + // ---------------------------------- + { + displayName: 'Item ID', + name: 'itemId', + type: 'string', + required: true, + default: '', + description: 'The ID of the item to retrieve.', + displayOptions: { + show: { + resource: [ + 'item', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // item: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'item', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'item', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting items. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'item', + ], + operation: [ + 'getAll', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..e683fd2e2b --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentAdditionalFieldsOptions.ts @@ -0,0 +1,9 @@ +export const paymentAdditionalFieldsOptions = [ + { + displayName: 'Transaction Date', + name: 'TxnDate', + description: 'Date when the transaction occurred.', + type: 'dateTime', + default: '', + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentDescription.ts new file mode 100644 index 0000000000..9b6c58bc85 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Payment/PaymentDescription.ts @@ -0,0 +1,399 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + paymentAdditionalFieldsOptions +} from './PaymentAdditionalFieldsOptions'; + +export const paymentOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Send', + value: 'send', + }, + { + name: 'Update', + value: 'update', + }, + { + name: 'Void', + value: 'void', + }, + ], + displayOptions: { + show: { + resource: [ + 'payment', + ], + }, + }, + }, +] as INodeProperties[]; + +export const paymentFields = [ + // ---------------------------------- + // payment: create + // ---------------------------------- + { + displayName: 'For Customer ID', + name: 'CustomerRef', + type: 'options', + required: true, + description: 'The ID of the customer who the payment is for.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCustomers', + }, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Total Amount', + name: 'TotalAmt', + description: 'Total amount of the transaction.', + type: 'number', + default: 0, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'create', + ], + }, + }, + options: paymentAdditionalFieldsOptions, + }, + + // ---------------------------------- + // payment: delete + // ---------------------------------- + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'string', + required: true, + default: '', + description: 'The ID of the payment to delete.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // payment: get + // ---------------------------------- + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'string', + required: true, + default: '', + description: 'The ID of the payment to retrieve.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Download', + name: 'download', + type: 'boolean', + required: true, + default: false, + description: 'Download estimate as PDF file', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Binary Property', + name: 'binaryProperty', + type: 'string', + required: true, + default: 'data', + description: 'Name of the binary property to which to write to.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + required: true, + default: '', + placeholder: 'data.pdf', + description: 'Name of the file that will be downloaded.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'get', + ], + download: [ + true, + ], + }, + }, + }, + + // ---------------------------------- + // payment: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting payments. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // payment: send + // ---------------------------------- + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'string', + required: true, + default: '', + description: 'The ID of the payment to send.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + description: 'The email of the recipient of the payment.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'send', + ], + }, + }, + }, + + // ---------------------------------- + // payment: void + // ---------------------------------- + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'string', + required: true, + default: '', + description: 'The ID of the payment to void.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'void', + ], + }, + }, + }, + + // ---------------------------------- + // payment: update + // ---------------------------------- + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'string', + required: true, + default: '', + description: 'The ID of the payment to update.', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'update', + ], + }, + }, + options: paymentAdditionalFieldsOptions, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Shared.interface.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Shared.interface.ts new file mode 100644 index 0000000000..b3994c66eb --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Shared.interface.ts @@ -0,0 +1,48 @@ +export interface BillingAddress { + Line4: string; + Line3: string; + Line2: string; + Line1: string; + Long: string; + Lat: string; +} + +export interface BillEmail { + Address: string; +} + +export interface CustomField { + DefinitionId: string; + Name: string; +} + +export interface CustomerMemo { + value: string; +} + +export interface GeneralAddress { + City: string; + Line1: string; + PostalCode: string; + Lat: string; + Long: string; + CountrySubDivisionCode: string; +} + +export interface LinkedTxn { + TxnId: string; + TxnType: string; +} + +export interface PrimaryEmailAddr { + Address: string; +} + +export interface PrimaryPhone { + FreeFormNumber: string; +} + +export interface Ref { + value: string; + name?: string; +} diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorAdditionalFieldsOptions.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorAdditionalFieldsOptions.ts new file mode 100644 index 0000000000..68b27a208b --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorAdditionalFieldsOptions.ts @@ -0,0 +1,117 @@ +export const vendorAdditionalFieldsOptions = [ + { + displayName: 'Account Number', + name: 'AcctNum', + type: 'string', + default: '', + }, + { + displayName: 'Active', + name: 'Active', + description: 'Whether the employee is currently enabled for use by QuickBooks.', + type: 'boolean', + default: false, + }, + { + displayName: 'Balance', + name: 'Balance', + description: 'The balance reflecting any payments made against the transaction.', + type: 'number', + default: 0, + }, + { + displayName: 'Billing Address', + name: 'BillAddr', + placeholder: 'Add Billing Address Fields', + type: 'fixedCollection', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'City', + name: 'City', + type: 'string', + default: '', + }, + { + displayName: 'Line 1', + name: 'Line1', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'PostalCode', + type: 'string', + default: '', + }, + { + displayName: 'Latitude', + name: 'Lat', + type: 'string', + default: '', + }, + { + displayName: 'Longitude', + name: 'Long', + type: 'string', + default: '', + }, + { + displayName: 'Country Subdivision Code', + name: 'CountrySubDivisionCode', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Company Name', + name: 'CompanyName', + type: 'string', + default: '', + }, + { + displayName: 'Family Name', + name: 'FamilyName', + type: 'string', + default: '', + }, + { + displayName: 'Given Name', + name: 'GivenName', + type: 'string', + default: '', + }, + { + displayName: 'Primary Email Address', + name: 'PrimaryEmailAddr', + type: 'string', + default: '', + }, + { + displayName: 'Primary Phone', + name: 'PrimaryPhone', + type: 'string', + default: '', + }, + { + displayName: 'Print-On-Check Name', + name: 'PrintOnCheckName', + description: 'Name of the vendor as printed on a check.', + type: 'string', + default: '', + }, + { + displayName: 'Vendor 1099', + name: 'Vendor1099', + description: 'Whether the vendor is an independent contractor, given a 1099-MISC form at the end of the year.', + type: 'boolean', + default: false, + }, +]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorDescription.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorDescription.ts new file mode 100644 index 0000000000..09c1030912 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/Vendor/VendorDescription.ts @@ -0,0 +1,222 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + vendorAdditionalFieldsOptions, +} from './VendorAdditionalFieldsOptions'; + +export const vendorOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'vendor', + ], + }, + }, + }, +] as INodeProperties[]; + +export const vendorFields = [ + // ---------------------------------- + // vendor: create + // ---------------------------------- + { + displayName: 'Display Name', + name: 'displayName', + type: 'string', + required: true, + default: '', + description: 'The display name of the vendor to create.', + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'create', + ], + }, + }, + options: vendorAdditionalFieldsOptions, + }, + + // ---------------------------------- + // vendor: get + // ---------------------------------- + { + displayName: 'Vendor ID', + name: 'vendorId', + type: 'string', + required: true, + default: '', + description: 'The ID of the vendor to retrieve.', + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // vendor: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all results.', + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 5, + description: 'The number of results to return.', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'', + description: 'The condition for selecting vendors. See the guide for supported syntax.', + typeOptions: { + alwaysOpenEditWindow: true, + }, + }, + ], + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + + // ---------------------------------- + // vendor: update + // ---------------------------------- + { + displayName: 'Vendor ID', + name: 'vendorId', + type: 'string', + required: true, + default: '', + description: 'The ID of the vendor to update.', + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + displayOptions: { + show: { + resource: [ + 'vendor', + ], + operation: [ + 'update', + ], + }, + }, + options: vendorAdditionalFieldsOptions, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/QuickBooks/descriptions/index.ts b/packages/nodes-base/nodes/QuickBooks/descriptions/index.ts new file mode 100644 index 0000000000..d8573f7223 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/descriptions/index.ts @@ -0,0 +1,8 @@ +export * from './Bill/BillDescription'; +export * from './Customer/CustomerDescription'; +export * from './Employee/EmployeeDescription'; +export * from './Estimate/EstimateDescription'; +export * from './Invoice/InvoiceDescription'; +export * from './Item/ItemDescription'; +export * from './Payment/PaymentDescription'; +export * from './Vendor/VendorDescription'; diff --git a/packages/nodes-base/nodes/QuickBooks/quickbooks.svg b/packages/nodes-base/nodes/QuickBooks/quickbooks.svg new file mode 100644 index 0000000000..ac04a2e791 --- /dev/null +++ b/packages/nodes-base/nodes/QuickBooks/quickbooks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6d91522707..0f958e35b4 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -177,6 +177,7 @@ "dist/credentials/PushcutApi.credentials.js", "dist/credentials/QuestDb.credentials.js", "dist/credentials/QuickBaseApi.credentials.js", + "dist/credentials/QuickBooksOAuth2Api.credentials.js", "dist/credentials/RabbitMQ.credentials.js", "dist/credentials/RedditOAuth2Api.credentials.js", "dist/credentials/Redis.credentials.js", @@ -427,6 +428,7 @@ "dist/nodes/Pushover/Pushover.node.js", "dist/nodes/QuestDb/QuestDb.node.js", "dist/nodes/QuickBase/QuickBase.node.js", + "dist/nodes/QuickBooks/QuickBooks.node.js", "dist/nodes/RabbitMQ/RabbitMQ.node.js", "dist/nodes/RabbitMQ/RabbitMQTrigger.node.js", "dist/nodes/ReadBinaryFile.node.js",