Xero Integration (#639)

*  Xero Integration

*  Add contact resource

* 🐛 Small fix
This commit is contained in:
Ricardo Espinoza 2020-07-12 12:12:32 -04:00 committed by GitHub
parent 2ee07c9e02
commit b46161aee7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 2715 additions and 0 deletions

View file

@ -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',
},
];
}

View file

@ -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. <a href="https://developer.xero.com/documentation/api/requests-and-responses#get-modified" target="_blank">Examples Here</a>`,
},
],
},
/* -------------------------------------------------------------------------- */
/* 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[];

View file

@ -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<any> { // 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<any> { // 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;
}

View file

@ -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;
}

View file

@ -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. <a href="https://developer.xero.com/documentation/api/requests-and-responses#get-modified" target="_blank">Examples Here</a>`,
},
],
},
] as INodeProperties[];

View file

@ -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;
}

View file

@ -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<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++) {
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)];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -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",