@pokash/n8n-nodes-optima-rest-api 1.1.21 → 1.2.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.
|
@@ -45,13 +45,50 @@ class OptimaRestApiCredentials {
|
|
|
45
45
|
description: 'Kod firmy w Optima',
|
|
46
46
|
required: false,
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
displayName: 'Login Model',
|
|
50
|
+
name: 'loginModel',
|
|
51
|
+
type: 'options',
|
|
52
|
+
options: [
|
|
53
|
+
{
|
|
54
|
+
name: 'Subscription Model (2026+)',
|
|
55
|
+
value: 'subscription',
|
|
56
|
+
description: 'Use subscription packages (Podstawowy, FinanseBasic, LogistykaBasic, etc.)',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Traditional Modules',
|
|
60
|
+
value: 'modules',
|
|
61
|
+
description: 'Use legacy module codes (KP, KH, FA, MAG, etc.)',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
default: 'subscription',
|
|
65
|
+
description: 'Choose login model based on your Optima version',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
displayName: 'Subscription Packages',
|
|
69
|
+
name: 'subscriptionPackages',
|
|
70
|
+
type: 'string',
|
|
71
|
+
default: 'Podstawowy',
|
|
72
|
+
placeholder: 'Podstawowy,LogistykaBasic,FinanseBasic',
|
|
73
|
+
description: 'Subscription packages (comma-separated): Podstawowy, FinanseBasic, FinansePro, LogistykaBasic, LogistykaPro, RetailBasic, RetailPro, HRBasic, HRPro, DodatekFA, DodatekCRM, DodatekKH, DodatekOBD, DodatekOW',
|
|
74
|
+
displayOptions: {
|
|
75
|
+
show: {
|
|
76
|
+
loginModel: ['subscription'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
48
80
|
{
|
|
49
81
|
displayName: 'Modules',
|
|
50
82
|
name: 'modules',
|
|
51
83
|
type: 'string',
|
|
52
84
|
default: 'KP',
|
|
53
|
-
placeholder: 'KP',
|
|
54
|
-
description: 'Moduły Optima (KP
|
|
85
|
+
placeholder: 'KP,KH,FA,MAG',
|
|
86
|
+
description: 'Moduły Optima (comma-separated): KP, KH, KHP, ST, FA, MAG, PK, PKXL, CRM, ANL, DET, BIU, SRW, ODB, KB, KBP, HA, CRMP',
|
|
87
|
+
displayOptions: {
|
|
88
|
+
show: {
|
|
89
|
+
loginModel: ['modules'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
55
92
|
},
|
|
56
93
|
];
|
|
57
94
|
this.authenticate = {
|
|
@@ -71,7 +108,8 @@ class OptimaRestApiCredentials {
|
|
|
71
108
|
username: '={{$credentials.username}}',
|
|
72
109
|
password: '={{$credentials.password}}',
|
|
73
110
|
company: '={{$credentials.company}}',
|
|
74
|
-
modules: '={{$credentials.modules}}',
|
|
111
|
+
modules: '={{$credentials.loginModel === "modules" ? $credentials.modules : undefined}}',
|
|
112
|
+
subscriptionPackages: '={{$credentials.loginModel === "subscription" ? $credentials.subscriptionPackages : undefined}}',
|
|
75
113
|
},
|
|
76
114
|
},
|
|
77
115
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OptimaRestApi = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const tokenCache = new Map();
|
|
5
6
|
class OptimaRestApi {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.description = {
|
|
@@ -401,22 +402,7 @@ class OptimaRestApi {
|
|
|
401
402
|
],
|
|
402
403
|
default: 'get',
|
|
403
404
|
},
|
|
404
|
-
// Product
|
|
405
|
-
{
|
|
406
|
-
displayName: 'Product ID',
|
|
407
|
-
name: 'productId',
|
|
408
|
-
type: 'number',
|
|
409
|
-
required: true,
|
|
410
|
-
displayOptions: {
|
|
411
|
-
show: {
|
|
412
|
-
resource: ['product'],
|
|
413
|
-
operation: ['get'],
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
default: 0,
|
|
417
|
-
description: 'The ID of the product',
|
|
418
|
-
},
|
|
419
|
-
// Product Options
|
|
405
|
+
// Product Options (for both get and getAll)
|
|
420
406
|
{
|
|
421
407
|
displayName: 'Options',
|
|
422
408
|
name: 'options',
|
|
@@ -426,10 +412,38 @@ class OptimaRestApi {
|
|
|
426
412
|
displayOptions: {
|
|
427
413
|
show: {
|
|
428
414
|
resource: ['product'],
|
|
429
|
-
operation: ['getAll'],
|
|
415
|
+
operation: ['get', 'getAll'],
|
|
430
416
|
},
|
|
431
417
|
},
|
|
432
418
|
options: [
|
|
419
|
+
{
|
|
420
|
+
displayName: 'ID',
|
|
421
|
+
name: 'id',
|
|
422
|
+
type: 'number',
|
|
423
|
+
default: undefined,
|
|
424
|
+
description: 'Product ID to retrieve',
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
displayName: 'Filter',
|
|
428
|
+
name: 'filter',
|
|
429
|
+
type: 'string',
|
|
430
|
+
default: '',
|
|
431
|
+
description: 'SQL-like filter expression (e.g., "Twr_Kod=\'PROD01\'" or "Twr_Nazwa LIKE \'%laptop%\'")',
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
displayName: 'Skip',
|
|
435
|
+
name: 'skip',
|
|
436
|
+
type: 'number',
|
|
437
|
+
default: 0,
|
|
438
|
+
description: 'Number of records to skip (for pagination)',
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
displayName: 'Take',
|
|
442
|
+
name: 'take',
|
|
443
|
+
type: 'number',
|
|
444
|
+
default: 100,
|
|
445
|
+
description: 'Maximum number of records to return (for pagination)',
|
|
446
|
+
},
|
|
433
447
|
{
|
|
434
448
|
displayName: 'Output Mode',
|
|
435
449
|
name: 'outputMode',
|
|
@@ -514,19 +528,44 @@ class OptimaRestApi {
|
|
|
514
528
|
// Get credentials
|
|
515
529
|
const credentials = await this.getCredentials('optimaRestApiCredentials');
|
|
516
530
|
const gatewayUrl = credentials.gatewayUrl;
|
|
517
|
-
//
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
531
|
+
// Create unique cache key based on credentials
|
|
532
|
+
const loginModel = credentials.loginModel || 'subscription';
|
|
533
|
+
const cacheKey = `${gatewayUrl}:${credentials.username}:${credentials.company}:${loginModel}`;
|
|
534
|
+
// Check if we have a valid cached token
|
|
535
|
+
const cached = tokenCache.get(cacheKey);
|
|
536
|
+
const now = Date.now();
|
|
537
|
+
let token;
|
|
538
|
+
if (cached && cached.expiresAt > now) {
|
|
539
|
+
// Token still valid, use from cache
|
|
540
|
+
token = cached.token;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
// Token expired or doesn't exist, authenticate
|
|
544
|
+
// Build login body based on selected model
|
|
545
|
+
const loginBody = {
|
|
522
546
|
username: credentials.username,
|
|
523
547
|
password: credentials.password,
|
|
524
548
|
company: credentials.company,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
549
|
+
};
|
|
550
|
+
if (loginModel === 'subscription') {
|
|
551
|
+
// Subscription model (Optima 2026+)
|
|
552
|
+
loginBody.subscriptionPackages = credentials.subscriptionPackages || 'Podstawowy';
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// Traditional modules model
|
|
556
|
+
loginBody.modules = credentials.modules || 'KP';
|
|
557
|
+
}
|
|
558
|
+
const loginResponse = await this.helpers.request({
|
|
559
|
+
method: 'POST',
|
|
560
|
+
url: `${gatewayUrl}/api/account/login`,
|
|
561
|
+
body: loginBody,
|
|
562
|
+
json: true,
|
|
563
|
+
});
|
|
564
|
+
token = loginResponse.Token;
|
|
565
|
+
// Cache token for 50 minutes (assuming 60 min expiry, refresh 10 min early)
|
|
566
|
+
const expiresAt = now + (50 * 60 * 1000);
|
|
567
|
+
tokenCache.set(cacheKey, { token, expiresAt });
|
|
568
|
+
}
|
|
530
569
|
for (let i = 0; i < items.length; i++) {
|
|
531
570
|
try {
|
|
532
571
|
if (resource === 'customer') {
|
|
@@ -796,40 +835,79 @@ class OptimaRestApi {
|
|
|
796
835
|
}
|
|
797
836
|
}
|
|
798
837
|
else if (resource === 'product') {
|
|
799
|
-
if (operation === 'get') {
|
|
800
|
-
const productId = this.getNodeParameter('productId', i);
|
|
801
|
-
const response = await this.helpers.request({
|
|
802
|
-
method: 'GET',
|
|
803
|
-
url: `${gatewayUrl}/api/product?id=${productId}`,
|
|
804
|
-
headers: {
|
|
805
|
-
Authorization: `Bearer ${token}`,
|
|
806
|
-
},
|
|
807
|
-
json: true,
|
|
808
|
-
});
|
|
809
|
-
const result = {
|
|
810
|
-
json: response
|
|
811
|
-
};
|
|
812
|
-
if (items[i].binary) {
|
|
813
|
-
result.binary = items[i].binary;
|
|
814
|
-
}
|
|
815
|
-
returnData.push(result);
|
|
816
|
-
}
|
|
817
|
-
else if (operation === 'getAll') {
|
|
838
|
+
if (operation === 'get' || operation === 'getAll') {
|
|
818
839
|
const options = this.getNodeParameter('options', i, {});
|
|
840
|
+
// Build query parameters (use PascalCase to match C# API)
|
|
841
|
+
const queryParams = new URLSearchParams();
|
|
842
|
+
if (options.id) {
|
|
843
|
+
queryParams.append('Id', String(options.id));
|
|
844
|
+
}
|
|
845
|
+
if (options.filter) {
|
|
846
|
+
queryParams.append('Filter', String(options.filter));
|
|
847
|
+
}
|
|
848
|
+
if (options.skip) {
|
|
849
|
+
queryParams.append('Skip', String(options.skip));
|
|
850
|
+
}
|
|
851
|
+
if (options.take) {
|
|
852
|
+
queryParams.append('Take', String(options.take));
|
|
853
|
+
}
|
|
854
|
+
const queryString = queryParams.toString();
|
|
855
|
+
const url = queryString
|
|
856
|
+
? `${gatewayUrl}/api/product?${queryString}`
|
|
857
|
+
: `${gatewayUrl}/api/product`;
|
|
819
858
|
const response = await this.helpers.request({
|
|
820
859
|
method: 'GET',
|
|
821
|
-
url
|
|
860
|
+
url,
|
|
822
861
|
headers: {
|
|
823
862
|
Authorization: `Bearer ${token}`,
|
|
824
863
|
},
|
|
825
864
|
json: true,
|
|
826
865
|
});
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
866
|
+
// API returns { Success: true, Products: [...], TotalCount: ... }
|
|
867
|
+
const responseData = response;
|
|
868
|
+
// Extract products array from response
|
|
869
|
+
if (responseData.Products && Array.isArray(responseData.Products)) {
|
|
870
|
+
const products = responseData.Products;
|
|
871
|
+
const outputMode = options.outputMode || 'split';
|
|
872
|
+
if (outputMode === 'array') {
|
|
873
|
+
// Return entire response as single item
|
|
874
|
+
const result = {
|
|
875
|
+
json: responseData
|
|
876
|
+
};
|
|
877
|
+
if (items[i].binary) {
|
|
878
|
+
result.binary = items[i].binary;
|
|
879
|
+
}
|
|
880
|
+
returnData.push(result);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
// Split mode - return each product as separate item
|
|
884
|
+
if (items[i].binary) {
|
|
885
|
+
// First item gets binary
|
|
886
|
+
returnData.push({
|
|
887
|
+
json: products[0] || {},
|
|
888
|
+
binary: items[i].binary
|
|
889
|
+
});
|
|
890
|
+
// Remaining items without binary
|
|
891
|
+
products.slice(1).forEach((item) => {
|
|
892
|
+
returnData.push({
|
|
893
|
+
json: item
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
// No binary - just add all products normally
|
|
899
|
+
products.forEach((item) => {
|
|
900
|
+
returnData.push({
|
|
901
|
+
json: item
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else if (responseData.Products) {
|
|
908
|
+
// Single product - preserve binary
|
|
831
909
|
const result = {
|
|
832
|
-
json:
|
|
910
|
+
json: responseData.Products
|
|
833
911
|
};
|
|
834
912
|
if (items[i].binary) {
|
|
835
913
|
result.binary = items[i].binary;
|
|
@@ -837,28 +915,14 @@ class OptimaRestApi {
|
|
|
837
915
|
returnData.push(result);
|
|
838
916
|
}
|
|
839
917
|
else {
|
|
840
|
-
//
|
|
918
|
+
// Fallback - return entire response
|
|
919
|
+
const result = {
|
|
920
|
+
json: responseData
|
|
921
|
+
};
|
|
841
922
|
if (items[i].binary) {
|
|
842
|
-
|
|
843
|
-
returnData.push({
|
|
844
|
-
json: products[0] || {},
|
|
845
|
-
binary: items[i].binary
|
|
846
|
-
});
|
|
847
|
-
// Remaining items without binary
|
|
848
|
-
products.slice(1).forEach((item) => {
|
|
849
|
-
returnData.push({
|
|
850
|
-
json: item
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
else {
|
|
855
|
-
// No binary - just add all products normally
|
|
856
|
-
products.forEach((item) => {
|
|
857
|
-
returnData.push({
|
|
858
|
-
json: item
|
|
859
|
-
});
|
|
860
|
-
});
|
|
923
|
+
result.binary = items[i].binary;
|
|
861
924
|
}
|
|
925
|
+
returnData.push(result);
|
|
862
926
|
}
|
|
863
927
|
}
|
|
864
928
|
}
|