@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 - księgowość i płace, CDN - handel)',
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 ID
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
- // First, authenticate and get token
518
- const loginResponse = await this.helpers.request({
519
- method: 'POST',
520
- url: `${gatewayUrl}/api/account/login`,
521
- body: {
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
- modules: credentials.modules || 'KP',
526
- },
527
- json: true,
528
- });
529
- const token = loginResponse.Token;
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: `${gatewayUrl}/api/product`,
860
+ url,
822
861
  headers: {
823
862
  Authorization: `Bearer ${token}`,
824
863
  },
825
864
  json: true,
826
865
  });
827
- const products = response;
828
- const outputMode = options.outputMode || 'split';
829
- if (outputMode === 'array') {
830
- // Return all products as array in single item
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: { Products: products, TotalCount: products.length }
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
- // Split mode - return each product as separate item
918
+ // Fallback - return entire response
919
+ const result = {
920
+ json: responseData
921
+ };
841
922
  if (items[i].binary) {
842
- // First item gets binary
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pokash/n8n-nodes-optima-rest-api",
3
- "version": "1.1.21",
3
+ "version": "1.2.0",
4
4
  "description": "n8n node for Comarch Optima REST API integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",