n8n/packages/nodes-base/nodes/Xero/Xero.node.ts
Ricardo Espinoza ab08c0df15
fix(Xero Node): Fix some operations and add support for setting address and phone number (#3048)
* 🐛 Fix issue when sending Organization ID - Xero node

* 👕 Fix linting issue
2022-03-27 10:38:49 +02:00

690 lines
24 KiB
TypeScript

import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import {
xeroApiRequest,
xeroApiRequestAllItems,
} from './GenericFunctions';
import {
invoiceFields,
invoiceOperations
} from './InvoiceDescription';
import {
contactFields,
contactOperations,
} from './ContactDescription';
import {
IInvoice,
ILineItem,
} from './InvoiceInterface';
import {
IAddress,
IContact,
IPhone,
} from './IContactInterface';
export class Xero implements INodeType {
description: INodeTypeDescription = {
displayName: 'Xero',
name: 'xero',
icon: 'file:xero.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Xero API',
defaults: {
name: 'Xero',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'xeroOAuth2Api',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Contact',
value: 'contact',
},
{
name: 'Invoice',
value: 'invoice',
},
],
default: 'invoice',
description: 'Resource to consume.',
},
// CONTACT
...contactOperations,
...contactFields,
// INVOICE
...invoiceOperations,
...invoiceFields,
],
};
methods = {
loadOptions: {
// Get all the item codes to display them to user so that he can
// select them easily
async getItemCodes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const organizationId = this.getCurrentNodeParameter('organizationId');
const returnData: INodePropertyOptions[] = [];
const { Items: items } = await xeroApiRequest.call(this, 'GET', '/items', { organizationId });
for (const item of items) {
const itemName = item.Description;
const itemId = item.Code;
returnData.push({
name: itemName,
value: itemId,
});
}
return returnData;
},
// Get all the account codes to display them to user so that he can
// select them easily
async getAccountCodes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const organizationId = this.getCurrentNodeParameter('organizationId');
const returnData: INodePropertyOptions[] = [];
const { Accounts: accounts } = await xeroApiRequest.call(this, 'GET', '/Accounts', { organizationId });
for (const account of accounts) {
const accountName = account.Name;
const accountId = account.Code;
returnData.push({
name: accountName,
value: accountId,
});
}
return returnData;
},
// Get all the tenants to display them to user so that he can
// select them easily
async getTenants(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const tenants = await xeroApiRequest.call(this, 'GET', '', {}, {}, 'https://api.xero.com/connections');
for (const tenant of tenants) {
const tenantName = tenant.tenantName;
const tenantId = tenant.tenantId;
returnData.push({
name: tenantName,
value: tenantId,
});
}
return returnData;
},
// Get all the brading themes to display them to user so that he can
// select them easily
async getBrandingThemes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const organizationId = this.getCurrentNodeParameter('organizationId');
const returnData: INodePropertyOptions[] = [];
const { BrandingThemes: themes } = await xeroApiRequest.call(this, 'GET', '/BrandingThemes', { organizationId });
for (const theme of themes) {
const themeName = theme.Name;
const themeId = theme.BrandingThemeID;
returnData.push({
name: themeName,
value: themeId,
});
}
return returnData;
},
// Get all the brading themes to display them to user so that he can
// select them easily
async getCurrencies(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const organizationId = this.getCurrentNodeParameter('organizationId');
const returnData: INodePropertyOptions[] = [];
const { Currencies: currencies } = await xeroApiRequest.call(this, 'GET', '/Currencies', { organizationId });
for (const currency of currencies) {
const currencyName = currency.Code;
const currencyId = currency.Description;
returnData.push({
name: currencyName,
value: currencyId,
});
}
return returnData;
},
// Get all the tracking categories to display them to user so that he can
// select them easily
async getTrakingCategories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const organizationId = this.getCurrentNodeParameter('organizationId');
const returnData: INodePropertyOptions[] = [];
const { TrackingCategories: categories } = await xeroApiRequest.call(this, 'GET', '/TrackingCategories', { organizationId });
for (const category of categories) {
const categoryName = category.Name;
const categoryId = category.TrackingCategoryID;
returnData.push({
name: categoryName,
value: categoryId,
});
}
return returnData;
},
// // Get all the tracking categories to display them to user so that he can
// // select them easily
// async getTrakingOptions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
// const organizationId = this.getCurrentNodeParameter('organizationId');
// const name = this.getCurrentNodeParameter('name');
// const returnData: INodePropertyOptions[] = [];
// const { TrackingCategories: categories } = await xeroApiRequest.call(this, 'GET', '/TrackingCategories', { organizationId });
// const { Options: options } = categories.filter((category: IDataObject) => category.Name === name)[0];
// for (const option of options) {
// const optionName = option.Name;
// const optionId = option.TrackingOptionID;
// returnData.push({
// name: optionName,
// value: optionId,
// });
// }
// return returnData;
// },
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
const qs: IDataObject = {};
let responseData;
for (let i = 0; i < length; i++) {
try {
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
//https://developer.xero.com/documentation/api/invoices
if (resource === 'invoice') {
if (operation === 'create') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const type = this.getNodeParameter('type', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const contactId = this.getNodeParameter('contactId', i) as string;
const lineItemsValues = ((this.getNodeParameter('lineItemsUi', i) as IDataObject).lineItemsValues as IDataObject[]);
const body: IInvoice = {
organizationId,
Type: type,
Contact: { ContactID: contactId },
};
if (lineItemsValues) {
const lineItems: ILineItem[] = [];
for (const lineItemValue of lineItemsValues) {
const lineItem: ILineItem = {
Tracking: [],
};
lineItem.AccountCode = lineItemValue.accountCode as string;
lineItem.Description = lineItemValue.description as string;
lineItem.DiscountRate = lineItemValue.discountRate as string;
lineItem.ItemCode = lineItemValue.itemCode as string;
lineItem.LineAmount = lineItemValue.lineAmount as string;
lineItem.Quantity = (lineItemValue.quantity as number).toString();
lineItem.TaxAmount = lineItemValue.taxAmount as string;
lineItem.TaxType = lineItemValue.taxType as string;
lineItem.UnitAmount = lineItemValue.unitAmount as string;
// if (lineItemValue.trackingUi) {
// //@ts-ignore
// const { trackingValues } = lineItemValue.trackingUi as IDataObject[];
// if (trackingValues) {
// for (const trackingValue of trackingValues) {
// const tracking: IDataObject = {};
// tracking.Name = trackingValue.name as string;
// tracking.Option = trackingValue.option as string;
// lineItem.Tracking!.push(tracking);
// }
// }
// }
lineItems.push(lineItem);
}
body.LineItems = lineItems;
}
if (additionalFields.brandingThemeId) {
body.BrandingThemeID = additionalFields.brandingThemeId as string;
}
if (additionalFields.currency) {
body.CurrencyCode = additionalFields.currency as string;
}
if (additionalFields.currencyRate) {
body.CurrencyRate = additionalFields.currencyRate as string;
}
if (additionalFields.date) {
body.Date = additionalFields.date as string;
}
if (additionalFields.dueDate) {
body.DueDate = additionalFields.dueDate as string;
}
if (additionalFields.dueDate) {
body.DueDate = additionalFields.dueDate as string;
}
if (additionalFields.expectedPaymentDate) {
body.ExpectedPaymentDate = additionalFields.expectedPaymentDate as string;
}
if (additionalFields.invoiceNumber) {
body.InvoiceNumber = additionalFields.invoiceNumber as string;
}
if (additionalFields.lineAmountType) {
body.LineAmountType = additionalFields.lineAmountType as string;
}
if (additionalFields.plannedPaymentDate) {
body.PlannedPaymentDate = additionalFields.plannedPaymentDate as string;
}
if (additionalFields.reference) {
body.Reference = additionalFields.reference as string;
}
if (additionalFields.sendToContact) {
body.SentToContact = additionalFields.sendToContact as boolean;
}
if (additionalFields.status) {
body.Status = additionalFields.status as string;
}
if (additionalFields.url) {
body.Url = additionalFields.url as string;
}
responseData = await xeroApiRequest.call(this, 'POST', '/Invoices', body);
responseData = responseData.Invoices;
}
if (operation === 'update') {
const invoiceId = this.getNodeParameter('invoiceId', i) as string;
const organizationId = this.getNodeParameter('organizationId', i) as string;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const body: IInvoice = {
organizationId,
};
if (updateFields.lineItemsUi) {
const lineItemsValues = (updateFields.lineItemsUi as IDataObject).lineItemsValues as IDataObject[];
if (lineItemsValues) {
const lineItems: ILineItem[] = [];
for (const lineItemValue of lineItemsValues) {
const lineItem: ILineItem = {
Tracking: [],
};
lineItem.AccountCode = lineItemValue.accountCode as string;
lineItem.Description = lineItemValue.description as string;
lineItem.DiscountRate = lineItemValue.discountRate as string;
lineItem.ItemCode = lineItemValue.itemCode as string;
lineItem.LineAmount = lineItemValue.lineAmount as string;
lineItem.Quantity = (lineItemValue.quantity as number).toString();
lineItem.TaxAmount = lineItemValue.taxAmount as string;
lineItem.TaxType = lineItemValue.taxType as string;
lineItem.UnitAmount = lineItemValue.unitAmount as string;
// if (lineItemValue.trackingUi) {
// //@ts-ignore
// const { trackingValues } = lineItemValue.trackingUi as IDataObject[];
// if (trackingValues) {
// for (const trackingValue of trackingValues) {
// const tracking: IDataObject = {};
// tracking.Name = trackingValue.name as string;
// tracking.Option = trackingValue.option as string;
// lineItem.Tracking!.push(tracking);
// }
// }
// }
lineItems.push(lineItem);
}
body.LineItems = lineItems;
}
}
if (updateFields.type) {
body.Type = updateFields.type as string;
}
if (updateFields.Contact) {
body.Contact = { ContactID: updateFields.contactId as string };
}
if (updateFields.brandingThemeId) {
body.BrandingThemeID = updateFields.brandingThemeId as string;
}
if (updateFields.currency) {
body.CurrencyCode = updateFields.currency as string;
}
if (updateFields.currencyRate) {
body.CurrencyRate = updateFields.currencyRate as string;
}
if (updateFields.date) {
body.Date = updateFields.date as string;
}
if (updateFields.dueDate) {
body.DueDate = updateFields.dueDate as string;
}
if (updateFields.dueDate) {
body.DueDate = updateFields.dueDate as string;
}
if (updateFields.expectedPaymentDate) {
body.ExpectedPaymentDate = updateFields.expectedPaymentDate as string;
}
if (updateFields.invoiceNumber) {
body.InvoiceNumber = updateFields.invoiceNumber as string;
}
if (updateFields.lineAmountType) {
body.LineAmountType = updateFields.lineAmountType as string;
}
if (updateFields.plannedPaymentDate) {
body.PlannedPaymentDate = updateFields.plannedPaymentDate as string;
}
if (updateFields.reference) {
body.Reference = updateFields.reference as string;
}
if (updateFields.sendToContact) {
body.SentToContact = updateFields.sendToContact as boolean;
}
if (updateFields.status) {
body.Status = updateFields.status as string;
}
if (updateFields.url) {
body.Url = updateFields.url as string;
}
responseData = await xeroApiRequest.call(this, 'POST', `/Invoices/${invoiceId}`, body);
responseData = responseData.Invoices;
}
if (operation === 'get') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const invoiceId = this.getNodeParameter('invoiceId', i) as string;
responseData = await xeroApiRequest.call(this, 'GET', `/Invoices/${invoiceId}`, { organizationId });
responseData = responseData.Invoices;
}
if (operation === 'getAll') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject;
if (options.statuses) {
qs.statuses = (options.statuses as string[]).join(',');
}
if (options.orderBy) {
qs.order = `${options.orderBy} ${(options.sortOrder === undefined) ? 'DESC' : options.sortOrder}`;
}
if (options.where) {
qs.where = options.where;
}
if (options.createdByMyApp) {
qs.createdByMyApp = options.createdByMyApp as boolean;
}
if (returnAll) {
responseData = await xeroApiRequestAllItems.call(this, 'Invoices', 'GET', '/Invoices', { organizationId }, qs);
} else {
const limit = this.getNodeParameter('limit', i) as number;
responseData = await xeroApiRequest.call(this, 'GET', `/Invoices`, { organizationId }, qs);
responseData = responseData.Invoices;
responseData = responseData.splice(0, limit);
}
}
}
if (resource === 'contact') {
if (operation === 'create') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const name = this.getNodeParameter('name', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const addressesUi = additionalFields.addressesUi as IDataObject;
const phonesUi = additionalFields.phonesUi as IDataObject;
const body: IContact = {
Name: name,
};
if (additionalFields.accountNumber) {
body.AccountNumber = additionalFields.accountNumber as string;
}
if (additionalFields.bankAccountDetails) {
body.BankAccountDetails = additionalFields.bankAccountDetails as string;
}
if (additionalFields.contactNumber) {
body.ContactNumber = additionalFields.contactNumber as string;
}
if (additionalFields.contactStatus) {
body.ContactStatus = additionalFields.contactStatus as string;
}
if (additionalFields.defaultCurrency) {
body.DefaultCurrency = additionalFields.defaultCurrency as string;
}
if (additionalFields.emailAddress) {
body.EmailAddress = additionalFields.emailAddress as string;
}
if (additionalFields.firstName) {
body.FirstName = additionalFields.firstName as string;
}
if (additionalFields.lastName) {
body.LastName = additionalFields.lastName as string;
}
if (additionalFields.purchasesDefaultAccountCode) {
body.PurchasesDefaultAccountCode = additionalFields.purchasesDefaultAccountCode as string;
}
if (additionalFields.salesDefaultAccountCode) {
body.SalesDefaultAccountCode = additionalFields.salesDefaultAccountCode as string;
}
if (additionalFields.skypeUserName) {
body.SkypeUserName = additionalFields.skypeUserName as string;
}
if (additionalFields.taxNumber) {
body.taxNumber = additionalFields.taxNumber as string;
}
if (additionalFields.xeroNetworkKey) {
body.xeroNetworkKey = additionalFields.xeroNetworkKey as string;
}
if (phonesUi) {
const phoneValues = phonesUi?.phonesValues as IDataObject[];
if (phoneValues) {
const phones: IPhone[] = [];
for (const phoneValue of phoneValues) {
const phone: IPhone = {};
phone.PhoneType = phoneValue.phoneType as string;
phone.PhoneNumber = phoneValue.phoneNumber as string;
phone.PhoneAreaCode = phoneValue.phoneAreaCode as string;
phone.PhoneCountryCode = phoneValue.phoneCountryCode as string;
phones.push(phone);
}
body.Phones = phones;
}
}
if (addressesUi) {
const addressValues = addressesUi?.addressesValues as IDataObject[];
if (addressValues) {
const addresses: IAddress[] = [];
for (const addressValue of addressValues) {
const address: IAddress = {};
address.AddressType = addressValue.type as string;
address.AddressLine1 = addressValue.line1 as string;
address.AddressLine2 = addressValue.line2 as string;
address.City = addressValue.city as string;
address.Region = addressValue.region as string;
address.PostalCode = addressValue.postalCode as string;
address.Country = addressValue.country as string;
address.AttentionTo = addressValue.attentionTo as string;
addresses.push(address);
}
body.Addresses = addresses;
}
}
responseData = await xeroApiRequest.call(this, 'POST', '/Contacts', { organizationId, Contacts: [body] });
responseData = responseData.Contacts;
}
if (operation === 'get') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const contactId = this.getNodeParameter('contactId', i) as string;
responseData = await xeroApiRequest.call(this, 'GET', `/Contacts/${contactId}`, { organizationId });
responseData = responseData.Contacts;
}
if (operation === 'getAll') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject;
if (options.includeArchived) {
qs.includeArchived = options.includeArchived as boolean;
}
if (options.orderBy) {
qs.order = `${options.orderBy} ${(options.sortOrder === undefined) ? 'DESC' : options.sortOrder}`;
}
if (options.where) {
qs.where = options.where;
}
if (returnAll) {
responseData = await xeroApiRequestAllItems.call(this, 'Contacts', 'GET', '/Contacts', { organizationId }, qs);
} else {
const limit = this.getNodeParameter('limit', i) as number;
responseData = await xeroApiRequest.call(this, 'GET', `/Contacts`, { organizationId }, qs);
responseData = responseData.Contacts;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'update') {
const organizationId = this.getNodeParameter('organizationId', i) as string;
const contactId = this.getNodeParameter('contactId', i) as string;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const addressesUi = updateFields.addressesUi as IDataObject;
const phonesUi = updateFields.phonesUi as IDataObject;
const body: IContact = {};
if (updateFields.accountNumber) {
body.AccountNumber = updateFields.accountNumber as string;
}
if (updateFields.name) {
body.Name = updateFields.name as string;
}
if (updateFields.bankAccountDetails) {
body.BankAccountDetails = updateFields.bankAccountDetails as string;
}
if (updateFields.contactNumber) {
body.ContactNumber = updateFields.contactNumber as string;
}
if (updateFields.contactStatus) {
body.ContactStatus = updateFields.contactStatus as string;
}
if (updateFields.defaultCurrency) {
body.DefaultCurrency = updateFields.defaultCurrency as string;
}
if (updateFields.emailAddress) {
body.EmailAddress = updateFields.emailAddress as string;
}
if (updateFields.firstName) {
body.FirstName = updateFields.firstName as string;
}
if (updateFields.lastName) {
body.LastName = updateFields.lastName as string;
}
if (updateFields.purchasesDefaultAccountCode) {
body.PurchasesDefaultAccountCode = updateFields.purchasesDefaultAccountCode as string;
}
if (updateFields.salesDefaultAccountCode) {
body.SalesDefaultAccountCode = updateFields.salesDefaultAccountCode as string;
}
if (updateFields.skypeUserName) {
body.SkypeUserName = updateFields.skypeUserName as string;
}
if (updateFields.taxNumber) {
body.taxNumber = updateFields.taxNumber as string;
}
if (updateFields.xeroNetworkKey) {
body.xeroNetworkKey = updateFields.xeroNetworkKey as string;
}
if (phonesUi) {
const phoneValues = phonesUi?.phonesValues as IDataObject[];
if (phoneValues) {
const phones: IPhone[] = [];
for (const phoneValue of phoneValues) {
const phone: IPhone = {};
phone.PhoneType = phoneValue.phoneType as string;
phone.PhoneNumber = phoneValue.phoneNumber as string;
phone.PhoneAreaCode = phoneValue.phoneAreaCode as string;
phone.PhoneCountryCode = phoneValue.phoneCountryCode as string;
phones.push(phone);
}
body.Phones = phones;
}
}
if (addressesUi) {
const addressValues = addressesUi?.addressesValues as IDataObject[];
if (addressValues) {
const addresses: IAddress[] = [];
for (const addressValue of addressValues) {
const address: IAddress = {};
address.AddressType = addressValue.type as string;
address.AddressLine1 = addressValue.line1 as string;
address.AddressLine2 = addressValue.line2 as string;
address.City = addressValue.city as string;
address.Region = addressValue.region as string;
address.PostalCode = addressValue.postalCode as string;
address.Country = addressValue.country as string;
address.AttentionTo = addressValue.attentionTo as string;
addresses.push(address);
}
body.Addresses = addresses;
}
}
responseData = await xeroApiRequest.call(this, 'POST', `/Contacts/${contactId}`, { organizationId, Contacts: [body] });
responseData = responseData.Contacts;
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: (error as JsonObject).message });
continue;
}
throw error;
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}