mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
Merge branch 'master' of https://github.com/n8n-io/n8n
This commit is contained in:
commit
52b5eb4a1e
25
docker/compose/subfolderWithSSL/.env
Normal file
25
docker/compose/subfolderWithSSL/.env
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Folder where data should be saved
|
||||
DATA_FOLDER=/root/n8n/
|
||||
|
||||
# The top level domain to serve from
|
||||
DOMAIN_NAME=example.com
|
||||
|
||||
# The subfolder to serve from
|
||||
SUBFOLDER=app1
|
||||
N8N_PATH=/app1/
|
||||
|
||||
# DOMAIN_NAME and SUBDOMAIN combined decide where n8n will be reachable from
|
||||
# above example would result in: https://example.com/n8n/
|
||||
|
||||
# The user name to use for autentication - IMPORTANT ALWAYS CHANGE!
|
||||
N8N_BASIC_AUTH_USER=user
|
||||
|
||||
# The password to use for autentication - IMPORTANT ALWAYS CHANGE!
|
||||
N8N_BASIC_AUTH_PASSWORD=password
|
||||
|
||||
# Optional timezone to set which gets used by Cron-Node by default
|
||||
# If not set New York time will be used
|
||||
GENERIC_TIMEZONE=Europe/Berlin
|
||||
|
||||
# The email address to use for the SSL certificate creation
|
||||
SSL_EMAIL=user@example.com
|
26
docker/compose/subfolderWithSSL/README.md
Normal file
26
docker/compose/subfolderWithSSL/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# n8n on Subfolder with SSL
|
||||
|
||||
Starts n8n and deployes it on a subfolder
|
||||
|
||||
|
||||
## Start
|
||||
|
||||
To start n8n in a subfolder simply start docker-compose by executing the following
|
||||
command in the current folder.
|
||||
|
||||
|
||||
**IMPORTANT:** But before you do that change the default users and passwords in the `.env` file!
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To stop it execute:
|
||||
|
||||
```
|
||||
docker-compose stop
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The default name of the database, user and password for MongoDB can be changed in the `.env` file in the current directory.
|
57
docker/compose/subfolderWithSSL/docker-compose.yml
Normal file
57
docker/compose/subfolderWithSSL/docker-compose.yml
Normal file
|
@ -0,0 +1,57 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik"
|
||||
command:
|
||||
- "--api=true"
|
||||
- "--api.insecure=true"
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
|
||||
- "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
|
||||
- /home/jan/www/n8n/n8n:/data
|
||||
ports:
|
||||
- "443:443"
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ${DATA_FOLDER}/letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
n8n:
|
||||
image: n8nio/n8n
|
||||
ports:
|
||||
- "127.0.0.1:5678:5678"
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.n8n.rule=Host(`${DOMAIN_NAME}`)
|
||||
- traefik.http.routers.n8n.tls=true
|
||||
- traefik.http.routers.n8n.entrypoints=websecure
|
||||
- "traefik.http.routers.n8n.rule=PathPrefix(`/${SUBFOLDER}{regex:$$|/.*}`)"
|
||||
- "traefik.http.middlewares.n8n-stripprefix.stripprefix.prefixes=/${SUBFOLDER}"
|
||||
- "traefik.http.routers.n8n.middlewares=n8n-stripprefix"
|
||||
- traefik.http.routers.n8n.tls.certresolver=mytlschallenge
|
||||
- traefik.http.middlewares.n8n.headers.SSLRedirect=true
|
||||
- traefik.http.middlewares.n8n.headers.STSSeconds=315360000
|
||||
- traefik.http.middlewares.n8n.headers.browserXSSFilter=true
|
||||
- traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
|
||||
- traefik.http.middlewares.n8n.headers.forceSTSHeader=true
|
||||
- traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME}
|
||||
- traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
|
||||
- traefik.http.middlewares.n8n.headers.STSPreload=true
|
||||
environment:
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER
|
||||
- N8N_BASIC_AUTH_PASSWORD
|
||||
- N8N_HOST=${DOMAIN_NAME}
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=https
|
||||
- NODE_ENV=production
|
||||
- N8N_PATH
|
||||
- WEBHOOK_TUNNEL_URL=http://${DOMAIN_NAME}${N8N_PATH}
|
||||
- VUE_APP_URL_BASE_API=http://${DOMAIN_NAME}${N8N_PATH}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${DATA_FOLDER}/.n8n:/root/.n8n
|
|
@ -161,8 +161,8 @@ const config = convict({
|
|||
|
||||
// If a workflow executes all the data gets saved by default. This
|
||||
// could be a problem when a workflow gets executed a lot and processes
|
||||
// a lot of data. To not write the database full it is possible to
|
||||
// not save the execution at all.
|
||||
// a lot of data. To not exceed the database's capacity it is possible to
|
||||
// prune the database regularly or to not save the execution at all.
|
||||
// Depending on if the execution did succeed or error a different
|
||||
// save behaviour can be set.
|
||||
saveDataOnError: {
|
||||
|
@ -188,6 +188,27 @@ const config = convict({
|
|||
default: false,
|
||||
env: 'EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS'
|
||||
},
|
||||
|
||||
// To not exceed the database's capacity and keep its size moderate
|
||||
// the execution data gets pruned regularly (default: 1 hour interval).
|
||||
// All saved execution data older than the max age will be deleted.
|
||||
// Pruning is currently not activated by default, which will change in
|
||||
// a future version.
|
||||
pruneData: {
|
||||
doc: 'Delete data of past executions on a rolling basis',
|
||||
default: false,
|
||||
env: 'EXECUTIONS_DATA_PRUNE'
|
||||
},
|
||||
pruneDataMaxAge: {
|
||||
doc: 'How old (hours) the execution data has to be to get deleted',
|
||||
default: 336,
|
||||
env: 'EXECUTIONS_DATA_MAX_AGE'
|
||||
},
|
||||
pruneDataTimeout: {
|
||||
doc: 'Timeout (seconds) after execution data has been pruned',
|
||||
default: 3600,
|
||||
env: 'EXECUTIONS_DATA_PRUNE_TIMEOUT'
|
||||
},
|
||||
},
|
||||
|
||||
generic: {
|
||||
|
|
|
@ -40,11 +40,12 @@ export function getBaseUrl(): string {
|
|||
const protocol = config.get('protocol') as string;
|
||||
const host = config.get('host') as string;
|
||||
const port = config.get('port') as number;
|
||||
const path = config.get('path') as string;
|
||||
|
||||
if (protocol === 'http' && port === 80 || protocol === 'https' && port === 443) {
|
||||
return `${protocol}://${host}/`;
|
||||
return `${protocol}://${host}${path}`;
|
||||
}
|
||||
return `${protocol}://${host}:${port}/`;
|
||||
return `${protocol}://${host}:${port}${path}`;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -931,7 +931,8 @@ class App {
|
|||
// Authorize OAuth Data
|
||||
this.app.get(`/${this.restEndpoint}/oauth1-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<string> => {
|
||||
if (req.query.id === undefined) {
|
||||
throw new Error('Required credential id is missing!');
|
||||
res.status(500).send('Required credential id is missing!');
|
||||
return '';
|
||||
}
|
||||
|
||||
const result = await Db.collections.Credentials!.findOne(req.query.id as string);
|
||||
|
@ -943,7 +944,8 @@ class App {
|
|||
let encryptionKey = undefined;
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
throw new Error('No encryption key got found to decrypt the credentials!');
|
||||
res.status(500).send('No encryption key got found to decrypt the credentials!');
|
||||
return '';
|
||||
}
|
||||
|
||||
// Decrypt the currently saved credentials
|
||||
|
@ -1015,7 +1017,8 @@ class App {
|
|||
const { oauth_verifier, oauth_token, cid } = req.query;
|
||||
|
||||
if (oauth_verifier === undefined || oauth_token === undefined) {
|
||||
throw new Error('Insufficient parameters for OAuth1 callback');
|
||||
const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth1 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503);
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
const result = await Db.collections.Credentials!.findOne(cid as any); // tslint:disable-line:no-any
|
||||
|
@ -1085,7 +1088,8 @@ class App {
|
|||
// Authorize OAuth Data
|
||||
this.app.get(`/${this.restEndpoint}/oauth2-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<string> => {
|
||||
if (req.query.id === undefined) {
|
||||
throw new Error('Required credential id is missing!');
|
||||
res.status(500).send('Required credential id is missing.');
|
||||
return '';
|
||||
}
|
||||
|
||||
const result = await Db.collections.Credentials!.findOne(req.query.id as string);
|
||||
|
@ -1097,7 +1101,8 @@ class App {
|
|||
let encryptionKey = undefined;
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
throw new Error('No encryption key got found to decrypt the credentials!');
|
||||
res.status(500).send('No encryption key got found to decrypt the credentials!');
|
||||
return '';
|
||||
}
|
||||
|
||||
// Decrypt the currently saved credentials
|
||||
|
@ -1161,7 +1166,8 @@ class App {
|
|||
const {code, state: stateEncoded } = req.query;
|
||||
|
||||
if (code === undefined || stateEncoded === undefined) {
|
||||
throw new Error('Insufficient parameters for OAuth2 callback');
|
||||
const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth2 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503);
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
let state;
|
||||
|
@ -1211,17 +1217,20 @@ class App {
|
|||
},
|
||||
};
|
||||
}
|
||||
const redirectUri = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`;
|
||||
|
||||
const oAuthObj = new clientOAuth2({
|
||||
clientId: _.get(oauthCredentials, 'clientId') as string,
|
||||
clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string,
|
||||
accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string,
|
||||
authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string,
|
||||
redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`,
|
||||
redirectUri,
|
||||
scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',')
|
||||
});
|
||||
|
||||
const oauthToken = await oAuthObj.code.getToken(req.originalUrl, options);
|
||||
const queryParameters = req.originalUrl.split('?').splice(1, 1).join('');
|
||||
|
||||
const oauthToken = await oAuthObj.code.getToken(`${redirectUri}?${queryParameters}`, options);
|
||||
|
||||
if (oauthToken === undefined) {
|
||||
const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404);
|
||||
|
|
|
@ -41,6 +41,8 @@ import {
|
|||
|
||||
import * as config from '../config';
|
||||
|
||||
import { LessThanOrEqual } from "typeorm";
|
||||
|
||||
|
||||
/**
|
||||
* Checks if there was an error and if errorWorkflow is defined. If so it collects
|
||||
|
@ -79,6 +81,30 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes Saved Execution which are older than configured.
|
||||
* Throttled to be executed just once in configured timeframe.
|
||||
*
|
||||
*/
|
||||
let throttling = false;
|
||||
function pruneExecutionData(): void {
|
||||
if (!throttling) {
|
||||
throttling = true;
|
||||
const timeout = config.get('executions.pruneDataTimeout') as number; // in seconds
|
||||
const maxAge = config.get('executions.pruneDataMaxAge') as number; // in h
|
||||
const date = new Date(); // today
|
||||
date.setHours(date.getHours() - maxAge);
|
||||
|
||||
// throttle just on success to allow for self healing on failure
|
||||
Db.collections.Execution!.delete({ stoppedAt: LessThanOrEqual(date.toISOString()) })
|
||||
.then(data =>
|
||||
setTimeout(() => {
|
||||
throttling = false;
|
||||
}, timeout * 1000)
|
||||
).catch(err => throttling = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pushes the execution out to all connected clients
|
||||
|
@ -189,6 +215,11 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
|
||||
// Prune old execution data
|
||||
if (config.get('executions.pruneData')) {
|
||||
pruneExecutionData();
|
||||
}
|
||||
|
||||
const isManualMode = [this.mode, parentProcessMode].includes('manual');
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# n8n-core
|
||||
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/docs/images/n8n-logo.png)
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png)
|
||||
|
||||
Core components for n8n
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# n8n-editor-ui
|
||||
|
||||
![n8n.io - Workflow Automation](https://n8n.io/n8n-logo.png)
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png)
|
||||
|
||||
The UI to create and update n8n workflows
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<el-menu-item index="logo" class="logo-item">
|
||||
<a href="https://n8n.io" target="_blank" class="logo">
|
||||
<img :src="basePath + '/n8n-icon-small.png'" class="icon" alt="n8n.io"/>
|
||||
<img :src="basePath + 'n8n-icon-small.png'" class="icon" alt="n8n.io"/>
|
||||
<span class="logo-text" slot="title">n8n.io</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# n8n-node-dev
|
||||
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/docs/images/n8n-logo.png)
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png)
|
||||
|
||||
Currently very simple and not very sophisticated CLI which makes it easier
|
||||
to create credentials and nodes in TypeScript for n8n.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# n8n-nodes-base
|
||||
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/docs/images/n8n-logo.png)
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png)
|
||||
|
||||
The nodes which are included by default in n8n
|
||||
|
||||
|
|
51
packages/nodes-base/credentials/XeroOAuth2Api.credentials.ts
Normal file
51
packages/nodes-base/credentials/XeroOAuth2Api.credentials.ts
Normal 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',
|
||||
},
|
||||
];
|
||||
}
|
80
packages/nodes-base/nodes/HackerNews/GenericFunctions.ts
Normal file
80
packages/nodes-base/nodes/HackerNews/GenericFunctions.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
|
||||
/**
|
||||
* Make an API request to HackerNews
|
||||
*
|
||||
* @param {IHookFunctions} this
|
||||
* @param {string} method
|
||||
* @param {string} endpoint
|
||||
* @param {IDataObject} qs
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function hackerNewsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, qs: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
qs,
|
||||
uri: `http://hn.algolia.com/api/v1/${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
||||
if (error.response && error.response.body && error.response.body.error) {
|
||||
// Try to return the error prettier
|
||||
throw new Error(`Hacker News error response [${error.statusCode}]: ${error.response.body.error}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make an API request to HackerNews
|
||||
* and return all results
|
||||
*
|
||||
* @export
|
||||
* @param {(IHookFunctions | IExecuteFunctions)} this
|
||||
* @param {string} method
|
||||
* @param {string} endpoint
|
||||
* @param {IDataObject} qs
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function hackerNewsApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, qs: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
qs.hitsPerPage = 100;
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let itemsReceived = 0;
|
||||
|
||||
do {
|
||||
responseData = await hackerNewsApiRequest.call(this, method, endpoint, qs);
|
||||
returnData.push.apply(returnData, responseData.hits);
|
||||
|
||||
if (returnData !== undefined) {
|
||||
itemsReceived += returnData.length;
|
||||
}
|
||||
|
||||
} while (
|
||||
responseData.nbHits > itemsReceived
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
384
packages/nodes-base/nodes/HackerNews/HackerNews.node.ts
Normal file
384
packages/nodes-base/nodes/HackerNews/HackerNews.node.ts
Normal file
|
@ -0,0 +1,384 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
hackerNewsApiRequest,
|
||||
hackerNewsApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class HackerNews implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Hacker News',
|
||||
name: 'hackerNews',
|
||||
icon: 'file:hackernews.png',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Hacker News API',
|
||||
defaults: {
|
||||
name: 'Hacker News',
|
||||
color: '#ff6600',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
// ----------------------------------
|
||||
// Resources
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
name: 'Article',
|
||||
value: 'article',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'article',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
|
||||
|
||||
// ----------------------------------
|
||||
// Operations
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'all',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all items',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'Operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'article',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a Hacker News article',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'Operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a Hacker News user',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'Operation to perform.',
|
||||
},
|
||||
// ----------------------------------
|
||||
// Fields
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Article ID',
|
||||
name: 'articleId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The ID of the Hacker News article to be returned',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'article',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The Hacker News user to be returned',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results for the query or only up to a limit.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'all',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
description: 'Limit of Hacker News articles to be returned for the query.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'all',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'article',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include comments',
|
||||
name: 'includeComments',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to include all the comments in a Hacker News article.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'all',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Keyword',
|
||||
name: 'keyword',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The keyword for filtering the results of the query.',
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Story',
|
||||
value: 'story',
|
||||
description: 'Returns query results filtered by story tag',
|
||||
},
|
||||
{
|
||||
name: 'Comment',
|
||||
value: 'comment',
|
||||
description: 'Returns query results filtered by comment tag',
|
||||
},
|
||||
{
|
||||
name: 'Poll',
|
||||
value: 'poll',
|
||||
description: 'Returns query results filtered by poll tag',
|
||||
},
|
||||
{
|
||||
name: 'Show HN',
|
||||
value: 'show_hn', // snake case per HN tags
|
||||
description: 'Returns query results filtered by Show HN tag',
|
||||
},
|
||||
{
|
||||
name: 'Ask HN',
|
||||
value: 'ask_hn', // snake case per HN tags
|
||||
description: 'Returns query results filtered by Ask HN tag',
|
||||
},
|
||||
{
|
||||
name: 'Front Page',
|
||||
value: 'front_page', // snake case per HN tags
|
||||
description: 'Returns query results filtered by Front Page tag',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Tags for filtering the results of the query.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
let returnAll = false;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
let qs: IDataObject = {};
|
||||
let endpoint = '';
|
||||
let includeComments = false;
|
||||
|
||||
if (resource === 'all') {
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const keyword = additionalFields.keyword as string;
|
||||
const tags = additionalFields.tags as string[];
|
||||
|
||||
qs = {
|
||||
query: keyword,
|
||||
tags: tags ? tags.join() : '',
|
||||
};
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (!returnAll) {
|
||||
qs.hitsPerPage = this.getNodeParameter('limit', i) as number;
|
||||
}
|
||||
|
||||
endpoint = 'search?';
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation '${operation}' is unknown!`);
|
||||
}
|
||||
} else if (resource === 'article') {
|
||||
|
||||
if (operation === 'get') {
|
||||
|
||||
endpoint = `items/${this.getNodeParameter('articleId', i)}`;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
includeComments = additionalFields.includeComments as boolean;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation '${operation}' is unknown!`);
|
||||
}
|
||||
|
||||
} else if (resource === 'user') {
|
||||
|
||||
if (operation === 'get') {
|
||||
endpoint = `users/${this.getNodeParameter('username', i)}`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation '${operation}' is unknown!`);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`The resource '${resource}' is unknown!`);
|
||||
}
|
||||
|
||||
|
||||
let responseData;
|
||||
if (returnAll === true) {
|
||||
responseData = await hackerNewsApiRequestAllItems.call(this, 'GET', endpoint, qs);
|
||||
} else {
|
||||
responseData = await hackerNewsApiRequest.call(this, 'GET', endpoint, qs);
|
||||
if (resource === 'all' && operation === 'getAll') {
|
||||
responseData = responseData.hits;
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'article' && operation === 'get' && !includeComments) {
|
||||
delete responseData.children;
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/HackerNews/hackernews.png
Normal file
BIN
packages/nodes-base/nodes/HackerNews/hackernews.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -113,7 +113,9 @@ export class Uplead implements INodeType {
|
|||
if (Array.isArray(responseData.data)) {
|
||||
returnData.push.apply(returnData, responseData.data as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData.data as IDataObject);
|
||||
if (responseData.data !== null) {
|
||||
returnData.push(responseData.data as IDataObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
|
|
838
packages/nodes-base/nodes/Xero/ContactDescription.ts
Normal file
838
packages/nodes-base/nodes/Xero/ContactDescription.ts
Normal 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[];
|
76
packages/nodes-base/nodes/Xero/GenericFunctions.ts
Normal file
76
packages/nodes-base/nodes/Xero/GenericFunctions.ts
Normal 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;
|
||||
}
|
44
packages/nodes-base/nodes/Xero/IContactInterface.ts
Normal file
44
packages/nodes-base/nodes/Xero/IContactInterface.ts
Normal 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;
|
||||
}
|
983
packages/nodes-base/nodes/Xero/InvoiceDescription.ts
Normal file
983
packages/nodes-base/nodes/Xero/InvoiceDescription.ts
Normal 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[];
|
40
packages/nodes-base/nodes/Xero/InvoiceInterface.ts
Normal file
40
packages/nodes-base/nodes/Xero/InvoiceInterface.ts
Normal 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;
|
||||
}
|
681
packages/nodes-base/nodes/Xero/Xero.node.ts
Normal file
681
packages/nodes-base/nodes/Xero/Xero.node.ts
Normal 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)];
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/Xero/xero.png
Normal file
BIN
packages/nodes-base/nodes/Xero/xero.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -37,8 +37,8 @@
|
|||
"dist/credentials/BannerbearApi.credentials.js",
|
||||
"dist/credentials/BitbucketApi.credentials.js",
|
||||
"dist/credentials/BitlyApi.credentials.js",
|
||||
"dist/credentials/CircleCiApi.credentials.js",
|
||||
"dist/credentials/ChargebeeApi.credentials.js",
|
||||
"dist/credentials/CircleCiApi.credentials.js",
|
||||
"dist/credentials/ClearbitApi.credentials.js",
|
||||
"dist/credentials/ClickUpApi.credentials.js",
|
||||
"dist/credentials/ClockifyApi.credentials.js",
|
||||
|
@ -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",
|
||||
|
@ -171,9 +172,9 @@
|
|||
"dist/nodes/Bitbucket/BitbucketTrigger.node.js",
|
||||
"dist/nodes/Bitly/Bitly.node.js",
|
||||
"dist/nodes/Calendly/CalendlyTrigger.node.js",
|
||||
"dist/nodes/CircleCi/CircleCi.node.js",
|
||||
"dist/nodes/Chargebee/Chargebee.node.js",
|
||||
"dist/nodes/Chargebee/ChargebeeTrigger.node.js",
|
||||
"dist/nodes/CircleCi/CircleCi.node.js",
|
||||
"dist/nodes/Clearbit/Clearbit.node.js",
|
||||
"dist/nodes/ClickUp/ClickUp.node.js",
|
||||
"dist/nodes/ClickUp/ClickUpTrigger.node.js",
|
||||
|
@ -212,6 +213,7 @@
|
|||
"dist/nodes/Google/Task/GoogleTasks.node.js",
|
||||
"dist/nodes/GraphQL/GraphQL.node.js",
|
||||
"dist/nodes/Gumroad/GumroadTrigger.node.js",
|
||||
"dist/nodes/HackerNews/HackerNews.node.js",
|
||||
"dist/nodes/Harvest/Harvest.node.js",
|
||||
"dist/nodes/HelpScout/HelpScout.node.js",
|
||||
"dist/nodes/HelpScout/HelpScoutTrigger.node.js",
|
||||
|
@ -304,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",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# n8n-workflow
|
||||
|
||||
![n8n.io - Workflow Automation](https://n8n.io/n8n-logo.png)
|
||||
![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png)
|
||||
|
||||
Workflow base code for n8n
|
||||
|
||||
|
|
Loading…
Reference in a new issue