n8n-nodes-ifood-completo 0.1.21 → 0.1.23

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.23
4
+
5
+ - Corrige o node `iFood Analise Conciliacao` para nao usar eventos logisticos/operacionais em `diferencas`.
6
+ - Passa a montar `diferencas` apenas com lancamentos financeiros com valor numerico, priorizando `billingSummary.billingEntries`.
7
+ - Completa o valor bruto com `saleGrossValue` ou `ORDER_PAYMENT` e calcula `liquidoVenda` por `billingEntries` quando `saleBalance` nao vier na resposta.
8
+ - Melhora a leitura de lote, valor e status do repasse a partir de `closingItems`, priorizando o item `REPASSE`.
9
+
10
+ ## 0.1.22
11
+
12
+ - Altera a saida do node `iFood Analise Conciliacao` para um unico item JSON consolidado com `merchantId`, `periodo`, `vendas` e `repasses`.
13
+ - Remove os payloads brutos de origem e campos extras da resposta, mantendo apenas o modelo sintetico de conciliacao solicitado.
14
+
3
15
  ## 0.1.21
4
16
 
5
17
  - Ajusta `Financial Events` para paginar a partir de `page=1` e consultar o periodo semanal do corte, respeitando o limite da API.
package/README.md CHANGED
@@ -60,7 +60,7 @@ O node `iFood Authentication` nao exige credencial nem recebe `Access Token`; el
60
60
 
61
61
  `iFood Produtos do Catalogo` gera o token internamente por `client_credentials`. Informe somente `Client ID`, `Client Secret`, `Merchant ID` e `Catalog ID`; a saída contém um item n8n por produto, com categoria, código externo, valor, situação, pausa e grupos de complementos.
62
62
 
63
- `iFood Analise Conciliacao` tambem gera o token internamente por `client_credentials`. Informe `Client ID`, `Client Secret`, `Merchant ID da Loja` e `Data` no formato `yyyy-mm-dd`; o node consulta vendas do dia, eventos financeiros e repasses relacionados, retornando um item JSON por venda com corte semanal, lote de repasse, valores e diferencas financeiras.
63
+ `iFood Analise Conciliacao` tambem gera o token internamente por `client_credentials`. Informe `Client ID`, `Client Secret`, `Merchant ID da Loja` e `Data` no formato `yyyy-mm-dd`; o node consulta vendas do dia, eventos financeiros e repasses relacionados, retornando um unico JSON consolidado com `merchantId`, `periodo`, `vendas` e `repasses`.
64
64
 
65
65
  ## Tutorial
66
66
 
package/TUTORIAL.md CHANGED
@@ -421,7 +421,8 @@ Reconciliation Arquivo:
421
421
 
422
422
  O node `iFood Analise Conciliacao` executa o fluxo financeiro consolidado para uma
423
423
  data especifica. Ele recebe os dados de credenciamento manualmente, gera o token
424
- OAuth internamente e retorna um item n8n por venda localizada no dia informado.
424
+ OAuth internamente e retorna um unico item n8n consolidado com as vendas e repasses
425
+ da data informada.
425
426
 
426
427
  Parametros obrigatorios:
427
428
 
@@ -436,41 +437,54 @@ Fluxo executado:
436
437
 
437
438
  1. Gera o token em `/authentication/v1.0/oauth/token`.
438
439
  2. Consulta vendas em `/v3/sales` no dia informado.
439
- 3. Consulta eventos financeiros em `/v3/financial-events` em janela ampliada para vincular impactos e repasse.
440
+ 3. Consulta eventos financeiros em `/v3/financial-events` quando disponivel para vincular impactos e repasse.
440
441
  4. Consulta repasses em `/financial/v3.0/merchants/{merchantId}/settlements` para identificar lote, valor e data de pagamento.
441
- 5. Agrupa eventos por pedido e retorna a conciliacao de cada venda.
442
+ 5. Monta `diferencas` apenas com lancamentos financeiros com valor, priorizando `billingSummary.billingEntries` da venda.
442
443
 
443
- Modelo de saida por venda:
444
+ Modelo de saida consolidada:
444
445
 
