n8n-nodes-perfexcrm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OBS Technologies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # n8n-nodes-perfexcrm
2
+
3
+ This is an n8n community node. It lets you use PerfexCRM in your n8n workflows.
4
+
5
+ PerfexCRM is a powerful customer relationship management system. This node allows you to interact with the PerfexCRM API and receive webhooks for real-time events.
6
+
7
+ [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
8
+
9
+ ## Installation
10
+
11
+ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
12
+
13
+ ### Manual Installation
14
+
15
+ 1. Clone or download this repository
16
+ 2. In your n8n installation folder, navigate to `~/.n8n/nodes/`
17
+ 3. Create a folder called `n8n-nodes-perfexcrm`
18
+ 4. Copy all files from this repository into that folder
19
+ 5. Build the node:
20
+ ```bash
21
+ cd ~/.n8n/nodes/n8n-nodes-perfexcrm
22
+ npm install
23
+ npm run build
24
+ ```
25
+ 6. Restart n8n
26
+
27
+ ## Operations
28
+
29
+ ### PerfexCRM Node
30
+
31
+ This node allows you to perform CRUD operations on various PerfexCRM resources:
32
+
33
+ #### Customers
34
+ - Create a new customer
35
+ - Get a customer by ID
36
+ - Get all customers with filters
37
+ - Update a customer
38
+ - Delete a customer
39
+
40
+ #### Tickets
41
+ - Create a new ticket
42
+ - Get a ticket by ID
43
+ - Get all tickets with filters
44
+ - Update a ticket
45
+ - Delete a ticket
46
+ - Add a reply to a ticket
47
+
48
+ #### Invoices
49
+ - Create a new invoice
50
+ - Get an invoice by ID
51
+ - Get all invoices with filters
52
+
53
+ #### Leads
54
+ - Create a new lead
55
+ - Get a lead by ID
56
+ - Convert a lead to customer
57
+
58
+ #### Projects
59
+ - Create a new project
60
+ - Get a project by ID
61
+
62
+ #### Contracts
63
+ - Create a new contract
64
+ - Get a contract by ID
65
+
66
+ ### PerfexCRM Trigger Node
67
+
68
+ This trigger node listens for webhooks from PerfexCRM and starts workflows when events occur:
69
+
70
+ #### Supported Events
71
+ - Customer events (created, updated, deleted)
72
+ - Contact events (created, updated, deleted)
73
+ - Lead events (created, updated, converted, deleted)
74
+ - Invoice events (created, updated, paid, overdue, deleted)
75
+ - Payment events (recorded, failed)
76
+ - Proposal events (created, sent, accepted, declined)
77
+ - Estimate events (created, sent, accepted, declined, converted)
78
+ - Contract events (created, signed, expiring, expired)
79
+ - Project events (created, updated, completed)
80
+ - Task events (created, updated, completed, comment added)
81
+ - Ticket events (created, updated, status changed, reply added, assigned, closed)
82
+ - Staff events (created, login)
83
+ - Expense events (created, updated)
84
+
85
+ ## Credentials
86
+
87
+ You'll need to enter the following credentials to use this node:
88
+
89
+ 1. **Base URL**: The URL of your PerfexCRM installation (e.g., `https://your-perfex.com`)
90
+ 2. **API Key**: Your PerfexCRM API key (starts with `pk_`)
91
+ 3. **API Version**: The API version to use (currently only `v1` is supported)
92
+
93
+ ### Getting your API Key
94
+
95
+ 1. Log in to your PerfexCRM admin panel
96
+ 2. Navigate to **Setup** → **API & Webhooks**
97
+ 3. Click on **API Keys**
98
+ 4. Create a new API key with the appropriate permissions
99
+ 5. Copy the API key (you'll only see it once!)
100
+
101
+ ## Compatibility
102
+
103
+ - Requires n8n version 0.180.0 or later
104
+ - Tested with PerfexCRM 2.3.x and later
105
+
106
+ ## Resources
107
+
108
+ * [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
109
+ * [PerfexCRM API Documentation](https://your-perfex.com/admin/api_webhooks/documentation)
110
+
111
+ ## Support
112
+
113
+ For issues specific to this node, please create an issue in this repository.
114
+
115
+ For general n8n support, visit the [n8n community forum](https://community.n8n.io/).
116
+
117
+ ## License
118
+
119
+ [MIT](https://github.com/your-org/n8n-nodes-perfexcrm/blob/master/LICENSE.md)
@@ -0,0 +1,9 @@
1
+ import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class PerfexCrmApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PerfexCrmApi = void 0;
4
+ class PerfexCrmApi {
5
+ constructor() {
6
+ this.name = 'perfexCrmApi';
7
+ this.displayName = 'PerfexCRM API';
8
+ this.documentationUrl = 'https://github.com/your-org/n8n-nodes-perfexcrm';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Base URL',
12
+ name: 'baseUrl',
13
+ type: 'string',
14
+ default: '',
15
+ placeholder: 'https://your-perfex-instance.com',
16
+ required: true,
17
+ description: 'The base URL of your PerfexCRM instance',
18
+ },
19
+ {
20
+ displayName: 'API Key',
21
+ name: 'apiKey',
22
+ type: 'string',
23
+ typeOptions: {
24
+ password: true,
25
+ },
26
+ default: '',
27
+ required: true,
28
+ description: 'Your PerfexCRM API key (starts with pk_)',
29
+ },
30
+ {
31
+ displayName: 'API Version',
32
+ name: 'apiVersion',
33
+ type: 'options',
34
+ options: [
35
+ {
36
+ name: 'V1',
37
+ value: 'v1',
38
+ },
39
+ ],
40
+ default: 'v1',
41
+ description: 'The API version to use',
42
+ },
43
+ ];
44
+ this.authenticate = {
45
+ type: 'generic',
46
+ properties: {
47
+ headers: {
48
+ 'X-API-KEY': '={{$credentials.apiKey}}',
49
+ },
50
+ },
51
+ };
52
+ this.test = {
53
+ request: {
54
+ baseURL: '={{$credentials.baseUrl}}',
55
+ url: '/api/{{$credentials.apiVersion}}/customers?limit=1',
56
+ method: 'GET',
57
+ },
58
+ rules: [
59
+ {
60
+ type: 'responseSuccessBody',
61
+ properties: {
62
+ key: 'success',
63
+ value: true,
64
+ message: 'Authentication successful!',
65
+ },
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ }
71
+ exports.PerfexCrmApi = PerfexCrmApi;
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class PerfexCrm implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,409 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PerfexCrm = void 0;
4
+ const CustomerDescription_1 = require("./descriptions/CustomerDescription");
5
+ const TicketDescription_1 = require("./descriptions/TicketDescription");
6
+ const InvoiceDescription_1 = require("./descriptions/InvoiceDescription");
7
+ const LeadDescription_1 = require("./descriptions/LeadDescription");
8
+ const ProjectDescription_1 = require("./descriptions/ProjectDescription");
9
+ const ContractDescription_1 = require("./descriptions/ContractDescription");
10
+ class PerfexCrm {
11
+ constructor() {
12
+ this.description = {
13
+ displayName: 'PerfexCRM',
14
+ name: 'perfexCrm',
15
+ icon: 'file:perfexcrm.svg',
16
+ group: ['transform'],
17
+ version: 1,
18
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
19
+ description: 'Interact with PerfexCRM API',
20
+ defaults: {
21
+ name: 'PerfexCRM',
22
+ },
23
+ inputs: ["main" /* NodeConnectionType.Main */],
24
+ outputs: ["main" /* NodeConnectionType.Main */],
25
+ credentials: [
26
+ {
27
+ name: 'perfexCrmApi',
28
+ required: true,
29
+ },
30
+ ],
31
+ properties: [
32
+ {
33
+ displayName: 'Resource',
34
+ name: 'resource',
35
+ type: 'options',
36
+ noDataExpression: true,
37
+ options: [
38
+ {
39
+ name: 'Customer',
40
+ value: 'customer',
41
+ },
42
+ {
43
+ name: 'Ticket',
44
+ value: 'ticket',
45
+ },
46
+ {
47
+ name: 'Invoice',
48
+ value: 'invoice',
49
+ },
50
+ {
51
+ name: 'Lead',
52
+ value: 'lead',
53
+ },
54
+ {
55
+ name: 'Project',
56
+ value: 'project',
57
+ },
58
+ {
59
+ name: 'Contract',
60
+ value: 'contract',
61
+ },
62
+ ],
63
+ default: 'customer',
64
+ },
65
+ ...CustomerDescription_1.customerOperations,
66
+ ...CustomerDescription_1.customerFields,
67
+ ...TicketDescription_1.ticketOperations,
68
+ ...TicketDescription_1.ticketFields,
69
+ ...InvoiceDescription_1.invoiceOperations,
70
+ ...InvoiceDescription_1.invoiceFields,
71
+ ...LeadDescription_1.leadOperations,
72
+ ...LeadDescription_1.leadFields,
73
+ ...ProjectDescription_1.projectOperations,
74
+ ...ProjectDescription_1.projectFields,
75
+ ...ContractDescription_1.contractOperations,
76
+ ...ContractDescription_1.contractFields,
77
+ ],
78
+ };
79
+ }
80
+ async execute() {
81
+ const items = this.getInputData();
82
+ const returnData = [];
83
+ const credentials = await this.getCredentials('perfexCrmApi');
84
+ const resource = this.getNodeParameter('resource', 0);
85
+ const operation = this.getNodeParameter('operation', 0);
86
+ let responseData;
87
+ const baseUrl = credentials.baseUrl;
88
+ const apiVersion = credentials.apiVersion;
89
+ for (let i = 0; i < items.length; i++) {
90
+ try {
91
+ if (resource === 'customer') {
92
+ if (operation === 'create') {
93
+ const company = this.getNodeParameter('company', i);
94
+ const additionalFields = this.getNodeParameter('additionalFields', i);
95
+ const body = {
96
+ company,
97
+ ...additionalFields,
98
+ };
99
+ responseData = await this.helpers.request({
100
+ method: 'POST',
101
+ url: `${baseUrl}/api/${apiVersion}/customers`,
102
+ body,
103
+ json: true,
104
+ });
105
+ }
106
+ else if (operation === 'get') {
107
+ const customerId = this.getNodeParameter('customerId', i);
108
+ responseData = await this.helpers.request({
109
+ method: 'GET',
110
+ url: `${baseUrl}/api/${apiVersion}/customers/${customerId}`,
111
+ json: true,
112
+ });
113
+ }
114
+ else if (operation === 'getAll') {
115
+ const returnAll = this.getNodeParameter('returnAll', i);
116
+ const filters = this.getNodeParameter('filters', i);
117
+ const qs = {};
118
+ if (!returnAll) {
119
+ qs.limit = this.getNodeParameter('limit', i);
120
+ }
121
+ else {
122
+ qs.limit = 1000;
123
+ }
124
+ Object.assign(qs, filters);
125
+ responseData = await this.helpers.request({
126
+ method: 'GET',
127
+ url: `${baseUrl}/api/${apiVersion}/customers`,
128
+ qs,
129
+ json: true,
130
+ });
131
+ if (responseData.data) {
132
+ responseData = responseData.data;
133
+ }
134
+ }
135
+ else if (operation === 'update') {
136
+ const customerId = this.getNodeParameter('customerId', i);
137
+ const updateFields = this.getNodeParameter('updateFields', i);
138
+ const body = updateFields;
139
+ responseData = await this.helpers.request({
140
+ method: 'PUT',
141
+ url: `${baseUrl}/api/${apiVersion}/customers/${customerId}`,
142
+ body,
143
+ json: true,
144
+ });
145
+ }
146
+ else if (operation === 'delete') {
147
+ const customerId = this.getNodeParameter('customerId', i);
148
+ responseData = await this.helpers.request({
149
+ method: 'DELETE',
150
+ url: `${baseUrl}/api/${apiVersion}/customers/${customerId}`,
151
+ json: true,
152
+ });
153
+ }
154
+ }
155
+ else if (resource === 'ticket') {
156
+ if (operation === 'create') {
157
+ const subject = this.getNodeParameter('subject', i);
158
+ const department = this.getNodeParameter('department', i);
159
+ const additionalFields = this.getNodeParameter('additionalFields', i);
160
+ const body = {
161
+ subject,
162
+ department,
163
+ ...additionalFields,
164
+ };
165
+ responseData = await this.helpers.request({
166
+ method: 'POST',
167
+ url: `${baseUrl}/api/${apiVersion}/tickets`,
168
+ body,
169
+ json: true,
170
+ });
171
+ }
172
+ else if (operation === 'get') {
173
+ const ticketId = this.getNodeParameter('ticketId', i);
174
+ const options = this.getNodeParameter('options', i);
175
+ const qs = {};
176
+ if (options.includeReplies) {
177
+ qs.include = 'replies';
178
+ }
179
+ responseData = await this.helpers.request({
180
+ method: 'GET',
181
+ url: `${baseUrl}/api/${apiVersion}/tickets/${ticketId}`,
182
+ qs,
183
+ json: true,
184
+ });
185
+ }
186
+ else if (operation === 'getAll') {
187
+ const returnAll = this.getNodeParameter('returnAll', i);
188
+ const filters = this.getNodeParameter('filters', i);
189
+ const qs = {};
190
+ if (!returnAll) {
191
+ qs.limit = this.getNodeParameter('limit', i);
192
+ }
193
+ else {
194
+ qs.limit = 1000;
195
+ }
196
+ // Handle status filter
197
+ if (filters.status) {
198
+ qs.status = filters.status;
199
+ }
200
+ if (filters.department) {
201
+ qs.department = filters.department;
202
+ }
203
+ if (filters.priority) {
204
+ qs.priority = filters.priority;
205
+ }
206
+ responseData = await this.helpers.request({
207
+ method: 'GET',
208
+ url: `${baseUrl}/api/${apiVersion}/tickets`,
209
+ qs,
210
+ json: true,
211
+ });
212
+ if (responseData.data) {
213
+ responseData = responseData.data;
214
+ }
215
+ }
216
+ else if (operation === 'update') {
217
+ const ticketId = this.getNodeParameter('ticketId', i);
218
+ const updateFields = this.getNodeParameter('updateFields', i);
219
+ const body = updateFields;
220
+ responseData = await this.helpers.request({
221
+ method: 'PUT',
222
+ url: `${baseUrl}/api/${apiVersion}/tickets/${ticketId}`,
223
+ body,
224
+ json: true,
225
+ });
226
+ }
227
+ else if (operation === 'delete') {
228
+ const ticketId = this.getNodeParameter('ticketId', i);
229
+ responseData = await this.helpers.request({
230
+ method: 'DELETE',
231
+ url: `${baseUrl}/api/${apiVersion}/tickets/${ticketId}`,
232
+ json: true,
233
+ });
234
+ }
235
+ else if (operation === 'addReply') {
236
+ const ticketId = this.getNodeParameter('ticketId', i);
237
+ const message = this.getNodeParameter('message', i);
238
+ const additionalFields = this.getNodeParameter('additionalFields', i);
239
+ const body = {
240
+ message,
241
+ ...additionalFields,
242
+ };
243
+ responseData = await this.helpers.request({
244
+ method: 'POST',
245
+ url: `${baseUrl}/api/${apiVersion}/tickets/${ticketId}/replies`,
246
+ body,
247
+ json: true,
248
+ });
249
+ }
250
+ }
251
+ else if (resource === 'invoice') {
252
+ if (operation === 'create') {
253
+ const clientId = this.getNodeParameter('clientId', i);
254
+ const number = this.getNodeParameter('number', i);
255
+ const date = this.getNodeParameter('date', i);
256
+ const duedate = this.getNodeParameter('duedate', i);
257
+ const additionalFields = this.getNodeParameter('additionalFields', i);
258
+ const body = {
259
+ clientid: clientId,
260
+ number,
261
+ date,
262
+ duedate,
263
+ ...additionalFields,
264
+ };
265
+ responseData = await this.helpers.request({
266
+ method: 'POST',
267
+ url: `${baseUrl}/api/${apiVersion}/invoices`,
268
+ body,
269
+ json: true,
270
+ });
271
+ }
272
+ else if (operation === 'get') {
273
+ const invoiceId = this.getNodeParameter('invoiceId', i);
274
+ responseData = await this.helpers.request({
275
+ method: 'GET',
276
+ url: `${baseUrl}/api/${apiVersion}/invoices/${invoiceId}`,
277
+ json: true,
278
+ });
279
+ }
280
+ else if (operation === 'getAll') {
281
+ const returnAll = this.getNodeParameter('returnAll', i);
282
+ const filters = this.getNodeParameter('filters', i);
283
+ const qs = {};
284
+ if (!returnAll) {
285
+ qs.limit = this.getNodeParameter('limit', i);
286
+ }
287
+ else {
288
+ qs.limit = 1000;
289
+ }
290
+ Object.assign(qs, filters);
291
+ responseData = await this.helpers.request({
292
+ method: 'GET',
293
+ url: `${baseUrl}/api/${apiVersion}/invoices`,
294
+ qs,
295
+ json: true,
296
+ });
297
+ if (responseData.data) {
298
+ responseData = responseData.data;
299
+ }
300
+ }
301
+ }
302
+ else if (resource === 'lead') {
303
+ if (operation === 'create') {
304
+ const name = this.getNodeParameter('name', i);
305
+ const additionalFields = this.getNodeParameter('additionalFields', i);
306
+ const body = {
307
+ name,
308
+ ...additionalFields,
309
+ };
310
+ responseData = await this.helpers.request({
311
+ method: 'POST',
312
+ url: `${baseUrl}/api/${apiVersion}/leads`,
313
+ body,
314
+ json: true,
315
+ });
316
+ }
317
+ else if (operation === 'get') {
318
+ const leadId = this.getNodeParameter('leadId', i);
319
+ responseData = await this.helpers.request({
320
+ method: 'GET',
321
+ url: `${baseUrl}/api/${apiVersion}/leads/${leadId}`,
322
+ json: true,
323
+ });
324
+ }
325
+ else if (operation === 'convert') {
326
+ const leadId = this.getNodeParameter('leadId', i);
327
+ responseData = await this.helpers.request({
328
+ method: 'POST',
329
+ url: `${baseUrl}/api/${apiVersion}/leads/${leadId}/convert`,
330
+ json: true,
331
+ });
332
+ }
333
+ }
334
+ else if (resource === 'project') {
335
+ if (operation === 'create') {
336
+ const name = this.getNodeParameter('name', i);
337
+ const clientId = this.getNodeParameter('clientId', i);
338
+ const additionalFields = this.getNodeParameter('additionalFields', i);
339
+ const body = {
340
+ name,
341
+ clientid: clientId,
342
+ ...additionalFields,
343
+ };
344
+ responseData = await this.helpers.request({
345
+ method: 'POST',
346
+ url: `${baseUrl}/api/${apiVersion}/projects`,
347
+ body,
348
+ json: true,
349
+ });
350
+ }
351
+ else if (operation === 'get') {
352
+ const projectId = this.getNodeParameter('projectId', i);
353
+ responseData = await this.helpers.request({
354
+ method: 'GET',
355
+ url: `${baseUrl}/api/${apiVersion}/projects/${projectId}`,
356
+ json: true,
357
+ });
358
+ }
359
+ }
360
+ else if (resource === 'contract') {
361
+ if (operation === 'create') {
362
+ const subject = this.getNodeParameter('subject', i);
363
+ const client = this.getNodeParameter('client', i);
364
+ const datestart = this.getNodeParameter('datestart', i);
365
+ const dateend = this.getNodeParameter('dateend', i);
366
+ const additionalFields = this.getNodeParameter('additionalFields', i);
367
+ const body = {
368
+ subject,
369
+ client,
370
+ datestart,
371
+ dateend,
372
+ ...additionalFields,
373
+ };
374
+ responseData = await this.helpers.request({
375
+ method: 'POST',
376
+ url: `${baseUrl}/api/${apiVersion}/contracts`,
377
+ body,
378
+ json: true,
379
+ });
380
+ }
381
+ else if (operation === 'get') {
382
+ const contractId = this.getNodeParameter('contractId', i);
383
+ responseData = await this.helpers.request({
384
+ method: 'GET',
385
+ url: `${baseUrl}/api/${apiVersion}/contracts/${contractId}`,
386
+ json: true,
387
+ });
388
+ }
389
+ }
390
+ if (Array.isArray(responseData)) {
391
+ returnData.push(...responseData.map((item) => ({ json: item })));
392
+ }
393
+ else {
394
+ returnData.push({ json: responseData });
395
+ }
396
+ }
397
+ catch (error) {
398
+ if (this.continueOnFail()) {
399
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
400
+ returnData.push({ json: { error: errorMessage } });
401
+ continue;
402
+ }
403
+ throw error;
404
+ }
405
+ }
406
+ return [returnData];
407
+ }
408
+ }
409
+ exports.PerfexCrm = PerfexCrm;
@@ -0,0 +1,12 @@
1
+ import { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class PerfexCrmTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhookMethods: {
5
+ default: {
6
+ checkExists(this: IHookFunctions): Promise<boolean>;
7
+ create(this: IHookFunctions): Promise<boolean>;
8
+ delete(this: IHookFunctions): Promise<boolean>;
9
+ };
10
+ };
11
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
12
+ }