@pokash/n8n-nodes-optima-rest-api 1.1.8

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) 2025 POKASH.PL Sp. z o. o.
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,152 @@
1
+ # n8n-nodes-optima-rest-api
2
+
3
+ Node n8n do integracji z Comarch Optima ERP poprzez REST API Gateway.
4
+
5
+ ## Instalacja
6
+
7
+ ### Community Node (n8n Cloud lub Self-hosted)
8
+
9
+ W n8n przejdź do **Settings > Community Nodes** i zainstaluj:
10
+
11
+ ```
12
+ n8n-nodes-optima-rest-api
13
+ ```
14
+
15
+ ### Manualna instalacja (Development)
16
+
17
+ 1. Przejdź do folderu z custom nodes:
18
+ ```bash
19
+ cd ~/.n8n/custom
20
+ ```
21
+
22
+ 2. Sklonuj lub skopiuj ten folder
23
+
24
+ 3. Zainstaluj zależności:
25
+ ```bash
26
+ npm install
27
+ ```
28
+
29
+ 4. Zbuduj projekt:
30
+ ```bash
31
+ npm run build
32
+ ```
33
+
34
+ 5. Zrestartuj n8n
35
+
36
+ ## Konfiguracja
37
+
38
+ ### Credentials
39
+
40
+ Utwórz credentials typu **Optima REST API**:
41
+
42
+ - **Gateway URL**: URL adres serwera Gateway (np. `http://localhost:5000`)
43
+ - **Username**: Nazwa użytkownika Optima
44
+ - **Password**: Hasło użytkownika
45
+ - **Company**: Kod firmy w Optima (np. `FIRMA01`)
46
+ - **Modules**: Moduły Optima (np. `KP` dla księgowości, `CDN` dla handlu)
47
+
48
+ ## Dostępne operacje
49
+
50
+ ### Customer (Kontrahent)
51
+
52
+ - **Get**: Pobierz kontrahenta po ID
53
+ - **Get All**: Pobierz wszystkich kontrahentów
54
+ - **Create**: Utwórz nowego kontrahenta
55
+ - **Update**: Zaktualizuj kontrahenta
56
+ - **Delete**: Usuń kontrahenta
57
+
58
+ ### Document (Dokument)
59
+
60
+ - **Create Invoice**: Utwórz fakturę sprzedaży lub zakupu
61
+
62
+ ### Product (Towar)
63
+
64
+ - **Get**: Pobierz towar po ID
65
+ - **Get All**: Pobierz wszystkie towary
66
+
67
+ ### Print (Wydruk)
68
+
69
+ - **Print Document**: Wygeneruj PDF dokumentu
70
+
71
+ ## Przykłady użycia
72
+
73
+ ### Utworzenie kontrahenta
74
+
75
+ ```json
76
+ {
77
+ "akronim": "KLIENT01",
78
+ "nazwa": "Firma Example Sp. z o.o.",
79
+ "nip": "1234567890",
80
+ "ulica": "Główna 1",
81
+ "kod": "00-001",
82
+ "miasto": "Warszawa"
83
+ }
84
+ ```
85
+
86
+ ### Utworzenie faktury sprzedaży
87
+
88
+ ```json
89
+ {
90
+ "platnik": {
91
+ "akronim": "KLIENT01"
92
+ },
93
+ "pozycje": [
94
+ {
95
+ "kod": "PROD01",
96
+ "ilosc": 2,
97
+ "cena": 100.00
98
+ }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ ### Wydruk dokumentu do PDF
104
+
105
+ Parametry:
106
+ - **Document ID**: 123 (ID dokumentu w Optima)
107
+ - **Format ID**: 1 (ID formatu wydruku z Optima)
108
+
109
+ Alternatywnie można użyć **SQL Filter**:
110
+ ```
111
+ TrN_TrnId = 123
112
+ ```
113
+
114
+ ## Development
115
+
116
+ ### Build
117
+
118
+ ```bash
119
+ npm run build
120
+ ```
121
+
122
+ ### Watch mode
123
+
124
+ ```bash
125
+ npm run dev
126
+ ```
127
+
128
+ ### Linting
129
+
130
+ ```bash
131
+ npm run lint
132
+ npm run lintfix
133
+ ```
134
+
135
+ ### Format code
136
+
137
+ ```bash
138
+ npm run format
139
+ ```
140
+
141
+ ## Licencja
142
+
143
+ MIT
144
+
145
+ ## Autor
146
+
147
+ POKASH.PL Sp. z o. o. (hello@pokash.cloud)
148
+
149
+ ## Links
150
+
151
+ - [GitHub Repository](https://github.com/pokash-pl/n8n-nodes-optima-rest-api)
152
+ - [npm Package](https://www.npmjs.com/package/@pokash/n8n-nodes-optima-rest-api)
@@ -0,0 +1,9 @@
1
+ import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class OptimaRestApiCredentials implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OptimaRestApiCredentials = void 0;
4
+ class OptimaRestApiCredentials {
5
+ constructor() {
6
+ this.name = 'optimaRestApiCredentials';
7
+ this.displayName = 'Optima REST API';
8
+ this.documentationUrl = 'https://github.com/yourusername/n8n-nodes-optima-rest-api';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Gateway URL',
12
+ name: 'gatewayUrl',
13
+ type: 'string',
14
+ default: 'http://localhost:5000',
15
+ placeholder: 'http://localhost:5000',
16
+ description: 'URL adres Optima REST API Gateway',
17
+ required: true,
18
+ },
19
+ {
20
+ displayName: 'Username',
21
+ name: 'username',
22
+ type: 'string',
23
+ default: '',
24
+ placeholder: 'admin',
25
+ description: 'Nazwa użytkownika Optima',
26
+ required: true,
27
+ },
28
+ {
29
+ displayName: 'Password',
30
+ name: 'password',
31
+ type: 'string',
32
+ typeOptions: {
33
+ password: true,
34
+ },
35
+ default: '',
36
+ description: 'Hasło użytkownika Optima',
37
+ required: false,
38
+ },
39
+ {
40
+ displayName: 'Company',
41
+ name: 'company',
42
+ type: 'string',
43
+ default: '',
44
+ placeholder: 'FIRMA01',
45
+ description: 'Kod firmy w Optima',
46
+ required: false,
47
+ },
48
+ {
49
+ displayName: 'Modules',
50
+ name: 'modules',
51
+ type: 'string',
52
+ default: 'KP',
53
+ placeholder: 'KP',
54
+ description: 'Moduły Optima (KP - księgowość i płace, CDN - handel)',
55
+ },
56
+ ];
57
+ this.authenticate = {
58
+ type: 'generic',
59
+ properties: {
60
+ headers: {
61
+ Authorization: '={{"Bearer " + $credentials.token}}',
62
+ },
63
+ },
64
+ };
65
+ this.test = {
66
+ request: {
67
+ baseURL: '={{$credentials.gatewayUrl}}',
68
+ url: '/api/account/login',
69
+ method: 'POST',
70
+ body: {
71
+ username: '={{$credentials.username}}',
72
+ password: '={{$credentials.password}}',
73
+ company: '={{$credentials.company}}',
74
+ modules: '={{$credentials.modules}}',
75
+ },
76
+ },
77
+ };
78
+ }
79
+ }
80
+ exports.OptimaRestApiCredentials = OptimaRestApiCredentials;
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class OptimaRestApi implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,690 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OptimaRestApi = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class OptimaRestApi {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Optima REST API',
9
+ name: 'optimaRestApi',
10
+ icon: 'file:optima.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
14
+ description: 'Interact with Comarch Optima ERP via REST API',
15
+ defaults: {
16
+ name: 'Optima REST API',
17
+ },
18
+ inputs: ['main'],
19
+ outputs: ['main'],
20
+ credentials: [
21
+ {
22
+ name: 'optimaRestApiCredentials',
23
+ required: true,
24
+ },
25
+ ],
26
+ properties: [
27
+ {
28
+ displayName: 'Resource',
29
+ name: 'resource',
30
+ type: 'options',
31
+ noDataExpression: true,
32
+ options: [
33
+ {
34
+ name: 'Customer',
35
+ value: 'customer',
36
+ },
37
+ {
38
+ name: 'Dictionary',
39
+ value: 'dictionary',
40
+ },
41
+ {
42
+ name: 'Document',
43
+ value: 'document',
44
+ },
45
+ {
46
+ name: 'Product',
47
+ value: 'product',
48
+ },
49
+ {
50
+ name: 'Print',
51
+ value: 'print',
52
+ },
53
+ ],
54
+ default: 'customer',
55
+ },
56
+ // Customer Operations
57
+ {
58
+ displayName: 'Operation',
59
+ name: 'operation',
60
+ type: 'options',
61
+ noDataExpression: true,
62
+ displayOptions: {
63
+ show: {
64
+ resource: ['customer'],
65
+ },
66
+ },
67
+ options: [
68
+ {
69
+ name: 'Create',
70
+ value: 'create',
71
+ description: 'Create a customer',
72
+ action: 'Create a customer',
73
+ },
74
+ {
75
+ name: 'Get',
76
+ value: 'get',
77
+ description: 'Get a customer',
78
+ action: 'Get a customer',
79
+ },
80
+ {
81
+ name: 'Get All',
82
+ value: 'getAll',
83
+ description: 'Get all customers',
84
+ action: 'Get all customers',
85
+ },
86
+ {
87
+ name: 'Update',
88
+ value: 'update',
89
+ description: 'Update a customer',
90
+ action: 'Update a customer',
91
+ },
92
+ {
93
+ name: 'Delete',
94
+ value: 'delete',
95
+ description: 'Delete a customer',
96
+ action: 'Delete a customer',
97
+ },
98
+ ],
99
+ default: 'get',
100
+ },
101
+ // Customer ID field
102
+ {
103
+ displayName: 'Customer ID',
104
+ name: 'customerId',
105
+ type: 'number',
106
+ required: true,
107
+ displayOptions: {
108
+ show: {
109
+ resource: ['customer'],
110
+ operation: ['update', 'delete'],
111
+ },
112
+ },
113
+ default: 0,
114
+ description: 'The ID of the customer',
115
+ },
116
+ // Customer Get/GetAll - Filter options
117
+ {
118
+ displayName: 'Options',
119
+ name: 'options',
120
+ type: 'collection',
121
+ placeholder: 'Add Option',
122
+ default: {},
123
+ displayOptions: {
124
+ show: {
125
+ resource: ['customer'],
126
+ operation: ['get', 'getAll'],
127
+ },
128
+ },
129
+ options: [
130
+ {
131
+ displayName: 'Customer ID',
132
+ name: 'id',
133
+ type: 'number',
134
+ default: 0,
135
+ description: 'Specific customer ID to retrieve',
136
+ },
137
+ {
138
+ displayName: 'Filter',
139
+ name: 'filter',
140
+ type: 'string',
141
+ default: '',
142
+ placeholder: 'Knt_Kod = \'TEST\'',
143
+ description: 'SQL-like filter string for searching customers (e.g., Knt_Kod = \'ACME\' or Knt_Nip = \'1234567890\')',
144
+ },
145
+ {
146
+ displayName: 'Skip',
147
+ name: 'skip',
148
+ type: 'number',
149
+ default: 0,
150
+ description: 'Number of records to skip (for pagination)',
151
+ },
152
+ {
153
+ displayName: 'Take',
154
+ name: 'take',
155
+ type: 'number',
156
+ default: 100,
157
+ description: 'Maximum number of records to return (for pagination)',
158
+ },
159
+ ],
160
+ },
161
+ // Customer fields for create/update
162
+ {
163
+ displayName: 'Customer Data',
164
+ name: 'customerData',
165
+ type: 'json',
166
+ required: true,
167
+ displayOptions: {
168
+ show: {
169
+ resource: ['customer'],
170
+ operation: ['create', 'update'],
171
+ },
172
+ },
173
+ default: '{\n "akronim": "KLIENT01",\n "nazwa": "Firma Example",\n "nip": "1234567890"\n}',
174
+ description: 'Customer data in JSON format',
175
+ },
176
+ // Dictionary Operations
177
+ {
178
+ displayName: 'Dictionary Type',
179
+ name: 'operation',
180
+ type: 'options',
181
+ noDataExpression: true,
182
+ displayOptions: {
183
+ show: {
184
+ resource: ['dictionary'],
185
+ },
186
+ },
187
+ options: [
188
+ {
189
+ name: 'Payment Methods',
190
+ value: 'paymentMethods',
191
+ description: 'Get payment methods dictionary',
192
+ action: 'Get payment methods',
193
+ },
194
+ {
195
+ name: 'Warehouses',
196
+ value: 'warehouses',
197
+ description: 'Get warehouses dictionary',
198
+ action: 'Get warehouses',
199
+ },
200
+ {
201
+ name: 'Currencies',
202
+ value: 'currencies',
203
+ description: 'Get currencies dictionary',
204
+ action: 'Get currencies',
205
+ },
206
+ {
207
+ name: 'Document Definitions',
208
+ value: 'documentDefinitions',
209
+ description: 'Get document definitions dictionary',
210
+ action: 'Get document definitions',
211
+ },
212
+ {
213
+ name: 'Accounting Categories',
214
+ value: 'accountingCategories',
215
+ description: 'Get accounting categories dictionary',
216
+ action: 'Get accounting categories',
217
+ },
218
+ ],
219
+ default: 'paymentMethods',
220
+ },
221
+ // Document Operations
222
+ {
223
+ displayName: 'Operation',
224
+ name: 'operation',
225
+ type: 'options',
226
+ noDataExpression: true,
227
+ displayOptions: {
228
+ show: {
229
+ resource: ['document'],
230
+ },
231
+ },
232
+ options: [
233
+ {
234
+ name: 'Create Invoice',
235
+ value: 'createInvoice',
236
+ description: 'Create a sales or purchase invoice',
237
+ action: 'Create an invoice',
238
+ },
239
+ ],
240
+ default: 'createInvoice',
241
+ },
242
+ // Document Type
243
+ {
244
+ displayName: 'Document Type',
245
+ name: 'documentType',
246
+ type: 'options',
247
+ displayOptions: {
248
+ show: {
249
+ resource: ['document'],
250
+ operation: ['createInvoice'],
251
+ },
252
+ },
253
+ options: [
254
+ {
255
+ name: 'Sales Invoice',
256
+ value: 'Sale',
257
+ },
258
+ {
259
+ name: 'Purchase Invoice',
260
+ value: 'Purchase',
261
+ },
262
+ ],
263
+ default: 'Sale',
264
+ },
265
+ // Document Data
266
+ {
267
+ displayName: 'Document Data',
268
+ name: 'documentData',
269
+ type: 'json',
270
+ required: true,
271
+ displayOptions: {
272
+ show: {
273
+ resource: ['document'],
274
+ operation: ['createInvoice'],
275
+ },
276
+ },
277
+ default: '{\n "platnik": {\n "akronim": "KLIENT01"\n },\n "pozycje": [\n {\n "kod": "PROD01",\n "ilosc": 1,\n "cena": 100\n }\n ]\n}',
278
+ description: 'Document data in JSON format',
279
+ },
280
+ // Product Operations
281
+ {
282
+ displayName: 'Operation',
283
+ name: 'operation',
284
+ type: 'options',
285
+ noDataExpression: true,
286
+ displayOptions: {
287
+ show: {
288
+ resource: ['product'],
289
+ },
290
+ },
291
+ options: [
292
+ {
293
+ name: 'Get',
294
+ value: 'get',
295
+ description: 'Get a product',
296
+ action: 'Get a product',
297
+ },
298
+ {
299
+ name: 'Get All',
300
+ value: 'getAll',
301
+ description: 'Get all products',
302
+ action: 'Get all products',
303
+ },
304
+ ],
305
+ default: 'get',
306
+ },
307
+ // Product ID
308
+ {
309
+ displayName: 'Product ID',
310
+ name: 'productId',
311
+ type: 'number',
312
+ required: true,
313
+ displayOptions: {
314
+ show: {
315
+ resource: ['product'],
316
+ operation: ['get'],
317
+ },
318
+ },
319
+ default: 0,
320
+ description: 'The ID of the product',
321
+ },
322
+ // Print Operations
323
+ {
324
+ displayName: 'Operation',
325
+ name: 'operation',
326
+ type: 'options',
327
+ noDataExpression: true,
328
+ displayOptions: {
329
+ show: {
330
+ resource: ['print'],
331
+ },
332
+ },
333
+ options: [
334
+ {
335
+ name: 'Print Document',
336
+ value: 'printDocument',
337
+ description: 'Print/export document to PDF',
338
+ action: 'Print a document',
339
+ },
340
+ ],
341
+ default: 'printDocument',
342
+ },
343
+ // Print SQL Filter
344
+ {
345
+ displayName: 'SQL Filter',
346
+ name: 'filtrSQL',
347
+ type: 'string',
348
+ required: true,
349
+ displayOptions: {
350
+ show: {
351
+ resource: ['print'],
352
+ operation: ['printDocument'],
353
+ },
354
+ },
355
+ default: '',
356
+ placeholder: 'TrN_TrnId = 123',
357
+ description: 'SQL filter to select documents',
358
+ },
359
+ // Print Format ID
360
+ {
361
+ displayName: 'Format ID',
362
+ name: 'formatId',
363
+ type: 'number',
364
+ required: true,
365
+ displayOptions: {
366
+ show: {
367
+ resource: ['print'],
368
+ operation: ['printDocument'],
369
+ },
370
+ },
371
+ default: 1,
372
+ description: 'ID of the print format from Optima',
373
+ },
374
+ ],
375
+ };
376
+ }
377
+ async execute() {
378
+ const items = this.getInputData();
379
+ const returnData = [];
380
+ const resource = this.getNodeParameter('resource', 0);
381
+ const operation = this.getNodeParameter('operation', 0);
382
+ // Get credentials
383
+ const credentials = await this.getCredentials('optimaRestApiCredentials');
384
+ const gatewayUrl = credentials.gatewayUrl;
385
+ // First, authenticate and get token
386
+ const loginResponse = await this.helpers.request({
387
+ method: 'POST',
388
+ url: `${gatewayUrl}/api/account/login`,
389
+ body: {
390
+ username: credentials.username,
391
+ password: credentials.password,
392
+ company: credentials.company,
393
+ modules: credentials.modules || 'KP',
394
+ },
395
+ json: true,
396
+ });
397
+ const token = loginResponse.Token;
398
+ for (let i = 0; i < items.length; i++) {
399
+ try {
400
+ if (resource === 'customer') {
401
+ if (operation === 'get' || operation === 'getAll') {
402
+ const options = this.getNodeParameter('options', i, {});
403
+ // Build query parameters (use PascalCase to match C# API)
404
+ const queryParams = new URLSearchParams();
405
+ if (options.id) {
406
+ queryParams.append('Id', String(options.id));
407
+ }
408
+ if (options.filter) {
409
+ queryParams.append('Filter', String(options.filter));
410
+ }
411
+ if (options.skip) {
412
+ queryParams.append('Skip', String(options.skip));
413
+ }
414
+ if (options.take) {
415
+ queryParams.append('Take', String(options.take));
416
+ }
417
+ const queryString = queryParams.toString();
418
+ const url = queryString
419
+ ? `${gatewayUrl}/api/customer?${queryString}`
420
+ : `${gatewayUrl}/api/customer`;
421
+ const response = await this.helpers.request({
422
+ method: 'GET',
423
+ url,
424
+ headers: {
425
+ Authorization: `Bearer ${token}`,
426
+ },
427
+ json: true,
428
+ });
429
+ // API returns { Success: true, Customers: [...], TotalCount: ... }
430
+ const responseData = response;
431
+ // Extract customers array from response
432
+ if (responseData.Customers && Array.isArray(responseData.Customers)) {
433
+ const customers = responseData.Customers;
434
+ customers.forEach((item) => {
435
+ const result = {
436
+ json: item
437
+ };
438
+ if (items[i].binary) {
439
+ result.binary = items[i].binary;
440
+ }
441
+ returnData.push(result);
442
+ });
443
+ }
444
+ else if (responseData.Customers) {
445
+ // Single customer - preserve binary
446
+ const result = {
447
+ json: responseData.Customers
448
+ };
449
+ if (items[i].binary) {
450
+ result.binary = items[i].binary;
451
+ }
452
+ returnData.push(result);
453
+ }
454
+ else {
455
+ // Fallback - return entire response if structure is unexpected
456
+ const result = {
457
+ json: responseData
458
+ };
459
+ if (items[i].binary) {
460
+ result.binary = items[i].binary;
461
+ }
462
+ returnData.push(result);
463
+ }
464
+ }
465
+ else if (operation === 'create') {
466
+ const customerData = JSON.parse(this.getNodeParameter('customerData', i));
467
+ const response = await this.helpers.request({
468
+ method: 'POST',
469
+ url: `${gatewayUrl}/api/customer`,
470
+ headers: {
471
+ Authorization: `Bearer ${token}`,
472
+ },
473
+ body: customerData,
474
+ json: true,
475
+ });
476
+ const result = {
477
+ json: response
478
+ };
479
+ if (items[i].binary) {
480
+ result.binary = items[i].binary;
481
+ }
482
+ returnData.push(result);
483
+ }
484
+ else if (operation === 'update') {
485
+ const customerId = this.getNodeParameter('customerId', i);
486
+ const customerData = JSON.parse(this.getNodeParameter('customerData', i));
487
+ // Add ID to the customer data for PUT request
488
+ customerData.ID = customerId;
489
+ const response = await this.helpers.request({
490
+ method: 'PUT',
491
+ url: `${gatewayUrl}/api/customer`,
492
+ headers: {
493
+ Authorization: `Bearer ${token}`,
494
+ },
495
+ body: customerData,
496
+ json: true,
497
+ });
498
+ const result = {
499
+ json: response
500
+ };
501
+ if (items[i].binary) {
502
+ result.binary = items[i].binary;
503
+ }
504
+ returnData.push(result);
505
+ }
506
+ else if (operation === 'delete') {
507
+ const customerId = this.getNodeParameter('customerId', i);
508
+ await this.helpers.request({
509
+ method: 'DELETE',
510
+ url: `${gatewayUrl}/api/customer/${customerId}`,
511
+ headers: {
512
+ Authorization: `Bearer ${token}`,
513
+ },
514
+ json: true,
515
+ });
516
+ const result = {
517
+ json: { success: true, id: customerId }
518
+ };
519
+ if (items[i].binary) {
520
+ result.binary = items[i].binary;
521
+ }
522
+ returnData.push(result);
523
+ }
524
+ }
525
+ else if (resource === 'document') {
526
+ if (operation === 'createInvoice') {
527
+ const documentType = this.getNodeParameter('documentType', i);
528
+ const documentData = JSON.parse(this.getNodeParameter('documentData', i));
529
+ const response = await this.helpers.request({
530
+ method: 'POST',
531
+ url: `${gatewayUrl}/api/Documents/Invoice/${documentType}`,
532
+ headers: {
533
+ Authorization: `Bearer ${token}`,
534
+ },
535
+ body: documentData,
536
+ json: true,
537
+ });
538
+ const result = {
539
+ json: response
540
+ };
541
+ if (items[i].binary) {
542
+ result.binary = items[i].binary;
543
+ }
544
+ returnData.push(result);
545
+ }
546
+ }
547
+ else if (resource === 'dictionary') {
548
+ // Dictionary operations - map operation names to API endpoints
549
+ const dictionaryEndpoints = {
550
+ paymentMethods: { endpoint: 'Dictionary/PaymentMethods', dataKey: 'PaymentMethods' },
551
+ warehouses: { endpoint: 'Dictionary/Warehouses', dataKey: 'Warehouses' },
552
+ currencies: { endpoint: 'Dictionary/Currencies', dataKey: 'Currencies' },
553
+ documentDefinitions: { endpoint: 'Dictionary/DocumentDefinitions', dataKey: 'DocumentDefinitions' },
554
+ accountingCategories: { endpoint: 'Dictionary/AccountingCategories', dataKey: 'AccountingCategories' },
555
+ };
556
+ const dictConfig = dictionaryEndpoints[operation];
557
+ if (dictConfig) {
558
+ const response = await this.helpers.request({
559
+ method: 'GET',
560
+ url: `${gatewayUrl}/api/${dictConfig.endpoint}`,
561
+ headers: {
562
+ Authorization: `Bearer ${token}`,
563
+ },
564
+ json: true,
565
+ });
566
+ // API returns { Success: true, [DataKey]: [...], TotalCount: ... }
567
+ const responseData = response;
568
+ // Extract dictionary array from response
569
+ if (responseData[dictConfig.dataKey] && Array.isArray(responseData[dictConfig.dataKey])) {
570
+ const dictItems = responseData[dictConfig.dataKey];
571
+ dictItems.forEach((item) => {
572
+ const result = {
573
+ json: item
574
+ };
575
+ if (items[i].binary) {
576
+ result.binary = items[i].binary;
577
+ }
578
+ returnData.push(result);
579
+ });
580
+ }
581
+ else {
582
+ // Fallback - return entire response if structure is unexpected
583
+ const result = {
584
+ json: responseData
585
+ };
586
+ if (items[i].binary) {
587
+ result.binary = items[i].binary;
588
+ }
589
+ returnData.push(result);
590
+ }
591
+ }
592
+ }
593
+ else if (resource === 'product') {
594
+ if (operation === 'get') {
595
+ const productId = this.getNodeParameter('productId', i);
596
+ const response = await this.helpers.request({
597
+ method: 'GET',
598
+ url: `${gatewayUrl}/api/product?id=${productId}`,
599
+ headers: {
600
+ Authorization: `Bearer ${token}`,
601
+ },
602
+ json: true,
603
+ });
604
+ const result = {
605
+ json: response
606
+ };
607
+ if (items[i].binary) {
608
+ result.binary = items[i].binary;
609
+ }
610
+ returnData.push(result);
611
+ }
612
+ else if (operation === 'getAll') {
613
+ const response = await this.helpers.request({
614
+ method: 'GET',
615
+ url: `${gatewayUrl}/api/product`,
616
+ headers: {
617
+ Authorization: `Bearer ${token}`,
618
+ },
619
+ json: true,
620
+ });
621
+ const products = response;
622
+ products.forEach((item) => {
623
+ const result = {
624
+ json: item
625
+ };
626
+ if (items[i].binary) {
627
+ result.binary = items[i].binary;
628
+ }
629
+ returnData.push(result);
630
+ });
631
+ }
632
+ }
633
+ else if (resource === 'print') {
634
+ if (operation === 'printDocument') {
635
+ const filtrSQL = this.getNodeParameter('filtrSQL', i);
636
+ const formatId = this.getNodeParameter('formatId', i);
637
+ const requestBody = {
638
+ FormatId: formatId,
639
+ FiltrSQL: filtrSQL,
640
+ };
641
+ const response = await this.helpers.request({
642
+ method: 'POST',
643
+ url: `${gatewayUrl}/api/Documents/Print`,
644
+ headers: {
645
+ Authorization: `Bearer ${token}`,
646
+ },
647
+ body: requestBody,
648
+ json: false,
649
+ encoding: null, // Get binary data (PDF file)
650
+ });
651
+ // API returns PDF file directly as binary
652
+ const buffer = Buffer.isBuffer(response)
653
+ ? response
654
+ : Buffer.from(response);
655
+ const fileName = `document_${Date.now()}.pdf`;
656
+ returnData.push({
657
+ json: {
658
+ success: true,
659
+ fileName: fileName,
660
+ },
661
+ binary: {
662
+ data: {
663
+ data: buffer.toString('base64'),
664
+ mimeType: 'application/pdf',
665
+ fileName: fileName,
666
+ },
667
+ },
668
+ });
669
+ }
670
+ }
671
+ }
672
+ catch (error) {
673
+ const errorMessage = error instanceof Error ? error.message : String(error);
674
+ if (this.continueOnFail()) {
675
+ const result = {
676
+ json: { error: errorMessage }
677
+ };
678
+ if (items[i].binary) {
679
+ result.binary = items[i].binary;
680
+ }
681
+ returnData.push(result);
682
+ continue;
683
+ }
684
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
685
+ }
686
+ }
687
+ return [returnData];
688
+ }
689
+ }
690
+ exports.OptimaRestApi = OptimaRestApi;
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <rect width="100" height="100" fill="#0066cc" rx="10"/>
3
+ <text x="50" y="65" font-family="Arial, sans-serif" font-size="48" font-weight="bold" fill="white" text-anchor="middle">O</text>
4
+ </svg>
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@pokash/n8n-nodes-optima-rest-api",
3
+ "version": "1.1.8",
4
+ "description": "n8n node for Comarch Optima REST API integration",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "optima",
9
+ "comarch",
10
+ "erp"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://github.com/pokash-pl/n8n-nodes-optima-rest-api",
14
+ "author": {
15
+ "name": "POKASH.PL Sp. z o. o.",
16
+ "email": "hello@pokash.cloud"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/pokash-pl/n8n-nodes-optima-rest-api.git"
21
+ },
22
+ "main": "index.js",
23
+ "scripts": {
24
+ "build": "tsc && gulp build:icons",
25
+ "dev": "tsc --watch",
26
+ "format": "prettier nodes --write",
27
+ "lint": "eslint nodes/**/*.ts package.json",
28
+ "lintfix": "eslint nodes/**/*.ts package.json --fix",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "n8n": {
35
+ "n8nNodesApiVersion": 1,
36
+ "credentials": [
37
+ "dist/credentials/OptimaRestApiCredentials.credentials.js"
38
+ ],
39
+ "nodes": [
40
+ "dist/nodes/OptimaRestApi/OptimaRestApi.node.js"
41
+ ]
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.0.0",
45
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
46
+ "@typescript-eslint/parser": "^6.21.0",
47
+ "eslint": "^8.57.0",
48
+ "eslint-plugin-n8n-nodes-base": "^1.16.0",
49
+ "gulp": "^5.0.0",
50
+ "n8n-workflow": "^2.2.2",
51
+ "prettier": "^3.3.0",
52
+ "typescript": "~5.6.0"
53
+ },
54
+ "peerDependencies": {
55
+ "n8n-workflow": "^2.0.0"
56
+ },
57
+ "overrides": {
58
+ "form-data": "^4.0.4"
59
+ }
60
+ }