diff --git a/packages/nodes-base/credentials/XeroOAuth2Api.credentials.ts b/packages/nodes-base/credentials/XeroOAuth2Api.credentials.ts
new file mode 100644
index 0000000000..2db47c13de
--- /dev/null
+++ b/packages/nodes-base/credentials/XeroOAuth2Api.credentials.ts
@@ -0,0 +1,51 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+const scopes = [
+ 'offline_access',
+ 'accounting.transactions',
+ 'accounting.settings',
+ 'accounting.contacts',
+];
+
+export class XeroOAuth2Api implements ICredentialType {
+ name = 'xeroOAuth2Api';
+ extends = [
+ 'oAuth2Api',
+ ];
+ displayName = 'Xero OAuth2 API';
+ properties = [
+ {
+ displayName: 'Authorization URL',
+ name: 'authUrl',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'https://login.xero.com/identity/connect/authorize',
+ },
+ {
+ displayName: 'Access Token URL',
+ name: 'accessTokenUrl',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'https://identity.xero.com/connect/token',
+ },
+ {
+ 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',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Xero/ContactDescription.ts b/packages/nodes-base/nodes/Xero/ContactDescription.ts
new file mode 100644
index 0000000000..418aef44ac
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/ContactDescription.ts
@@ -0,0 +1,838 @@
+import {
+ INodeProperties,
+ } from 'n8n-workflow';
+
+export const contactOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'create a contact',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a contact',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all contacts',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a contact',
+ },
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const contactFields = [
+
+/* -------------------------------------------------------------------------- */
+/* contact:create */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Full name of contact/organisation',
+ required: true,
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Account Number',
+ name: 'accountNumber',
+ type: 'string',
+ default: '',
+ description: 'A user defined account number',
+ },
+ // {
+ // displayName: 'Addresses',
+ // name: 'addressesUi',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: '',
+ // placeholder: 'Add Address',
+ // options: [
+ // {
+ // name: 'addressesValues',
+ // displayName: 'Address',
+ // values: [
+ // {
+ // displayName: 'Type',
+ // name: 'type',
+ // type: 'options',
+ // options: [
+ // {
+ // name: 'PO Box',
+ // value: 'POBOX',
+ // },
+ // {
+ // name: 'Street',
+ // value: 'STREET',
+ // },
+ // ],
+ // default: '',
+ // },
+ // {
+ // displayName: 'Line 1',
+ // name: 'line1',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Line 2',
+ // name: 'line2',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'City',
+ // name: 'city',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Region',
+ // name: 'region',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Postal Code',
+ // name: 'postalCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Country',
+ // name: 'country',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Attention To',
+ // name: 'attentionTo',
+ // type: 'string',
+ // default: '',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ {
+ displayName: 'Bank Account Details',
+ name: 'bankAccountDetails',
+ type: 'string',
+ default: '',
+ description: 'Bank account number of contact',
+ },
+ {
+ displayName: 'Contact Number',
+ name: 'contactNumber',
+ type: 'string',
+ default: '',
+ description: 'This field is read only on the Xero contact screen, used to identify contacts in external systems',
+ },
+ {
+ displayName: 'Contact Status',
+ name: 'contactStatus',
+ type: 'options',
+ options: [
+ {
+ name: 'Active',
+ value: 'ACTIVE',
+ description: 'The Contact is active and can be used in transactions',
+ },
+ {
+ name: 'Archived',
+ value: 'ARCHIVED',
+ description: 'The Contact is archived and can no longer be used in transactions',
+ },
+ {
+ name: 'GDPR Request',
+ value: 'GDPRREQUEST',
+ description: 'The Contact is the subject of a GDPR erasure request',
+ },
+ ],
+ default: '',
+ description: 'Current status of a contact - see contact status types',
+ },
+ {
+ displayName: 'Default Currency',
+ name: 'defaultCurrency',
+ type: 'string',
+ default: '',
+ description: 'Default currency for raising invoices against contact',
+ },
+ {
+ displayName: 'Email',
+ name: 'emailAddress',
+ type: 'string',
+ default: '',
+ description: 'Email address of contact person (umlauts not supported) (max length = 255)',
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: '',
+ description: 'First name of contact person (max length = 255)',
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: '',
+ description: 'Last name of contact person (max length = 255)',
+ },
+ // {
+ // displayName: 'Phones',
+ // name: 'phonesUi',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: '',
+ // placeholder: 'Add Phone',
+ // options: [
+ // {
+ // name: 'phonesValues',
+ // displayName: 'Phones',
+ // values: [
+ // {
+ // displayName: 'Type',
+ // name: 'type',
+ // type: 'options',
+ // options: [
+ // {
+ // name: 'Default',
+ // value: 'DEFAULT',
+ // },
+ // {
+ // name: 'DDI',
+ // value: 'DDI',
+ // },
+ // {
+ // name: 'Mobile',
+ // value: 'MOBILE',
+ // },
+ // {
+ // name: 'Fax',
+ // value: 'FAX',
+ // },
+ // ],
+ // default: '',
+ // },
+ // {
+ // displayName: 'Number',
+ // name: 'phoneNumber',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Area Code',
+ // name: 'phoneAreaCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Country Code',
+ // name: 'phoneCountryCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ {
+ displayName: 'Purchase Default Account Code',
+ name: 'purchasesDefaultAccountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ },
+ default: '',
+ description: 'The default purchases account code for contacts',
+ },
+ {
+ displayName: 'Sales Default Account Code',
+ name: 'salesDefaultAccountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ },
+ default: '',
+ description: 'The default sales account code for contacts',
+ },
+ {
+ displayName: 'Skype',
+ name: 'skypeUserName',
+ type: 'string',
+ default: '',
+ description: 'Skype user name of contact',
+ },
+ {
+ displayName: 'Tax Number',
+ name: 'taxNumber',
+ type: 'string',
+ default: '',
+ description: 'Tax number of contact',
+ },
+ {
+ displayName: 'Xero Network Key',
+ name: 'xeroNetworkKey',
+ type: 'string',
+ default: '',
+ description: 'Store XeroNetworkKey for contacts',
+ },
+ ],
+ },
+/* -------------------------------------------------------------------------- */
+/* contact:get */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'get',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Contact ID',
+ name: 'contactId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'get',
+ ],
+ },
+ },
+ required: true,
+ },
+/* -------------------------------------------------------------------------- */
+/* contact:getAll */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Include Archived',
+ name: 'includeArchived',
+ type: 'boolean',
+ default: false,
+ description: `Contacts with a status of ARCHIVED will be included in the response`,
+ },
+ {
+ displayName: 'Order By',
+ name: 'orderBy',
+ type: 'string',
+ placeholder: 'contactID',
+ default: '',
+ description: 'Order by any element returned',
+ },
+ {
+ displayName: 'Sort Order',
+ name: 'sortOrder',
+ type: 'options',
+ options: [
+ {
+ name: 'Asc',
+ value: 'ASC',
+ },
+ {
+ name: 'Desc',
+ value: 'DESC',
+ },
+ ],
+ default: '',
+ description: 'Sort order',
+ },
+ {
+ displayName: 'Where',
+ name: 'where',
+ type: 'string',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ placeholder: 'EmailAddress!=null&&EmailAddress.StartsWith("boom")',
+ default: '',
+ description: `The where parameter allows you to filter on endpoints and elements that don't have explicit parameters. Examples Here`,
+ },
+ ],
+ },
+/* -------------------------------------------------------------------------- */
+/* contact:update */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Contact ID',
+ name: 'contactId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'contact',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Account Number',
+ name: 'accountNumber',
+ type: 'string',
+ default: '',
+ description: 'A user defined account number',
+ },
+ // {
+ // displayName: 'Addresses',
+ // name: 'addressesUi',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: '',
+ // placeholder: 'Add Address',
+ // options: [
+ // {
+ // name: 'addressesValues',
+ // displayName: 'Address',
+ // values: [
+ // {
+ // displayName: 'Type',
+ // name: 'type',
+ // type: 'options',
+ // options: [
+ // {
+ // name: 'PO Box',
+ // value: 'POBOX',
+ // },
+ // {
+ // name: 'Street',
+ // value: 'STREET',
+ // },
+ // ],
+ // default: '',
+ // },
+ // {
+ // displayName: 'Line 1',
+ // name: 'line1',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Line 2',
+ // name: 'line2',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'City',
+ // name: 'city',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Region',
+ // name: 'region',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Postal Code',
+ // name: 'postalCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Country',
+ // name: 'country',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Attention To',
+ // name: 'attentionTo',
+ // type: 'string',
+ // default: '',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ {
+ displayName: 'Bank Account Details',
+ name: 'bankAccountDetails',
+ type: 'string',
+ default: '',
+ description: 'Bank account number of contact',
+ },
+ {
+ displayName: 'Contact Number',
+ name: 'contactNumber',
+ type: 'string',
+ default: '',
+ description: 'This field is read only on the Xero contact screen, used to identify contacts in external systems',
+ },
+ {
+ displayName: 'Contact Status',
+ name: 'contactStatus',
+ type: 'options',
+ options: [
+ {
+ name: 'Active',
+ value: 'ACTIVE',
+ description: 'The Contact is active and can be used in transactions',
+ },
+ {
+ name: 'Archived',
+ value: 'ARCHIVED',
+ description: 'The Contact is archived and can no longer be used in transactions',
+ },
+ {
+ name: 'GDPR Request',
+ value: 'GDPRREQUEST',
+ description: 'The Contact is the subject of a GDPR erasure request',
+ },
+ ],
+ default: '',
+ description: 'Current status of a contact - see contact status types',
+ },
+ {
+ displayName: 'Default Currency',
+ name: 'defaultCurrency',
+ type: 'string',
+ default: '',
+ description: 'Default currency for raising invoices against contact',
+ },
+ {
+ displayName: 'Email',
+ name: 'emailAddress',
+ type: 'string',
+ default: '',
+ description: 'Email address of contact person (umlauts not supported) (max length = 255)',
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: '',
+ description: 'First name of contact person (max length = 255)',
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: '',
+ description: 'Last name of contact person (max length = 255)',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Full name of contact/organisation',
+ },
+ // {
+ // displayName: 'Phones',
+ // name: 'phonesUi',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: '',
+ // placeholder: 'Add Phone',
+ // options: [
+ // {
+ // name: 'phonesValues',
+ // displayName: 'Phones',
+ // values: [
+ // {
+ // displayName: 'Type',
+ // name: 'type',
+ // type: 'options',
+ // options: [
+ // {
+ // name: 'Default',
+ // value: 'DEFAULT',
+ // },
+ // {
+ // name: 'DDI',
+ // value: 'DDI',
+ // },
+ // {
+ // name: 'Mobile',
+ // value: 'MOBILE',
+ // },
+ // {
+ // name: 'Fax',
+ // value: 'FAX',
+ // },
+ // ],
+ // default: '',
+ // },
+ // {
+ // displayName: 'Number',
+ // name: 'phoneNumber',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Area Code',
+ // name: 'phoneAreaCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // {
+ // displayName: 'Country Code',
+ // name: 'phoneCountryCode',
+ // type: 'string',
+ // default: '',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ {
+ displayName: 'Purchase Default Account Code',
+ name: 'purchasesDefaultAccountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ },
+ default: '',
+ description: 'The default purchases account code for contacts',
+ },
+ {
+ displayName: 'Sales Default Account Code',
+ name: 'salesDefaultAccountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ },
+ default: '',
+ description: 'The default sales account code for contacts',
+ },
+ {
+ displayName: 'Skype',
+ name: 'skypeUserName',
+ type: 'string',
+ default: '',
+ description: 'Skype user name of contact',
+ },
+ {
+ displayName: 'Tax Number',
+ name: 'taxNumber',
+ type: 'string',
+ default: '',
+ description: 'Tax number of contact',
+ },
+ {
+ displayName: 'Xero Network Key',
+ name: 'xeroNetworkKey',
+ type: 'string',
+ default: '',
+ description: 'Store XeroNetworkKey for contacts',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Xero/GenericFunctions.ts b/packages/nodes-base/nodes/Xero/GenericFunctions.ts
new file mode 100644
index 0000000000..840579bf2f
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/GenericFunctions.ts
@@ -0,0 +1,76 @@
+import {
+ OptionsWithUri,
+ } from 'request';
+
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+} from 'n8n-workflow';
+
+export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ const options: OptionsWithUri = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ method,
+ body,
+ qs,
+ uri: uri || `https://api.xero.com/api.xro/2.0${resource}`,
+ json: true
+ };
+ try {
+ if (body.organizationId) {
+ options.headers = { ...options.headers, 'Xero-tenant-id': body.organizationId };
+ delete body.organizationId;
+ }
+ if (Object.keys(headers).length !== 0) {
+ options.headers = Object.assign({}, options.headers, headers);
+ }
+ if (Object.keys(body).length === 0) {
+ delete options.body;
+ }
+ //@ts-ignore
+ return await this.helpers.requestOAuth2.call(this, 'xeroOAuth2Api', options);
+ } catch (error) {
+ let errorMessage;
+
+ if (error.response && error.response.body && error.response.body.Message) {
+
+ errorMessage = error.response.body.Message;
+
+ if (error.response.body.Elements) {
+ const elementErrors = [];
+ for (const element of error.response.body.Elements) {
+ elementErrors.push(element.ValidationErrors.map((error: IDataObject) => error.Message).join('|'));
+ }
+ errorMessage = elementErrors.join('-');
+ }
+ // Try to return the error prettier
+ throw new Error(`Xero error response [${error.statusCode}]: ${errorMessage}`);
+ }
+ throw error;
+ }
+}
+
+export async function xeroApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any
+
+ const returnData: IDataObject[] = [];
+
+ let responseData;
+ query.page = 1;
+
+ do {
+ responseData = await xeroApiRequest.call(this, method, endpoint, body, query);
+ query.page++;
+ returnData.push.apply(returnData, responseData[propertyName]);
+ } while (
+ responseData[propertyName].length !== 0
+ );
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Xero/IContactInterface.ts b/packages/nodes-base/nodes/Xero/IContactInterface.ts
new file mode 100644
index 0000000000..1fc5eebe6a
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/IContactInterface.ts
@@ -0,0 +1,44 @@
+
+export interface IAddress {
+ Type?: string;
+ AddressLine1?: string;
+ AddressLine2?: string;
+ City?: string;
+ Region?: string;
+ PostalCode?: string;
+ Country?: string;
+ AttentionTo?: string;
+}
+
+export interface IPhone {
+ Type?: string;
+ PhoneNumber?: string;
+ PhoneAreaCode?: string;
+ PhoneCountryCode?: string;
+}
+
+export interface IContact extends ITenantId {
+ AccountNumber?: string;
+ Addresses?: IAddress[];
+ BankAccountDetails?: string;
+ ContactId?: string;
+ ContactNumber?: string;
+ ContactStatus?: string;
+ DefaultCurrency?: string;
+ EmailAddress?: string;
+ FirstName?: string;
+ LastName?: string;
+ Name?: string;
+ Phones?: IPhone[];
+ PurchaseTrackingCategory?: string;
+ PurchasesDefaultAccountCode?: string;
+ SalesDefaultAccountCode?: string;
+ SalesTrackingCategory?: string;
+ SkypeUserName?: string;
+ taxNumber?: string;
+ xeroNetworkKey?: string;
+}
+
+export interface ITenantId {
+ organizationId?: string;
+}
diff --git a/packages/nodes-base/nodes/Xero/InvoiceDescription.ts b/packages/nodes-base/nodes/Xero/InvoiceDescription.ts
new file mode 100644
index 0000000000..6591adc24c
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/InvoiceDescription.ts
@@ -0,0 +1,983 @@
+import {
+ INodeProperties,
+ } from 'n8n-workflow';
+
+export const invoiceOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a invoice',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a invoice',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all invoices',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a invoice',
+ },
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const invoiceFields = [
+
+/* -------------------------------------------------------------------------- */
+/* invoice:create */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ {
+ name: 'Bill',
+ value: 'ACCPAY',
+ description: 'Accounts Payable or supplier invoice'
+ },
+ {
+ name: 'Sales Invoice',
+ value: 'ACCREC',
+ description: ' Accounts Receivable or customer invoice'
+ },
+ ],
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: true,
+ description: 'Invoice Type',
+ },
+ {
+ displayName: 'Contact ID',
+ name: 'contactId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: true,
+ description: 'Contact ID',
+ },
+ {
+ displayName: 'Line Items',
+ name: 'lineItemsUi',
+ placeholder: 'Add Line Item',
+ type: 'fixedCollection',
+ default: '',
+ typeOptions: {
+ multipleValues: true,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ description: 'Line item data',
+ options: [
+ {
+ name: 'lineItemsValues',
+ displayName: 'Line Item',
+ values: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'A line item with just a description',
+ },
+ {
+ displayName: 'Quantity',
+ name: 'quantity',
+ type: 'number',
+ default: 1,
+ typeOptions: {
+ minValue: 1,
+ },
+ description: 'LineItem Quantity',
+ },
+ {
+ displayName: 'Unit Amount',
+ name: 'unitAmount',
+ type: 'string',
+ default: '',
+ description: 'Lineitem unit amount. By default, unit amount will be rounded to two decimal places.',
+ },
+ {
+ displayName: 'Item Code',
+ name: 'itemCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getItemCodes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Account Code',
+ name: 'accountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Tax Type',
+ name: 'taxType',
+ type: 'options',
+ options: [
+ {
+ name: 'Tax on Purchases',
+ value: 'INPUT',
+ },
+ {
+ name: 'Tax Exempt',
+ value: 'NONE',
+ },
+ {
+ name: 'Tax on Sales',
+ value: 'OUTPUT',
+ },
+ {
+ name: 'Sales Tax on Imports ',
+ value: 'GSTONIMPORTS',
+ },
+ ],
+ default: '',
+ required: true,
+ description: 'Tax Type',
+ },
+ {
+ displayName: 'Tax Amount',
+ name: 'taxAmount',
+ type: 'string',
+ default: '',
+ description: 'The tax amount is auto calculated as a percentage of the line amount based on the tax rate.',
+ },
+ {
+ displayName: 'Line Amount',
+ name: 'lineAmount',
+ type: 'string',
+ default: '',
+ description: 'The line amount reflects the discounted price if a DiscountRate has been used',
+ },
+ {
+ displayName: 'Discount Rate',
+ name: 'discountRate',
+ type: 'string',
+ default: '',
+ description: 'Percentage discount or discount amount being applied to a line item. Only supported on ACCREC invoices - ACCPAY invoices and credit notes in Xero do not support discounts',
+ },
+ // {
+ // displayName: 'Tracking',
+ // name: 'trackingUi',
+ // placeholder: 'Add Tracking',
+ // description: 'Any LineItem can have a maximum of 2 TrackingCategory elements.',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: {},
+ // options: [
+ // {
+ // name: 'trackingValues',
+ // displayName: 'Tracking',
+ // values: [
+ // {
+ // displayName: 'Name',
+ // name: 'name',
+ // type: 'options',
+ // typeOptions: {
+ // loadOptionsMethod: 'getTrakingCategories',
+ // loadOptionsDependsOn: [
+ // 'organizationId',
+ // ],
+ // },
+ // default: '',
+ // description: 'Name of the tracking category',
+ // },
+ // {
+ // displayName: 'Option',
+ // name: 'option',
+ // type: 'options',
+ // typeOptions: {
+ // loadOptionsMethod: 'getTrakingOptions',
+ // loadOptionsDependsOn: [
+ // '/name',
+ // ],
+ // },
+ // default: '',
+ // description: 'Name of the option',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Branding Theme ID',
+ name: 'brandingThemeId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getBrandingThemes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Currency',
+ name: 'currency',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getCurrencies',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Currency Rate',
+ name: 'currencyRate',
+ type: 'string',
+ default: '',
+ description: 'The currency rate for a multicurrency invoice. If no rate is specified, the XE.com day rate is used.',
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'dateTime',
+ default: '',
+ description: 'Date invoice was issued - YYYY-MM-DD. If the Date element is not specified it will default to the current date based on the timezone setting of the organisation',
+ },
+ {
+ displayName: 'Due Date',
+ name: 'dueDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date invoice is due - YYYY-MM-DD',
+ },
+ {
+ displayName: 'Expected Payment Date',
+ name: 'expectedPaymentDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Shown on sales invoices (Accounts Receivable) when this has been set',
+ },
+ {
+ displayName: 'Invoice Number',
+ name: 'invoiceNumber',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Line Amount Type',
+ name: 'lineAmountType',
+ type: 'options',
+ options: [
+ {
+ name: 'Exclusive',
+ value: 'Exclusive',
+ description: 'Line items are exclusive of tax',
+ },
+ {
+ name: 'Inclusive',
+ value: 'Inclusive',
+ description: 'Line items are inclusive tax',
+ },
+ {
+ name: 'NoTax',
+ value: 'NoTax',
+ description: 'Line have no tax',
+ },
+ ],
+ default: 'Exclusive',
+ },
+ {
+ displayName: 'Planned Payment Date ',
+ name: 'plannedPaymentDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Shown on bills (Accounts Payable) when this has been set',
+ },
+ {
+ displayName: 'Reference',
+ name: 'reference',
+ type: 'string',
+ default: '',
+ description: 'ACCREC only - additional reference number (max length = 255)',
+ },
+ {
+ displayName: 'Send To Contact',
+ name: 'sendToContact',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the invoice in the Xero app should be marked as "sent". This can be set only on invoices that have been approved',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Draft',
+ value: 'DRAFT',
+ },
+ {
+ name: 'Submitted',
+ value: 'SUBMITTED',
+ },
+ {
+ name: 'Authorised',
+ value: 'AUTHORISED',
+ },
+ ],
+ default: 'DRAFT',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ description: 'URL link to a source document - shown as "Go to [appName]" in the Xero app',
+ },
+ ],
+ },
+/* -------------------------------------------------------------------------- */
+/* invoice:update */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Invoice ID',
+ name: 'invoiceId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ description: 'Invoice ID',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Branding Theme ID',
+ name: 'brandingThemeId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getBrandingThemes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Contact ID',
+ name: 'contactId',
+ type: 'string',
+ default: '',
+ description: 'Contact ID',
+ },
+ {
+ displayName: 'Currency',
+ name: 'currency',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getCurrencies',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Currency Rate',
+ name: 'currencyRate',
+ type: 'string',
+ default: '',
+ description: 'The currency rate for a multicurrency invoice. If no rate is specified, the XE.com day rate is used.',
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'dateTime',
+ default: '',
+ description: 'Date invoice was issued - YYYY-MM-DD. If the Date element is not specified it will default to the current date based on the timezone setting of the organisation',
+ },
+ {
+ displayName: 'Due Date',
+ name: 'dueDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date invoice is due - YYYY-MM-DD',
+ },
+ {
+ displayName: 'Expected Payment Date',
+ name: 'expectedPaymentDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Shown on sales invoices (Accounts Receivable) when this has been set',
+ },
+ {
+ displayName: 'Invoice Number',
+ name: 'invoiceNumber',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Line Amount Type',
+ name: 'lineAmountType',
+ type: 'options',
+ options: [
+ {
+ name: 'Exclusive',
+ value: 'Exclusive',
+ description: 'Line items are exclusive of tax',
+ },
+ {
+ name: 'Inclusive',
+ value: 'Inclusive',
+ description: 'Line items are inclusive tax',
+ },
+ {
+ name: 'NoTax',
+ value: 'NoTax',
+ description: 'Line have no tax',
+ },
+ ],
+ default: 'Exclusive',
+ },
+ {
+ displayName: 'Line Items',
+ name: 'lineItemsUi',
+ placeholder: 'Add Line Item',
+ type: 'fixedCollection',
+ default: '',
+ typeOptions: {
+ multipleValues: true,
+ },
+ description: 'Line item data',
+ options: [
+ {
+ name: 'lineItemsValues',
+ displayName: 'Line Item',
+ values: [
+ {
+ displayName: 'Line Item ID',
+ name: 'lineItemId',
+ type: 'string',
+ default: '',
+ description: 'The Xero generated identifier for a LineItem',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'A line item with just a description',
+ },
+ {
+ displayName: 'Quantity',
+ name: 'quantity',
+ type: 'number',
+ default: 1,
+ typeOptions: {
+ minValue: 1,
+ },
+ description: 'LineItem Quantity',
+ },
+ {
+ displayName: 'Unit Amount',
+ name: 'unitAmount',
+ type: 'string',
+ default: '',
+ description: 'Lineitem unit amount. By default, unit amount will be rounded to two decimal places.',
+ },
+ {
+ displayName: 'Item Code',
+ name: 'itemCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getItemCodes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Account Code',
+ name: 'accountCode',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getAccountCodes',
+ loadOptionsDependsOn: [
+ 'organizationId',
+ ],
+ },
+ default: '',
+ },
+ {
+ displayName: 'Tax Type',
+ name: 'taxType',
+ type: 'options',
+ options: [
+ {
+ name: 'Tax on Purchases',
+ value: 'INPUT',
+ },
+ {
+ name: 'Tax Exempt',
+ value: 'NONE',
+ },
+ {
+ name: 'Tax on Sales',
+ value: 'OUTPUT',
+ },
+ {
+ name: 'Sales Tax on Imports ',
+ value: 'GSTONIMPORTS',
+ },
+ ],
+ default: '',
+ required: true,
+ description: 'Tax Type',
+ },
+ {
+ displayName: 'Tax Amount',
+ name: 'taxAmount',
+ type: 'string',
+ default: '',
+ description: 'The tax amount is auto calculated as a percentage of the line amount based on the tax rate.',
+ },
+ {
+ displayName: 'Line Amount',
+ name: 'lineAmount',
+ type: 'string',
+ default: '',
+ description: 'The line amount reflects the discounted price if a DiscountRate has been used',
+ },
+ {
+ displayName: 'Discount Rate',
+ name: 'discountRate',
+ type: 'string',
+ default: '',
+ description: 'Percentage discount or discount amount being applied to a line item. Only supported on ACCREC invoices - ACCPAY invoices and credit notes in Xero do not support discounts',
+ },
+ // {
+ // displayName: 'Tracking',
+ // name: 'trackingUi',
+ // placeholder: 'Add Tracking',
+ // description: 'Any LineItem can have a maximum of 2 TrackingCategory elements.',
+ // type: 'fixedCollection',
+ // typeOptions: {
+ // multipleValues: true,
+ // },
+ // default: {},
+ // options: [
+ // {
+ // name: 'trackingValues',
+ // displayName: 'Tracking',
+ // values: [
+ // {
+ // displayName: 'Name',
+ // name: 'name',
+ // type: 'options',
+ // typeOptions: {
+ // loadOptionsMethod: 'getTrakingCategories',
+ // loadOptionsDependsOn: [
+ // 'organizationId',
+ // ],
+ // },
+ // default: '',
+ // description: 'Name of the tracking category',
+ // },
+ // {
+ // displayName: 'Option',
+ // name: 'option',
+ // type: 'options',
+ // typeOptions: {
+ // loadOptionsMethod: 'getTrakingOptions',
+ // loadOptionsDependsOn: [
+ // '/name',
+ // ],
+ // },
+ // default: '',
+ // description: 'Name of the option',
+ // },
+ // ],
+ // },
+ // ],
+ // },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Planned Payment Date ',
+ name: 'plannedPaymentDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Shown on bills (Accounts Payable) when this has been set',
+ },
+ {
+ displayName: 'Reference',
+ name: 'reference',
+ type: 'string',
+ default: '',
+ description: 'ACCREC only - additional reference number (max length = 255)',
+ },
+ {
+ displayName: 'Send To Contact',
+ name: 'sendToContact',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the invoice in the Xero app should be marked as "sent". This can be set only on invoices that have been approved',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Draft',
+ value: 'DRAFT',
+ },
+ {
+ name: 'Submitted',
+ value: 'SUBMITTED',
+ },
+ {
+ name: 'Authorised',
+ value: 'AUTHORISED',
+ },
+ ],
+ default: 'DRAFT',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ description: 'URL link to a source document - shown as "Go to [appName]" in the Xero app',
+ },
+ ],
+ },
+/* -------------------------------------------------------------------------- */
+/* invoice:get */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'get',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Invoice ID',
+ name: 'invoiceId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'get',
+ ],
+ },
+ },
+ description: 'Invoice ID',
+ },
+/* -------------------------------------------------------------------------- */
+/* invoice:getAll */
+/* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Organization ID',
+ name: 'organizationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTenants',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ required: true,
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'invoice',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Created By My App',
+ name: 'createdByMyApp',
+ type: 'boolean',
+ default: false,
+ description: `When set to true you'll only retrieve Invoices created by your app`,
+ },
+ {
+ displayName: 'Order By',
+ name: 'orderBy',
+ type: 'string',
+ placeholder: 'InvoiceID',
+ default: '',
+ description: 'Order by any element returned',
+ },
+ {
+ displayName: 'Sort Order',
+ name: 'sortOrder',
+ type: 'options',
+ options: [
+ {
+ name: 'Asc',
+ value: 'ASC',
+ },
+ {
+ name: 'Desc',
+ value: 'DESC',
+ },
+ ],
+ default: '',
+ description: 'Sort order',
+ },
+ {
+ displayName: 'Statuses',
+ name: 'statuses',
+ type: 'multiOptions',
+ options: [
+ {
+ name: 'Draft',
+ value: 'DRAFT',
+ },
+ {
+ name: 'Submitted',
+ value: 'SUBMITTED',
+ },
+ {
+ name: 'Authorised',
+ value: 'AUTHORISED',
+ },
+ ],
+ default: [],
+ },
+ {
+ displayName: 'Where',
+ name: 'where',
+ type: 'string',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ placeholder: 'EmailAddress!=null&&EmailAddress.StartsWith("boom")',
+ default: '',
+ description: `The where parameter allows you to filter on endpoints and elements that don't have explicit parameters. Examples Here`,
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Xero/InvoiceInterface.ts b/packages/nodes-base/nodes/Xero/InvoiceInterface.ts
new file mode 100644
index 0000000000..6d6da63fb9
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/InvoiceInterface.ts
@@ -0,0 +1,40 @@
+import {
+ IDataObject,
+ } from 'n8n-workflow';
+
+ export interface ILineItem {
+ Description?: string;
+ Quantity?: string;
+ UnitAmount?: string;
+ ItemCode?: string;
+ AccountCode?: string;
+ LineItemID?: string;
+ TaxType?: string;
+ TaxAmount?: string;
+ LineAmount?: string;
+ DiscountRate?: string;
+ Tracking?: IDataObject[];
+}
+
+export interface IInvoice extends ITenantId {
+ Type?: string;
+ LineItems?: ILineItem[];
+ Contact?: IDataObject;
+ Date?: string;
+ DueDate?: string;
+ LineAmountType?: string;
+ InvoiceNumber?: string;
+ Reference?: string;
+ BrandingThemeID?: string;
+ Url?: string;
+ CurrencyCode?: string;
+ CurrencyRate?: string;
+ Status?: string;
+ SentToContact?: boolean;
+ ExpectedPaymentDate?: string;
+ PlannedPaymentDate?: string;
+}
+
+export interface ITenantId {
+ organizationId?: string;
+}
diff --git a/packages/nodes-base/nodes/Xero/Xero.node.ts b/packages/nodes-base/nodes/Xero/Xero.node.ts
new file mode 100644
index 0000000000..7703bf0c88
--- /dev/null
+++ b/packages/nodes-base/nodes/Xero/Xero.node.ts
@@ -0,0 +1,681 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeTypeDescription,
+ INodeExecutionData,
+ INodeType,
+ ILoadOptionsFunctions,
+ INodePropertyOptions,
+} from 'n8n-workflow';
+
+import {
+ xeroApiRequest,
+ xeroApiRequestAllItems,
+} from './GenericFunctions';
+
+import {
+ invoiceFields,
+ invoiceOperations
+} from './InvoiceDescription';
+
+import {
+ contactFields,
+ contactOperations,
+} from './ContactDescription';
+
+import {
+ IInvoice,
+ ILineItem,
+} from './InvoiceInterface';
+
+import {
+ IContact,
+ IPhone,
+ IAddress,
+} from './IContactInterface';
+
+export class Xero implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Xero',
+ name: 'xero',
+ icon: 'file:xero.png',
+ group: ['output'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Consume Xero API',
+ defaults: {
+ name: 'Xero',
+ color: '#13b5ea',
+ },
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ // 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 {
+ 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++) {
+ 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.Type = phoneValue.type 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.Type = 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.Type = phoneValue.type 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.Type = 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);
+ }
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/Xero/xero.png b/packages/nodes-base/nodes/Xero/xero.png
new file mode 100644
index 0000000000..a9d46c10aa
Binary files /dev/null and b/packages/nodes-base/nodes/Xero/xero.png differ
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 27151efcda..159fdd8cd0 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -143,6 +143,7 @@
"dist/credentials/WebflowOAuth2Api.credentials.js",
"dist/credentials/WooCommerceApi.credentials.js",
"dist/credentials/WordpressApi.credentials.js",
+ "dist/credentials/XeroOAuth2Api.credentials.js",
"dist/credentials/ZendeskApi.credentials.js",
"dist/credentials/ZendeskOAuth2Api.credentials.js",
"dist/credentials/ZohoOAuth2Api.credentials.js",
@@ -305,6 +306,7 @@
"dist/nodes/WooCommerce/WooCommerce.node.js",
"dist/nodes/WooCommerce/WooCommerceTrigger.node.js",
"dist/nodes/WriteBinaryFile.node.js",
+ "dist/nodes/Xero/Xero.node.js",
"dist/nodes/Xml.node.js",
"dist/nodes/Zendesk/Zendesk.node.js",
"dist/nodes/Zendesk/ZendeskTrigger.node.js",