n8n-nodes-prestashop8 1.1.3 → 1.2.2

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.
@@ -154,16 +154,23 @@ exports.PrestaShop8Description = {
154
154
  placeholder: '[id_DESC] ou [name_ASC]',
155
155
  description: 'Sort criteria (e.g. [id_DESC], [name_ASC], [date_add_DESC])',
156
156
  },
157
+ {
158
+ displayName: 'Date Format',
159
+ name: 'dateFormat',
160
+ type: 'boolean',
161
+ default: false,
162
+ description: 'Enable date processing in filters (adds date=1 parameter)',
163
+ },
157
164
  ],
158
165
  },
159
- // Display options (always visible for list/search operations)
166
+ // Display options (always visible for list/search/getById operations)
160
167
  {
161
168
  displayName: 'Display',
162
169
  name: 'display',
163
170
  type: 'options',
164
171
  displayOptions: {
165
172
  show: {
166
- operation: ['list', 'search'],
173
+ operation: ['list', 'search', 'getById'],
167
174
  },
168
175
  },
169
176
  options: [
@@ -193,7 +200,7 @@ exports.PrestaShop8Description = {
193
200
  type: 'string',
194
201
  displayOptions: {
195
202
  show: {
196
- operation: ['list', 'search'],
203
+ operation: ['list', 'search', 'getById'],
197
204
  display: ['custom'],
198
205
  },
199
206
  },
@@ -226,9 +233,13 @@ exports.PrestaShop8Description = {
226
233
  name: 'field',
227
234
  type: 'string',
228
235
  default: '',
229
- required: true,
230
236
  placeholder: 'name, reference, price, etc.',
231
237
  description: 'Name of field to filter. Examples: name, reference, price, active, id_manufacturer, etc.',
238
+ displayOptions: {
239
+ hide: {
240
+ operator: ['CUSTOM'],
241
+ },
242
+ },
232
243
  },
233
244
  {
234
245
  displayName: 'Operator',
@@ -237,16 +248,33 @@ exports.PrestaShop8Description = {
237
248
  options: types_1.FILTER_OPERATORS,
238
249
  default: '=',
239
250
  noDataExpression: true,
240
- description: 'Comparison operator for filtering. Available operators: = (exact match), LIKE (contains text), > (greater than), >= (greater or equal), < (less than), <= (less or equal), != (not equal), BEGINS (starts with), ENDS (ends with)',
251
+ description: 'Comparison operator for filtering. Custom allows you to write your own filter expression.',
241
252
  },
242
253
  {
243
254
  displayName: 'Value',
244
255
  name: 'value',
245
256
  type: 'string',
246
257
  default: '',
247
- required: true,
248
- placeholder: 'search value',
249
- description: 'Value to search for. Examples: "Product Name" (for LIKE), "1" (for active), "29.99" (for price), etc.',
258
+ placeholder: 'search value or interval',
259
+ description: 'Value to search for. Examples: "Product Name", "1" (active), "29.99" (price). For Equal/Not Equal: use "10,20" for intervals (becomes [10,20]).',
260
+ displayOptions: {
261
+ hide: {
262
+ operator: ['IS_EMPTY', 'IS_NOT_EMPTY', 'CUSTOM'],
263
+ },
264
+ },
265
+ },
266
+ {
267
+ displayName: 'Custom Filter Expression',
268
+ name: 'customFilter',
269
+ type: 'string',
270
+ default: '',
271
+ placeholder: 'filter[name]=[Product]%',
272
+ description: 'Complete filter expression in PrestaShop format. Example: filter[name]=[Product]%, filter[price]=>[100], etc.',
273
+ displayOptions: {
274
+ show: {
275
+ operator: ['CUSTOM'],
276
+ },
277
+ },
250
278
  },
251
279
  ],
252
280
  },
@@ -610,34 +638,66 @@ exports.PrestaShop8Description = {
610
638
  ],
611
639
  description: 'Fields to update in the resource. Add multiple field/value pairs as needed. For multilingual fields (names, descriptions), use format: fieldname-langid (e.g., name-1, name-2 for different languages).',
612
640
  },
613
- // Debug Options
641
+ // Options
614
642
  {
615
- displayName: 'Debug Options',
616
- name: 'debugOptions',
643
+ displayName: 'Options',
644
+ name: 'options',
617
645
  type: 'collection',
618
- default: {},
619
646
  placeholder: 'Add Option',
647
+ default: {},
620
648
  options: [
621
649
  {
622
- displayName: 'Raw Mode',
623
- name: 'rawMode',
624
- type: 'boolean',
625
- default: false,
626
- description: 'Return raw PrestaShop XML format instead of simplified JSON. Useful for debugging and accessing all original data fields.',
627
- },
628
- {
629
- displayName: 'Show Request URL',
630
- name: 'showUrl',
631
- type: 'boolean',
632
- default: false,
633
- description: 'Add request URL to the response',
650
+ displayName: 'Request',
651
+ name: 'request',
652
+ type: 'collection',
653
+ placeholder: 'Add Request Option',
654
+ default: {},
655
+ options: [
656
+ {
657
+ displayName: 'Show Request Info',
658
+ name: 'showRequestInfo',
659
+ type: 'boolean',
660
+ default: false,
661
+ description: 'Add complete HTTP request information to the response (method, URL, headers, authentication, body, etc.) - useful for debugging API communication',
662
+ },
663
+ {
664
+ displayName: 'Show Request URL',
665
+ name: 'showRequestUrl',
666
+ type: 'boolean',
667
+ default: false,
668
+ description: 'Add the complete request URL to the response - useful for debugging API calls',
669
+ },
670
+ ],
634
671
  },
635
672
  {
636
- displayName: 'Show Request Info',
637
- name: 'showHeaders',
638
- type: 'boolean',
639
- default: false,
640
- description: 'Add complete HTTP request information to the response (method, URL, headers, authentication, body, etc.) - useful for debugging API communication',
673
+ displayName: 'Response',
674
+ name: 'response',
675
+ type: 'collection',
676
+ placeholder: 'Add Response Option',
677
+ default: {},
678
+ options: [
679
+ {
680
+ displayName: 'Include Response Headers and Status',
681
+ name: 'includeResponseHeaders',
682
+ type: 'boolean',
683
+ default: false,
684
+ description: 'Whether to return the full response (headers and response status code) data instead of only the body',
685
+ },
686
+ {
687
+ displayName: 'Never Error',
688
+ name: 'neverError',
689
+ type: 'boolean',
690
+ default: false,
691
+ description: 'Whether to succeeds also when status code is not 2xx',
692
+ },
693
+ {
694
+ displayName: 'Raw Mode',
695
+ name: 'rawMode',
696
+ type: 'boolean',
697
+ default: false,
698
+ description: 'Return raw PrestaShop XML/JSON response without n8n processing - useful for debugging or custom XML handling',
699
+ },
700
+ ],
641
701
  },
642
702
  {
643
703
  displayName: 'Timeout (ms)',
@@ -67,17 +67,41 @@ function buildHttpOptions(method, url, credentials, rawMode, timeout, body) {
67
67
  };
68
68
  }
69
69
  /**
70
- * Execute HTTP request with debug capture
70
+ * Execute HTTP request with debug capture and response metadata
71
71
  */
72
- async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, body) {
72
+ async function executeHttpRequest(helpers, options, credentials, rawMode, operation, resource, neverError = false, body) {
73
+ var _a, _b, _c, _d;
73
74
  const requestUrl = options.url;
74
75
  const debugInfo = captureRequestDebugInfo(options, credentials, rawMode, operation, resource, body);
75
- const response = await helpers.httpRequest(options);
76
- return {
77
- response,
78
- debugInfo,
79
- url: requestUrl,
80
- };
76
+ try {
77
+ const response = await helpers.httpRequest(options);
78
+ // For n8n's httpRequest, the response IS the body, headers are not directly available
79
+ // We return the response as-is since n8n handles the HTTP layer
80
+ return {
81
+ response,
82
+ debugInfo,
83
+ url: requestUrl,
84
+ responseHeaders: {},
85
+ statusCode: 200, // Assume success if no error thrown
86
+ };
87
+ }
88
+ catch (error) {
89
+ if (neverError) {
90
+ // Return structured error response for Never Error mode
91
+ const errorResponse = {
92
+ status: error.httpCode || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) || 500,
93
+ message: ((_b = error.response) === null || _b === void 0 ? void 0 : _b.data) || ''
94
+ };
95
+ return {
96
+ response: errorResponse,
97
+ debugInfo,
98
+ url: requestUrl,
99
+ responseHeaders: ((_c = error.response) === null || _c === void 0 ? void 0 : _c.headers) || {},
100
+ statusCode: error.httpCode || ((_d = error.response) === null || _d === void 0 ? void 0 : _d.status) || 500,
101
+ };
102
+ }
103
+ throw error;
104
+ }
81
105
  }
82
106
  /**
83
107
  * Collect required fields from individual input parameters
@@ -266,6 +290,7 @@ class PrestaShop8 {
266
290
  };
267
291
  }
268
292
  async execute() {
293
+ var _a, _b, _c, _d;
269
294
  const items = this.getInputData();
270
295
  const returnData = [];
271
296
  const credentials = await this.getCredentials('prestaShop8Api');
@@ -278,7 +303,7 @@ class PrestaShop8 {
278
303
  let requestUrl = '';
279
304
  let requestHeaders = {};
280
305
  let requestDebugInfo = {};
281
- const rawMode = this.getNodeParameter('debugOptions.rawMode', i, false);
306
+ const rawMode = this.getNodeParameter('options.response.rawMode', i, false);
282
307
  // Use resource for response processing
283
308
  const currentMode = resource;
284
309
  switch (operation) {
@@ -286,28 +311,23 @@ class PrestaShop8 {
286
311
  const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
287
312
  const display = this.getNodeParameter('display', i, 'full');
288
313
  const customFields = this.getNodeParameter('customFields', i, '');
289
- // Handle display parameter - minimal = no display param (IDs only)
290
314
  const displayValue = (0, utils_1.processDisplayParameter)(display, resource, customFields);
291
- requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, {
315
+ const urlParams = {
292
316
  limit: advancedOptions.limit,
293
317
  sort: advancedOptions.sort,
294
- display: displayValue,
295
- }, rawMode);
296
- const options = {
297
- method: 'GET',
298
- url: requestUrl,
299
- auth: {
300
- username: credentials.apiKey,
301
- password: '',
302
- },
303
- headers: buildHeaders(rawMode),
304
- timeout: this.getNodeParameter('debugOptions.timeout', i, 30000),
305
- ...(rawMode ? { json: false } : {}),
306
318
  };
307
- // Capture complete request information for debug
308
- requestDebugInfo = captureRequestDebugInfo(options, credentials, rawMode, operation, resource);
309
- requestHeaders = requestDebugInfo.headers;
310
- let response;
319
+ // Add date=1 parameter if Date Format option is enabled
320
+ if (advancedOptions.dateFormat) {
321
+ urlParams.date = 1;
322
+ }
323
+ // Only add display parameter if not null (minimal mode returns null)
324
+ if (displayValue !== null) {
325
+ urlParams.display = displayValue;
326
+ }
327
+ requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
328
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
329
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
330
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
311
331
  if (rawMode) {
312
332
  // In Raw mode, use axios directly to avoid n8n automatic parsing
313
333
  const axios = require('axios');
@@ -319,15 +339,47 @@ class PrestaShop8 {
319
339
  username: credentials.apiKey,
320
340
  password: ''
321
341
  },
322
- headers: options.headers,
323
- timeout: options.timeout || 30000,
324
- transformResponse: [(data) => data] // Keep raw response
342
+ headers: buildHeaders(rawMode),
343
+ timeout: timeout || 30000,
344
+ transformResponse: [(data) => data],
345
+ validateStatus: neverError ? () => true : undefined // Accept all status codes if neverError is true
325
346
  });
326
- response = axiosResponse.data;
347
+ requestDebugInfo = captureRequestDebugInfo({
348
+ method: 'GET',
349
+ url: requestUrl,
350
+ headers: buildHeaders(rawMode),
351
+ timeout: timeout
352
+ }, credentials, rawMode, operation, resource);
353
+ requestHeaders = requestDebugInfo.headers;
354
+ // Check if this is an error response with Never Error mode
355
+ if (neverError && (axiosResponse.status < 200 || axiosResponse.status >= 300)) {
356
+ responseData = {
357
+ status: axiosResponse.status,
358
+ message: axiosResponse.data || ''
359
+ };
360
+ }
361
+ else {
362
+ let processedResponse = { raw: axiosResponse.data };
363
+ // Add response headers and status if requested
364
+ if (includeResponseHeaders) {
365
+ responseData = {
366
+ body: processedResponse,
367
+ headers: axiosResponse.headers,
368
+ statusCode: axiosResponse.status,
369
+ statusMessage: axiosResponse.status >= 200 && axiosResponse.status < 300 ? 'OK' : 'Error'
370
+ };
371
+ }
372
+ else {
373
+ responseData = processedResponse;
374
+ }
375
+ }
327
376
  }
328
377
  catch (error) {
329
- if (error.response) {
330
- response = error.response.data;
378
+ if (neverError) {
379
+ responseData = {
380
+ status: ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) || 500,
381
+ message: ((_b = error.response) === null || _b === void 0 ? void 0 : _b.data) || ''
382
+ };
331
383
  }
332
384
  else {
333
385
  throw error;
@@ -335,56 +387,62 @@ class PrestaShop8 {
335
387
  }
336
388
  }
337
389
  else {
338
- response = await this.helpers.httpRequest(options);
390
+ const options = buildHttpOptions('GET', requestUrl, credentials, rawMode, timeout);
391
+ const { response, debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError);
392
+ requestUrl = url;
393
+ requestDebugInfo = debugInfo;
394
+ requestHeaders = debugInfo.headers;
395
+ let processedResponse = (0, utils_1.processResponseForMode)(response, resource, currentMode);
396
+ // Add response headers and status if requested
397
+ if (includeResponseHeaders) {
398
+ responseData = {
399
+ body: processedResponse,
400
+ headers: responseHeaders,
401
+ statusCode: statusCode,
402
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
403
+ };
404
+ }
405
+ else {
406
+ responseData = processedResponse;
407
+ }
339
408
  }
340
- responseData = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
341
409
  break;
342
410
  }
343
411
  case 'getById': {
344
412
  const id = this.getNodeParameter('id', i);
345
- const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
413
+ const display = this.getNodeParameter('display', i, 'full');
414
+ const customFields = this.getNodeParameter('customFields', i, '');
346
415
  if (!id) {
347
416
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'ID required for this operation');
348
417
  }
349
- requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}/${id}`, {
350
- display: advancedOptions.display === 'custom' ? advancedOptions.customFields : advancedOptions.display,
351
- }, rawMode);
352
- const timeout = this.getNodeParameter('debugOptions.timeout', i, 30000);
418
+ const displayValue = (0, utils_1.processDisplayParameter)(display, resource, customFields);
419
+ const urlParams = {};
420
+ // Only add display parameter if not null (minimal mode returns null)
421
+ if (displayValue !== null) {
422
+ urlParams.display = displayValue;
423
+ }
424
+ requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}/${id}`, urlParams, rawMode);
425
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
426
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
427
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
353
428
  const options = buildHttpOptions('GET', requestUrl, credentials, rawMode, timeout);
354
- const { response, debugInfo, url } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource);
429
+ const { response, debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError);
355
430
  requestUrl = url;
356
431
  requestDebugInfo = debugInfo;
357
432
  requestHeaders = debugInfo.headers;
358
- responseData = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
359
- break;
360
- }
361
- case 'search': {
362
- const filtersParam = this.getNodeParameter('filters', i, {});
363
- const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
364
- const display = this.getNodeParameter('display', i, 'full');
365
- const customFields = this.getNodeParameter('customFields', i, '');
366
- const filters = filtersParam.filter || [];
367
- // Handle display parameter - minimal = no display param (IDs only)
368
- const displayValue = (0, utils_1.processDisplayParameter)(display, resource, customFields);
369
- requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, {
370
- filters,
371
- limit: advancedOptions.limit,
372
- sort: advancedOptions.sort,
373
- display: displayValue,
374
- }, rawMode);
375
- const options = {
376
- method: 'GET',
377
- url: requestUrl,
378
- auth: {
379
- username: credentials.apiKey,
380
- password: '',
381
- },
382
- headers: buildHeaders(rawMode),
383
- timeout: this.getNodeParameter('debugOptions.timeout', i, 30000),
384
- ...(rawMode ? { json: false } : {}),
385
- };
386
- const response = await this.helpers.httpRequest(options);
387
- responseData = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
433
+ let processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
434
+ // Add response headers and status if requested
435
+ if (includeResponseHeaders) {
436
+ responseData = {
437
+ body: processedResponse,
438
+ headers: responseHeaders,
439
+ statusCode: statusCode,
440
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
441
+ };
442
+ }
443
+ else {
444
+ responseData = processedResponse;
445
+ }
388
446
  break;
389
447
  }
390
448
  case 'create': {
@@ -416,13 +474,27 @@ class PrestaShop8 {
416
474
  }
417
475
  // Build XML using new format
418
476
  body = (0, utils_1.buildCreateXml)(resource, fieldsToCreate);
419
- const timeout = this.getNodeParameter('debugOptions.timeout', i, 30000);
477
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
478
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
479
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
420
480
  const options = buildHttpOptions('POST', `${credentials.baseUrl}/${resource}`, credentials, rawMode, timeout, body);
421
- const { response, debugInfo, url } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, body);
481
+ const { response, debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
422
482
  requestUrl = url;
423
483
  requestDebugInfo = debugInfo;
424
484
  requestHeaders = debugInfo.headers;
425
- responseData = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
485
+ let processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
486
+ // Add response headers and status if requested
487
+ if (includeResponseHeaders) {
488
+ responseData = {
489
+ body: processedResponse,
490
+ headers: responseHeaders,
491
+ statusCode: statusCode,
492
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
493
+ };
494
+ }
495
+ else {
496
+ responseData = processedResponse;
497
+ }
426
498
  break;
427
499
  }
428
500
  case 'update': {
@@ -450,13 +522,244 @@ class PrestaShop8 {
450
522
  }
451
523
  // Build XML using new format
452
524
  body = (0, utils_1.buildUpdateXml)(resource, id, fieldsToUpdate);
453
- const timeout = this.getNodeParameter('debugOptions.timeout', i, 30000);
525
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
526
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
527
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
454
528
  const options = buildHttpOptions('PATCH', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout, body);
455
- const { response, debugInfo, url } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, body);
529
+ const { response, debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError, body);
456
530
  requestUrl = url;
457
531
  requestDebugInfo = debugInfo;
458
532
  requestHeaders = debugInfo.headers;
459
- responseData = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
533
+ let processedResponse = rawMode ? { raw: response } : (0, utils_1.processResponseForMode)(response, resource, currentMode);
534
+ // Add response headers and status if requested
535
+ if (includeResponseHeaders) {
536
+ responseData = {
537
+ body: processedResponse,
538
+ headers: responseHeaders,
539
+ statusCode: statusCode,
540
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
541
+ };
542
+ }
543
+ else {
544
+ responseData = processedResponse;
545
+ }
546
+ break;
547
+ }
548
+ case 'search': {
549
+ const filtersParam = this.getNodeParameter('filters', i, {});
550
+ const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
551
+ const display = this.getNodeParameter('display', i, 'full');
552
+ const customFields = this.getNodeParameter('customFields', i, '');
553
+ // Debug: Log the filters parameter structure
554
+ const showRequestInfo = this.getNodeParameter('options.request.showRequestInfo', i, false);
555
+ if (showRequestInfo) {
556
+ console.log('DEBUG - filtersParam:', JSON.stringify(filtersParam, null, 2));
557
+ }
558
+ const filters = filtersParam.filter || [];
559
+ if (!filters.length) {
560
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'At least one filter is required for search');
561
+ }
562
+ // Build filter parameters for URL
563
+ const filterParams = {};
564
+ for (const filter of filters) {
565
+ if (showRequestInfo) {
566
+ console.log('DEBUG - Processing filter:', JSON.stringify(filter, null, 2));
567
+ }
568
+ // Handle CUSTOM filter operator
569
+ if (filter.operator === 'CUSTOM') {
570
+ if (filter.customFilter && filter.customFilter.trim()) {
571
+ // Add custom filter directly to URL without any interpretation
572
+ // User writes exactly what they want: date=1, filter[name]=test, etc.
573
+ const customFilter = filter.customFilter.trim();
574
+ // Parse the custom filter to extract key=value pairs for URL construction
575
+ const parts = customFilter.split('&');
576
+ for (const part of parts) {
577
+ const [key, value] = part.split('=', 2);
578
+ if (key && value !== undefined) {
579
+ filterParams[key.trim()] = value.trim();
580
+ }
581
+ }
582
+ }
583
+ continue;
584
+ }
585
+ const key = `filter[${filter.field}]`;
586
+ // Handle different operators with correct PrestaShop formats
587
+ switch (filter.operator) {
588
+ case '=':
589
+ if (filter.value && filter.value.trim()) {
590
+ const value = filter.value.trim();
591
+ // Check if value contains comma for interval (e.g., "10,20" → "[10,20]")
592
+ if (value.includes(',') && !value.startsWith('[')) {
593
+ filterParams[key] = `[${value}]`;
594
+ }
595
+ else {
596
+ filterParams[key] = `[${value}]`;
597
+ }
598
+ }
599
+ break;
600
+ case '!=':
601
+ if (filter.value && filter.value.trim()) {
602
+ const value = filter.value.trim();
603
+ // Check if value contains comma for interval (e.g., "10,20" → "![10,20]")
604
+ if (value.includes(',') && !value.startsWith('[')) {
605
+ filterParams[key] = `![${value}]`;
606
+ }
607
+ else {
608
+ filterParams[key] = `![${value}]`;
609
+ }
610
+ }
611
+ break;
612
+ case '>':
613
+ if (filter.value && filter.value.trim()) {
614
+ filterParams[key] = `>[${filter.value}]`;
615
+ }
616
+ break;
617
+ case '>=':
618
+ if (filter.value && filter.value.trim()) {
619
+ filterParams[key] = `>=[${filter.value}]`;
620
+ }
621
+ break;
622
+ case '<':
623
+ if (filter.value && filter.value.trim()) {
624
+ filterParams[key] = `<[${filter.value}]`;
625
+ }
626
+ break;
627
+ case '<=':
628
+ if (filter.value && filter.value.trim()) {
629
+ filterParams[key] = `<=[${filter.value}]`;
630
+ }
631
+ break;
632
+ case 'CONTAINS':
633
+ if (filter.value && filter.value.trim()) {
634
+ filterParams[key] = `%[${filter.value}]%`;
635
+ }
636
+ break;
637
+ case 'BEGINS':
638
+ if (filter.value && filter.value.trim()) {
639
+ filterParams[key] = `[${filter.value}]%`;
640
+ }
641
+ break;
642
+ case 'ENDS':
643
+ if (filter.value && filter.value.trim()) {
644
+ filterParams[key] = `%[${filter.value}]`;
645
+ }
646
+ break;
647
+ case 'IS_EMPTY':
648
+ filterParams[key] = `[]`;
649
+ break;
650
+ case 'IS_NOT_EMPTY':
651
+ filterParams[key] = `![]`;
652
+ break;
653
+ default:
654
+ if (filter.value && filter.value.trim()) {
655
+ filterParams[key] = `[${filter.value}]`;
656
+ }
657
+ }
658
+ }
659
+ // Handle display parameter
660
+ const displayValue = (0, utils_1.processDisplayParameter)(display, resource, customFields);
661
+ const urlParams = {
662
+ ...filterParams,
663
+ limit: advancedOptions.limit,
664
+ sort: advancedOptions.sort,
665
+ };
666
+ // Add date=1 parameter if Date Format option is enabled
667
+ if (advancedOptions.dateFormat) {
668
+ urlParams.date = 1;
669
+ }
670
+ // Only add display parameter if not null (minimal mode returns null)
671
+ if (displayValue !== null) {
672
+ urlParams.display = displayValue;
673
+ }
674
+ if (showRequestInfo) {
675
+ console.log('DEBUG - Final filterParams:', JSON.stringify(filterParams, null, 2));
676
+ console.log('DEBUG - Final urlParams:', JSON.stringify(urlParams, null, 2));
677
+ }
678
+ requestUrl = (0, utils_1.buildUrlWithFilters)(`${credentials.baseUrl}/${resource}`, urlParams, rawMode);
679
+ if (showRequestInfo) {
680
+ console.log('DEBUG - Final URL:', requestUrl);
681
+ }
682
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
683
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
684
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
685
+ if (rawMode) {
686
+ // In Raw mode, use axios directly to avoid n8n automatic parsing
687
+ const axios = require('axios');
688
+ try {
689
+ const axiosResponse = await axios({
690
+ method: 'GET',
691
+ url: requestUrl,
692
+ auth: {
693
+ username: credentials.apiKey,
694
+ password: ''
695
+ },
696
+ headers: buildHeaders(rawMode),
697
+ timeout: timeout || 30000,
698
+ transformResponse: [(data) => data],
699
+ validateStatus: neverError ? () => true : undefined // Accept all status codes if neverError is true
700
+ });
701
+ requestDebugInfo = captureRequestDebugInfo({
702
+ method: 'GET',
703
+ url: requestUrl,
704
+ headers: buildHeaders(rawMode),
705
+ timeout: timeout
706
+ }, credentials, rawMode, operation, resource);
707
+ requestHeaders = requestDebugInfo.headers;
708
+ // Check if this is an error response with Never Error mode
709
+ if (neverError && (axiosResponse.status < 200 || axiosResponse.status >= 300)) {
710
+ responseData = {
711
+ status: axiosResponse.status,
712
+ message: axiosResponse.data || ''
713
+ };
714
+ }
715
+ else {
716
+ let processedResponse = { raw: axiosResponse.data };
717
+ // Add response headers and status if requested
718
+ if (includeResponseHeaders) {
719
+ responseData = {
720
+ body: processedResponse,
721
+ headers: axiosResponse.headers,
722
+ statusCode: axiosResponse.status,
723
+ statusMessage: axiosResponse.status >= 200 && axiosResponse.status < 300 ? 'OK' : 'Error'
724
+ };
725
+ }
726
+ else {
727
+ responseData = processedResponse;
728
+ }
729
+ }
730
+ }
731
+ catch (error) {
732
+ if (neverError) {
733
+ responseData = {
734
+ status: ((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) || 500,
735
+ message: ((_d = error.response) === null || _d === void 0 ? void 0 : _d.data) || ''
736
+ };
737
+ }
738
+ else {
739
+ throw error;
740
+ }
741
+ }
742
+ }
743
+ else {
744
+ const options = buildHttpOptions('GET', requestUrl, credentials, rawMode, timeout);
745
+ const { response, debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError);
746
+ requestUrl = url;
747
+ requestDebugInfo = debugInfo;
748
+ requestHeaders = debugInfo.headers;
749
+ let processedResponse = (0, utils_1.processResponseForMode)(response, resource, currentMode);
750
+ // Add response headers and status if requested
751
+ if (includeResponseHeaders) {
752
+ responseData = {
753
+ body: processedResponse,
754
+ headers: responseHeaders,
755
+ statusCode: statusCode,
756
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
757
+ };
758
+ }
759
+ else {
760
+ responseData = processedResponse;
761
+ }
762
+ }
460
763
  break;
461
764
  }
462
765
  case 'delete': {
@@ -464,52 +767,60 @@ class PrestaShop8 {
464
767
  if (!id) {
465
768
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'ID required for this operation');
466
769
  }
467
- const timeout = this.getNodeParameter('debugOptions.timeout', i, 30000);
770
+ const timeout = this.getNodeParameter('options.timeout', i, 30000);
771
+ const neverError = this.getNodeParameter('options.response.neverError', i, false);
772
+ const includeResponseHeaders = this.getNodeParameter('options.response.includeResponseHeaders', i, false);
468
773
  const options = buildHttpOptions('DELETE', `${credentials.baseUrl}/${resource}/${id}`, credentials, rawMode, timeout);
469
- const { debugInfo, url } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource);
774
+ const { debugInfo, url, responseHeaders, statusCode } = await executeHttpRequest(this.helpers, options, credentials, rawMode, operation, resource, neverError);
470
775
  requestUrl = url;
471
776
  requestDebugInfo = debugInfo;
472
777
  requestHeaders = debugInfo.headers;
473
- responseData = {
778
+ let deleteResponse = {
474
779
  success: true,
475
780
  message: `${resource} with ID ${id} deleted successfully`,
476
781
  deletedId: id,
477
782
  };
783
+ // Add response headers and status if requested
784
+ if (includeResponseHeaders) {
785
+ responseData = {
786
+ body: deleteResponse,
787
+ headers: responseHeaders,
788
+ statusCode: statusCode,
789
+ statusMessage: statusCode && statusCode >= 200 && statusCode < 300 ? 'OK' : 'Error'
790
+ };
791
+ }
792
+ else {
793
+ responseData = deleteResponse;
794
+ }
478
795
  break;
479
796
  }
480
797
  default:
481
798
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation "${operation}" not supported`);
482
799
  }
483
800
  // Add debug metadata if requested
484
- const debugOptions = this.getNodeParameter('debugOptions', i, {});
801
+ const showRequestUrl = this.getNodeParameter('options.request.showRequestUrl', i, false);
802
+ const showRequestInfo = this.getNodeParameter('options.request.showRequestInfo', i, false);
485
803
  // Capture request information for debug purposes if needed
486
- if (debugOptions.showHeaders && Object.keys(requestDebugInfo).length === 0) {
804
+ if (showRequestInfo && Object.keys(requestDebugInfo).length === 0) {
487
805
  // Fallback if no request debug info was captured
488
806
  requestDebugInfo = {
489
807
  method: 'GET',
490
808
  url: requestUrl,
491
- headers: {
492
- ...buildHeaders(rawMode),
493
- 'Authorization': `Basic ${Buffer.from(credentials.apiKey + ':').toString('base64')}`,
494
- 'User-Agent': 'n8n-prestashop8-node/1.0.0',
495
- },
496
- authentication: {
497
- type: 'Basic Auth',
498
- username: credentials.apiKey,
499
- password: '[HIDDEN]',
500
- baseUrl: credentials.baseUrl,
501
- },
502
- operation: operation,
503
- resource: resource,
504
- mode: rawMode ? 'Raw XML' : 'JSON',
809
+ headers: {},
810
+ auth: 'Basic (hidden)',
811
+ timeout: 30000,
812
+ timestamp: new Date().toISOString(),
505
813
  };
814
+ }
815
+ // Add headers to output if requested
816
+ if (showRequestInfo) {
506
817
  requestHeaders = requestDebugInfo.headers;
507
818
  }
508
- if (debugOptions.showUrl || debugOptions.showHeaders) {
819
+ if (showRequestUrl || showRequestInfo) {
509
820
  responseData = {
510
821
  data: responseData,
511
- ...(debugOptions.showUrl && { requestUrl }),
512
- ...(debugOptions.showHeaders && { requestInfo: requestDebugInfo }),
822
+ ...(showRequestUrl && { requestUrl }),
823
+ ...(showRequestInfo && { requestInfo: requestDebugInfo }),
513
824
  };
514
825
  }
515
826
  processResponseData(responseData, returnData, i);
@@ -6,6 +6,7 @@ export interface IPrestaShopFilter {
6
6
  field: string;
7
7
  operator: string;
8
8
  value: string;
9
+ customFilter?: string;
9
10
  }
10
11
  export interface IPrestaShopRequestOptions {
11
12
  resource: string;
@@ -360,8 +360,10 @@ exports.FILTER_OPERATORS = [
360
360
  { name: '≥ Greater than or equal to', value: '>=' },
361
361
  { name: '< Less than', value: '<' },
362
362
  { name: '≤ Less than or equal to', value: '<=' },
363
- { name: '∋ Contains', value: 'LIKE' },
364
- { name: '∌ Does not contain', value: 'NOT LIKE' },
363
+ { name: '∋ Contains', value: 'CONTAINS' },
365
364
  { name: '↦ Starts with', value: 'BEGINS' },
366
365
  { name: '↤ Ends with', value: 'ENDS' },
366
+ { name: '∅ Is Empty', value: 'IS_EMPTY' },
367
+ { name: '∄ Is not Empty', value: 'IS_NOT_EMPTY' },
368
+ { name: '⚙️ Custom Filter', value: 'CUSTOM' },
367
369
  ];
@@ -429,30 +429,21 @@ exports.parseXmlToJson = parseXmlToJson;
429
429
  function buildUrlWithFilters(baseUrl, options, rawMode) {
430
430
  const url = new URL(baseUrl);
431
431
  const params = new URLSearchParams();
432
- // Ajouter les filtres
433
- if (options.filters && Array.isArray(options.filters)) {
434
- for (const filter of options.filters) {
435
- if (filter.field && filter.value) {
436
- const filterKey = `filter[${filter.field}]`;
437
- let filterValue = filter.value;
438
- // Apply operator if it's not '='
439
- if (filter.operator && filter.operator !== '=') {
440
- filterValue = `[${filter.operator}]${filterValue}`;
441
- }
442
- params.append(filterKey, filterValue);
432
+ // Add all parameters from options object
433
+ for (const [key, value] of Object.entries(options)) {
434
+ if (value !== null && value !== undefined && value !== '') {
435
+ if (key === 'sort') {
436
+ // Special handling for sort parameter
437
+ const normalizedSort = processSortParameter(String(value));
438
+ if (normalizedSort)
439
+ params.append('sort', normalizedSort);
440
+ }
441
+ else {
442
+ // Add all other parameters as-is (including custom user parameters)
443
+ params.append(key, String(value));
443
444
  }
444
445
  }
445
446
  }
446
- // Add other parameters
447
- if (options.limit)
448
- params.append('limit', options.limit);
449
- if (options.sort) {
450
- const normalizedSort = processSortParameter(options.sort);
451
- if (normalizedSort)
452
- params.append('sort', normalizedSort);
453
- }
454
- if (options.display !== null && options.display !== undefined)
455
- params.append('display', options.display);
456
447
  // Ajouter output_format seulement si pas en mode Raw
457
448
  if (!rawMode) {
458
449
  params.append('output_format', 'JSON');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-prestashop8",
3
- "version": "1.1.3",
3
+ "version": "1.2.2",
4
4
  "description": "Nœud n8n personnalisé pour PrestaShop 8 avec support CRUD complet et conversion XML/JSON automatique",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",