@wondai/n8n-nodes-nucleo 0.7.0 → 0.8.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.
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Nucleo = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
const quote_contract_1 = require("./quote-contract");
|
|
6
7
|
/**
|
|
7
8
|
* Node Núcleo Wondai — satélite de IA → Núcleo (ADR-003/013/015/016).
|
|
8
9
|
*
|
|
@@ -336,6 +337,15 @@ class Nucleo {
|
|
|
336
337
|
description: "ID da mensagem enviada (opcional), para auditoria da apresentação.",
|
|
337
338
|
displayOptions: { show: { resource: ["pedido"], operation: ["apresentarCotacao"] } },
|
|
338
339
|
},
|
|
340
|
+
{
|
|
341
|
+
displayName: "Message ID da Cotação",
|
|
342
|
+
name: "cotacaoRequestMessageId",
|
|
343
|
+
type: "string",
|
|
344
|
+
default: "",
|
|
345
|
+
required: true,
|
|
346
|
+
description: "messageID real do turno. O package combina este valor com o hash do payload para gerar a Idempotency-Key.",
|
|
347
|
+
displayOptions: { show: { resource: ["pedido"], operation: ["cotar"] } },
|
|
348
|
+
},
|
|
339
349
|
{
|
|
340
350
|
displayName: "Telefone do Cliente",
|
|
341
351
|
name: "cotacaoTelefone",
|
|
@@ -1529,15 +1539,16 @@ class Nucleo {
|
|
|
1529
1539
|
}
|
|
1530
1540
|
else if (resource === "pedido" && operation === "cotar") {
|
|
1531
1541
|
const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1542
|
+
(0, quote_contract_1.assertQuoteContract)(body);
|
|
1543
|
+
const messageId = this.getNodeParameter("cotacaoRequestMessageId", i).trim();
|
|
1544
|
+
idempotencyKey = (0, quote_contract_1.buildQuoteIdempotencyKey)(tokenId, messageId, body);
|
|
1535
1545
|
method = "POST";
|
|
1536
1546
|
path = "/api/v1/agent/pedido/cotacao";
|
|
1537
1547
|
bodyObj = body;
|
|
1538
1548
|
}
|
|
1539
1549
|
else if (resource === "pedido" && operation === "validarCotacao") {
|
|
1540
1550
|
const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
|
|
1551
|
+
(0, quote_contract_1.assertQuoteContract)(body);
|
|
1541
1552
|
method = "POST";
|
|
1542
1553
|
path = "/api/v1/agent/pedido/cotacao/validar";
|
|
1543
1554
|
bodyObj = body;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IDataObject } from "n8n-workflow";
|
|
2
|
+
export declare function canonicalQuotePayload(payload: IDataObject): string;
|
|
3
|
+
export declare function buildQuoteIdempotencyKey(tokenId: string, messageId: string, payload: IDataObject): string;
|
|
4
|
+
export declare function assertQuoteContract(payload: IDataObject): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalQuotePayload = canonicalQuotePayload;
|
|
4
|
+
exports.buildQuoteIdempotencyKey = buildQuoteIdempotencyKey;
|
|
5
|
+
exports.assertQuoteContract = assertQuoteContract;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
function canonicalize(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map(canonicalize);
|
|
10
|
+
if (!value || typeof value !== "object") {
|
|
11
|
+
return typeof value === "string" ? value.normalize("NFC") : value;
|
|
12
|
+
}
|
|
13
|
+
return Object.fromEntries(Object.entries(value)
|
|
14
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
15
|
+
.map(([key, child]) => [key, canonicalize(child)]));
|
|
16
|
+
}
|
|
17
|
+
function canonicalQuotePayload(payload) {
|
|
18
|
+
return JSON.stringify(canonicalize(payload));
|
|
19
|
+
}
|
|
20
|
+
function buildQuoteIdempotencyKey(tokenId, messageId, payload) {
|
|
21
|
+
const message = messageId.trim();
|
|
22
|
+
if (!message)
|
|
23
|
+
throw new Error("Cotar pedido: messageID obrigatorio.");
|
|
24
|
+
const hash = (0, node_crypto_1.createHash)("sha256")
|
|
25
|
+
.update(canonicalQuotePayload(payload), "utf8")
|
|
26
|
+
.digest("hex")
|
|
27
|
+
.slice(0, 16);
|
|
28
|
+
return `quote:${tokenId}:${message}:${hash}`;
|
|
29
|
+
}
|
|
30
|
+
function assertQuoteContract(payload) {
|
|
31
|
+
const items = payload.itens;
|
|
32
|
+
if (!Array.isArray(items) || items.length === 0 || items.length > 10) {
|
|
33
|
+
throw new Error("Cotacao: itens precisa conter entre 1 e 10 linhas.");
|
|
34
|
+
}
|
|
35
|
+
for (const [index, raw] of items.entries()) {
|
|
36
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
37
|
+
throw new Error(`Cotacao: itens[${index}] invalido.`);
|
|
38
|
+
}
|
|
39
|
+
const item = raw;
|
|
40
|
+
const informed = item.quantidade_informada;
|
|
41
|
+
if (!String(item.produto_id ?? "").trim() ||
|
|
42
|
+
!String(item.consulta_original ?? "").trim() ||
|
|
43
|
+
!String(item.quantidade_original ?? "").trim() ||
|
|
44
|
+
!informed ||
|
|
45
|
+
!Number.isFinite(Number(informed.valor)) ||
|
|
46
|
+
Number(informed.valor) <= 0 ||
|
|
47
|
+
!String(informed.unidade ?? "").trim()) {
|
|
48
|
+
throw new Error(`Cotacao: itens[${index}] exige produto_id, consulta_original, quantidade_original e quantidade_informada.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const charges = Array.isArray(payload.cobrancas) ? payload.cobrancas : [];
|
|
52
|
+
for (const [index, raw] of charges.entries()) {
|
|
53
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
54
|
+
continue;
|
|
55
|
+
const charge = raw;
|
|
56
|
+
if (charge.tipo !== "paid_addon")
|
|
57
|
+
continue;
|
|
58
|
+
if (!String(charge.regra_chave ?? "").trim() ||
|
|
59
|
+
!String(charge.operational_config_revision_id ?? "").trim()) {
|
|
60
|
+
throw new Error(`Cotacao: cobrancas[${index}] paid_addon exige regra_chave e operational_config_revision_id.`);
|
|
61
|
+
}
|
|
62
|
+
if (charge.nome != null || charge.valor != null) {
|
|
63
|
+
throw new Error(`Cotacao: cobrancas[${index}] nao pode enviar nome ou valor; o Nucleo resolve pela revisao.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wondai/n8n-nodes-nucleo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Node n8n para o Núcleo Wondai — atendimento de IA multi-vertical (gate liga/desliga, cliente/paciente, catálogo fuzzy, pedidos [padaria], procedimento/agenda/agendamento [odonto], contexto operacional, telemetria) com assinatura HMAC v1. Tenant e vertical vêm do token (a parede, ADR-013/038).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "node scripts/build.mjs",
|
|
27
27
|
"dev": "tsc --watch",
|
|
28
|
+
"test": "node scripts/test.mjs",
|
|
28
29
|
"prepublishOnly": "node scripts/build.mjs"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|