@sinete/nfe 0.1.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 +55 -50
- package/dist/build/build.d.ts +23 -23
- package/dist/build/build.d.ts.map +1 -1
- package/dist/build/destinatario.d.ts +2 -2
- package/dist/build/ibscbs.d.ts.map +1 -1
- package/dist/build/nfce.d.ts +5 -5
- package/dist/build/nfce.d.ts.map +1 -1
- package/dist/build/pl.d.ts +4 -4
- package/dist/build/pl.d.ts.map +1 -1
- package/dist/build/rejeicoes.d.ts +7 -7
- package/dist/build/rejeicoes.d.ts.map +1 -1
- package/dist/build/textos.d.ts +26 -0
- package/dist/build/textos.d.ts.map +1 -0
- package/dist/build/values.d.ts +8 -8
- package/dist/build/values.d.ts.map +1 -1
- package/dist/decimal.d.ts +2 -2
- package/dist/decimal.d.ts.map +1 -1
- package/dist/format.d.ts +19 -19
- package/dist/format.d.ts.map +1 -1
- package/dist/ibs-cbs.d.ts +10 -5
- package/dist/ibs-cbs.d.ts.map +1 -1
- package/dist/ibs-cbs.js +35 -35
- package/dist/ibs-cbs.js.map +3 -3
- package/dist/index.d.ts +18 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +796 -588
- package/dist/index.js.map +27 -26
- package/dist/issues.d.ts +10 -10
- package/dist/issues.d.ts.map +1 -1
- package/dist/model.d.ts +4 -4
- package/dist/ports.d.ts +15 -15
- package/dist/ports.d.ts.map +1 -1
- package/dist/rotulo.d.ts +2 -2
- package/dist/rotulo.d.ts.map +1 -1
- package/dist/rtc.d.ts +23 -23
- package/dist/rtc.d.ts.map +1 -1
- package/dist/services/client.d.ts +44 -40
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/gzip.d.ts +1 -1
- package/dist/services/gzip.d.ts.map +1 -1
- package/dist/services/index.d.ts +4 -4
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/outcome.d.ts +3 -3
- package/dist/services/outcome.d.ts.map +1 -1
- package/dist/services/proc.d.ts +6 -6
- package/dist/services/proc.d.ts.map +1 -1
- package/dist/services/recuperar.d.ts +8 -7
- package/dist/services/recuperar.d.ts.map +1 -1
- package/dist/services/resolver.d.ts +12 -11
- package/dist/services/resolver.d.ts.map +1 -1
- package/dist/services/soap.d.ts +8 -8
- package/dist/services/soap.d.ts.map +1 -1
- package/dist/time.d.ts +4 -4
- package/dist/time.d.ts.map +1 -1
- package/dist/versao-gerada.d.ts +1 -1
- package/package.json +9 -9
- package/src/build/build.ts +213 -203
- package/src/build/destinatario.ts +8 -8
- package/src/build/ibscbs.ts +10 -6
- package/src/build/icms.ts +6 -6
- package/src/build/nfce.ts +19 -19
- package/src/build/pl.ts +12 -12
- package/src/build/rejeicoes.ts +13 -13
- package/src/build/textos.ts +228 -0
- package/src/build/values.ts +14 -14
- package/src/data/arredondamento.json +1 -1
- package/src/data/cstat.json +6 -6
- package/src/data/fusos.json +1 -1
- package/src/data/nfce-urls.json +2 -2
- package/src/data/produtor-rural.json +1 -1
- package/src/data/resp-tec.json +1 -1
- package/src/decimal.ts +10 -9
- package/src/format.ts +29 -28
- package/src/ibs-cbs.ts +79 -74
- package/src/index.ts +29 -27
- package/src/issues.ts +11 -11
- package/src/model.ts +4 -4
- package/src/ports.ts +15 -15
- package/src/rotulo.ts +4 -4
- package/src/rtc.ts +91 -91
- package/src/services/client.ts +248 -217
- package/src/services/gzip.ts +7 -7
- package/src/services/index.ts +12 -10
- package/src/services/outcome.ts +9 -9
- package/src/services/proc.ts +50 -49
- package/src/services/recuperar.ts +29 -25
- package/src/services/resolver.ts +25 -23
- package/src/services/soap.ts +35 -35
- package/src/time.ts +6 -6
- package/src/versao-gerada.ts +1 -1
package/src/build/build.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Montagem da NF-e: entrada do domínio (`
|
|
2
|
+
* Montagem da NF-e: entrada do domínio (`DadosNfe`) para o objeto tipado do `@sinete/schemas` no PL vigente, com os
|
|
3
3
|
* derivados e os totais em decimal exato, a chave de acesso e as regras conferidas antes de qualquer serialização. A
|
|
4
|
-
* saída é a string canônica de `<NFe>` ainda sem assinatura, já validada contra o schema; `
|
|
4
|
+
* saída é a string canônica de `<NFe>` ainda sem assinatura, já validada contra o schema; `assinarNfe` insere a
|
|
5
5
|
* `Signature` por splice (ADR 0003) e nada mais toca nela.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Ambiente,
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
import type { Ambiente, Assinador, ContextoDeTempo, Ocorrencia, Uf } from '@sinete/core';
|
|
9
|
+
import {
|
|
10
|
+
ErroDeConfiguracao,
|
|
11
|
+
ErroDeValidacao,
|
|
12
|
+
ehUf,
|
|
13
|
+
formatarVerProc,
|
|
14
|
+
relogioFixo,
|
|
15
|
+
tpAmbDoAmbiente,
|
|
16
|
+
ufPorSigla,
|
|
17
|
+
} from '@sinete/core';
|
|
18
|
+
import { assinarXml, codificarBase64, lerXml, primeiroFilho } from '@sinete/core/xml';
|
|
19
|
+
import type { ComplexType, EntradaDeVigencia } from '@sinete/schemas';
|
|
20
|
+
import { ErroSerializacao, serializar, validar } from '@sinete/schemas';
|
|
13
21
|
import type {
|
|
14
22
|
TNFe_infNFe,
|
|
15
23
|
TNFe_infNFe_det,
|
|
@@ -21,7 +29,7 @@ import type {
|
|
|
21
29
|
TTribNFe,
|
|
22
30
|
} from '@sinete/schemas/nfe/PL_010f';
|
|
23
31
|
import { TIS as TISCt, TNFe_infNFeSupl, TTribNFe as TTribNFeCt } from '@sinete/schemas/nfe/PL_010f';
|
|
24
|
-
import {
|
|
32
|
+
import { lerChaveAcesso, lerCnpj, lerCpf, lerIe, montarChaveAcesso } from '@sinete/validators';
|
|
25
33
|
import arredondamento from '../data/arredondamento.json' with { type: 'json' };
|
|
26
34
|
import produtorRural from '../data/produtor-rural.json' with { type: 'json' };
|
|
27
35
|
import reforma from '../data/reforma.json' with { type: 'json' };
|
|
@@ -31,6 +39,7 @@ import { Decimal } from '../decimal.ts';
|
|
|
31
39
|
import { D0302, D0302A04, D1104V, D1110V, D1203, D1302, D1302_OPC } from '../format.ts';
|
|
32
40
|
import { Issues } from '../issues.ts';
|
|
33
41
|
import type {
|
|
42
|
+
DadosNfe,
|
|
34
43
|
Destinatario,
|
|
35
44
|
DocumentoPessoa,
|
|
36
45
|
Emitente,
|
|
@@ -38,14 +47,13 @@ import type {
|
|
|
38
47
|
EnderecoExterior,
|
|
39
48
|
Item,
|
|
40
49
|
Local,
|
|
41
|
-
NfeInput,
|
|
42
50
|
Referenciada,
|
|
43
51
|
ResponsavelTecnico,
|
|
44
52
|
} from '../model.ts';
|
|
45
|
-
import type {
|
|
46
|
-
import {
|
|
53
|
+
import type { CalculadoraIbsCbs, PedidoIbsCbsItem } from '../ports.ts';
|
|
54
|
+
import { calculadoraIbsCbs } from '../rtc.ts';
|
|
47
55
|
import type { Instante } from '../time.ts';
|
|
48
|
-
import {
|
|
56
|
+
import { deslocamentoDaUf, formatarDh } from '../time.ts';
|
|
49
57
|
import { VERSAO_PACOTE } from '../versao-gerada.ts';
|
|
50
58
|
import { conferirDestinatario } from './destinatario.ts';
|
|
51
59
|
import { decimaisInvalidos, grupoInvalido, ibsCbsDoItem, totalIbsCbs } from './ibscbs.ts';
|
|
@@ -62,15 +70,16 @@ import {
|
|
|
62
70
|
} from './nfce.ts';
|
|
63
71
|
import { camposForaDoPl, escolherPl } from './pl.ts';
|
|
64
72
|
import { cstComIsento, vencimentos } from './rejeicoes.ts';
|
|
73
|
+
import { conferirTextosDaEntrada, textoXmlValido } from './textos.ts';
|
|
65
74
|
import { buildIcmsUfDest, buildIi, buildIpi, buildIssqn, buildPisCofins, buildPisCofinsSt } from './tributos.ts';
|
|
66
75
|
import type { Familia } from './values.ts';
|
|
67
76
|
import { Ctx, clean, digits, TOLERANCIA } from './values.ts';
|
|
68
77
|
|
|
69
|
-
let padrao:
|
|
78
|
+
let padrao: CalculadoraIbsCbs | undefined;
|
|
70
79
|
|
|
71
|
-
/** A calculadora de IBS/CBS usada quando `
|
|
72
|
-
function calculadoraPadrao():
|
|
73
|
-
padrao ??=
|
|
80
|
+
/** A calculadora de IBS/CBS usada quando `opcoes.ibsCbs` não vem: o motor do sinete, criada uma vez por processo. */
|
|
81
|
+
function calculadoraPadrao(): CalculadoraIbsCbs {
|
|
82
|
+
padrao ??= calculadoraIbsCbs();
|
|
74
83
|
return padrao;
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -84,18 +93,18 @@ export const XPROD_HOMOLOGACAO_NFCE = 'NOTA FISCAL EMITIDA EM AMBIENTE DE HOMOLO
|
|
|
84
93
|
|
|
85
94
|
export type ExigenciaRespTec = 'obrigatorio' | 'opcional';
|
|
86
95
|
|
|
87
|
-
export interface
|
|
96
|
+
export interface MontarNfeOpcoes {
|
|
88
97
|
readonly ambiente: Ambiente;
|
|
89
98
|
/** Relógio de emissão (dhEmi, PL vigente, CSRT) e de fato gerador (IBS/CBS). */
|
|
90
|
-
readonly
|
|
99
|
+
readonly tempo: ContextoDeTempo;
|
|
91
100
|
/**
|
|
92
|
-
* Calculadora de IBS/CBS dos itens com `ibsCbs.classificacao`. Padrão: `
|
|
101
|
+
* Calculadora de IBS/CBS dos itens com `ibsCbs.classificacao`. Padrão: `calculadoraIbsCbs()`, o motor do sinete com o
|
|
93
102
|
* dataset embarcado (importado na primeira nota que precisar dele) e as alíquotas oficiais. Informe outra para trocar
|
|
94
103
|
* dataset, alíquotas, base ou regras, ou para calcular fora do sinete.
|
|
95
104
|
*/
|
|
96
|
-
readonly ibsCbs?:
|
|
105
|
+
readonly ibsCbs?: CalculadoraIbsCbs;
|
|
97
106
|
/** Fuso do emitente em minutos; padrão pela UF (`data/fusos.json`). */
|
|
98
|
-
readonly
|
|
107
|
+
readonly deslocamentoMin?: number;
|
|
99
108
|
/** Versão do aplicativo emissor (`verProc`); padrão `sinete <versão do @sinete/nfe>` (`formatarVerProc`). */
|
|
100
109
|
readonly verProc?: string;
|
|
101
110
|
/** Sobrepõe o modo de arredondamento de uma família (`data/arredondamento.json`). */
|
|
@@ -105,7 +114,7 @@ export interface BuildNfeOptions {
|
|
|
105
114
|
/** Sobrepõe a exigência de infRespTec e CSRT da tabela por UF (`data/resp-tec.json`). */
|
|
106
115
|
readonly exigencias?: { readonly infRespTec?: ExigenciaRespTec; readonly csrt?: ExigenciaRespTec };
|
|
107
116
|
/** Fonte de aleatoriedade para o cNF; padrão `crypto.getRandomValues`. */
|
|
108
|
-
readonly
|
|
117
|
+
readonly aleatorio?: (bytes: Uint8Array) => Uint8Array;
|
|
109
118
|
/**
|
|
110
119
|
* O `vPag` do único `detPag` passa a ser o `vNF` calculado na mesma montagem, e o valor informado nele é ignorado.
|
|
111
120
|
* Serve a quem recebe à vista o total da nota e não quer calcular o `vNF` antes de montar. Mais de um `detPag`,
|
|
@@ -128,7 +137,7 @@ export interface BuildNfeOptions {
|
|
|
128
137
|
readonly urlChave?: string;
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
export interface
|
|
140
|
+
export interface NfeMontada {
|
|
132
141
|
/** Chave de acesso (44 posições). */
|
|
133
142
|
readonly chave: string;
|
|
134
143
|
/** `Id` do `infNFe` (`NFe` + chave): é o que a assinatura referencia. */
|
|
@@ -140,21 +149,21 @@ export interface BuiltNfe {
|
|
|
140
149
|
readonly tpEmis: string;
|
|
141
150
|
readonly dhEmi: string;
|
|
142
151
|
/**
|
|
143
|
-
* Só NFC-e: o `infNFeSupl` a acrescentar (`comQrCode`, `
|
|
152
|
+
* Só NFC-e: o `infNFeSupl` a acrescentar (`comQrCode`, `assinarNfe`). Na versão 3 em contingência off-line, falta a
|
|
144
153
|
* assinatura dos parâmetros, que só o certificado faz (`assinaturaQrCode`).
|
|
145
154
|
*/
|
|
146
155
|
readonly nfce?: NfceSupl;
|
|
147
156
|
/** Pacote de liberação usado (escolhido pela vigência). */
|
|
148
|
-
readonly pl:
|
|
157
|
+
readonly pl: EntradaDeVigencia;
|
|
149
158
|
/** O objeto tipado, na forma do PL_010f (o mais novo que o pacote conhece). */
|
|
150
159
|
readonly infNFe: TNFe_infNFe;
|
|
151
160
|
/** `<NFe xmlns="...">` com o `infNFe` canônico, sem assinatura, já validado contra o schema. */
|
|
152
161
|
readonly xml: string;
|
|
153
162
|
}
|
|
154
163
|
|
|
155
|
-
export type
|
|
156
|
-
| { readonly ok: true; readonly
|
|
157
|
-
| { readonly ok: false; readonly
|
|
164
|
+
export type ResultadoMontagemNfe =
|
|
165
|
+
| { readonly ok: true; readonly valor: NfeMontada }
|
|
166
|
+
| { readonly ok: false; readonly ocorrencias: readonly Ocorrencia[] };
|
|
158
167
|
|
|
159
168
|
const MODES: Record<Familia, RoundingMode> = Object.fromEntries(
|
|
160
169
|
Object.entries(arredondamento.familias).map(([k, v]) => [k, v.modo as RoundingMode]),
|
|
@@ -164,7 +173,7 @@ const SEM_GTIN = 'SEM GTIN';
|
|
|
164
173
|
const BRASILIA = -180;
|
|
165
174
|
|
|
166
175
|
function diaBrasilia(d: Instante): string {
|
|
167
|
-
return
|
|
176
|
+
return formatarDh(d, BRASILIA).slice(0, 10);
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
/** Exigência de infRespTec ou CSRT para a UF no ambiente e na data (tabela `data/resp-tec.json`). */
|
|
@@ -192,29 +201,29 @@ export function exigenciaRespTec(
|
|
|
192
201
|
/** `hashCSRT`: Base64(SHA-1(CSRT + chave de acesso)) (NT 2018.005, campo ZD09). */
|
|
193
202
|
export async function hashCsrt(csrt: string, chave: string): Promise<string> {
|
|
194
203
|
const d = await globalThis.crypto.subtle.digest('SHA-1', new TextEncoder().encode(csrt + chave));
|
|
195
|
-
return
|
|
204
|
+
return codificarBase64(new Uint8Array(d));
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
/** Normaliza CNPJ ou CPF, apontando a ocorrência do validador. */
|
|
199
208
|
function documento(doc: DocumentoPessoa, path: string, issues: Issues): { CNPJ: string } | { CPF: string } {
|
|
200
209
|
if (doc.CNPJ !== undefined) {
|
|
201
|
-
const r =
|
|
202
|
-
if (!r.ok) issues.list.push(r.
|
|
203
|
-
return { CNPJ: r.ok ? r.
|
|
210
|
+
const r = lerCnpj(doc.CNPJ, { caminho: `${path}.CNPJ` });
|
|
211
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
212
|
+
return { CNPJ: r.ok ? r.valor : doc.CNPJ };
|
|
204
213
|
}
|
|
205
|
-
const r =
|
|
206
|
-
if (!r.ok) issues.list.push(r.
|
|
207
|
-
return { CPF: r.ok ? r.
|
|
214
|
+
const r = lerCpf(doc.CPF ?? '', { caminho: `${path}.CPF` });
|
|
215
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
216
|
+
return { CPF: r.ok ? r.valor : (doc.CPF ?? '') };
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
function ie(value: string | undefined, uf: Uf, path: string, issues: Issues, allowIsento: boolean): string | undefined {
|
|
211
220
|
if (value === undefined) return undefined;
|
|
212
|
-
const r =
|
|
221
|
+
const r = lerIe(value, uf, { caminho: path, aceitarIsento: allowIsento });
|
|
213
222
|
if (!r.ok) {
|
|
214
|
-
issues.list.push(r.
|
|
223
|
+
issues.list.push(r.erro);
|
|
215
224
|
return value;
|
|
216
225
|
}
|
|
217
|
-
return r.
|
|
226
|
+
return r.valor.valor;
|
|
218
227
|
}
|
|
219
228
|
|
|
220
229
|
function endereco(e: Endereco): Record<string, string | undefined> {
|
|
@@ -242,10 +251,10 @@ function gerarChave(
|
|
|
242
251
|
random: (b: Uint8Array) => Uint8Array,
|
|
243
252
|
issues: Issues,
|
|
244
253
|
): { chave: string; cNF: string } | undefined {
|
|
245
|
-
const tentar = (cNF: string): { chave: string; ok: boolean; issue?:
|
|
246
|
-
const chave =
|
|
247
|
-
const r =
|
|
248
|
-
return r.ok ? { chave, ok: true } : { chave, ok: false, issue: r.
|
|
254
|
+
const tentar = (cNF: string): { chave: string; ok: boolean; issue?: Ocorrencia } => {
|
|
255
|
+
const chave = montarChaveAcesso({ ...parts, cNF });
|
|
256
|
+
const r = lerChaveAcesso(chave, { emissao: true, caminho: 'chave' });
|
|
257
|
+
return r.ok ? { chave, ok: true } : { chave, ok: false, issue: r.erro };
|
|
249
258
|
};
|
|
250
259
|
if (cNFInformado !== undefined) {
|
|
251
260
|
if (!/^\d{8}$/.test(cNFInformado)) {
|
|
@@ -259,9 +268,9 @@ function gerarChave(
|
|
|
259
268
|
const doCnf = r.issue.code === 'chave_cnf_invalido';
|
|
260
269
|
issues.list.push({
|
|
261
270
|
...r.issue,
|
|
262
|
-
|
|
271
|
+
caminho: doCnf ? 'cNF' : r.issue.caminho,
|
|
263
272
|
code: 'chave_invalida',
|
|
264
|
-
|
|
273
|
+
mensagem: `${r.issue.mensagem} (${r.issue.code})`,
|
|
265
274
|
origem: doCnf ? 'entrada' : 'montagem',
|
|
266
275
|
});
|
|
267
276
|
}
|
|
@@ -279,7 +288,7 @@ function gerarChave(
|
|
|
279
288
|
issues.list.push({
|
|
280
289
|
...r.issue,
|
|
281
290
|
code: 'chave_invalida',
|
|
282
|
-
|
|
291
|
+
mensagem: `${r.issue.mensagem} (${r.issue.code})`,
|
|
283
292
|
origem: 'montagem',
|
|
284
293
|
});
|
|
285
294
|
return undefined;
|
|
@@ -291,9 +300,9 @@ function gerarChave(
|
|
|
291
300
|
|
|
292
301
|
function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNFe_ide_NFref {
|
|
293
302
|
const chave = (c: string, p: string): string => {
|
|
294
|
-
const r =
|
|
295
|
-
if (!r.ok) issues.list.push(r.
|
|
296
|
-
return r.ok ? r.
|
|
303
|
+
const r = lerChaveAcesso(c, { caminho: p });
|
|
304
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
305
|
+
return r.ok ? r.valor.chave : c;
|
|
297
306
|
};
|
|
298
307
|
if ('refNFe' in ref) return { refNFe: chave(ref.refNFe, `${path}.refNFe`) };
|
|
299
308
|
if ('refNFeSig' in ref) {
|
|
@@ -312,7 +321,7 @@ function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNF
|
|
|
312
321
|
// Nota de produtor em papel (modelo 04) extinta: só referência a nota emitida antes do fim na UF dela.
|
|
313
322
|
if (nfp.mod === '04') {
|
|
314
323
|
const porUf = produtorRural.refNFP.ufs as Readonly<Record<string, { readonly vedadaDesde: string }>>;
|
|
315
|
-
const cUfSigla = Object.keys(porUf).find((k) =>
|
|
324
|
+
const cUfSigla = Object.keys(porUf).find((k) => ufPorSigla(k)?.cUF === nfp.cUF);
|
|
316
325
|
const vedadaDesde = cUfSigla ? (porUf[cUfSigla]?.vedadaDesde ?? '') : produtorRural.refNFP.modelo04.vedadaDesde;
|
|
317
326
|
if (nfp.AAMM >= vedadaDesde) {
|
|
318
327
|
issues.add(
|
|
@@ -336,25 +345,10 @@ function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNF
|
|
|
336
345
|
} as TNFe_infNFe_ide_NFref;
|
|
337
346
|
}
|
|
338
347
|
|
|
339
|
-
/** 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. */
|
|
340
|
-
function textoXmlValido(texto: string): boolean {
|
|
341
|
-
for (const ch of texto) {
|
|
342
|
-
const c = ch.codePointAt(0) ?? 0;
|
|
343
|
-
const ok =
|
|
344
|
-
c === 0x9 ||
|
|
345
|
-
c === 0xa ||
|
|
346
|
-
c === 0xd ||
|
|
347
|
-
(c >= 0x20 && c <= 0xd7ff) ||
|
|
348
|
-
(c >= 0xe000 && c <= 0xfffd) ||
|
|
349
|
-
(c >= 0x10000 && c <= 0x10ffff);
|
|
350
|
-
if (!ok) return false;
|
|
351
|
-
}
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
348
|
/**
|
|
356
|
-
* Texto com caractere que o XML não representa
|
|
357
|
-
*
|
|
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.
|
|
358
352
|
*/
|
|
359
353
|
function textosForaDoXml(value: unknown, path: string, issues: Issues): void {
|
|
360
354
|
if (typeof value === 'string') {
|
|
@@ -576,10 +570,10 @@ function montarItem(ctx: Ctx, item: Item, n: number, normal: boolean): ItemMonta
|
|
|
576
570
|
if (item.infAdProd !== undefined) det.infAdProd = item.infAdProd;
|
|
577
571
|
if (item.obsItem !== undefined) det.obsItem = item.obsItem;
|
|
578
572
|
if (item.DFeReferenciado !== undefined) {
|
|
579
|
-
const r =
|
|
580
|
-
if (!r.ok) ctx.issues.list.push(r.
|
|
573
|
+
const r = lerChaveAcesso(item.DFeReferenciado.chaveAcesso, { caminho: `${path}.DFeReferenciado.chaveAcesso` });
|
|
574
|
+
if (!r.ok) ctx.issues.list.push(r.erro);
|
|
581
575
|
det.DFeReferenciado = clean({
|
|
582
|
-
chaveAcesso: r.ok ? r.
|
|
576
|
+
chaveAcesso: r.ok ? r.valor.chave : item.DFeReferenciado.chaveAcesso,
|
|
583
577
|
nItem: item.DFeReferenciado.nItem === undefined ? undefined : String(item.DFeReferenciado.nItem),
|
|
584
578
|
});
|
|
585
579
|
}
|
|
@@ -616,44 +610,44 @@ function composicao(m: ItemMontado, incluirProd: boolean): Decimal {
|
|
|
616
610
|
}
|
|
617
611
|
|
|
618
612
|
/** Monta, calcula e valida. Nunca lança por dado do chamador: devolve as ocorrências. */
|
|
619
|
-
export async function
|
|
613
|
+
export async function montarNfe(entrada: DadosNfe, opcoes: MontarNfeOpcoes): Promise<ResultadoMontagemNfe> {
|
|
620
614
|
const issues = new Issues();
|
|
621
|
-
const finNFeIn =
|
|
622
|
-
const mod =
|
|
615
|
+
const finNFeIn = entrada.finNFe ?? '1';
|
|
616
|
+
const mod = entrada.modelo ?? '55';
|
|
623
617
|
if (mod !== '55' && mod !== '65') {
|
|
624
618
|
issues.add('modelo', 'modelo_nao_suportado', `modelo ${String(mod)}: o builder monta NF-e (55) e NFC-e (65)`);
|
|
625
|
-
return { ok: false,
|
|
619
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
626
620
|
}
|
|
627
621
|
const nfce = mod === '65';
|
|
628
622
|
// Padrões da identificação resolvidos uma vez: o mesmo valor vai para o ide e para a calculadora de IBS/CBS. A NFC-e
|
|
629
623
|
// é presencial e para consumidor final (MOC 7.0 Anexo I, B25a-10 e B25b-20).
|
|
630
|
-
const indPres =
|
|
631
|
-
const indFinal =
|
|
632
|
-
const ctx = new Ctx(issues, { ...MODES, ...
|
|
633
|
-
const pRedutorGov =
|
|
634
|
-
const emitUf =
|
|
635
|
-
if (!
|
|
624
|
+
const indPres = entrada.indPres ?? (nfce ? '1' : finNFeIn === '2' || finNFeIn === '3' ? '0' : '9');
|
|
625
|
+
const indFinal = entrada.indFinal ?? (nfce || entrada.destinatario?.indIEDest === '9' ? '1' : '0');
|
|
626
|
+
const ctx = new Ctx(issues, { ...MODES, ...opcoes.arredondamento }, finNFeIn !== '2' && finNFeIn !== '3');
|
|
627
|
+
const pRedutorGov = entrada.gCompraGov && ctx.req(entrada.gCompraGov.pRedutor, 'gCompraGov.pRedutor', D0302A04);
|
|
628
|
+
const emitUf = entrada.emitente.endereco.UF;
|
|
629
|
+
if (!ehUf(emitUf)) {
|
|
636
630
|
issues.add('emitente.endereco.UF', 'campo_invalido', 'UF do emitente inválida');
|
|
637
|
-
return { ok: false,
|
|
631
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
638
632
|
}
|
|
639
|
-
const cUF =
|
|
640
|
-
const offset =
|
|
641
|
-
const agora =
|
|
633
|
+
const cUF = ufPorSigla(emitUf)?.cUF ?? '';
|
|
634
|
+
const offset = opcoes.deslocamentoMin ?? deslocamentoDaUf(emitUf);
|
|
635
|
+
const agora = opcoes.tempo.emissao.agora();
|
|
642
636
|
// Um instante de fato gerador por montagem: o mesmo vai para a calculadora e para a regra de composição do vItem.
|
|
643
|
-
const fatoGerador =
|
|
644
|
-
const dhEmi =
|
|
637
|
+
const fatoGerador = opcoes.tempo.fatoGerador.agora();
|
|
638
|
+
const dhEmi = formatarDh(agora, offset);
|
|
645
639
|
const aamm = dhEmi.slice(2, 4) + dhEmi.slice(5, 7);
|
|
646
|
-
const tpAmb =
|
|
640
|
+
const tpAmb = tpAmbDoAmbiente(opcoes.ambiente);
|
|
647
641
|
|
|
648
|
-
// PL vigente (
|
|
642
|
+
// PL vigente (ErroVigencia do schemas propaga: data fora de toda vigência é erro de configuração, não de dado).
|
|
649
643
|
// O PL sai do mesmo instante do dhEmi: reler o relógio numa virada de vigência escolheria outro PL.
|
|
650
|
-
const pl = escolherPl(
|
|
644
|
+
const pl = escolherPl(opcoes.ambiente, relogioFixo(agora));
|
|
651
645
|
|
|
652
646
|
// Emitente (grupo C)
|
|
653
|
-
const e: Emitente =
|
|
647
|
+
const e: Emitente = entrada.emitente;
|
|
654
648
|
const emitDoc = documento(e, 'emitente', issues);
|
|
655
|
-
const serie = Number(
|
|
656
|
-
const nNF = Number(
|
|
649
|
+
const serie = Number(entrada.serie);
|
|
650
|
+
const nNF = Number(entrada.nNF);
|
|
657
651
|
if (!Number.isInteger(serie) || serie < 0 || serie > 999) issues.add('serie', 'serie_invalida', 'série de 0 a 999');
|
|
658
652
|
if (!Number.isInteger(nNF) || nNF < 1 || nNF > 999_999_999)
|
|
659
653
|
issues.add('nNF', 'campo_invalido', 'nNF de 1 a 999999999');
|
|
@@ -687,7 +681,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
687
681
|
|
|
688
682
|
// Destinatário (grupo E): obrigatório na NF-e (RV E01-10); na NFC-e, só na entrega a domicílio (E01-20, conferida
|
|
689
683
|
// com as demais regras da NFC-e).
|
|
690
|
-
const d: Destinatario | undefined =
|
|
684
|
+
const d: Destinatario | undefined = entrada.destinatario;
|
|
691
685
|
let dest: Record<string, unknown> | undefined;
|
|
692
686
|
let destUf: Uf | 'EX' | undefined;
|
|
693
687
|
if (d === undefined) {
|
|
@@ -723,7 +717,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
723
717
|
: d.IE;
|
|
724
718
|
dest = clean({
|
|
725
719
|
...docDest,
|
|
726
|
-
xNome:
|
|
720
|
+
xNome: opcoes.ambiente === 'homologacao' ? XNOME_HOMOLOGACAO : d.xNome,
|
|
727
721
|
enderDest,
|
|
728
722
|
indIEDest: d.indIEDest,
|
|
729
723
|
IE: destIe,
|
|
@@ -734,7 +728,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
734
728
|
}
|
|
735
729
|
|
|
736
730
|
// Identificação: contingência e forma de emissão
|
|
737
|
-
const cont =
|
|
731
|
+
const cont = entrada.contingencia;
|
|
738
732
|
const tpEmis = cont?.tpEmis ?? '1';
|
|
739
733
|
if (cont !== undefined) {
|
|
740
734
|
if (cont.xJust.length < 15 || cont.xJust.length > 256) {
|
|
@@ -766,25 +760,25 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
766
760
|
// Identificação que as regras do modelo conferem antes de montar.
|
|
767
761
|
// Na NFC-e a operação é sempre interna (B11a-10, rejeição 707): o endereço do consumidor não a torna interestadual.
|
|
768
762
|
const idDest =
|
|
769
|
-
|
|
770
|
-
const tpImp =
|
|
763
|
+
entrada.idDest ?? (nfce ? '1' : destUf === 'EX' ? '3' : destUf !== undefined && destUf !== emitUf ? '2' : '1');
|
|
764
|
+
const tpImp = entrada.tpImp ?? (nfce ? '4' : '1');
|
|
771
765
|
conferirDestinatario(
|
|
772
|
-
|
|
766
|
+
entrada,
|
|
773
767
|
{
|
|
774
768
|
mod,
|
|
775
|
-
tpNF:
|
|
769
|
+
tpNF: entrada.tpNF,
|
|
776
770
|
idDest,
|
|
777
|
-
idDestInformado:
|
|
771
|
+
idDestInformado: entrada.idDest !== undefined,
|
|
778
772
|
indFinal,
|
|
779
|
-
producao:
|
|
773
|
+
producao: opcoes.ambiente === 'producao',
|
|
780
774
|
},
|
|
781
775
|
issues,
|
|
782
776
|
);
|
|
783
777
|
if (nfce) {
|
|
784
778
|
conferirNfce(
|
|
785
|
-
|
|
779
|
+
entrada,
|
|
786
780
|
{
|
|
787
|
-
tpNF:
|
|
781
|
+
tpNF: entrada.tpNF,
|
|
788
782
|
idDest,
|
|
789
783
|
tpImp,
|
|
790
784
|
finNFe: finNFeIn,
|
|
@@ -796,7 +790,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
796
790
|
} else if (tpImp === '4' || tpImp === '5') {
|
|
797
791
|
issues.add('tpImp', 'campo_invalido', 'tpImp 4 e 5 (DANFC-e) são da NFC-e (B21-20, rejeição 710)');
|
|
798
792
|
}
|
|
799
|
-
const qr: QrCodeNfceOpcoes =
|
|
793
|
+
const qr: QrCodeNfceOpcoes = opcoes.qrCode ?? { versao: '3' };
|
|
800
794
|
if (nfce && qr.versao === '2') {
|
|
801
795
|
// O CSC e o idCSC são opções do montador, não da nota: as ocorrências são da montagem (ADR 0011).
|
|
802
796
|
if (!/^\d{1,6}$/.test(qr.idCSC) || Number(qr.idCSC) === 0) {
|
|
@@ -824,12 +818,13 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
824
818
|
|
|
825
819
|
// Chave de acesso e cNF
|
|
826
820
|
const random =
|
|
827
|
-
|
|
821
|
+
opcoes.aleatorio ??
|
|
822
|
+
((b: Uint8Array): Uint8Array => globalThis.crypto.getRandomValues(b as Uint8Array<ArrayBuffer>));
|
|
828
823
|
const emitente14 = 'CNPJ' in emitDoc ? emitDoc.CNPJ : emitDoc.CPF;
|
|
829
824
|
const gerada = issues.empty
|
|
830
825
|
? gerarChave(
|
|
831
826
|
{ cUF, aamm, emitente: emitente14, mod, serie: String(serie), nNF: String(nNF), tpEmis },
|
|
832
|
-
|
|
827
|
+
entrada.cNF,
|
|
833
828
|
random,
|
|
834
829
|
issues,
|
|
835
830
|
)
|
|
@@ -839,24 +834,24 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
839
834
|
const cDV = chave.slice(43);
|
|
840
835
|
|
|
841
836
|
// Itens
|
|
842
|
-
if (
|
|
837
|
+
if (entrada.itens.length === 0 || entrada.itens.length > 990) {
|
|
843
838
|
issues.add('itens', 'itens_limite', 'a NF-e tem de 1 a 990 itens');
|
|
844
839
|
}
|
|
845
|
-
const itens =
|
|
840
|
+
const itens = entrada.itens.map((it, n) => montarItem(ctx, it, n, finNFeIn === '1'));
|
|
846
841
|
// Na NFC-e em homologação, a descrição do primeiro item é a literal da RV I04-10, como o nome do destinatário da
|
|
847
842
|
// E04-20: o builder a põe, e a descrição informada fica fora do XML de teste.
|
|
848
843
|
const primeiro = itens[0];
|
|
849
|
-
if (nfce &&
|
|
844
|
+
if (nfce && opcoes.ambiente === 'homologacao' && primeiro !== undefined) {
|
|
850
845
|
(primeiro.det.prod as unknown as Record<string, unknown>).xProd = XPROD_HOMOLOGACAO_NFCE;
|
|
851
846
|
}
|
|
852
847
|
|
|
853
848
|
// IBS/CBS pela calculadora das opções, ou pelo motor do sinete
|
|
854
|
-
const classificados =
|
|
849
|
+
const classificados = entrada.itens
|
|
855
850
|
.map((it, n) => ({ it, n }))
|
|
856
851
|
.filter(({ it }) => it.impostos.ibsCbs?.classificacao !== undefined);
|
|
857
852
|
if (classificados.length > 0) {
|
|
858
|
-
const calculadora =
|
|
859
|
-
const reqs:
|
|
853
|
+
const calculadora = opcoes.ibsCbs ?? calculadoraPadrao();
|
|
854
|
+
const reqs: PedidoIbsCbsItem[] = classificados.map(({ it, n }) => {
|
|
860
855
|
const c = it.impostos.ibsCbs?.classificacao as NonNullable<
|
|
861
856
|
NonNullable<typeof it.impostos.ibsCbs>['classificacao']
|
|
862
857
|
>;
|
|
@@ -894,29 +889,29 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
894
889
|
};
|
|
895
890
|
});
|
|
896
891
|
const destino =
|
|
897
|
-
|
|
898
|
-
? { UF:
|
|
892
|
+
entrada.entrega !== undefined
|
|
893
|
+
? { UF: entrada.entrega.UF, cMun: entrada.entrega.cMun }
|
|
899
894
|
: d?.endereco !== undefined
|
|
900
895
|
? isExterior(d.endereco)
|
|
901
896
|
? { UF: 'EX' as const, cMun: '9999999' }
|
|
902
897
|
: { UF: d.endereco.UF, cMun: d.endereco.cMun }
|
|
903
898
|
: undefined;
|
|
904
|
-
const compra =
|
|
899
|
+
const compra = entrada.gCompraGov;
|
|
905
900
|
const resp = await calculadora.calcular({
|
|
906
901
|
nota: {
|
|
907
902
|
fatoGerador,
|
|
908
903
|
emissao: agora,
|
|
909
|
-
ambiente:
|
|
904
|
+
ambiente: opcoes.ambiente,
|
|
910
905
|
mod,
|
|
911
|
-
tpNF:
|
|
906
|
+
tpNF: entrada.tpNF,
|
|
912
907
|
finNFe: finNFeIn,
|
|
913
|
-
...(
|
|
914
|
-
...(
|
|
908
|
+
...(entrada.tpNFDebito ? { tpNFDebito: entrada.tpNFDebito } : {}),
|
|
909
|
+
...(entrada.tpNFCredito ? { tpNFCredito: entrada.tpNFCredito } : {}),
|
|
915
910
|
indFinal,
|
|
916
911
|
indPres,
|
|
917
912
|
emitente: { UF: emitUf, cMun: e.endereco.cMun, CRT: e.CRT },
|
|
918
913
|
...(destino ? { destino } : {}),
|
|
919
|
-
...(
|
|
914
|
+
...(entrada.cMunFGIBS ? { cMunFGIBS: entrada.cMunFGIBS } : {}),
|
|
920
915
|
...(compra && pRedutorGov
|
|
921
916
|
? {
|
|
922
917
|
compraGov: {
|
|
@@ -929,7 +924,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
929
924
|
},
|
|
930
925
|
itens: reqs,
|
|
931
926
|
});
|
|
932
|
-
issues.addAll(resp.
|
|
927
|
+
issues.addAll(resp.ocorrencias ?? [], 'montagem');
|
|
933
928
|
for (const { n } of classificados) {
|
|
934
929
|
const g = resp.itens.find((x) => x.nItem === n + 1);
|
|
935
930
|
if (g === undefined) {
|
|
@@ -951,7 +946,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
951
946
|
const ib = `itens[${n}].impostos.ibsCbs`;
|
|
952
947
|
const is = `itens[${n}].impostos.is`;
|
|
953
948
|
// O IBSCBS pronto (ibsCbs.grupo) é da entrada; o que a calculadora devolveu é da montagem (ADR 0011).
|
|
954
|
-
const ibsDaCalculadora =
|
|
949
|
+
const ibsDaCalculadora = entrada.itens[n]?.impostos.ibsCbs?.grupo === undefined;
|
|
955
950
|
const antes = issues.list.length;
|
|
956
951
|
decimaisInvalidos(imp.IBSCBS, ib, issues);
|
|
957
952
|
if (ibsDaCalculadora) issues.reclassificarDesde(antes, 'montagem');
|
|
@@ -962,7 +957,14 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
962
957
|
grupoInvalido(TISCt as ComplexType, 'IS', imp.IS, is, issues);
|
|
963
958
|
}
|
|
964
959
|
});
|
|
965
|
-
|
|
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));
|
|
967
|
+
if (!issues.empty) return { ok: false, ocorrencias: issues.classificadas };
|
|
966
968
|
|
|
967
969
|
// Totais (grupo W)
|
|
968
970
|
const gruposIbsCbs = itens
|
|
@@ -1096,43 +1098,43 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1096
1098
|
const ide = clean({
|
|
1097
1099
|
cUF,
|
|
1098
1100
|
cNF,
|
|
1099
|
-
natOp:
|
|
1101
|
+
natOp: entrada.natOp,
|
|
1100
1102
|
mod,
|
|
1101
1103
|
serie: String(serie),
|
|
1102
1104
|
nNF: String(nNF),
|
|
1103
1105
|
dhEmi,
|
|
1104
|
-
dhSaiEnt:
|
|
1105
|
-
dPrevEntrega:
|
|
1106
|
-
tpNF:
|
|
1106
|
+
dhSaiEnt: entrada.dhSaiEnt === undefined ? undefined : formatarDh(entrada.dhSaiEnt, offset),
|
|
1107
|
+
dPrevEntrega: entrada.dPrevEntrega,
|
|
1108
|
+
tpNF: entrada.tpNF,
|
|
1107
1109
|
idDest,
|
|
1108
|
-
cMunFG:
|
|
1109
|
-
cMunFGIBS:
|
|
1110
|
+
cMunFG: entrada.cMunFG ?? e.endereco.cMun,
|
|
1111
|
+
cMunFGIBS: entrada.cMunFGIBS,
|
|
1110
1112
|
tpImp,
|
|
1111
1113
|
tpEmis,
|
|
1112
1114
|
cDV,
|
|
1113
1115
|
tpAmb,
|
|
1114
1116
|
finNFe,
|
|
1115
|
-
tpNFDebito:
|
|
1116
|
-
tpNFCredito:
|
|
1117
|
+
tpNFDebito: entrada.tpNFDebito,
|
|
1118
|
+
tpNFCredito: entrada.tpNFCredito,
|
|
1117
1119
|
indFinal,
|
|
1118
1120
|
indPres,
|
|
1119
1121
|
// NT 2020.006: indIntermed obrigatório com indPres 2, 3, 4 e 9.
|
|
1120
|
-
indIntermed:
|
|
1122
|
+
indIntermed: entrada.indIntermed ?? (['2', '3', '4', '9'].includes(indPres) ? '0' : undefined),
|
|
1121
1123
|
procEmi: '0',
|
|
1122
|
-
verProc:
|
|
1123
|
-
dhCont: cont === undefined ? undefined :
|
|
1124
|
+
verProc: opcoes.verProc ?? formatarVerProc('sinete', VERSAO_PACOTE),
|
|
1125
|
+
dhCont: cont === undefined ? undefined : formatarDh(cont.dhCont, offset),
|
|
1124
1126
|
xJust: cont?.xJust,
|
|
1125
|
-
NFref:
|
|
1127
|
+
NFref: entrada.referenciadas?.map((r, n) => referencia(r, `referenciadas[${n}]`, issues)),
|
|
1126
1128
|
gCompraGov:
|
|
1127
|
-
|
|
1129
|
+
entrada.gCompraGov === undefined
|
|
1128
1130
|
? undefined
|
|
1129
1131
|
: clean({
|
|
1130
|
-
tpEnteGov:
|
|
1132
|
+
tpEnteGov: entrada.gCompraGov.tpEnteGov,
|
|
1131
1133
|
pRedutor: ctx.s(pRedutorGov ?? Decimal.ZERO, D0302A04),
|
|
1132
|
-
tpOperGov:
|
|
1133
|
-
refDFeAnt:
|
|
1134
|
+
tpOperGov: entrada.gCompraGov.tpOperGov,
|
|
1135
|
+
refDFeAnt: entrada.gCompraGov.refDFeAnt as string[] | undefined,
|
|
1134
1136
|
}),
|
|
1135
|
-
gPagAntecipado:
|
|
1137
|
+
gPagAntecipado: entrada.gPagAntecipado === undefined ? undefined : { refNFe: [...entrada.gPagAntecipado] },
|
|
1136
1138
|
});
|
|
1137
1139
|
|
|
1138
1140
|
// Locais, autorizados, transporte, cobrança, pagamento, informações adicionais
|
|
@@ -1148,10 +1150,10 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1148
1150
|
email: l.email,
|
|
1149
1151
|
IE: l.IE,
|
|
1150
1152
|
});
|
|
1151
|
-
if ((
|
|
1152
|
-
const autXML =
|
|
1153
|
+
if ((entrada.autXML?.length ?? 0) > 10) issues.add('autXML', 'campo_invalido', 'no máximo 10 autorizados');
|
|
1154
|
+
const autXML = entrada.autXML?.map((a, n) => documento(a, `autXML[${n}]`, issues));
|
|
1153
1155
|
|
|
1154
|
-
const tr =
|
|
1156
|
+
const tr = entrada.transporte;
|
|
1155
1157
|
const transp: Record<string, unknown> = { modFrete: tr?.modFrete ?? '9' };
|
|
1156
1158
|
if (tr?.transportador !== undefined) {
|
|
1157
1159
|
const t = tr.transportador;
|
|
@@ -1194,8 +1196,8 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1194
1196
|
}
|
|
1195
1197
|
|
|
1196
1198
|
let cobr: Record<string, unknown> | undefined;
|
|
1197
|
-
if (
|
|
1198
|
-
const c =
|
|
1199
|
+
if (entrada.cobranca !== undefined) {
|
|
1200
|
+
const c = entrada.cobranca;
|
|
1199
1201
|
cobr = {};
|
|
1200
1202
|
if (c.fatura !== undefined) {
|
|
1201
1203
|
const f = c.fatura;
|
|
@@ -1225,10 +1227,10 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1225
1227
|
}
|
|
1226
1228
|
}
|
|
1227
1229
|
|
|
1228
|
-
const pagIn =
|
|
1229
|
-
const igualTotal =
|
|
1230
|
+
const pagIn = entrada.pagamento ?? { detPag: [{ tPag: '90' as const, vPag: '0' }] };
|
|
1231
|
+
const igualTotal = opcoes.pagamentoIgualTotal === true;
|
|
1230
1232
|
if (igualTotal) {
|
|
1231
|
-
const unico =
|
|
1233
|
+
const unico = entrada.pagamento?.detPag.length === 1 ? entrada.pagamento.detPag[0] : undefined;
|
|
1232
1234
|
if (unico === undefined) {
|
|
1233
1235
|
issues.add('pagamento.detPag', 'pagamento_igual_total', 'pagamentoIgualTotal pede exatamente um detPag');
|
|
1234
1236
|
} else if (unico.tPag === '90') {
|
|
@@ -1253,7 +1255,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1253
1255
|
}) as unknown as PagMontado;
|
|
1254
1256
|
// Pagamentos e troco contra o vNF (W16), não o vNFTot: é o total que a YA03-10 (NT 2025.001 v1.03) e a YA09-10 (MOC 7.0
|
|
1255
1257
|
// Anexo I) citam, e a NT 2025.002 (até a v1.51) não mudou essas regras nem o W16.
|
|
1256
|
-
if (nfce &&
|
|
1258
|
+
if (nfce && entrada.pagamento !== undefined && entrada.pagamento.detPag.length > 0 && issues.empty) {
|
|
1257
1259
|
pagamentoNfce(pag, vNF, issues);
|
|
1258
1260
|
}
|
|
1259
1261
|
// W16-40 (rejeição 750): acima do limite, a NFC-e identifica o destinatário por CNPJ, CPF ou idEstrangeiro.
|
|
@@ -1264,7 +1266,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1264
1266
|
`NFC-e acima de R$ ${NFCE_LIMITE_SEM_DESTINATARIO} identifica o destinatário (W16-40, rejeição 750)`,
|
|
1265
1267
|
);
|
|
1266
1268
|
}
|
|
1267
|
-
const ia =
|
|
1269
|
+
const ia = entrada.informacoesAdicionais;
|
|
1268
1270
|
const infAdic =
|
|
1269
1271
|
ia === undefined || Object.values(ia).every((x) => x === undefined)
|
|
1270
1272
|
? undefined
|
|
@@ -1277,18 +1279,18 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1277
1279
|
});
|
|
1278
1280
|
|
|
1279
1281
|
// Responsável técnico e CSRT (NT 2018.005), com a exigência por UF como dado
|
|
1280
|
-
const exig = { ...exigenciaRespTec(emitUf,
|
|
1281
|
-
const rt =
|
|
1282
|
+
const exig = { ...exigenciaRespTec(emitUf, opcoes.ambiente, agora), ...opcoes.exigencias };
|
|
1283
|
+
const rt = entrada.respTec ?? opcoes.respTec;
|
|
1282
1284
|
let infRespTec: Record<string, unknown> | undefined;
|
|
1283
1285
|
if (rt === undefined) {
|
|
1284
1286
|
if (exig.infRespTec === 'obrigatorio') {
|
|
1285
1287
|
issues.add('respTec', 'resp_tec_obrigatorio', `a UF ${emitUf} exige o responsável técnico (rejeição 972)`);
|
|
1286
1288
|
}
|
|
1287
1289
|
} else {
|
|
1288
|
-
const cnpjRt =
|
|
1289
|
-
if (!cnpjRt.ok) issues.list.push(cnpjRt.
|
|
1290
|
+
const cnpjRt = lerCnpj(rt.CNPJ, { caminho: 'respTec.CNPJ' });
|
|
1291
|
+
if (!cnpjRt.ok) issues.list.push(cnpjRt.erro);
|
|
1290
1292
|
infRespTec = {
|
|
1291
|
-
CNPJ: cnpjRt.ok ? cnpjRt.
|
|
1293
|
+
CNPJ: cnpjRt.ok ? cnpjRt.valor : rt.CNPJ,
|
|
1292
1294
|
xContato: rt.xContato,
|
|
1293
1295
|
email: rt.email,
|
|
1294
1296
|
fone: digits(rt.fone),
|
|
@@ -1307,51 +1309,54 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1307
1309
|
ide,
|
|
1308
1310
|
emit,
|
|
1309
1311
|
dest,
|
|
1310
|
-
retirada: local(
|
|
1311
|
-
entrega: local(
|
|
1312
|
+
retirada: local(entrada.retirada, 'retirada'),
|
|
1313
|
+
entrega: local(entrada.entrega, 'entrega'),
|
|
1312
1314
|
autXML,
|
|
1313
1315
|
det: itens.map((m) => m.det),
|
|
1314
1316
|
total,
|
|
1315
1317
|
transp,
|
|
1316
1318
|
cobr,
|
|
1317
1319
|
pag,
|
|
1318
|
-
infIntermed:
|
|
1320
|
+
infIntermed: entrada.infIntermed,
|
|
1319
1321
|
infAdic,
|
|
1320
|
-
exporta:
|
|
1321
|
-
compra:
|
|
1322
|
-
cana:
|
|
1322
|
+
exporta: entrada.exporta,
|
|
1323
|
+
compra: entrada.compra,
|
|
1324
|
+
cana: entrada.cana,
|
|
1323
1325
|
infRespTec,
|
|
1324
|
-
agropecuario:
|
|
1326
|
+
agropecuario: entrada.agropecuario,
|
|
1325
1327
|
}) as unknown as TNFe_infNFe;
|
|
1326
1328
|
|
|
1327
1329
|
// Regras da SEFAZ que só dependem do documento (ADR 0012).
|
|
1328
|
-
cstComIsento(
|
|
1329
|
-
vencimentos(
|
|
1330
|
+
cstComIsento(entrada, idDest, issues);
|
|
1331
|
+
vencimentos(entrada, { dhEmi, instante: agora }, issues);
|
|
1330
1332
|
|
|
1331
1333
|
camposForaDoPl(pl.infNFe, inf, 'infNFe', pl.vigencia.pl, issues);
|
|
1332
1334
|
textosForaDoXml(inf, 'infNFe', issues);
|
|
1333
|
-
if (!issues.empty) return { ok: false,
|
|
1335
|
+
if (!issues.empty) return { ok: false, ocorrencias: issues.classificadas };
|
|
1334
1336
|
|
|
1335
1337
|
// Serialização canônica e validação estrita contra o schema do PL vigente, antes de qualquer assinatura.
|
|
1336
|
-
// O serializer recusa com
|
|
1338
|
+
// O serializer recusa com ErroSerializacao o que não cabe no modelo (escolhas exclusivas informadas juntas, por
|
|
1337
1339
|
// exemplo vagao e balsa): vira ocorrência com o caminho do XML, nunca exceção.
|
|
1338
1340
|
let xml: string;
|
|
1339
1341
|
try {
|
|
1340
|
-
xml = `<NFe xmlns="${NFE_NS}">${
|
|
1342
|
+
xml = `<NFe xmlns="${NFE_NS}">${serializar(pl.infNFe, 'infNFe', inf, NFE_NS)}</NFe>`;
|
|
1341
1343
|
} catch (e) {
|
|
1342
|
-
if (!(e instanceof
|
|
1343
|
-
return {
|
|
1344
|
+
if (!(e instanceof ErroSerializacao)) throw e;
|
|
1345
|
+
return {
|
|
1346
|
+
ok: false,
|
|
1347
|
+
ocorrencias: [{ caminho: e.caminho, code: 'schema', mensagem: e.message, origem: 'montagem' }],
|
|
1348
|
+
};
|
|
1344
1349
|
}
|
|
1345
|
-
const doc =
|
|
1346
|
-
const infEl =
|
|
1347
|
-
const schemaIssues = infEl === undefined ? [] :
|
|
1350
|
+
const doc = lerXml(xml);
|
|
1351
|
+
const infEl = primeiroFilho(doc.raiz, 'infNFe', NFE_NS);
|
|
1352
|
+
const schemaIssues = infEl === undefined ? [] : validar(pl.infNFe, infEl);
|
|
1348
1353
|
if (schemaIssues.length > 0) {
|
|
1349
1354
|
return {
|
|
1350
1355
|
ok: false,
|
|
1351
|
-
|
|
1352
|
-
|
|
1356
|
+
ocorrencias: schemaIssues.map((i) => ({
|
|
1357
|
+
caminho: i.caminho,
|
|
1353
1358
|
code: 'schema',
|
|
1354
|
-
|
|
1359
|
+
mensagem: `${i.code}: ${i.mensagem}`,
|
|
1355
1360
|
origem: 'montagem',
|
|
1356
1361
|
})),
|
|
1357
1362
|
};
|
|
@@ -1359,14 +1364,14 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1359
1364
|
let supl: NfceSupl | undefined;
|
|
1360
1365
|
if (nfce) {
|
|
1361
1366
|
// infNFeSupl (grupo ZX, NT 2025.001 item 04): endereços da UF no dia da emissão, pelo horário de Brasília.
|
|
1362
|
-
const tabela = urlsNfce(emitUf,
|
|
1363
|
-
const urlQr =
|
|
1364
|
-
const urlChave =
|
|
1367
|
+
const tabela = urlsNfce(emitUf, opcoes.ambiente, diaBrasilia(agora));
|
|
1368
|
+
const urlQr = opcoes.urlQrCode ?? tabela.qrCode;
|
|
1369
|
+
const urlChave = opcoes.urlChave ?? tabela.urlChave;
|
|
1365
1370
|
if (urlQr === undefined || !/^https?:\/\//i.test(urlQr)) {
|
|
1366
1371
|
issues.montagem(
|
|
1367
1372
|
'urlQrCode',
|
|
1368
1373
|
'qrcode_invalido',
|
|
1369
|
-
`sem endereço completo do QR Code da NFC-e para ${emitUf} em ${
|
|
1374
|
+
`sem endereço completo do QR Code da NFC-e para ${emitUf} em ${opcoes.ambiente}: informe urlQrCode (data/nfce-urls.json)`,
|
|
1370
1375
|
);
|
|
1371
1376
|
}
|
|
1372
1377
|
if (urlChave === undefined) {
|
|
@@ -1377,11 +1382,11 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1377
1382
|
);
|
|
1378
1383
|
}
|
|
1379
1384
|
if (urlQr === undefined || urlChave === undefined || !issues.empty)
|
|
1380
|
-
return { ok: false,
|
|
1385
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
1381
1386
|
const p = await parametrosQrCode(
|
|
1382
1387
|
{
|
|
1383
1388
|
chave,
|
|
1384
|
-
ambiente:
|
|
1389
|
+
ambiente: opcoes.ambiente,
|
|
1385
1390
|
tpEmis,
|
|
1386
1391
|
dhEmi,
|
|
1387
1392
|
vNF: (inf.total as { ICMSTot: { vNF: string } }).ICMSTot.vNF,
|
|
@@ -1395,13 +1400,13 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1395
1400
|
// off-line, com uma assinatura do tamanho da de uma chave RSA de 2048 bits no lugar da que o certificado ainda vai
|
|
1396
1401
|
// fazer; o `comQrCode` confere de novo com a assinatura verdadeira.
|
|
1397
1402
|
const suplIssues = conferirSupl(
|
|
1398
|
-
inserirSupl({ xml, nfce: supl } as
|
|
1403
|
+
inserirSupl({ xml, nfce: supl } as NfeMontada, supl.assinar ? ASSINATURA_2048 : undefined),
|
|
1399
1404
|
);
|
|
1400
|
-
if (suplIssues.length > 0) return { ok: false,
|
|
1405
|
+
if (suplIssues.length > 0) return { ok: false, ocorrencias: suplIssues };
|
|
1401
1406
|
}
|
|
1402
1407
|
return {
|
|
1403
1408
|
ok: true,
|
|
1404
|
-
|
|
1409
|
+
valor: {
|
|
1405
1410
|
chave,
|
|
1406
1411
|
id: `NFe${chave}`,
|
|
1407
1412
|
cNF,
|
|
@@ -1418,13 +1423,18 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1418
1423
|
}
|
|
1419
1424
|
|
|
1420
1425
|
/** Base64 de uma assinatura RSA de 2048 bits (256 bytes): a forma da assinatura do QR Code off-line na montagem. */
|
|
1421
|
-
const ASSINATURA_2048 =
|
|
1426
|
+
const ASSINATURA_2048 = codificarBase64(new Uint8Array(256));
|
|
1422
1427
|
|
|
1423
1428
|
/** Ocorrências do `infNFeSupl` do XML contra o schema (`TNFe_infNFeSupl`), como de montagem. */
|
|
1424
|
-
function conferirSupl(xml: string):
|
|
1425
|
-
const el =
|
|
1426
|
-
const erros = el === undefined ? [] :
|
|
1427
|
-
return erros.map((i) => ({
|
|
1429
|
+
function conferirSupl(xml: string): Ocorrencia[] {
|
|
1430
|
+
const el = primeiroFilho(lerXml(xml).raiz, 'infNFeSupl', NFE_NS);
|
|
1431
|
+
const erros = el === undefined ? [] : validar(TNFe_infNFeSupl as ComplexType, el);
|
|
1432
|
+
return erros.map((i) => ({
|
|
1433
|
+
caminho: i.caminho,
|
|
1434
|
+
code: 'schema',
|
|
1435
|
+
mensagem: `${i.code}: ${i.mensagem}`,
|
|
1436
|
+
origem: 'montagem',
|
|
1437
|
+
}));
|
|
1428
1438
|
}
|
|
1429
1439
|
|
|
1430
1440
|
const escapeXml = (s: string): string => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -1434,43 +1444,43 @@ const escapeXml = (s: string): string => s.replace(/&/g, '&').replace(/</g,
|
|
|
1434
1444
|
* certificado que assina a nota; Manual do DANFE NFC-e 6.0, 4.4.2). `undefined` quando o QR Code não leva assinatura
|
|
1435
1445
|
* (NF-e, emissão normal, versão 2).
|
|
1436
1446
|
*/
|
|
1437
|
-
export async function assinaturaQrCode(
|
|
1438
|
-
return
|
|
1447
|
+
export async function assinaturaQrCode(nota: NfeMontada, assinador: Assinador): Promise<string | undefined> {
|
|
1448
|
+
return nota.nfce?.assinar === true ? assinarParametros(nota.nfce.parametros, assinador) : undefined;
|
|
1439
1449
|
}
|
|
1440
1450
|
|
|
1441
1451
|
/**
|
|
1442
1452
|
* A NFC-e montada com o `infNFeSupl` (QR Code e `urlChave`) inserido por splice antes do fechamento de `NFe`, pronta
|
|
1443
1453
|
* para a assinatura. Na versão 3 em contingência off-line, informe a `assinatura` (`assinaturaQrCode`); nos demais
|
|
1444
1454
|
* casos ela não existe (ZX02-330, rejeição 445; ZX02-334, rejeição 474). Para assinar em três fases (A3, HSM), passe
|
|
1445
|
-
* este texto ao `
|
|
1455
|
+
* este texto ao `prepararAssinatura` do `@sinete/core/xml` com o `id` da nota. Na NF-e (modelo 55), devolve o XML como
|
|
1446
1456
|
* veio: ela não tem `infNFeSupl` (ZX01-10, rejeição 393).
|
|
1447
1457
|
*/
|
|
1448
|
-
export function comQrCode(
|
|
1449
|
-
const xml = inserirSupl(
|
|
1458
|
+
export function comQrCode(nota: NfeMontada, assinatura?: string): string {
|
|
1459
|
+
const xml = inserirSupl(nota, assinatura);
|
|
1450
1460
|
// Com a assinatura verdadeira, o qrCode pode passar do tamanho que a montagem conferiu (chave maior que 2048 bits).
|
|
1451
1461
|
if (assinatura !== undefined) {
|
|
1452
1462
|
const issues = conferirSupl(xml);
|
|
1453
|
-
if (issues.length > 0) throw new
|
|
1463
|
+
if (issues.length > 0) throw new ErroDeValidacao('o QR Code da NFC-e não passou no schema', issues);
|
|
1454
1464
|
}
|
|
1455
1465
|
return xml;
|
|
1456
1466
|
}
|
|
1457
1467
|
|
|
1458
|
-
function inserirSupl(built:
|
|
1468
|
+
function inserirSupl(built: NfeMontada, assinatura: string | undefined): string {
|
|
1459
1469
|
const s = built.nfce;
|
|
1460
1470
|
if (s === undefined) {
|
|
1461
|
-
if (assinatura !== undefined) throw new
|
|
1471
|
+
if (assinatura !== undefined) throw new ErroDeConfiguracao('a NF-e (modelo 55) não tem QR Code');
|
|
1462
1472
|
return built.xml;
|
|
1463
1473
|
}
|
|
1464
1474
|
if (s.assinar && assinatura === undefined) {
|
|
1465
|
-
throw new
|
|
1475
|
+
throw new ErroDeConfiguracao(
|
|
1466
1476
|
'NFC-e em contingência off-line com QR Code versão 3 precisa da assinatura (ZX02-334, rejeição 474)',
|
|
1467
1477
|
);
|
|
1468
1478
|
}
|
|
1469
1479
|
if (!s.assinar && assinatura !== undefined) {
|
|
1470
|
-
throw new
|
|
1480
|
+
throw new ErroDeConfiguracao('este QR Code não leva assinatura (ZX02-330, rejeição 445)');
|
|
1471
1481
|
}
|
|
1472
1482
|
const fim = '</NFe>';
|
|
1473
|
-
if (!built.xml.endsWith(fim)) throw new
|
|
1483
|
+
if (!built.xml.endsWith(fim)) throw new ErroDeConfiguracao('NFC-e montada fora da forma esperada');
|
|
1474
1484
|
const qrCode = `${s.base}${s.parametros}${assinatura === undefined ? '' : `|${assinatura}`}`;
|
|
1475
1485
|
const supl = `<infNFeSupl><qrCode>${escapeXml(qrCode)}</qrCode><urlChave>${escapeXml(s.urlChave)}</urlChave></infNFeSupl>`;
|
|
1476
1486
|
return built.xml.slice(0, -fim.length) + supl + fim;
|
|
@@ -1481,6 +1491,6 @@ function inserirSupl(built: BuiltNfe, assinatura: string | undefined): string {
|
|
|
1481
1491
|
* contingência off-line pede); depois, a `Signature` como último filho de `NFe`, tudo por splice, e devolve a string
|
|
1482
1492
|
* final. É essa string que vai para a SEFAZ e para o banco; nada depois deve reparseá-la para reescrever.
|
|
1483
1493
|
*/
|
|
1484
|
-
export async function
|
|
1485
|
-
return
|
|
1494
|
+
export async function assinarNfe(nota: NfeMontada, assinador: Assinador): Promise<string> {
|
|
1495
|
+
return assinarXml(comQrCode(nota, await assinaturaQrCode(nota, assinador)), { id: nota.id }, assinador);
|
|
1486
1496
|
}
|