@sinete/nfe 0.2.0 → 0.3.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.
- package/README.md +6 -2
- package/dist/build/build.d.ts.map +1 -1
- package/dist/build/textos.d.ts +26 -0
- package/dist/build/textos.d.ts.map +1 -0
- package/dist/ibs-cbs.d.ts +5 -0
- package/dist/ibs-cbs.d.ts.map +1 -1
- package/dist/ibs-cbs.js.map +2 -2
- package/dist/index.js +200 -21
- package/dist/index.js.map +8 -7
- package/dist/services/recuperar.d.ts +3 -2
- package/dist/services/recuperar.d.ts.map +1 -1
- package/dist/services/resolver.d.ts +4 -3
- package/dist/services/resolver.d.ts.map +1 -1
- package/dist/versao-gerada.d.ts +1 -1
- package/package.json +9 -9
- package/src/build/build.ts +11 -18
- package/src/build/textos.ts +228 -0
- package/src/ibs-cbs.ts +5 -0
- package/src/services/recuperar.ts +4 -2
- package/src/services/resolver.ts +5 -3
- package/src/versao-gerada.ts +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* função lê o evento de lá e só o dá como registrado quando o retorno dele diz 135, 136 ou 155 para a mesma chave e o
|
|
9
9
|
* mesmo tipo.
|
|
10
10
|
*/
|
|
11
|
-
import type { ClienteNfe, EventoRegistrado, ResultadoConsulta } from './client.js';
|
|
11
|
+
import type { ClienteNfe, EnvioOpcoes, EventoRegistrado, ResultadoConsulta } from './client.js';
|
|
12
12
|
/** Resultado da recuperação: o evento registrado, com a consulta que o prova, ou só a consulta. */
|
|
13
13
|
export type RecuperacaoEvento = {
|
|
14
14
|
readonly registrado: true;
|
|
@@ -23,6 +23,7 @@ export type RecuperacaoEvento = {
|
|
|
23
23
|
* vários, como na CC-e; com `nSeqEvento`, só o dessa sequência). Serve depois de um pedido de evento sem resposta ou
|
|
24
24
|
* respondido com 573 ou 580: nunca conclua que o evento existe só pelo `cStat` do pedido. `registrado: false` quer
|
|
25
25
|
* dizer que a consulta não mostrou o evento (ou não decidiu: veja `consulta`); não quer dizer que o evento não existe.
|
|
26
|
+
* `opcoes.signal` cancela a consulta.
|
|
26
27
|
*/
|
|
27
|
-
export declare function recuperarEventoRegistrado(cliente: ClienteNfe, chave: string, tpEvento: string, nSeqEvento?: number): Promise<RecuperacaoEvento>;
|
|
28
|
+
export declare function recuperarEventoRegistrado(cliente: ClienteNfe, chave: string, tpEvento: string, nSeqEvento?: number, opcoes?: EnvioOpcoes): Promise<RecuperacaoEvento>;
|
|
28
29
|
//# sourceMappingURL=recuperar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recuperar.d.ts","sourceRoot":"","sources":["../../src/services/recuperar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"recuperar.d.ts","sourceRoot":"","sources":["../../src/services/recuperar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIhG,mGAAmG;AACnG,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,GACtG;IAAE,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAkDzE;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,UAAU,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAW5B"}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* Acesso [chNFe: ...]") e serviço de consulta protocolo (cStat 217; 561, 562 e 613 quando a numeração tem outra NF-e).
|
|
27
27
|
*/
|
|
28
28
|
import type { Recusado } from '@sinete/core';
|
|
29
|
-
import type { ClienteNfe, ResultadoAutorizacao, ResultadoConsulta } from './client.js';
|
|
29
|
+
import type { ClienteNfe, EnvioOpcoes, ResultadoAutorizacao, ResultadoConsulta } from './client.js';
|
|
30
30
|
/**
|
|
31
31
|
* O que o `digVal` do protocolo diz dos bytes assinados: `confere` (é o DigestValue deles), `sem-digval` (o protocolo
|
|
32
32
|
* não o traz) ou `difere` (a SEFAZ registrou outro conteúdo com a mesma chave).
|
|
@@ -82,7 +82,8 @@ export type ResolucaoEnvio =
|
|
|
82
82
|
export declare function chaveDaDuplicidade(xMotivo: string): string | undefined;
|
|
83
83
|
/**
|
|
84
84
|
* Resolve um envio de autorização sem resposta, ou cuja resposta foi 204 ou 539, consultando a chave da NF-e
|
|
85
|
-
* assinada. `anterior` é o desfecho do envio, quando houve um.
|
|
85
|
+
* assinada. `anterior` é o desfecho do envio, quando houve um. `opcoes.signal` cancela a consulta (lança o
|
|
86
|
+
* `ErroTransporte` com `code: 'cancelado'`).
|
|
86
87
|
*/
|
|
87
|
-
export declare function resolverEnvioSemResposta(cliente: ClienteNfe, nfeAssinada: string, anterior?: ResultadoAutorizacao): Promise<ResolucaoEnvio>;
|
|
88
|
+
export declare function resolverEnvioSemResposta(cliente: ClienteNfe, nfeAssinada: string, anterior?: ResultadoAutorizacao, opcoes?: EnvioOpcoes): Promise<ResolucaoEnvio>;
|
|
88
89
|
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/services/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAgB,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/services/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAgB,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIlH;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEtE,yFAAyF;AACzF,MAAM,MAAM,cAAc;AACxB;;;;GAIG;AACD;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;CAC1C;AACH,gFAAgF;GAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AAC7D;;;GAGG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,iBAAiB,CAAC;CAC/C;AACH;;;;GAIG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE;AACrH;;;GAGG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAK3E,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtE;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,oBAAoB,EAC/B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAiCzB"}
|
package/dist/versao-gerada.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sinete/nfe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "NF-e modelo 55: modelo de entrada tipado, montagem com totais em decimal exato, validação antes de assinar, autorização, consultas, eventos, inutilização, contingência SVC e Distribuição DF-e",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nfe",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"build": "bun ../../scripts/build.ts ."
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@sinete/core": "^0.
|
|
48
|
-
"@sinete/ibs-cbs": "^0.2.
|
|
49
|
-
"@sinete/ibs-cbs-dados": "
|
|
50
|
-
"@sinete/rejeicoes": "^0.
|
|
51
|
-
"@sinete/schemas": "^0.2.
|
|
52
|
-
"@sinete/transport": "^0.2.
|
|
53
|
-
"@sinete/validators": "^0.2.
|
|
47
|
+
"@sinete/core": "^0.3.0",
|
|
48
|
+
"@sinete/ibs-cbs": "^0.2.1",
|
|
49
|
+
"@sinete/ibs-cbs-dados": ">=2026.9.3",
|
|
50
|
+
"@sinete/rejeicoes": "^0.3.0",
|
|
51
|
+
"@sinete/schemas": "^0.2.1",
|
|
52
|
+
"@sinete/transport": "^0.2.1",
|
|
53
|
+
"@sinete/validators": "^0.2.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@sinete/sefaz-sim": "^0.2.
|
|
56
|
+
"@sinete/sefaz-sim": "^0.2.1"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|
package/src/build/build.ts
CHANGED
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
} from './nfce.ts';
|
|
71
71
|
import { camposForaDoPl, escolherPl } from './pl.ts';
|
|
72
72
|
import { cstComIsento, vencimentos } from './rejeicoes.ts';
|
|
73
|
+
import { conferirTextosDaEntrada, textoXmlValido } from './textos.ts';
|
|
73
74
|
import { buildIcmsUfDest, buildIi, buildIpi, buildIssqn, buildPisCofins, buildPisCofinsSt } from './tributos.ts';
|
|
74
75
|
import type { Familia } from './values.ts';
|
|
75
76
|
import { Ctx, clean, digits, TOLERANCIA } from './values.ts';
|
|
@@ -344,25 +345,10 @@ function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNF
|
|
|
344
345
|
} as TNFe_infNFe_ide_NFref;
|
|
345
346
|
}
|
|
346
347
|
|
|
347
|
-
/** Produção `Char` do XML 1.0: tab, LF, CR, U+0020 a U+D7FF, U+E000 a U+FFFD e U+10000 a U+10FFFF. */
|
|
348
|
-
function textoXmlValido(texto: string): boolean {
|
|
349
|
-
for (const ch of texto) {
|
|
350
|
-
const c = ch.codePointAt(0) ?? 0;
|
|
351
|
-
const ok =
|
|
352
|
-
c === 0x9 ||
|
|
353
|
-
c === 0xa ||
|
|
354
|
-
c === 0xd ||
|
|
355
|
-
(c >= 0x20 && c <= 0xd7ff) ||
|
|
356
|
-
(c >= 0xe000 && c <= 0xfffd) ||
|
|
357
|
-
(c >= 0x10000 && c <= 0x10ffff);
|
|
358
|
-
if (!ok) return false;
|
|
359
|
-
}
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
348
|
/**
|
|
364
|
-
* Texto com caractere que o XML não representa
|
|
365
|
-
*
|
|
349
|
+
* Texto com caractere que o XML não representa vira ocorrência com o caminho do campo, em vez de falhar no parse do
|
|
350
|
+
* documento montado. Os textos da entrada já foram conferidos com o caminho dela (`conferirTextosDaEntrada`): aqui só
|
|
351
|
+
* sobra o que veio das opções da montagem.
|
|
366
352
|
*/
|
|
367
353
|
function textosForaDoXml(value: unknown, path: string, issues: Issues): void {
|
|
368
354
|
if (typeof value === 'string') {
|
|
@@ -971,6 +957,13 @@ export async function montarNfe(entrada: DadosNfe, opcoes: MontarNfeOpcoes): Pro
|
|
|
971
957
|
grupoInvalido(TISCt as ComplexType, 'IS', imp.IS, is, issues);
|
|
972
958
|
}
|
|
973
959
|
});
|
|
960
|
+
// Texto e tamanho dos campos de texto pelo tipo do PL, com o caminho da entrada (ADR 0011); o campo que outra
|
|
961
|
+
// conferência já recusou fica só com a ocorrência dela.
|
|
962
|
+
// Em homologação, o nome do destinatário e, na NFC-e, a descrição do primeiro item são as literais da E04-20 e da I04-10:
|
|
963
|
+
// o texto informado não vai ao XML.
|
|
964
|
+
const substituidos =
|
|
965
|
+
opcoes.ambiente === 'homologacao' ? ['destinatario.xNome', ...(nfce ? ['itens[0].produto.xProd'] : [])] : [];
|
|
966
|
+
conferirTextosDaEntrada(entrada, pl.infNFe as ComplexType, issues, new Set(substituidos));
|
|
974
967
|
if (!issues.empty) return { ok: false, ocorrencias: issues.classificadas };
|
|
975
968
|
|
|
976
969
|
// Totais (grupo W)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texto e tamanho dos campos de texto da NF-e conferidos na entrada (ADR 0011, revisão de 01/10/2026).
|
|
3
|
+
*
|
|
4
|
+
* Antes, o caractere que o XML não representa e o texto fora do tipo do leiaute (tamanho, espaço nas pontas,
|
|
5
|
+
* caractere fora do `TString`) só apareciam na montagem, com o caminho do XML (`infNFe.det[0].prod.xProd` ou
|
|
6
|
+
* `/infNFe/det[1]/prod/xProd`). Conferidos aqui, saem como `campo_invalido`, com `origem: 'entrada'`, o caminho da
|
|
7
|
+
* entrada (`itens[0].produto.xProd`) e uma mensagem para quem preenche o campo (`no máximo 60 caracteres`), sem nome
|
|
8
|
+
* de validador nem de tipo do XSD.
|
|
9
|
+
*
|
|
10
|
+
* O tipo de cada campo vem do schema do PL da montagem (o `TString` com o `maxLength` do elemento), nunca de um número
|
|
11
|
+
* escrito aqui: a tabela abaixo só diz qual elemento do `infNFe` recebe cada campo da entrada.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ComplexType, OcorrenciaSchema, Particle, SimpleType } from '@sinete/schemas';
|
|
15
|
+
import { conferirTipoSimples, ehComplexType, ehElementParticle, ehWildcard } from '@sinete/schemas';
|
|
16
|
+
import type { Issues } from '../issues.ts';
|
|
17
|
+
import type { DadosNfe } from '../model.ts';
|
|
18
|
+
|
|
19
|
+
/** Produção `Char` do XML 1.0: tab, LF, CR, U+0020 a U+D7FF, U+E000 a U+FFFD e U+10000 a U+10FFFF. */
|
|
20
|
+
export function textoXmlValido(texto: string): boolean {
|
|
21
|
+
for (const ch of texto) {
|
|
22
|
+
const c = ch.codePointAt(0) ?? 0;
|
|
23
|
+
const ok =
|
|
24
|
+
c === 0x9 ||
|
|
25
|
+
c === 0xa ||
|
|
26
|
+
c === 0xd ||
|
|
27
|
+
(c >= 0x20 && c <= 0xd7ff) ||
|
|
28
|
+
(c >= 0xe000 && c <= 0xfffd) ||
|
|
29
|
+
(c >= 0x10000 && c <= 0x10ffff);
|
|
30
|
+
if (!ok) return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Campo de texto da entrada (com `[]` onde a entrada é uma lista) e o elemento do `infNFe` que o recebe como veio. Só
|
|
37
|
+
* os campos copiados sem transformação: o que a montagem calcula ou formata é conferido por ela.
|
|
38
|
+
*/
|
|
39
|
+
const CAMPOS: readonly (readonly [entrada: string, xml: string])[] = [
|
|
40
|
+
['natOp', 'ide.natOp'],
|
|
41
|
+
['emitente.xNome', 'emit.xNome'],
|
|
42
|
+
['emitente.xFant', 'emit.xFant'],
|
|
43
|
+
['emitente.endereco.xLgr', 'emit.enderEmit.xLgr'],
|
|
44
|
+
['emitente.endereco.nro', 'emit.enderEmit.nro'],
|
|
45
|
+
['emitente.endereco.xCpl', 'emit.enderEmit.xCpl'],
|
|
46
|
+
['emitente.endereco.xBairro', 'emit.enderEmit.xBairro'],
|
|
47
|
+
['emitente.endereco.xMun', 'emit.enderEmit.xMun'],
|
|
48
|
+
['destinatario.xNome', 'dest.xNome'],
|
|
49
|
+
['destinatario.email', 'dest.email'],
|
|
50
|
+
['destinatario.endereco.xLgr', 'dest.enderDest.xLgr'],
|
|
51
|
+
['destinatario.endereco.nro', 'dest.enderDest.nro'],
|
|
52
|
+
['destinatario.endereco.xCpl', 'dest.enderDest.xCpl'],
|
|
53
|
+
['destinatario.endereco.xBairro', 'dest.enderDest.xBairro'],
|
|
54
|
+
['destinatario.endereco.xMun', 'dest.enderDest.xMun'],
|
|
55
|
+
['destinatario.endereco.xPais', 'dest.enderDest.xPais'],
|
|
56
|
+
...(['retirada', 'entrega'] as const).flatMap((g) =>
|
|
57
|
+
(['xNome', 'xLgr', 'nro', 'xCpl', 'xBairro', 'xMun', 'email'] as const).map(
|
|
58
|
+
(c) => [`${g}.${c}`, `${g}.${c}`] as const,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
['itens[].produto.cProd', 'det.prod.cProd'],
|
|
62
|
+
['itens[].produto.xProd', 'det.prod.xProd'],
|
|
63
|
+
['itens[].produto.uCom', 'det.prod.uCom'],
|
|
64
|
+
['itens[].produto.uTrib', 'det.prod.uTrib'],
|
|
65
|
+
['itens[].produto.xPed', 'det.prod.xPed'],
|
|
66
|
+
['itens[].infAdProd', 'det.infAdProd'],
|
|
67
|
+
['transporte.transportador.xNome', 'transp.transporta.xNome'],
|
|
68
|
+
['transporte.transportador.xEnder', 'transp.transporta.xEnder'],
|
|
69
|
+
['transporte.transportador.xMun', 'transp.transporta.xMun'],
|
|
70
|
+
['transporte.volumes[].esp', 'transp.vol.esp'],
|
|
71
|
+
['transporte.volumes[].marca', 'transp.vol.marca'],
|
|
72
|
+
['transporte.volumes[].nVol', 'transp.vol.nVol'],
|
|
73
|
+
['cobranca.fatura.nFat', 'cobr.fat.nFat'],
|
|
74
|
+
['cobranca.duplicatas[].nDup', 'cobr.dup.nDup'],
|
|
75
|
+
['pagamento.detPag[].xPag', 'pag.detPag.xPag'],
|
|
76
|
+
['informacoesAdicionais.infAdFisco', 'infAdic.infAdFisco'],
|
|
77
|
+
['informacoesAdicionais.infCpl', 'infAdic.infCpl'],
|
|
78
|
+
['informacoesAdicionais.obsCont[].xTexto', 'infAdic.obsCont.xTexto'],
|
|
79
|
+
['informacoesAdicionais.obsFisco[].xTexto', 'infAdic.obsFisco.xTexto'],
|
|
80
|
+
['compra.xNEmp', 'compra.xNEmp'],
|
|
81
|
+
['compra.xPed', 'compra.xPed'],
|
|
82
|
+
['compra.xCont', 'compra.xCont'],
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/** O tipo do elemento `nome` entre as partículas (sequências e escolhas aninhadas). */
|
|
86
|
+
function elemento(p: Particle | undefined, nome: string): ComplexType | SimpleType | undefined {
|
|
87
|
+
if (p === undefined || ehWildcard(p)) return undefined;
|
|
88
|
+
if (ehElementParticle(p)) return p.e === nome ? p.t : undefined;
|
|
89
|
+
for (const i of p.i) {
|
|
90
|
+
const t = elemento(i, nome);
|
|
91
|
+
if (t !== undefined) return t;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** O tipo simples do elemento em `caminho` (pontos) a partir do `infNFe`; `undefined` se o PL não o tem. */
|
|
97
|
+
function tipoEm(infNFe: ComplexType, caminho: string): SimpleType | undefined {
|
|
98
|
+
let t: ComplexType | SimpleType | undefined = infNFe;
|
|
99
|
+
for (const nome of caminho.split('.')) {
|
|
100
|
+
if (t === undefined || !ehComplexType(t)) return undefined;
|
|
101
|
+
t = elemento(t.c, nome);
|
|
102
|
+
}
|
|
103
|
+
return t === undefined || ehComplexType(t) ? undefined : t;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const tiposPorPl = new WeakMap<ComplexType, ReadonlyMap<string, SimpleType>>();
|
|
107
|
+
|
|
108
|
+
/** Tipo de cada campo da tabela no PL, calculado uma vez por PL. */
|
|
109
|
+
function tiposDoPl(infNFe: ComplexType): ReadonlyMap<string, SimpleType> {
|
|
110
|
+
let m = tiposPorPl.get(infNFe);
|
|
111
|
+
if (m === undefined) {
|
|
112
|
+
const novo = new Map<string, SimpleType>();
|
|
113
|
+
for (const [entrada, xml] of CAMPOS) {
|
|
114
|
+
const t = tipoEm(infNFe, xml);
|
|
115
|
+
if (t !== undefined) novo.set(entrada, t);
|
|
116
|
+
}
|
|
117
|
+
m = novo;
|
|
118
|
+
tiposPorPl.set(infNFe, m);
|
|
119
|
+
}
|
|
120
|
+
return m;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Os valores de `caminho` (com `[]` nas listas) na entrada, com o caminho concreto de cada um. */
|
|
124
|
+
function valoresEm(raiz: unknown, caminho: string): [string, unknown][] {
|
|
125
|
+
let atuais: [string, unknown][] = [['', raiz]];
|
|
126
|
+
for (const parte of caminho.split('.')) {
|
|
127
|
+
const lista = parte.endsWith('[]');
|
|
128
|
+
const chave = lista ? parte.slice(0, -2) : parte;
|
|
129
|
+
const proximos: [string, unknown][] = [];
|
|
130
|
+
for (const [c, v] of atuais) {
|
|
131
|
+
if (typeof v !== 'object' || v === null) continue;
|
|
132
|
+
const filho = (v as Record<string, unknown>)[chave];
|
|
133
|
+
const base = c === '' ? chave : `${c}.${chave}`;
|
|
134
|
+
if (!lista) proximos.push([base, filho]);
|
|
135
|
+
else if (Array.isArray(filho)) for (const [n, f] of filho.entries()) proximos.push([`${base}[${n}]`, f]);
|
|
136
|
+
}
|
|
137
|
+
atuais = proximos;
|
|
138
|
+
}
|
|
139
|
+
return atuais;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Caminhos de todos os textos da entrada com caractere que o XML não representa. */
|
|
143
|
+
function textosForaDoXml(valor: unknown, caminho: string, saida: string[]): void {
|
|
144
|
+
if (typeof valor === 'string') {
|
|
145
|
+
if (!textoXmlValido(valor)) saida.push(caminho);
|
|
146
|
+
} else if (Array.isArray(valor)) {
|
|
147
|
+
for (const [n, v] of valor.entries()) textosForaDoXml(v, `${caminho}[${n}]`, saida);
|
|
148
|
+
} else if (typeof valor === 'object' && valor !== null) {
|
|
149
|
+
for (const [k, v] of Object.entries(valor)) textosForaDoXml(v, caminho === '' ? k : `${caminho}.${k}`, saida);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Mensagem do caractere que não vai ao documento e que a pessoa não consegue ver (controle, invisível). */
|
|
154
|
+
const CARACTERE_INVISIVEL = 'caractere não aceito (símbolo ou caractere de controle)';
|
|
155
|
+
|
|
156
|
+
/** Caractere que a pessoa enxerga: letra, marca, número, pontuação ou símbolo. */
|
|
157
|
+
const VISIVEL = /^[\p{L}\p{M}\p{N}\p{P}\p{S}]$/u;
|
|
158
|
+
|
|
159
|
+
/** O primeiro caractere de `valor` que o tipo não aceita no meio de um texto, ou `undefined` se todos passam. */
|
|
160
|
+
function caractereRecusado(tipo: SimpleType, valor: string): string | undefined {
|
|
161
|
+
for (const ch of new Set(valor)) {
|
|
162
|
+
const saida: OcorrenciaSchema[] = [];
|
|
163
|
+
conferirTipoSimples(tipo, `A${ch}A`, '', saida);
|
|
164
|
+
if (saida.some((o) => o.code === 'padrao')) return ch;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* As regras do tipo que `valor` viola, em texto para quem preenche o campo. O código do validador decide a regra; a
|
|
171
|
+
* mensagem diz o limite (do próprio tipo) e, no `padrao`, se o problema é o espaço nas pontas ou um caractere.
|
|
172
|
+
*/
|
|
173
|
+
function mensagensDoTexto(tipo: SimpleType, valor: string, violadas: readonly OcorrenciaSchema[]): string[] {
|
|
174
|
+
if (violadas.length === 0) return [];
|
|
175
|
+
if (valor.trim() === '') return ['não pode ficar em branco'];
|
|
176
|
+
const tamanho = [...valor].length;
|
|
177
|
+
const mensagens = new Set<string>();
|
|
178
|
+
for (const o of violadas) {
|
|
179
|
+
if (o.code === 'tamanho_maximo' && tipo.mx !== undefined)
|
|
180
|
+
mensagens.add(`no máximo ${tipo.mx} caracteres (tem ${tamanho})`);
|
|
181
|
+
else if (o.code === 'tamanho_minimo' && tipo.mn !== undefined)
|
|
182
|
+
mensagens.add(`no mínimo ${tipo.mn} caracteres (tem ${tamanho})`);
|
|
183
|
+
else if (o.code === 'tamanho' && tipo.l !== undefined)
|
|
184
|
+
mensagens.add(`exatamente ${tipo.l} caracteres (tem ${tamanho})`);
|
|
185
|
+
else if (o.code === 'padrao') {
|
|
186
|
+
const espaco = valor !== valor.trim();
|
|
187
|
+
const ch = caractereRecusado(tipo, valor);
|
|
188
|
+
if (espaco) mensagens.add('sem espaço no começo nem no fim');
|
|
189
|
+
if (ch !== undefined) mensagens.add(VISIVEL.test(ch) ? `caractere não aceito: “${ch}”` : CARACTERE_INVISIVEL);
|
|
190
|
+
if (!espaco && ch === undefined) mensagens.add('formato não aceito');
|
|
191
|
+
} else mensagens.add('valor não aceito neste campo');
|
|
192
|
+
}
|
|
193
|
+
return [...mensagens];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Confere os textos da entrada: em qualquer campo, o caractere que o XML não representa; nos campos da tabela, o tipo
|
|
198
|
+
* do elemento no PL (tamanho, espaço nas pontas, caractere fora do conjunto aceito). Tudo sai como `campo_invalido`,
|
|
199
|
+
* com o caminho da entrada, `origem: 'entrada'` e uma ocorrência por regra violada. O campo que já tem ocorrência (de
|
|
200
|
+
* outra conferência da entrada) não ganha outra. `substituidos` são os campos que a montagem troca por um texto fixo
|
|
201
|
+
* (o nome do destinatário em homologação): o tipo deles não é conferido, porque o texto informado não vai ao XML.
|
|
202
|
+
*/
|
|
203
|
+
export function conferirTextosDaEntrada(
|
|
204
|
+
entrada: DadosNfe,
|
|
205
|
+
infNFe: ComplexType,
|
|
206
|
+
issues: Issues,
|
|
207
|
+
substituidos: ReadonlySet<string> = new Set(),
|
|
208
|
+
): void {
|
|
209
|
+
const jaRecusados = new Set(issues.list.map((i) => i.caminho));
|
|
210
|
+
const foraDoXml: string[] = [];
|
|
211
|
+
textosForaDoXml(entrada, '', foraDoXml);
|
|
212
|
+
for (const c of foraDoXml) {
|
|
213
|
+
if (jaRecusados.has(c) || substituidos.has(c)) continue;
|
|
214
|
+
issues.add(c, 'campo_invalido', CARACTERE_INVISIVEL);
|
|
215
|
+
jaRecusados.add(c);
|
|
216
|
+
}
|
|
217
|
+
const tipos = tiposDoPl(infNFe);
|
|
218
|
+
for (const [campo] of CAMPOS) {
|
|
219
|
+
const tipo = tipos.get(campo);
|
|
220
|
+
if (tipo === undefined) continue;
|
|
221
|
+
for (const [caminho, valor] of valoresEm(entrada, campo)) {
|
|
222
|
+
if (typeof valor !== 'string' || jaRecusados.has(caminho) || substituidos.has(caminho)) continue;
|
|
223
|
+
const saida: OcorrenciaSchema[] = [];
|
|
224
|
+
conferirTipoSimples(tipo, valor, caminho, saida);
|
|
225
|
+
for (const m of mensagensDoTexto(tipo, valor, saida)) issues.add(caminho, 'campo_invalido', m);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
package/src/ibs-cbs.ts
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* embarcado não entra aqui, para não ir para o bundle de quem não o usa: `carregarDatasetEmbarcado()`, na raiz do
|
|
8
8
|
* `@sinete/nfe`, o importa sob demanda. `Dec`, `DataIso` e `Vigencia` vêm do motor (os de `Dec` e `DataIso` são os
|
|
9
9
|
* mesmos; a `Vigencia` dos dados, com os campos do dataset, fica acessível pelos tipos que a usam).
|
|
10
|
+
*
|
|
11
|
+
* Experimental (ADR 0016, seção 5) até o `@sinete/ibs-cbs` sair 1.0: a calculadora espera a norma da base de cálculo
|
|
12
|
+
* (NT 2025.002, UB16-10), e este subpath pode mudar em minor, sempre com changeset.
|
|
13
|
+
*
|
|
14
|
+
* @experimental
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
// biome-ignore lint/performance/noReExportAll: o subpath existe para reexportar o motor inteiro, e as duas pontas são do mesmo repo.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { DocumentoXml, ElementoXml } from '@sinete/core/xml';
|
|
13
13
|
import { lerXml, primeiroFilho, textoDe } from '@sinete/core/xml';
|
|
14
|
-
import type { ClienteNfe, EventoRegistrado, ResultadoConsulta } from './client.ts';
|
|
14
|
+
import type { ClienteNfe, EnvioOpcoes, EventoRegistrado, ResultadoConsulta } from './client.ts';
|
|
15
15
|
import { cstatEm } from './outcome.ts';
|
|
16
16
|
import { NFE_NS, recortarElemento } from './proc.ts';
|
|
17
17
|
|
|
@@ -73,14 +73,16 @@ function lerProcEvento(xml: string): EventoRegistrado | undefined {
|
|
|
73
73
|
* vários, como na CC-e; com `nSeqEvento`, só o dessa sequência). Serve depois de um pedido de evento sem resposta ou
|
|
74
74
|
* respondido com 573 ou 580: nunca conclua que o evento existe só pelo `cStat` do pedido. `registrado: false` quer
|
|
75
75
|
* dizer que a consulta não mostrou o evento (ou não decidiu: veja `consulta`); não quer dizer que o evento não existe.
|
|
76
|
+
* `opcoes.signal` cancela a consulta.
|
|
76
77
|
*/
|
|
77
78
|
export async function recuperarEventoRegistrado(
|
|
78
79
|
cliente: ClienteNfe,
|
|
79
80
|
chave: string,
|
|
80
81
|
tpEvento: string,
|
|
81
82
|
nSeqEvento?: number,
|
|
83
|
+
opcoes?: EnvioOpcoes,
|
|
82
84
|
): Promise<RecuperacaoEvento> {
|
|
83
|
-
const consulta = await cliente.consultar(chave);
|
|
85
|
+
const consulta = await cliente.consultar(chave, undefined, opcoes);
|
|
84
86
|
if (consulta.tipo !== 'autorizado' && consulta.tipo !== 'denegado') return { registrado: false, consulta };
|
|
85
87
|
let achado: EventoRegistrado | undefined;
|
|
86
88
|
for (const xml of consulta.valor.eventos) {
|
package/src/services/resolver.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import type { Recusado } from '@sinete/core';
|
|
30
30
|
import { criarAutorizado, criarDenegado } from '@sinete/core';
|
|
31
|
-
import type { ClienteNfe, ProtocoloNfe, ResultadoAutorizacao, ResultadoConsulta } from './client.ts';
|
|
31
|
+
import type { ClienteNfe, EnvioOpcoes, ProtocoloNfe, ResultadoAutorizacao, ResultadoConsulta } from './client.ts';
|
|
32
32
|
import { cstatEm } from './outcome.ts';
|
|
33
33
|
import { documentoAssinado } from './proc.ts';
|
|
34
34
|
|
|
@@ -85,12 +85,14 @@ export function chaveDaDuplicidade(xMotivo: string): string | undefined {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Resolve um envio de autorização sem resposta, ou cuja resposta foi 204 ou 539, consultando a chave da NF-e
|
|
88
|
-
* assinada. `anterior` é o desfecho do envio, quando houve um.
|
|
88
|
+
* assinada. `anterior` é o desfecho do envio, quando houve um. `opcoes.signal` cancela a consulta (lança o
|
|
89
|
+
* `ErroTransporte` com `code: 'cancelado'`).
|
|
89
90
|
*/
|
|
90
91
|
export async function resolverEnvioSemResposta(
|
|
91
92
|
cliente: ClienteNfe,
|
|
92
93
|
nfeAssinada: string,
|
|
93
94
|
anterior?: ResultadoAutorizacao,
|
|
95
|
+
opcoes?: EnvioOpcoes,
|
|
94
96
|
): Promise<ResolucaoEnvio> {
|
|
95
97
|
const a = documentoAssinado(nfeAssinada, 'NFe', 'infNFe');
|
|
96
98
|
const chave = a.id.slice(3);
|
|
@@ -98,7 +100,7 @@ export async function resolverEnvioSemResposta(
|
|
|
98
100
|
const outra = chaveDaDuplicidade(anterior.xMotivo);
|
|
99
101
|
return { acao: 'divergente', motivo: anterior, ...(outra === undefined ? {} : { chNFe: outra }) };
|
|
100
102
|
}
|
|
101
|
-
const consulta = await cliente.consultar(chave, nfeAssinada);
|
|
103
|
+
const consulta = await cliente.consultar(chave, nfeAssinada, opcoes);
|
|
102
104
|
if (consulta.tipo === 'recusado') {
|
|
103
105
|
if (cstatEm(consulta.cStat, 'naoConsta')) return { acao: 'reenviar', nfeAssinada: a.xml };
|
|
104
106
|
// 561, 562 e 613 na consulta: a chave local não consta, mas a numeração dela tem outra NF-e (outro mês, outro cNF
|
package/src/versao-gerada.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// GERADO por scripts/versao-gerada.ts a partir do package.json. Não edite: rode `bun scripts/versao-gerada.ts`.
|
|
2
2
|
/** Versão de `@sinete/nfe`, para o padrão de `verProc`/`verAplic` (`formatarVerProc` do @sinete/core). */
|
|
3
|
-
export const VERSAO_PACOTE = '0.
|
|
3
|
+
export const VERSAO_PACOTE = '0.3.0';
|