@sinete/nfe 0.1.0 → 0.2.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 +49 -48
- 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/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 +5 -5
- package/dist/ibs-cbs.d.ts.map +1 -1
- package/dist/ibs-cbs.js +35 -35
- package/dist/ibs-cbs.js.map +2 -2
- package/dist/index.d.ts +18 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +602 -573
- package/dist/index.js.map +25 -25
- 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 +7 -7
- package/dist/services/recuperar.d.ts.map +1 -1
- package/dist/services/resolver.d.ts +10 -10
- 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 +202 -185
- 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/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 +74 -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 +27 -25
- package/src/services/resolver.ts +22 -22
- 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';
|
|
@@ -66,11 +74,11 @@ import { buildIcmsUfDest, buildIi, buildIpi, buildIssqn, buildPisCofins, buildPi
|
|
|
66
74
|
import type { Familia } from './values.ts';
|
|
67
75
|
import { Ctx, clean, digits, TOLERANCIA } from './values.ts';
|
|
68
76
|
|
|
69
|
-
let padrao:
|
|
77
|
+
let padrao: CalculadoraIbsCbs | undefined;
|
|
70
78
|
|
|
71
|
-
/** A calculadora de IBS/CBS usada quando `
|
|
72
|
-
function calculadoraPadrao():
|
|
73
|
-
padrao ??=
|
|
79
|
+
/** A calculadora de IBS/CBS usada quando `opcoes.ibsCbs` não vem: o motor do sinete, criada uma vez por processo. */
|
|
80
|
+
function calculadoraPadrao(): CalculadoraIbsCbs {
|
|
81
|
+
padrao ??= calculadoraIbsCbs();
|
|
74
82
|
return padrao;
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -84,18 +92,18 @@ export const XPROD_HOMOLOGACAO_NFCE = 'NOTA FISCAL EMITIDA EM AMBIENTE DE HOMOLO
|
|
|
84
92
|
|
|
85
93
|
export type ExigenciaRespTec = 'obrigatorio' | 'opcional';
|
|
86
94
|
|
|
87
|
-
export interface
|
|
95
|
+
export interface MontarNfeOpcoes {
|
|
88
96
|
readonly ambiente: Ambiente;
|
|
89
97
|
/** Relógio de emissão (dhEmi, PL vigente, CSRT) e de fato gerador (IBS/CBS). */
|
|
90
|
-
readonly
|
|
98
|
+
readonly tempo: ContextoDeTempo;
|
|
91
99
|
/**
|
|
92
|
-
* Calculadora de IBS/CBS dos itens com `ibsCbs.classificacao`. Padrão: `
|
|
100
|
+
* Calculadora de IBS/CBS dos itens com `ibsCbs.classificacao`. Padrão: `calculadoraIbsCbs()`, o motor do sinete com o
|
|
93
101
|
* dataset embarcado (importado na primeira nota que precisar dele) e as alíquotas oficiais. Informe outra para trocar
|
|
94
102
|
* dataset, alíquotas, base ou regras, ou para calcular fora do sinete.
|
|
95
103
|
*/
|
|
96
|
-
readonly ibsCbs?:
|
|
104
|
+
readonly ibsCbs?: CalculadoraIbsCbs;
|
|
97
105
|
/** Fuso do emitente em minutos; padrão pela UF (`data/fusos.json`). */
|
|
98
|
-
readonly
|
|
106
|
+
readonly deslocamentoMin?: number;
|
|
99
107
|
/** Versão do aplicativo emissor (`verProc`); padrão `sinete <versão do @sinete/nfe>` (`formatarVerProc`). */
|
|
100
108
|
readonly verProc?: string;
|
|
101
109
|
/** Sobrepõe o modo de arredondamento de uma família (`data/arredondamento.json`). */
|
|
@@ -105,7 +113,7 @@ export interface BuildNfeOptions {
|
|
|
105
113
|
/** Sobrepõe a exigência de infRespTec e CSRT da tabela por UF (`data/resp-tec.json`). */
|
|
106
114
|
readonly exigencias?: { readonly infRespTec?: ExigenciaRespTec; readonly csrt?: ExigenciaRespTec };
|
|
107
115
|
/** Fonte de aleatoriedade para o cNF; padrão `crypto.getRandomValues`. */
|
|
108
|
-
readonly
|
|
116
|
+
readonly aleatorio?: (bytes: Uint8Array) => Uint8Array;
|
|
109
117
|
/**
|
|
110
118
|
* O `vPag` do único `detPag` passa a ser o `vNF` calculado na mesma montagem, e o valor informado nele é ignorado.
|
|
111
119
|
* 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 +136,7 @@ export interface BuildNfeOptions {
|
|
|
128
136
|
readonly urlChave?: string;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
export interface
|
|
139
|
+
export interface NfeMontada {
|
|
132
140
|
/** Chave de acesso (44 posições). */
|
|
133
141
|
readonly chave: string;
|
|
134
142
|
/** `Id` do `infNFe` (`NFe` + chave): é o que a assinatura referencia. */
|
|
@@ -140,21 +148,21 @@ export interface BuiltNfe {
|
|
|
140
148
|
readonly tpEmis: string;
|
|
141
149
|
readonly dhEmi: string;
|
|
142
150
|
/**
|
|
143
|
-
* Só NFC-e: o `infNFeSupl` a acrescentar (`comQrCode`, `
|
|
151
|
+
* Só NFC-e: o `infNFeSupl` a acrescentar (`comQrCode`, `assinarNfe`). Na versão 3 em contingência off-line, falta a
|
|
144
152
|
* assinatura dos parâmetros, que só o certificado faz (`assinaturaQrCode`).
|
|
145
153
|
*/
|
|
146
154
|
readonly nfce?: NfceSupl;
|
|
147
155
|
/** Pacote de liberação usado (escolhido pela vigência). */
|
|
148
|
-
readonly pl:
|
|
156
|
+
readonly pl: EntradaDeVigencia;
|
|
149
157
|
/** O objeto tipado, na forma do PL_010f (o mais novo que o pacote conhece). */
|
|
150
158
|
readonly infNFe: TNFe_infNFe;
|
|
151
159
|
/** `<NFe xmlns="...">` com o `infNFe` canônico, sem assinatura, já validado contra o schema. */
|
|
152
160
|
readonly xml: string;
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
export type
|
|
156
|
-
| { readonly ok: true; readonly
|
|
157
|
-
| { readonly ok: false; readonly
|
|
163
|
+
export type ResultadoMontagemNfe =
|
|
164
|
+
| { readonly ok: true; readonly valor: NfeMontada }
|
|
165
|
+
| { readonly ok: false; readonly ocorrencias: readonly Ocorrencia[] };
|
|
158
166
|
|
|
159
167
|
const MODES: Record<Familia, RoundingMode> = Object.fromEntries(
|
|
160
168
|
Object.entries(arredondamento.familias).map(([k, v]) => [k, v.modo as RoundingMode]),
|
|
@@ -164,7 +172,7 @@ const SEM_GTIN = 'SEM GTIN';
|
|
|
164
172
|
const BRASILIA = -180;
|
|
165
173
|
|
|
166
174
|
function diaBrasilia(d: Instante): string {
|
|
167
|
-
return
|
|
175
|
+
return formatarDh(d, BRASILIA).slice(0, 10);
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
/** Exigência de infRespTec ou CSRT para a UF no ambiente e na data (tabela `data/resp-tec.json`). */
|
|
@@ -192,29 +200,29 @@ export function exigenciaRespTec(
|
|
|
192
200
|
/** `hashCSRT`: Base64(SHA-1(CSRT + chave de acesso)) (NT 2018.005, campo ZD09). */
|
|
193
201
|
export async function hashCsrt(csrt: string, chave: string): Promise<string> {
|
|
194
202
|
const d = await globalThis.crypto.subtle.digest('SHA-1', new TextEncoder().encode(csrt + chave));
|
|
195
|
-
return
|
|
203
|
+
return codificarBase64(new Uint8Array(d));
|
|
196
204
|
}
|
|
197
205
|
|
|
198
206
|
/** Normaliza CNPJ ou CPF, apontando a ocorrência do validador. */
|
|
199
207
|
function documento(doc: DocumentoPessoa, path: string, issues: Issues): { CNPJ: string } | { CPF: string } {
|
|
200
208
|
if (doc.CNPJ !== undefined) {
|
|
201
|
-
const r =
|
|
202
|
-
if (!r.ok) issues.list.push(r.
|
|
203
|
-
return { CNPJ: r.ok ? r.
|
|
209
|
+
const r = lerCnpj(doc.CNPJ, { caminho: `${path}.CNPJ` });
|
|
210
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
211
|
+
return { CNPJ: r.ok ? r.valor : doc.CNPJ };
|
|
204
212
|
}
|
|
205
|
-
const r =
|
|
206
|
-
if (!r.ok) issues.list.push(r.
|
|
207
|
-
return { CPF: r.ok ? r.
|
|
213
|
+
const r = lerCpf(doc.CPF ?? '', { caminho: `${path}.CPF` });
|
|
214
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
215
|
+
return { CPF: r.ok ? r.valor : (doc.CPF ?? '') };
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
function ie(value: string | undefined, uf: Uf, path: string, issues: Issues, allowIsento: boolean): string | undefined {
|
|
211
219
|
if (value === undefined) return undefined;
|
|
212
|
-
const r =
|
|
220
|
+
const r = lerIe(value, uf, { caminho: path, aceitarIsento: allowIsento });
|
|
213
221
|
if (!r.ok) {
|
|
214
|
-
issues.list.push(r.
|
|
222
|
+
issues.list.push(r.erro);
|
|
215
223
|
return value;
|
|
216
224
|
}
|
|
217
|
-
return r.
|
|
225
|
+
return r.valor.valor;
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
function endereco(e: Endereco): Record<string, string | undefined> {
|
|
@@ -242,10 +250,10 @@ function gerarChave(
|
|
|
242
250
|
random: (b: Uint8Array) => Uint8Array,
|
|
243
251
|
issues: Issues,
|
|
244
252
|
): { 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.
|
|
253
|
+
const tentar = (cNF: string): { chave: string; ok: boolean; issue?: Ocorrencia } => {
|
|
254
|
+
const chave = montarChaveAcesso({ ...parts, cNF });
|
|
255
|
+
const r = lerChaveAcesso(chave, { emissao: true, caminho: 'chave' });
|
|
256
|
+
return r.ok ? { chave, ok: true } : { chave, ok: false, issue: r.erro };
|
|
249
257
|
};
|
|
250
258
|
if (cNFInformado !== undefined) {
|
|
251
259
|
if (!/^\d{8}$/.test(cNFInformado)) {
|
|
@@ -259,9 +267,9 @@ function gerarChave(
|
|
|
259
267
|
const doCnf = r.issue.code === 'chave_cnf_invalido';
|
|
260
268
|
issues.list.push({
|
|
261
269
|
...r.issue,
|
|
262
|
-
|
|
270
|
+
caminho: doCnf ? 'cNF' : r.issue.caminho,
|
|
263
271
|
code: 'chave_invalida',
|
|
264
|
-
|
|
272
|
+
mensagem: `${r.issue.mensagem} (${r.issue.code})`,
|
|
265
273
|
origem: doCnf ? 'entrada' : 'montagem',
|
|
266
274
|
});
|
|
267
275
|
}
|
|
@@ -279,7 +287,7 @@ function gerarChave(
|
|
|
279
287
|
issues.list.push({
|
|
280
288
|
...r.issue,
|
|
281
289
|
code: 'chave_invalida',
|
|
282
|
-
|
|
290
|
+
mensagem: `${r.issue.mensagem} (${r.issue.code})`,
|
|
283
291
|
origem: 'montagem',
|
|
284
292
|
});
|
|
285
293
|
return undefined;
|
|
@@ -291,9 +299,9 @@ function gerarChave(
|
|
|
291
299
|
|
|
292
300
|
function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNFe_ide_NFref {
|
|
293
301
|
const chave = (c: string, p: string): string => {
|
|
294
|
-
const r =
|
|
295
|
-
if (!r.ok) issues.list.push(r.
|
|
296
|
-
return r.ok ? r.
|
|
302
|
+
const r = lerChaveAcesso(c, { caminho: p });
|
|
303
|
+
if (!r.ok) issues.list.push(r.erro);
|
|
304
|
+
return r.ok ? r.valor.chave : c;
|
|
297
305
|
};
|
|
298
306
|
if ('refNFe' in ref) return { refNFe: chave(ref.refNFe, `${path}.refNFe`) };
|
|
299
307
|
if ('refNFeSig' in ref) {
|
|
@@ -312,7 +320,7 @@ function referencia(ref: Referenciada, path: string, issues: Issues): TNFe_infNF
|
|
|
312
320
|
// Nota de produtor em papel (modelo 04) extinta: só referência a nota emitida antes do fim na UF dela.
|
|
313
321
|
if (nfp.mod === '04') {
|
|
314
322
|
const porUf = produtorRural.refNFP.ufs as Readonly<Record<string, { readonly vedadaDesde: string }>>;
|
|
315
|
-
const cUfSigla = Object.keys(porUf).find((k) =>
|
|
323
|
+
const cUfSigla = Object.keys(porUf).find((k) => ufPorSigla(k)?.cUF === nfp.cUF);
|
|
316
324
|
const vedadaDesde = cUfSigla ? (porUf[cUfSigla]?.vedadaDesde ?? '') : produtorRural.refNFP.modelo04.vedadaDesde;
|
|
317
325
|
if (nfp.AAMM >= vedadaDesde) {
|
|
318
326
|
issues.add(
|
|
@@ -576,10 +584,10 @@ function montarItem(ctx: Ctx, item: Item, n: number, normal: boolean): ItemMonta
|
|
|
576
584
|
if (item.infAdProd !== undefined) det.infAdProd = item.infAdProd;
|
|
577
585
|
if (item.obsItem !== undefined) det.obsItem = item.obsItem;
|
|
578
586
|
if (item.DFeReferenciado !== undefined) {
|
|
579
|
-
const r =
|
|
580
|
-
if (!r.ok) ctx.issues.list.push(r.
|
|
587
|
+
const r = lerChaveAcesso(item.DFeReferenciado.chaveAcesso, { caminho: `${path}.DFeReferenciado.chaveAcesso` });
|
|
588
|
+
if (!r.ok) ctx.issues.list.push(r.erro);
|
|
581
589
|
det.DFeReferenciado = clean({
|
|
582
|
-
chaveAcesso: r.ok ? r.
|
|
590
|
+
chaveAcesso: r.ok ? r.valor.chave : item.DFeReferenciado.chaveAcesso,
|
|
583
591
|
nItem: item.DFeReferenciado.nItem === undefined ? undefined : String(item.DFeReferenciado.nItem),
|
|
584
592
|
});
|
|
585
593
|
}
|
|
@@ -616,44 +624,44 @@ function composicao(m: ItemMontado, incluirProd: boolean): Decimal {
|
|
|
616
624
|
}
|
|
617
625
|
|
|
618
626
|
/** Monta, calcula e valida. Nunca lança por dado do chamador: devolve as ocorrências. */
|
|
619
|
-
export async function
|
|
627
|
+
export async function montarNfe(entrada: DadosNfe, opcoes: MontarNfeOpcoes): Promise<ResultadoMontagemNfe> {
|
|
620
628
|
const issues = new Issues();
|
|
621
|
-
const finNFeIn =
|
|
622
|
-
const mod =
|
|
629
|
+
const finNFeIn = entrada.finNFe ?? '1';
|
|
630
|
+
const mod = entrada.modelo ?? '55';
|
|
623
631
|
if (mod !== '55' && mod !== '65') {
|
|
624
632
|
issues.add('modelo', 'modelo_nao_suportado', `modelo ${String(mod)}: o builder monta NF-e (55) e NFC-e (65)`);
|
|
625
|
-
return { ok: false,
|
|
633
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
626
634
|
}
|
|
627
635
|
const nfce = mod === '65';
|
|
628
636
|
// 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
637
|
// é 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 (!
|
|
638
|
+
const indPres = entrada.indPres ?? (nfce ? '1' : finNFeIn === '2' || finNFeIn === '3' ? '0' : '9');
|
|
639
|
+
const indFinal = entrada.indFinal ?? (nfce || entrada.destinatario?.indIEDest === '9' ? '1' : '0');
|
|
640
|
+
const ctx = new Ctx(issues, { ...MODES, ...opcoes.arredondamento }, finNFeIn !== '2' && finNFeIn !== '3');
|
|
641
|
+
const pRedutorGov = entrada.gCompraGov && ctx.req(entrada.gCompraGov.pRedutor, 'gCompraGov.pRedutor', D0302A04);
|
|
642
|
+
const emitUf = entrada.emitente.endereco.UF;
|
|
643
|
+
if (!ehUf(emitUf)) {
|
|
636
644
|
issues.add('emitente.endereco.UF', 'campo_invalido', 'UF do emitente inválida');
|
|
637
|
-
return { ok: false,
|
|
645
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
638
646
|
}
|
|
639
|
-
const cUF =
|
|
640
|
-
const offset =
|
|
641
|
-
const agora =
|
|
647
|
+
const cUF = ufPorSigla(emitUf)?.cUF ?? '';
|
|
648
|
+
const offset = opcoes.deslocamentoMin ?? deslocamentoDaUf(emitUf);
|
|
649
|
+
const agora = opcoes.tempo.emissao.agora();
|
|
642
650
|
// 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 =
|
|
651
|
+
const fatoGerador = opcoes.tempo.fatoGerador.agora();
|
|
652
|
+
const dhEmi = formatarDh(agora, offset);
|
|
645
653
|
const aamm = dhEmi.slice(2, 4) + dhEmi.slice(5, 7);
|
|
646
|
-
const tpAmb =
|
|
654
|
+
const tpAmb = tpAmbDoAmbiente(opcoes.ambiente);
|
|
647
655
|
|
|
648
|
-
// PL vigente (
|
|
656
|
+
// PL vigente (ErroVigencia do schemas propaga: data fora de toda vigência é erro de configuração, não de dado).
|
|
649
657
|
// O PL sai do mesmo instante do dhEmi: reler o relógio numa virada de vigência escolheria outro PL.
|
|
650
|
-
const pl = escolherPl(
|
|
658
|
+
const pl = escolherPl(opcoes.ambiente, relogioFixo(agora));
|
|
651
659
|
|
|
652
660
|
// Emitente (grupo C)
|
|
653
|
-
const e: Emitente =
|
|
661
|
+
const e: Emitente = entrada.emitente;
|
|
654
662
|
const emitDoc = documento(e, 'emitente', issues);
|
|
655
|
-
const serie = Number(
|
|
656
|
-
const nNF = Number(
|
|
663
|
+
const serie = Number(entrada.serie);
|
|
664
|
+
const nNF = Number(entrada.nNF);
|
|
657
665
|
if (!Number.isInteger(serie) || serie < 0 || serie > 999) issues.add('serie', 'serie_invalida', 'série de 0 a 999');
|
|
658
666
|
if (!Number.isInteger(nNF) || nNF < 1 || nNF > 999_999_999)
|
|
659
667
|
issues.add('nNF', 'campo_invalido', 'nNF de 1 a 999999999');
|
|
@@ -687,7 +695,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
687
695
|
|
|
688
696
|
// 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
697
|
// com as demais regras da NFC-e).
|
|
690
|
-
const d: Destinatario | undefined =
|
|
698
|
+
const d: Destinatario | undefined = entrada.destinatario;
|
|
691
699
|
let dest: Record<string, unknown> | undefined;
|
|
692
700
|
let destUf: Uf | 'EX' | undefined;
|
|
693
701
|
if (d === undefined) {
|
|
@@ -723,7 +731,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
723
731
|
: d.IE;
|
|
724
732
|
dest = clean({
|
|
725
733
|
...docDest,
|
|
726
|
-
xNome:
|
|
734
|
+
xNome: opcoes.ambiente === 'homologacao' ? XNOME_HOMOLOGACAO : d.xNome,
|
|
727
735
|
enderDest,
|
|
728
736
|
indIEDest: d.indIEDest,
|
|
729
737
|
IE: destIe,
|
|
@@ -734,7 +742,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
734
742
|
}
|
|
735
743
|
|
|
736
744
|
// Identificação: contingência e forma de emissão
|
|
737
|
-
const cont =
|
|
745
|
+
const cont = entrada.contingencia;
|
|
738
746
|
const tpEmis = cont?.tpEmis ?? '1';
|
|
739
747
|
if (cont !== undefined) {
|
|
740
748
|
if (cont.xJust.length < 15 || cont.xJust.length > 256) {
|
|
@@ -766,25 +774,25 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
766
774
|
// Identificação que as regras do modelo conferem antes de montar.
|
|
767
775
|
// Na NFC-e a operação é sempre interna (B11a-10, rejeição 707): o endereço do consumidor não a torna interestadual.
|
|
768
776
|
const idDest =
|
|
769
|
-
|
|
770
|
-
const tpImp =
|
|
777
|
+
entrada.idDest ?? (nfce ? '1' : destUf === 'EX' ? '3' : destUf !== undefined && destUf !== emitUf ? '2' : '1');
|
|
778
|
+
const tpImp = entrada.tpImp ?? (nfce ? '4' : '1');
|
|
771
779
|
conferirDestinatario(
|
|
772
|
-
|
|
780
|
+
entrada,
|
|
773
781
|
{
|
|
774
782
|
mod,
|
|
775
|
-
tpNF:
|
|
783
|
+
tpNF: entrada.tpNF,
|
|
776
784
|
idDest,
|
|
777
|
-
idDestInformado:
|
|
785
|
+
idDestInformado: entrada.idDest !== undefined,
|
|
778
786
|
indFinal,
|
|
779
|
-
producao:
|
|
787
|
+
producao: opcoes.ambiente === 'producao',
|
|
780
788
|
},
|
|
781
789
|
issues,
|
|
782
790
|
);
|
|
783
791
|
if (nfce) {
|
|
784
792
|
conferirNfce(
|
|
785
|
-
|
|
793
|
+
entrada,
|
|
786
794
|
{
|
|
787
|
-
tpNF:
|
|
795
|
+
tpNF: entrada.tpNF,
|
|
788
796
|
idDest,
|
|
789
797
|
tpImp,
|
|
790
798
|
finNFe: finNFeIn,
|
|
@@ -796,7 +804,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
796
804
|
} else if (tpImp === '4' || tpImp === '5') {
|
|
797
805
|
issues.add('tpImp', 'campo_invalido', 'tpImp 4 e 5 (DANFC-e) são da NFC-e (B21-20, rejeição 710)');
|
|
798
806
|
}
|
|
799
|
-
const qr: QrCodeNfceOpcoes =
|
|
807
|
+
const qr: QrCodeNfceOpcoes = opcoes.qrCode ?? { versao: '3' };
|
|
800
808
|
if (nfce && qr.versao === '2') {
|
|
801
809
|
// 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
810
|
if (!/^\d{1,6}$/.test(qr.idCSC) || Number(qr.idCSC) === 0) {
|
|
@@ -824,12 +832,13 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
824
832
|
|
|
825
833
|
// Chave de acesso e cNF
|
|
826
834
|
const random =
|
|
827
|
-
|
|
835
|
+
opcoes.aleatorio ??
|
|
836
|
+
((b: Uint8Array): Uint8Array => globalThis.crypto.getRandomValues(b as Uint8Array<ArrayBuffer>));
|
|
828
837
|
const emitente14 = 'CNPJ' in emitDoc ? emitDoc.CNPJ : emitDoc.CPF;
|
|
829
838
|
const gerada = issues.empty
|
|
830
839
|
? gerarChave(
|
|
831
840
|
{ cUF, aamm, emitente: emitente14, mod, serie: String(serie), nNF: String(nNF), tpEmis },
|
|
832
|
-
|
|
841
|
+
entrada.cNF,
|
|
833
842
|
random,
|
|
834
843
|
issues,
|
|
835
844
|
)
|
|
@@ -839,24 +848,24 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
839
848
|
const cDV = chave.slice(43);
|
|
840
849
|
|
|
841
850
|
// Itens
|
|
842
|
-
if (
|
|
851
|
+
if (entrada.itens.length === 0 || entrada.itens.length > 990) {
|
|
843
852
|
issues.add('itens', 'itens_limite', 'a NF-e tem de 1 a 990 itens');
|
|
844
853
|
}
|
|
845
|
-
const itens =
|
|
854
|
+
const itens = entrada.itens.map((it, n) => montarItem(ctx, it, n, finNFeIn === '1'));
|
|
846
855
|
// 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
856
|
// E04-20: o builder a põe, e a descrição informada fica fora do XML de teste.
|
|
848
857
|
const primeiro = itens[0];
|
|
849
|
-
if (nfce &&
|
|
858
|
+
if (nfce && opcoes.ambiente === 'homologacao' && primeiro !== undefined) {
|
|
850
859
|
(primeiro.det.prod as unknown as Record<string, unknown>).xProd = XPROD_HOMOLOGACAO_NFCE;
|
|
851
860
|
}
|
|
852
861
|
|
|
853
862
|
// IBS/CBS pela calculadora das opções, ou pelo motor do sinete
|
|
854
|
-
const classificados =
|
|
863
|
+
const classificados = entrada.itens
|
|
855
864
|
.map((it, n) => ({ it, n }))
|
|
856
865
|
.filter(({ it }) => it.impostos.ibsCbs?.classificacao !== undefined);
|
|
857
866
|
if (classificados.length > 0) {
|
|
858
|
-
const calculadora =
|
|
859
|
-
const reqs:
|
|
867
|
+
const calculadora = opcoes.ibsCbs ?? calculadoraPadrao();
|
|
868
|
+
const reqs: PedidoIbsCbsItem[] = classificados.map(({ it, n }) => {
|
|
860
869
|
const c = it.impostos.ibsCbs?.classificacao as NonNullable<
|
|
861
870
|
NonNullable<typeof it.impostos.ibsCbs>['classificacao']
|
|
862
871
|
>;
|
|
@@ -894,29 +903,29 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
894
903
|
};
|
|
895
904
|
});
|
|
896
905
|
const destino =
|
|
897
|
-
|
|
898
|
-
? { UF:
|
|
906
|
+
entrada.entrega !== undefined
|
|
907
|
+
? { UF: entrada.entrega.UF, cMun: entrada.entrega.cMun }
|
|
899
908
|
: d?.endereco !== undefined
|
|
900
909
|
? isExterior(d.endereco)
|
|
901
910
|
? { UF: 'EX' as const, cMun: '9999999' }
|
|
902
911
|
: { UF: d.endereco.UF, cMun: d.endereco.cMun }
|
|
903
912
|
: undefined;
|
|
904
|
-
const compra =
|
|
913
|
+
const compra = entrada.gCompraGov;
|
|
905
914
|
const resp = await calculadora.calcular({
|
|
906
915
|
nota: {
|
|
907
916
|
fatoGerador,
|
|
908
917
|
emissao: agora,
|
|
909
|
-
ambiente:
|
|
918
|
+
ambiente: opcoes.ambiente,
|
|
910
919
|
mod,
|
|
911
|
-
tpNF:
|
|
920
|
+
tpNF: entrada.tpNF,
|
|
912
921
|
finNFe: finNFeIn,
|
|
913
|
-
...(
|
|
914
|
-
...(
|
|
922
|
+
...(entrada.tpNFDebito ? { tpNFDebito: entrada.tpNFDebito } : {}),
|
|
923
|
+
...(entrada.tpNFCredito ? { tpNFCredito: entrada.tpNFCredito } : {}),
|
|
915
924
|
indFinal,
|
|
916
925
|
indPres,
|
|
917
926
|
emitente: { UF: emitUf, cMun: e.endereco.cMun, CRT: e.CRT },
|
|
918
927
|
...(destino ? { destino } : {}),
|
|
919
|
-
...(
|
|
928
|
+
...(entrada.cMunFGIBS ? { cMunFGIBS: entrada.cMunFGIBS } : {}),
|
|
920
929
|
...(compra && pRedutorGov
|
|
921
930
|
? {
|
|
922
931
|
compraGov: {
|
|
@@ -929,7 +938,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
929
938
|
},
|
|
930
939
|
itens: reqs,
|
|
931
940
|
});
|
|
932
|
-
issues.addAll(resp.
|
|
941
|
+
issues.addAll(resp.ocorrencias ?? [], 'montagem');
|
|
933
942
|
for (const { n } of classificados) {
|
|
934
943
|
const g = resp.itens.find((x) => x.nItem === n + 1);
|
|
935
944
|
if (g === undefined) {
|
|
@@ -951,7 +960,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
951
960
|
const ib = `itens[${n}].impostos.ibsCbs`;
|
|
952
961
|
const is = `itens[${n}].impostos.is`;
|
|
953
962
|
// O IBSCBS pronto (ibsCbs.grupo) é da entrada; o que a calculadora devolveu é da montagem (ADR 0011).
|
|
954
|
-
const ibsDaCalculadora =
|
|
963
|
+
const ibsDaCalculadora = entrada.itens[n]?.impostos.ibsCbs?.grupo === undefined;
|
|
955
964
|
const antes = issues.list.length;
|
|
956
965
|
decimaisInvalidos(imp.IBSCBS, ib, issues);
|
|
957
966
|
if (ibsDaCalculadora) issues.reclassificarDesde(antes, 'montagem');
|
|
@@ -962,7 +971,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
962
971
|
grupoInvalido(TISCt as ComplexType, 'IS', imp.IS, is, issues);
|
|
963
972
|
}
|
|
964
973
|
});
|
|
965
|
-
if (!issues.empty) return { ok: false,
|
|
974
|
+
if (!issues.empty) return { ok: false, ocorrencias: issues.classificadas };
|
|
966
975
|
|
|
967
976
|
// Totais (grupo W)
|
|
968
977
|
const gruposIbsCbs = itens
|
|
@@ -1096,43 +1105,43 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1096
1105
|
const ide = clean({
|
|
1097
1106
|
cUF,
|
|
1098
1107
|
cNF,
|
|
1099
|
-
natOp:
|
|
1108
|
+
natOp: entrada.natOp,
|
|
1100
1109
|
mod,
|
|
1101
1110
|
serie: String(serie),
|
|
1102
1111
|
nNF: String(nNF),
|
|
1103
1112
|
dhEmi,
|
|
1104
|
-
dhSaiEnt:
|
|
1105
|
-
dPrevEntrega:
|
|
1106
|
-
tpNF:
|
|
1113
|
+
dhSaiEnt: entrada.dhSaiEnt === undefined ? undefined : formatarDh(entrada.dhSaiEnt, offset),
|
|
1114
|
+
dPrevEntrega: entrada.dPrevEntrega,
|
|
1115
|
+
tpNF: entrada.tpNF,
|
|
1107
1116
|
idDest,
|
|
1108
|
-
cMunFG:
|
|
1109
|
-
cMunFGIBS:
|
|
1117
|
+
cMunFG: entrada.cMunFG ?? e.endereco.cMun,
|
|
1118
|
+
cMunFGIBS: entrada.cMunFGIBS,
|
|
1110
1119
|
tpImp,
|
|
1111
1120
|
tpEmis,
|
|
1112
1121
|
cDV,
|
|
1113
1122
|
tpAmb,
|
|
1114
1123
|
finNFe,
|
|
1115
|
-
tpNFDebito:
|
|
1116
|
-
tpNFCredito:
|
|
1124
|
+
tpNFDebito: entrada.tpNFDebito,
|
|
1125
|
+
tpNFCredito: entrada.tpNFCredito,
|
|
1117
1126
|
indFinal,
|
|
1118
1127
|
indPres,
|
|
1119
1128
|
// NT 2020.006: indIntermed obrigatório com indPres 2, 3, 4 e 9.
|
|
1120
|
-
indIntermed:
|
|
1129
|
+
indIntermed: entrada.indIntermed ?? (['2', '3', '4', '9'].includes(indPres) ? '0' : undefined),
|
|
1121
1130
|
procEmi: '0',
|
|
1122
|
-
verProc:
|
|
1123
|
-
dhCont: cont === undefined ? undefined :
|
|
1131
|
+
verProc: opcoes.verProc ?? formatarVerProc('sinete', VERSAO_PACOTE),
|
|
1132
|
+
dhCont: cont === undefined ? undefined : formatarDh(cont.dhCont, offset),
|
|
1124
1133
|
xJust: cont?.xJust,
|
|
1125
|
-
NFref:
|
|
1134
|
+
NFref: entrada.referenciadas?.map((r, n) => referencia(r, `referenciadas[${n}]`, issues)),
|
|
1126
1135
|
gCompraGov:
|
|
1127
|
-
|
|
1136
|
+
entrada.gCompraGov === undefined
|
|
1128
1137
|
? undefined
|
|
1129
1138
|
: clean({
|
|
1130
|
-
tpEnteGov:
|
|
1139
|
+
tpEnteGov: entrada.gCompraGov.tpEnteGov,
|
|
1131
1140
|
pRedutor: ctx.s(pRedutorGov ?? Decimal.ZERO, D0302A04),
|
|
1132
|
-
tpOperGov:
|
|
1133
|
-
refDFeAnt:
|
|
1141
|
+
tpOperGov: entrada.gCompraGov.tpOperGov,
|
|
1142
|
+
refDFeAnt: entrada.gCompraGov.refDFeAnt as string[] | undefined,
|
|
1134
1143
|
}),
|
|
1135
|
-
gPagAntecipado:
|
|
1144
|
+
gPagAntecipado: entrada.gPagAntecipado === undefined ? undefined : { refNFe: [...entrada.gPagAntecipado] },
|
|
1136
1145
|
});
|
|
1137
1146
|
|
|
1138
1147
|
// Locais, autorizados, transporte, cobrança, pagamento, informações adicionais
|
|
@@ -1148,10 +1157,10 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1148
1157
|
email: l.email,
|
|
1149
1158
|
IE: l.IE,
|
|
1150
1159
|
});
|
|
1151
|
-
if ((
|
|
1152
|
-
const autXML =
|
|
1160
|
+
if ((entrada.autXML?.length ?? 0) > 10) issues.add('autXML', 'campo_invalido', 'no máximo 10 autorizados');
|
|
1161
|
+
const autXML = entrada.autXML?.map((a, n) => documento(a, `autXML[${n}]`, issues));
|
|
1153
1162
|
|
|
1154
|
-
const tr =
|
|
1163
|
+
const tr = entrada.transporte;
|
|
1155
1164
|
const transp: Record<string, unknown> = { modFrete: tr?.modFrete ?? '9' };
|
|
1156
1165
|
if (tr?.transportador !== undefined) {
|
|
1157
1166
|
const t = tr.transportador;
|
|
@@ -1194,8 +1203,8 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1194
1203
|
}
|
|
1195
1204
|
|
|
1196
1205
|
let cobr: Record<string, unknown> | undefined;
|
|
1197
|
-
if (
|
|
1198
|
-
const c =
|
|
1206
|
+
if (entrada.cobranca !== undefined) {
|
|
1207
|
+
const c = entrada.cobranca;
|
|
1199
1208
|
cobr = {};
|
|
1200
1209
|
if (c.fatura !== undefined) {
|
|
1201
1210
|
const f = c.fatura;
|
|
@@ -1225,10 +1234,10 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1225
1234
|
}
|
|
1226
1235
|
}
|
|
1227
1236
|
|
|
1228
|
-
const pagIn =
|
|
1229
|
-
const igualTotal =
|
|
1237
|
+
const pagIn = entrada.pagamento ?? { detPag: [{ tPag: '90' as const, vPag: '0' }] };
|
|
1238
|
+
const igualTotal = opcoes.pagamentoIgualTotal === true;
|
|
1230
1239
|
if (igualTotal) {
|
|
1231
|
-
const unico =
|
|
1240
|
+
const unico = entrada.pagamento?.detPag.length === 1 ? entrada.pagamento.detPag[0] : undefined;
|
|
1232
1241
|
if (unico === undefined) {
|
|
1233
1242
|
issues.add('pagamento.detPag', 'pagamento_igual_total', 'pagamentoIgualTotal pede exatamente um detPag');
|
|
1234
1243
|
} else if (unico.tPag === '90') {
|
|
@@ -1253,7 +1262,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1253
1262
|
}) as unknown as PagMontado;
|
|
1254
1263
|
// 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
1264
|
// Anexo I) citam, e a NT 2025.002 (até a v1.51) não mudou essas regras nem o W16.
|
|
1256
|
-
if (nfce &&
|
|
1265
|
+
if (nfce && entrada.pagamento !== undefined && entrada.pagamento.detPag.length > 0 && issues.empty) {
|
|
1257
1266
|
pagamentoNfce(pag, vNF, issues);
|
|
1258
1267
|
}
|
|
1259
1268
|
// W16-40 (rejeição 750): acima do limite, a NFC-e identifica o destinatário por CNPJ, CPF ou idEstrangeiro.
|
|
@@ -1264,7 +1273,7 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1264
1273
|
`NFC-e acima de R$ ${NFCE_LIMITE_SEM_DESTINATARIO} identifica o destinatário (W16-40, rejeição 750)`,
|
|
1265
1274
|
);
|
|
1266
1275
|
}
|
|
1267
|
-
const ia =
|
|
1276
|
+
const ia = entrada.informacoesAdicionais;
|
|
1268
1277
|
const infAdic =
|
|
1269
1278
|
ia === undefined || Object.values(ia).every((x) => x === undefined)
|
|
1270
1279
|
? undefined
|
|
@@ -1277,18 +1286,18 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1277
1286
|
});
|
|
1278
1287
|
|
|
1279
1288
|
// 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 =
|
|
1289
|
+
const exig = { ...exigenciaRespTec(emitUf, opcoes.ambiente, agora), ...opcoes.exigencias };
|
|
1290
|
+
const rt = entrada.respTec ?? opcoes.respTec;
|
|
1282
1291
|
let infRespTec: Record<string, unknown> | undefined;
|
|
1283
1292
|
if (rt === undefined) {
|
|
1284
1293
|
if (exig.infRespTec === 'obrigatorio') {
|
|
1285
1294
|
issues.add('respTec', 'resp_tec_obrigatorio', `a UF ${emitUf} exige o responsável técnico (rejeição 972)`);
|
|
1286
1295
|
}
|
|
1287
1296
|
} else {
|
|
1288
|
-
const cnpjRt =
|
|
1289
|
-
if (!cnpjRt.ok) issues.list.push(cnpjRt.
|
|
1297
|
+
const cnpjRt = lerCnpj(rt.CNPJ, { caminho: 'respTec.CNPJ' });
|
|
1298
|
+
if (!cnpjRt.ok) issues.list.push(cnpjRt.erro);
|
|
1290
1299
|
infRespTec = {
|
|
1291
|
-
CNPJ: cnpjRt.ok ? cnpjRt.
|
|
1300
|
+
CNPJ: cnpjRt.ok ? cnpjRt.valor : rt.CNPJ,
|
|
1292
1301
|
xContato: rt.xContato,
|
|
1293
1302
|
email: rt.email,
|
|
1294
1303
|
fone: digits(rt.fone),
|
|
@@ -1307,51 +1316,54 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1307
1316
|
ide,
|
|
1308
1317
|
emit,
|
|
1309
1318
|
dest,
|
|
1310
|
-
retirada: local(
|
|
1311
|
-
entrega: local(
|
|
1319
|
+
retirada: local(entrada.retirada, 'retirada'),
|
|
1320
|
+
entrega: local(entrada.entrega, 'entrega'),
|
|
1312
1321
|
autXML,
|
|
1313
1322
|
det: itens.map((m) => m.det),
|
|
1314
1323
|
total,
|
|
1315
1324
|
transp,
|
|
1316
1325
|
cobr,
|
|
1317
1326
|
pag,
|
|
1318
|
-
infIntermed:
|
|
1327
|
+
infIntermed: entrada.infIntermed,
|
|
1319
1328
|
infAdic,
|
|
1320
|
-
exporta:
|
|
1321
|
-
compra:
|
|
1322
|
-
cana:
|
|
1329
|
+
exporta: entrada.exporta,
|
|
1330
|
+
compra: entrada.compra,
|
|
1331
|
+
cana: entrada.cana,
|
|
1323
1332
|
infRespTec,
|
|
1324
|
-
agropecuario:
|
|
1333
|
+
agropecuario: entrada.agropecuario,
|
|
1325
1334
|
}) as unknown as TNFe_infNFe;
|
|
1326
1335
|
|
|
1327
1336
|
// Regras da SEFAZ que só dependem do documento (ADR 0012).
|
|
1328
|
-
cstComIsento(
|
|
1329
|
-
vencimentos(
|
|
1337
|
+
cstComIsento(entrada, idDest, issues);
|
|
1338
|
+
vencimentos(entrada, { dhEmi, instante: agora }, issues);
|
|
1330
1339
|
|
|
1331
1340
|
camposForaDoPl(pl.infNFe, inf, 'infNFe', pl.vigencia.pl, issues);
|
|
1332
1341
|
textosForaDoXml(inf, 'infNFe', issues);
|
|
1333
|
-
if (!issues.empty) return { ok: false,
|
|
1342
|
+
if (!issues.empty) return { ok: false, ocorrencias: issues.classificadas };
|
|
1334
1343
|
|
|
1335
1344
|
// Serialização canônica e validação estrita contra o schema do PL vigente, antes de qualquer assinatura.
|
|
1336
|
-
// O serializer recusa com
|
|
1345
|
+
// O serializer recusa com ErroSerializacao o que não cabe no modelo (escolhas exclusivas informadas juntas, por
|
|
1337
1346
|
// exemplo vagao e balsa): vira ocorrência com o caminho do XML, nunca exceção.
|
|
1338
1347
|
let xml: string;
|
|
1339
1348
|
try {
|
|
1340
|
-
xml = `<NFe xmlns="${NFE_NS}">${
|
|
1349
|
+
xml = `<NFe xmlns="${NFE_NS}">${serializar(pl.infNFe, 'infNFe', inf, NFE_NS)}</NFe>`;
|
|
1341
1350
|
} catch (e) {
|
|
1342
|
-
if (!(e instanceof
|
|
1343
|
-
return {
|
|
1351
|
+
if (!(e instanceof ErroSerializacao)) throw e;
|
|
1352
|
+
return {
|
|
1353
|
+
ok: false,
|
|
1354
|
+
ocorrencias: [{ caminho: e.caminho, code: 'schema', mensagem: e.message, origem: 'montagem' }],
|
|
1355
|
+
};
|
|
1344
1356
|
}
|
|
1345
|
-
const doc =
|
|
1346
|
-
const infEl =
|
|
1347
|
-
const schemaIssues = infEl === undefined ? [] :
|
|
1357
|
+
const doc = lerXml(xml);
|
|
1358
|
+
const infEl = primeiroFilho(doc.raiz, 'infNFe', NFE_NS);
|
|
1359
|
+
const schemaIssues = infEl === undefined ? [] : validar(pl.infNFe, infEl);
|
|
1348
1360
|
if (schemaIssues.length > 0) {
|
|
1349
1361
|
return {
|
|
1350
1362
|
ok: false,
|
|
1351
|
-
|
|
1352
|
-
|
|
1363
|
+
ocorrencias: schemaIssues.map((i) => ({
|
|
1364
|
+
caminho: i.caminho,
|
|
1353
1365
|
code: 'schema',
|
|
1354
|
-
|
|
1366
|
+
mensagem: `${i.code}: ${i.mensagem}`,
|
|
1355
1367
|
origem: 'montagem',
|
|
1356
1368
|
})),
|
|
1357
1369
|
};
|
|
@@ -1359,14 +1371,14 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1359
1371
|
let supl: NfceSupl | undefined;
|
|
1360
1372
|
if (nfce) {
|
|
1361
1373
|
// 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 =
|
|
1374
|
+
const tabela = urlsNfce(emitUf, opcoes.ambiente, diaBrasilia(agora));
|
|
1375
|
+
const urlQr = opcoes.urlQrCode ?? tabela.qrCode;
|
|
1376
|
+
const urlChave = opcoes.urlChave ?? tabela.urlChave;
|
|
1365
1377
|
if (urlQr === undefined || !/^https?:\/\//i.test(urlQr)) {
|
|
1366
1378
|
issues.montagem(
|
|
1367
1379
|
'urlQrCode',
|
|
1368
1380
|
'qrcode_invalido',
|
|
1369
|
-
`sem endereço completo do QR Code da NFC-e para ${emitUf} em ${
|
|
1381
|
+
`sem endereço completo do QR Code da NFC-e para ${emitUf} em ${opcoes.ambiente}: informe urlQrCode (data/nfce-urls.json)`,
|
|
1370
1382
|
);
|
|
1371
1383
|
}
|
|
1372
1384
|
if (urlChave === undefined) {
|
|
@@ -1377,11 +1389,11 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1377
1389
|
);
|
|
1378
1390
|
}
|
|
1379
1391
|
if (urlQr === undefined || urlChave === undefined || !issues.empty)
|
|
1380
|
-
return { ok: false,
|
|
1392
|
+
return { ok: false, ocorrencias: issues.classificadas };
|
|
1381
1393
|
const p = await parametrosQrCode(
|
|
1382
1394
|
{
|
|
1383
1395
|
chave,
|
|
1384
|
-
ambiente:
|
|
1396
|
+
ambiente: opcoes.ambiente,
|
|
1385
1397
|
tpEmis,
|
|
1386
1398
|
dhEmi,
|
|
1387
1399
|
vNF: (inf.total as { ICMSTot: { vNF: string } }).ICMSTot.vNF,
|
|
@@ -1395,13 +1407,13 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1395
1407
|
// off-line, com uma assinatura do tamanho da de uma chave RSA de 2048 bits no lugar da que o certificado ainda vai
|
|
1396
1408
|
// fazer; o `comQrCode` confere de novo com a assinatura verdadeira.
|
|
1397
1409
|
const suplIssues = conferirSupl(
|
|
1398
|
-
inserirSupl({ xml, nfce: supl } as
|
|
1410
|
+
inserirSupl({ xml, nfce: supl } as NfeMontada, supl.assinar ? ASSINATURA_2048 : undefined),
|
|
1399
1411
|
);
|
|
1400
|
-
if (suplIssues.length > 0) return { ok: false,
|
|
1412
|
+
if (suplIssues.length > 0) return { ok: false, ocorrencias: suplIssues };
|
|
1401
1413
|
}
|
|
1402
1414
|
return {
|
|
1403
1415
|
ok: true,
|
|
1404
|
-
|
|
1416
|
+
valor: {
|
|
1405
1417
|
chave,
|
|
1406
1418
|
id: `NFe${chave}`,
|
|
1407
1419
|
cNF,
|
|
@@ -1418,13 +1430,18 @@ export async function buildNfe(input: NfeInput, options: BuildNfeOptions): Promi
|
|
|
1418
1430
|
}
|
|
1419
1431
|
|
|
1420
1432
|
/** 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 =
|
|
1433
|
+
const ASSINATURA_2048 = codificarBase64(new Uint8Array(256));
|
|
1422
1434
|
|
|
1423
1435
|
/** 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) => ({
|
|
1436
|
+
function conferirSupl(xml: string): Ocorrencia[] {
|
|
1437
|
+
const el = primeiroFilho(lerXml(xml).raiz, 'infNFeSupl', NFE_NS);
|
|
1438
|
+
const erros = el === undefined ? [] : validar(TNFe_infNFeSupl as ComplexType, el);
|
|
1439
|
+
return erros.map((i) => ({
|
|
1440
|
+
caminho: i.caminho,
|
|
1441
|
+
code: 'schema',
|
|
1442
|
+
mensagem: `${i.code}: ${i.mensagem}`,
|
|
1443
|
+
origem: 'montagem',
|
|
1444
|
+
}));
|
|
1428
1445
|
}
|
|
1429
1446
|
|
|
1430
1447
|
const escapeXml = (s: string): string => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -1434,43 +1451,43 @@ const escapeXml = (s: string): string => s.replace(/&/g, '&').replace(/</g,
|
|
|
1434
1451
|
* 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
1452
|
* (NF-e, emissão normal, versão 2).
|
|
1436
1453
|
*/
|
|
1437
|
-
export async function assinaturaQrCode(
|
|
1438
|
-
return
|
|
1454
|
+
export async function assinaturaQrCode(nota: NfeMontada, assinador: Assinador): Promise<string | undefined> {
|
|
1455
|
+
return nota.nfce?.assinar === true ? assinarParametros(nota.nfce.parametros, assinador) : undefined;
|
|
1439
1456
|
}
|
|
1440
1457
|
|
|
1441
1458
|
/**
|
|
1442
1459
|
* A NFC-e montada com o `infNFeSupl` (QR Code e `urlChave`) inserido por splice antes do fechamento de `NFe`, pronta
|
|
1443
1460
|
* para a assinatura. Na versão 3 em contingência off-line, informe a `assinatura` (`assinaturaQrCode`); nos demais
|
|
1444
1461
|
* 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 `
|
|
1462
|
+
* este texto ao `prepararAssinatura` do `@sinete/core/xml` com o `id` da nota. Na NF-e (modelo 55), devolve o XML como
|
|
1446
1463
|
* veio: ela não tem `infNFeSupl` (ZX01-10, rejeição 393).
|
|
1447
1464
|
*/
|
|
1448
|
-
export function comQrCode(
|
|
1449
|
-
const xml = inserirSupl(
|
|
1465
|
+
export function comQrCode(nota: NfeMontada, assinatura?: string): string {
|
|
1466
|
+
const xml = inserirSupl(nota, assinatura);
|
|
1450
1467
|
// Com a assinatura verdadeira, o qrCode pode passar do tamanho que a montagem conferiu (chave maior que 2048 bits).
|
|
1451
1468
|
if (assinatura !== undefined) {
|
|
1452
1469
|
const issues = conferirSupl(xml);
|
|
1453
|
-
if (issues.length > 0) throw new
|
|
1470
|
+
if (issues.length > 0) throw new ErroDeValidacao('o QR Code da NFC-e não passou no schema', issues);
|
|
1454
1471
|
}
|
|
1455
1472
|
return xml;
|
|
1456
1473
|
}
|
|
1457
1474
|
|
|
1458
|
-
function inserirSupl(built:
|
|
1475
|
+
function inserirSupl(built: NfeMontada, assinatura: string | undefined): string {
|
|
1459
1476
|
const s = built.nfce;
|
|
1460
1477
|
if (s === undefined) {
|
|
1461
|
-
if (assinatura !== undefined) throw new
|
|
1478
|
+
if (assinatura !== undefined) throw new ErroDeConfiguracao('a NF-e (modelo 55) não tem QR Code');
|
|
1462
1479
|
return built.xml;
|
|
1463
1480
|
}
|
|
1464
1481
|
if (s.assinar && assinatura === undefined) {
|
|
1465
|
-
throw new
|
|
1482
|
+
throw new ErroDeConfiguracao(
|
|
1466
1483
|
'NFC-e em contingência off-line com QR Code versão 3 precisa da assinatura (ZX02-334, rejeição 474)',
|
|
1467
1484
|
);
|
|
1468
1485
|
}
|
|
1469
1486
|
if (!s.assinar && assinatura !== undefined) {
|
|
1470
|
-
throw new
|
|
1487
|
+
throw new ErroDeConfiguracao('este QR Code não leva assinatura (ZX02-330, rejeição 445)');
|
|
1471
1488
|
}
|
|
1472
1489
|
const fim = '</NFe>';
|
|
1473
|
-
if (!built.xml.endsWith(fim)) throw new
|
|
1490
|
+
if (!built.xml.endsWith(fim)) throw new ErroDeConfiguracao('NFC-e montada fora da forma esperada');
|
|
1474
1491
|
const qrCode = `${s.base}${s.parametros}${assinatura === undefined ? '' : `|${assinatura}`}`;
|
|
1475
1492
|
const supl = `<infNFeSupl><qrCode>${escapeXml(qrCode)}</qrCode><urlChave>${escapeXml(s.urlChave)}</urlChave></infNFeSupl>`;
|
|
1476
1493
|
return built.xml.slice(0, -fim.length) + supl + fim;
|
|
@@ -1481,6 +1498,6 @@ function inserirSupl(built: BuiltNfe, assinatura: string | undefined): string {
|
|
|
1481
1498
|
* contingência off-line pede); depois, a `Signature` como último filho de `NFe`, tudo por splice, e devolve a string
|
|
1482
1499
|
* final. É essa string que vai para a SEFAZ e para o banco; nada depois deve reparseá-la para reescrever.
|
|
1483
1500
|
*/
|
|
1484
|
-
export async function
|
|
1485
|
-
return
|
|
1501
|
+
export async function assinarNfe(nota: NfeMontada, assinador: Assinador): Promise<string> {
|
|
1502
|
+
return assinarXml(comQrCode(nota, await assinaturaQrCode(nota, assinador)), { id: nota.id }, assinador);
|
|
1486
1503
|
}
|