From 5a2c7e00a0ca1a151a7fec56da5f99b086c25b1f Mon Sep 17 00:00:00 2001
From: CodeShakingSheep <19874562+CodeShakingSheep@users.noreply.github.com>
Date: Tue, 17 Sep 2024 04:42:05 -0500
Subject: [PATCH] feat(Invoice Ninja Node): Add actions for bank transactions
(#10389)
---
.../BankTransactionDescription.ts | 213 ++++++++++++++++++
.../InvoiceNinja/BankTransactionInterface.ts | 9 +
.../nodes/InvoiceNinja/InvoiceNinja.node.ts | 179 ++++++++++++++-
3 files changed, 398 insertions(+), 3 deletions(-)
create mode 100644 packages/nodes-base/nodes/InvoiceNinja/BankTransactionDescription.ts
create mode 100644 packages/nodes-base/nodes/InvoiceNinja/BankTransactionInterface.ts
diff --git a/packages/nodes-base/nodes/InvoiceNinja/BankTransactionDescription.ts b/packages/nodes-base/nodes/InvoiceNinja/BankTransactionDescription.ts
new file mode 100644
index 0000000000..b72d88db48
--- /dev/null
+++ b/packages/nodes-base/nodes/InvoiceNinja/BankTransactionDescription.ts
@@ -0,0 +1,213 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const bankTransactionOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a new bank transaction',
+ action: 'Create a bank transaction',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a bank transaction',
+ action: 'Delete a bank transaction',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get data of a bank transaction',
+ action: 'Get a bank transaction',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ description: 'Get data of many bank transactions',
+ action: 'Get many bank transactions',
+ },
+ {
+ name: 'Match Payment',
+ value: 'matchPayment',
+ description: 'Match payment to a bank transaction',
+ action: 'Match payment to a bank transaction',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const bankTransactionFields: INodeProperties[] = [
+ /* -------------------------------------------------------------------------- */
+ /* bankTransaction:create */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['create'],
+ resource: ['bank_transaction'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Amount',
+ name: 'amount',
+ type: 'number',
+ default: 0,
+ },
+ {
+ displayName: 'Bank Integration Name or ID',
+ name: 'bankIntegrationId',
+ type: 'options',
+ description:
+ 'Choose from the list, or specify an ID using an expression',
+ typeOptions: {
+ loadOptionsMethod: 'getBankIntegrations',
+ },
+ default: '',
+ },
+ {
+ displayName: 'Base Type',
+ name: 'baseType',
+ type: 'options',
+ options: [
+ {
+ name: 'Deposit',
+ value: 'CREDIT',
+ },
+ {
+ name: 'Withdrawal',
+ value: 'DEBIT',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'dateTime',
+ default: '',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ },
+ ],
+ },
+ /* -------------------------------------------------------------------------- */
+ /* bankTransaction:delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Bank Transaction ID',
+ name: 'bankTransactionId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['delete'],
+ },
+ },
+ },
+ /* -------------------------------------------------------------------------- */
+ /* bankTransaction:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Bank Transaction ID',
+ name: 'bankTransactionId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['get'],
+ },
+ },
+ },
+ /* -------------------------------------------------------------------------- */
+ /* bankTransaction:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['getAll'],
+ },
+ },
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['getAll'],
+ returnAll: [false],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 60,
+ },
+ default: 50,
+ description: 'Max number of results to return',
+ },
+ /* -------------------------------------------------------------------------- */
+ /* bankTransaction:matchPayment */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Bank Transaction ID',
+ name: 'bankTransactionId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['matchPayment'],
+ },
+ },
+ },
+ {
+ displayName: 'Payment Name or ID',
+ name: 'paymentId',
+ type: 'options',
+ description:
+ 'Choose from the list, or specify an ID using an expression',
+ typeOptions: {
+ loadOptionsMethod: 'getPayments',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: ['bank_transaction'],
+ operation: ['matchPayment'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/InvoiceNinja/BankTransactionInterface.ts b/packages/nodes-base/nodes/InvoiceNinja/BankTransactionInterface.ts
new file mode 100644
index 0000000000..735f339ab8
--- /dev/null
+++ b/packages/nodes-base/nodes/InvoiceNinja/BankTransactionInterface.ts
@@ -0,0 +1,9 @@
+export interface IBankTransaction {
+ amount?: number;
+ bank_integration_id?: number;
+ base_type?: string;
+ date?: string;
+ description?: string;
+ id?: string;
+ paymentId?: string;
+}
diff --git a/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinja.node.ts b/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinja.node.ts
index a23a7ff8c5..fd43eb8e86 100644
--- a/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinja.node.ts
+++ b/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinja.node.ts
@@ -36,6 +36,10 @@ import { quoteFields, quoteOperations } from './QuoteDescription';
import type { IQuote } from './QuoteInterface';
import { isoCountryCodes } from '@utils/ISOCountryCodes';
+import { bankTransactionFields, bankTransactionOperations } from './BankTransactionDescription';
+
+import type { IBankTransaction } from './BankTransactionInterface';
+
export class InvoiceNinja implements INodeType {
description: INodeTypeDescription = {
displayName: 'Invoice Ninja',
@@ -107,6 +111,15 @@ export class InvoiceNinja implements INodeType {
type: 'options',
noDataExpression: true,
options: [
+ {
+ name: 'Bank Transaction',
+ value: 'bank_transaction',
+ displayOptions: {
+ show: {
+ apiVersion: ['v5'],
+ },
+ },
+ },
{
name: 'Client',
value: 'client',
@@ -146,6 +159,8 @@ export class InvoiceNinja implements INodeType {
...expenseFields,
...quoteOperations,
...quoteFields,
+ ...bankTransactionOperations,
+ ...bankTransactionFields,
],
};
@@ -255,6 +270,58 @@ export class InvoiceNinja implements INodeType {
}
return returnData;
},
+ // Get all the available bank integrations to display them to user so that they can
+ // select them easily
+ async getBankIntegrations(this: ILoadOptionsFunctions): Promise {
+ const returnData: INodePropertyOptions[] = [];
+ let banks = await invoiceNinjaApiRequestAllItems.call(
+ this,
+ 'data',
+ 'GET',
+ '/bank_integrations',
+ );
+ banks = banks.filter((e) => !e.is_deleted);
+ for (const bank of banks) {
+ const providerName = bank.provider_name as string;
+ const accountName = bank.bank_account_name as string;
+ const bankId = bank.id as string;
+ returnData.push({
+ name:
+ providerName != accountName
+ ? `${providerName} - ${accountName}`
+ : accountName || providerName,
+ value: bankId,
+ });
+ }
+ return returnData;
+ },
+ // Get all the available users to display them to user so that they can
+ // select them easily
+ async getPayments(this: ILoadOptionsFunctions): Promise {
+ const returnData: INodePropertyOptions[] = [];
+ const qs: IDataObject = {};
+ // Only select payments that can be matched to transactions
+ qs.match_transactions = true;
+ const payments = await invoiceNinjaApiRequestAllItems.call(
+ this,
+ 'data',
+ 'GET',
+ '/payments',
+ {},
+ qs,
+ );
+ for (const payment of payments) {
+ const paymentName = [payment.number, payment.date, payment.amount]
+ .filter((e) => e)
+ .join(' - ');
+ const paymentId = payment.id as string;
+ returnData.push({
+ name: paymentName,
+ value: paymentId,
+ });
+ }
+ return returnData;
+ },
},
};
@@ -858,6 +925,106 @@ export class InvoiceNinja implements INodeType {
responseData = responseData.data;
}
}
+ if (resource === 'bank_transaction') {
+ const resourceEndpoint = '/bank_transactions';
+ if (operation === 'create') {
+ const additionalFields = this.getNodeParameter('additionalFields', i);
+ const body: IBankTransaction = {};
+ if (additionalFields.amount) {
+ body.amount = additionalFields.amount as number;
+ }
+ if (additionalFields.baseType) {
+ body.base_type = additionalFields.baseType as string;
+ }
+ if (additionalFields.bankIntegrationId) {
+ body.bank_integration_id = additionalFields.bankIntegrationId as number;
+ }
+ if (additionalFields.client) {
+ body.date = additionalFields.date as string;
+ }
+ if (additionalFields.email) {
+ body.description = additionalFields.description as string;
+ }
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'POST',
+ resourceEndpoint,
+ body as IDataObject,
+ );
+ responseData = responseData.data;
+ }
+ if (operation === 'get') {
+ const bankTransactionId = this.getNodeParameter('bankTransactionId', i) as string;
+ const options = this.getNodeParameter('options', i);
+ if (options.include) {
+ qs.include = options.include as string;
+ }
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'GET',
+ `${resourceEndpoint}/${bankTransactionId}`,
+ {},
+ qs,
+ );
+ responseData = responseData.data;
+ }
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', 0);
+ const options = this.getNodeParameter('options', i);
+ if (options.include) {
+ qs.include = options.include as string;
+ }
+ if (options.invoiceNumber) {
+ qs.invoice_number = options.invoiceNumber as string;
+ }
+ if (returnAll) {
+ responseData = await invoiceNinjaApiRequestAllItems.call(
+ this,
+ 'data',
+ 'GET',
+ resourceEndpoint,
+ {},
+ qs,
+ );
+ } else {
+ qs.per_page = this.getNodeParameter('limit', 0);
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'GET',
+ resourceEndpoint,
+ {},
+ qs,
+ );
+ responseData = responseData.data;
+ }
+ }
+ if (operation === 'delete') {
+ const bankTransactionId = this.getNodeParameter('bankTransactionId', i) as string;
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'DELETE',
+ `${resourceEndpoint}/${bankTransactionId}`,
+ );
+ responseData = responseData.data;
+ }
+ if (operation === 'matchPayment') {
+ const bankTransactionId = this.getNodeParameter('bankTransactionId', i) as string;
+ const paymentId = this.getNodeParameter('paymentId', i) as string;
+ const body: IBankTransaction = {};
+ if (bankTransactionId) {
+ body.id = bankTransactionId as string;
+ }
+ if (paymentId) {
+ body.paymentId = paymentId as string;
+ }
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'POST',
+ `${resourceEndpoint}/match`,
+ body as IDataObject,
+ );
+ }
+ }
if (resource === 'quote') {
const resourceEndpoint = apiVersion === 'v4' ? '/invoices' : '/quotes';
if (operation === 'create') {
@@ -983,7 +1150,7 @@ export class InvoiceNinja implements INodeType {
responseData = await invoiceNinjaApiRequest.call(
this,
'GET',
- `/quotes/${quoteId}/email`,
+ `${resourceEndpoint}/${quoteId}/email`,
);
}
}
@@ -1016,13 +1183,19 @@ export class InvoiceNinja implements INodeType {
this,
'data',
'GET',
- '/quotes',
+ resourceEndpoint,
{},
qs,
);
} else {
qs.per_page = this.getNodeParameter('limit', 0);
- responseData = await invoiceNinjaApiRequest.call(this, 'GET', '/quotes', {}, qs);
+ responseData = await invoiceNinjaApiRequest.call(
+ this,
+ 'GET',
+ resourceEndpoint,
+ {},
+ qs,
+ );
responseData = responseData.data;
}
}