445
446
  ```json
446
447
  {
447
448
  "merchantId": "MERCHANT_ID",
448
- "dataConsulta": "2026-09-06",
449
- "venda": {
450
- "orderId": "ORDER_ID",
451
- "shortId": "1234",
452
- "statusFinal": "CONCLUDED",
453
- "dataVenda": "2026-09-06T18:40:00Z"
454
- },
455
- "corteSemanal": {
456
- "inicio": "2026-08-31",
449
+ "periodo": {
450
+ "inicio": "2026-09-06",
457
451
  "fim": "2026-09-06"
458
452
  },
459
- "repasse": {
460
- "dataPrevista": "2026-09-16",
461
- "dataPagamento": "2026-09-16",
462
- "loteId": "LOTE_ID",
463
- "valorLote": 14082.41,
464
- "status": "SUCCEED"
465
- },
466
- "valores": {
467
- "bruto": 100,
468
- "descontos": 10,
469
- "acrescimos": 0,
470
- "liquidoVenda": 90,
471
- "liquidoRepasse": 72.5
472
- },
473
- "diferencas": []
453
+ "vendas": [
454
+ {
455
+ "orderId": "ORDER_ID",
456
+ "shortId": "1234",
457
+ "statusFinal": "CONCLUDED",
458
+ "dataVenda": "2026-09-06T18:40:00Z",
459
+ "corteSemanal": {
460
+ "inicio": "2026-08-31",
461
+ "fim": "2026-09-06"
462
+ },
463
+ "repasse": {
464
+ "dataPrevista": "2026-09-16",
465
+ "dataPagamento": "2026-09-16",
466
+ "loteId": "LOTE_ID",
467
+ "valorLote": 14082.41,
468
+ "status": "SUCCEED"
469
+ },
470
+ "valores": {
471
+ "bruto": 100,
472
+ "descontos": 10,
473
+ "acrescimos": 0,
474
+ "liquidoVenda": 90,
475
+ "liquidoRepasse": 72.5
476
+ },
477
+ "diferencas": []
478
+ }
479
+ ],
480
+ "repasses": [
481
+ {
482
+ "dataPagamento": "2026-09-16",
483
+ "loteId": "LOTE_ID",
484
+ "valor": 14082.41,
485
+ "vendas": ["ORDER_ID"]
486
+ }
487
+ ]
474
488
  }
475
489
  ```
476
490
 
@@ -69,6 +69,8 @@ class IfoodCompletoAnaliseConciliacao {
69
69
 
70
70
  const eventsByOrder = groupFinancialEventsByOrder(financialEvents);
71
71
  const settlementsIndex = indexSettlements(settlements);
72
+ const vendas = [];
73
+ const repassesMap = {};
72
74
 
73
75
  for (const sale of sales) {
74
76
  const saleId = saleIdentifier(sale);
@@ -77,10 +79,19 @@ class IfoodCompletoAnaliseConciliacao {
77
79
  const orderEvents = mergeEvents(saleId && eventsByOrder[saleId] ? eventsByOrder[saleId] : [], salesOrderEvents);
78
80
  const corte = weekCut(saleDate.slice(0, 10));
79
81
  const settlement = matchSettlement(orderEvents, settlementsIndex, corte);
80
- returnData.push({
81
- json: buildSaleConciliation({ merchantId, data, sale, saleId, saleDate, orderEvents, corte, settlement }),
82
- });
82
+ const venda = buildSaleConciliation({ sale, saleId, saleDate, orderEvents, corte, settlement });
83
+ vendas.push(venda);
84
+ addRepasseSale(repassesMap, venda);
83
85
  }
86
+
87
+ returnData.push({
88
+ json: {
89
+ merchantId,
90
+ periodo: { inicio: data, fim: data },
91
+ vendas,
92
+ repasses: Object.keys(repassesMap).map((key) => repassesMap[key]),
93
+ },
94
+ });
84
95
  }
85
96
 
86
97
  return [returnData];
@@ -215,11 +226,8 @@ function groupFinancialEventsByOrder(events) {
215
226
 
216
227
  function normalizeSaleOrderEvents(sale) {
217
228
  const events = [];
218
- for (const event of arrayFrom(sale, ['orderEvents'])) {
219
- events.push(event);
220
- }
221
229
  for (const entry of arrayFrom(sale, ['billingEntries', 'billingSummary.billingEntries'])) {
222
- events.push(Object.assign({ hasTransferImpact: true }, entry));
230
+ events.push(Object.assign({ hasTransferImpact: true, source: 'billingEntries' }, entry));
223
231
  }
224
232
  return events;
225
233
  }
@@ -290,33 +298,34 @@ function periodMatches(settlement, corte) {
290
298
  }
291
299
 
292
300
  function settlementSummary(settlement, matchedId) {
301
+ const closingItem = settlementClosingItem(settlement);
293
302
  return {
294
- loteId: matchedId || firstText(settlement, ['id', 'settlementId', 'transactionId', 'closingItemId']),
295
- dataPagamento: firstText(settlement, ['paymentDate', 'paidAt', 'date', 'settlementDate']),
296
- valorLote: firstNumber(settlement, ['balance', 'amount', 'value', 'totalAmount']),
297
- status: firstText(settlement, ['status', 'situation']),
298
- banco: objectValue(settlement, ['accountDetails', 'bankAccount', 'bank']),
299
- origem: settlement,
303
+ loteId: matchedId || firstText(closingItem, ['id', 'settlementId', 'transactionId', 'closingItemId']) || firstText(settlement, ['id', 'settlementId', 'transactionId', 'closingItemId']),
304
+ dataPagamento: firstText(closingItem, ['paymentDate', 'paidAt', 'date', 'settlementDate']) || firstText(settlement, ['paymentDate', 'paidAt', 'date', 'settlementDate']),
305
+ valorLote: firstNumber(closingItem, ['balance', 'amount', 'value', 'totalAmount']) || firstNumber(settlement, ['balance', 'amount', 'value', 'totalAmount']),
306
+ status: firstText(closingItem, ['status', 'situation']) || firstText(settlement, ['status', 'situation']),
300
307
  };
301
308
  }
302
309
 
310
+ function settlementClosingItem(settlement) {
311
+ const items = arrayFrom(settlement, ['closingItems', 'items']);
312
+ if (!items.length) return {};
313
+ return items.find((item) => firstText(item, ['type']).toUpperCase() === 'REPASSE') || items[0];
314
+ }
315
+
303
316
  function buildSaleConciliation(ctx) {
304
- const { merchantId, data, sale, saleId, saleDate, orderEvents, corte, settlement } = ctx;
317
+ const { sale, saleId, saleDate, orderEvents, corte, settlement } = ctx;
305
318
  const valoresVenda = saleValues(sale);
306
- const diferencas = orderEvents.map(normalizeFinancialEvent);
319
+ const diferencas = orderEvents.map(normalizeFinancialEvent).filter(isFinancialDifference);
307
320
  const liquidoRepasse = calculateTransferNet(diferencas, valoresVenda.liquidoVenda);
308
321
  const descontosEventos = sumBySignAndType(diferencas, 'desconto');
309
322
  const acrescimosEventos = sumBySignAndType(diferencas, 'acrescimo');
310
323
 
311
324
  return {
312
- merchantId,
313
- dataConsulta: data,
314
- venda: {
315
- orderId: saleId,
316
- shortId: firstText(sale, ['shortId', 'displayId', 'order.displayId']),
317
- statusFinal: firstText(sale, ['currentStatus', 'status', 'order.status']),
318
- dataVenda: saleDate,
319
- },
325
+ orderId: saleId,
326
+ shortId: firstText(sale, ['shortId', 'displayId', 'order.displayId']),
327
+ statusFinal: firstText(sale, ['currentStatus', 'status', 'order.status']),
328
+ dataVenda: saleDate,
320
329
  corteSemanal: corte,
321
330
  repasse: settlement ? {
322
331
  dataPrevista: firstTextFromEvents(orderEvents, ['settlement.expectedDate', 'expectedPaymentDate', 'paymentDate']) || settlement.dataPagamento,
@@ -324,14 +333,12 @@ function buildSaleConciliation(ctx) {
324
333
  loteId: settlement.loteId,
325
334
  valorLote: settlement.valorLote,
326
335
  status: settlement.status,
327
- banco: settlement.banco,
328
336
  } : {
329
337
  dataPrevista: firstTextFromEvents(orderEvents, ['settlement.expectedDate', 'expectedPaymentDate', 'paymentDate']),
330
338
  dataPagamento: '',
331
339
  loteId: '',
332
340
  valorLote: null,
333
341
  status: '',
334
- banco: null,
335
342
  },
336
343
  valores: {
337
344
  bruto: valoresVenda.bruto,
@@ -341,37 +348,104 @@ function buildSaleConciliation(ctx) {
341
348
  liquidoRepasse,
342
349
  },
343
350
  diferencas,
344
- repassesRelacionados: settlement ? [settlement.origem] : [],
345
- origem: {
346
- sale,
347
- financialEvents: orderEvents,
348
- },
349
351
  };
350
352
  }
351
353
 
354
+ function addRepasseSale(map, venda) {
355
+ const repasse = venda && venda.repasse ? venda.repasse : null;
356
+ if (!repasse || !repasse.loteId) return;
357
+ const key = `${repasse.dataPagamento}|${repasse.loteId}`;
358
+ if (!map[key]) {
359
+ map[key] = {
360
+ dataPagamento: repasse.dataPagamento,
361
+ loteId: repasse.loteId,
362
+ valor: repasse.valorLote,
363
+ vendas: [],
364
+ };
365
+ }
366
+ if (venda.orderId && map[key].vendas.indexOf(venda.orderId) === -1) {
367
+ map[key].vendas.push(venda.orderId);
368
+ }
369
+ }
370
+
352
371
  function saleValues(sale) {
372
+ const billingEntries = arrayFrom(sale, ['billingEntries', 'billingSummary.billingEntries']);
373
+ const bruto = saleGrossValue(sale) || billingEntryValue(billingEntries, 'ORDER_PAYMENT');
374
+ const liquidoVenda = firstNumber(sale, ['billingSummary.saleBalance', 'billingSummary.netValue', 'netValue', 'saleBalance']);
353
375
  return {
354
- bruto: firstNumber(sale, ['billingSummary.grossValue', 'billingSummary.total', 'total.orderAmount', 'total.price', 'orderAmount', 'amount']),
355
- descontos: firstNumber(sale, ['billingSummary.discount', 'billingSummary.discounts', 'total.discount', 'discount', 'discounts']),
356
- acrescimos: firstNumber(sale, ['billingSummary.additionalFees', 'billingSummary.addition', 'total.additionalFees', 'additionalFees', 'additions']),
357
- liquidoVenda: firstNumber(sale, ['billingSummary.saleBalance', 'billingSummary.netValue', 'netValue', 'saleBalance']),
376
+ bruto: bruto !== null ? bruto : firstNumber(sale, ['billingSummary.grossValue', 'billingSummary.total', 'total.orderAmount', 'total.price', 'orderAmount', 'amount']),
377
+ descontos: saleDiscountValue(sale),
378
+ acrescimos: firstNumber(sale, ['billingSummary.additionalFees', 'billingSummary.addition', 'total.additionalFees', 'additionalFees', 'additions', 'additionalFee']),
379
+ liquidoVenda: liquidoVenda !== null ? liquidoVenda : billingEntriesBalance(billingEntries),
358
380
  };
359
381
  }
360
382
 
383
+ function billingEntryValue(entries, name) {
384
+ for (const entry of entries) {
385
+ if (firstText(entry, ['name', 'type']).toUpperCase() !== name) continue;
386
+ const value = firstNumber(entry, ['value', 'amount.value', 'amount', 'total']);
387
+ if (value !== null) return value;
388
+ }
389
+ return null;
390
+ }
391
+
392
+ function billingEntriesBalance(entries) {
393
+ if (!entries.length) return null;
394
+ let total = 0;
395
+ let found = false;
396
+ for (const entry of entries) {
397
+ if (firstText(entry, ['name', 'type']).toUpperCase() === 'STORE_SUBSIDY') continue;
398
+ const value = firstNumber(entry, ['value', 'amount.value', 'amount', 'total']);
399
+ if (value !== null) {
400
+ total += value;
401
+ found = true;
402
+ }
403
+ }
404
+ return found ? roundCurrency(total) : null;
405
+ }
406
+
407
+ function saleGrossValue(sale) {
408
+ const total = firstNumber(sale, ['saleGrossValue.total', 'saleGrossValue.grossValue', 'saleGrossValue.value']);
409
+ if (total !== null) return total;
410
+ const bag = firstNumber(sale, ['saleGrossValue.bag']);
411
+ const deliveryFee = firstNumber(sale, ['saleGrossValue.deliveryFee']);
412
+ const serviceFee = firstNumber(sale, ['saleGrossValue.serviceFee']);
413
+ if (bag === null && deliveryFee === null && serviceFee === null) return null;
414
+ return roundCurrency((bag || 0) + (deliveryFee || 0) + (serviceFee || 0));
415
+ }
416
+
417
+ function saleDiscountValue(sale) {
418
+ const direct = firstNumber(sale, ['billingSummary.discount', 'billingSummary.discounts', 'total.discount', 'discount', 'discounts']);
419
+ if (direct !== null) return direct;
420
+ let total = 0;
421
+ let found = false;
422
+ for (const benefit of arrayFrom(sale, ['benefits', 'benefits.items', 'benefits.delivery'])) {
423
+ const value = firstNumber(benefit, ['value', 'amount', 'discount', 'sponsorshipValues.IFood', 'sponsorshipValues.MERCHANT', 'sponsorshipValues.CHAIN']);
424
+ if (value !== null) {
425
+ total += Math.abs(value);
426
+ found = true;
427
+ }
428
+ }
429
+ return found ? roundCurrency(total) : null;
430
+ }
431
+
361
432
  function normalizeFinancialEvent(event) {
362
- const tipo = firstText(event, ['type', 'eventType', 'code', 'classification']);
363
- const valor = firstNumber(event, ['amount.value', 'amount', 'value', 'total']);
433
+ const tipo = firstText(event, ['name', 'type', 'eventType', 'code', 'classification']);
434
+ const valor = firstNumber(event, ['value', 'amount.value', 'amount', 'total']);
364
435
  return {
365
436
  tipo,
366
437
  descricao: firstText(event, ['description', 'name', 'title', 'detail.description']),
367
438
  valor,
368
439
  impactaRepasse: Boolean(valueAt(event, 'hasTransferImpact')),
369
- dataEvento: firstText(event, ['eventDate', 'date', 'createdAt', 'paymentDate']),
370
- loteId: firstText(event, ['settlement.id', 'settlementId', 'payment.id', 'paymentId', 'closingItem.id', 'closingItemId']),
371
- origem: event,
372
440
  };
373
441
  }
374
442
 
443
+ function isFinancialDifference(event) {
444
+ if (!event || typeof event.valor !== 'number') return false;
445
+ if (!event.tipo && !event.descricao) return false;
446
+ return true;
447
+ }
448
+
375
449
  function calculateTransferNet(events, fallback) {
376
450
  const impacting = events.filter((event) => event.impactaRepasse && typeof event.valor === 'number');
377
451
  if (!impacting.length) return fallback;
@@ -422,14 +496,6 @@ function valueAt(object, path) {
422
496
  }, object);
423
497
  }
424
498
 
425
- function objectValue(object, paths) {
426
- for (const path of paths) {
427
- const value = valueAt(object, path);
428
- if (value && typeof value === 'object') return value;
429
- }
430
- return null;
431
- }
432
-
433
499
  function arrayFrom(object, paths) {
434
500
  for (const path of paths) {
435
501
  const value = valueAt(object, path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-ifood-completo",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Nodes n8n modulares para Merchant API iFood: autenticação, lojas, eventos, pedidos, financeiro, análise de conciliação, catálogo, produtos de catálogo, logística, shipping e avaliações.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",