ghinfe 0.6.0 → 0.6.2
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/.env.example +24 -15
- package/LICENSE +21 -21
- package/README.md +491 -413
- package/docs/developer-guide.html +421 -421
- package/docs/openapi.yaml +428 -428
- package/examples/dce-homologacao.js +141 -0
- package/examples/emitir-nfce.js +51 -51
- package/examples/emitir-nfe.js +83 -83
- package/examples/emitir-nfse.js +46 -46
- package/examples/endpoints-sefaz.js +14 -14
- package/examples/eventos-nfe.js +49 -49
- package/examples/gerar-chave.js +25 -25
- package/examples/gerar-dace.js +59 -0
- package/examples/gerar-danfe.js +75 -75
- package/examples/gerar-dce.js +58 -0
- package/examples/layouts-nfse.js +51 -51
- package/examples/mock-sefaz.js +52 -52
- package/examples/nfse-eventos.js +57 -57
- package/examples/reforma-ibscbs.js +27 -27
- package/examples/reforma-nfe-xml.js +52 -52
- package/examples/status-sefaz.js +12 -12
- package/examples/validar-xsd.js +48 -48
- package/package.json +59 -68
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCeTiposBasico_v1.00.xsd +123 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCe_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCeTiposBasico_v1.00.xsd +110 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCe_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/dceTiposBasico_v1.00.xsd +1388 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsSitDCe_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsStatServDCe_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/tiposGeralDCe_v1.00.xsd +712 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/xmldsig-core-schema_v1.01.xsd +98 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/dceTiposBasico_v1.00.xsd +1410 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/dce_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/evCancDCe_v1.00.xsd +36 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCeTiposBasico_v1.00.xsd +283 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCe_v1.00.xsd +9 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/procEventoDCe_v1.00.xsd +10 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/retEventoDCe_v1.00.xsd +9 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/tiposGeralDCe_v1.00.xsd +712 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/evento/xmldsig-core-schema_v1.01.xsd +98 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/procDCe_v1.00.xsd +37 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/retDCe_v1.00.xsd +9 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/tiposGeralDCe_v1.00.xsd +712 -0
- package/schemas/dce/PL_DCe_100a_NT2024.001/xmldsig-core-schema_v1.01.xsd +98 -0
- package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +8 -8
- package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +97 -97
- package/scripts/download-schemas.js +39 -39
- package/scripts/test-xsd.js +34 -34
- package/src/api/server.js +316 -316
- package/src/constants/sefaz.js +69 -69
- package/src/danfe/DanfeService.js +205 -205
- package/src/danfe/xml-extract.js +69 -69
- package/src/dce/builders/DCeEventoXmlBuilder.js +86 -0
- package/src/dce/builders/DCeXmlBuilder.js +345 -0
- package/src/dce/constants/dce.js +87 -0
- package/src/dce/dace/DaceService.js +356 -0
- package/src/dce/dace/dace-extract.js +114 -0
- package/src/dce/services/DCeService.js +263 -0
- package/src/dce/services/DCeSoapService.js +235 -0
- package/src/dce/services/DCeXmlService.js +67 -0
- package/src/dce/types/DCeDocument.js +106 -0
- package/src/dce/utils/qrcode.utils.js +59 -0
- package/src/errors/GhinfeError.js +13 -13
- package/src/errors/SefazError.js +25 -25
- package/src/errors/XsdValidationError.js +15 -15
- package/src/index.js +84 -60
- package/src/nfce/builders/NFCeXmlBuilder.js +178 -178
- package/src/nfce/constants/qrcode.js +26 -26
- package/src/nfce/services/NFCeService.js +188 -188
- package/src/nfce/services/NFCeXmlService.js +38 -38
- package/src/nfce/types/NFCeDocument.js +42 -42
- package/src/nfce/utils/qrcode.utils.js +92 -92
- package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -110
- package/src/nfe/builders/NFeXmlBuilder.js +215 -215
- package/src/nfe/mocks/sefaz-responses.js +195 -195
- package/src/nfe/services/NFeEventoService.js +95 -95
- package/src/nfe/services/NFeService.js +196 -196
- package/src/nfe/services/NFeSignService.js +127 -111
- package/src/nfe/services/NFeSoapService.js +437 -437
- package/src/nfe/services/NFeXmlService.js +41 -41
- package/src/nfe/services/NFeXsdService.js +56 -56
- package/src/nfe/types/NFeDocument.js +92 -92
- package/src/nfe/types/NFeEvento.js +44 -44
- package/src/nfse/NFSeMunicipioResolver.js +42 -42
- package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -92
- package/src/nfse/builders/NFSeXmlBuilder.js +16 -16
- package/src/nfse/constants/municipios.js +82 -82
- package/src/nfse/layouts/adapters.js +202 -202
- package/src/nfse/mocks/nfse-responses.js +142 -142
- package/src/nfse/services/NFSeService.js +191 -191
- package/src/nfse/services/NFSeSoapService.js +232 -232
- package/src/nfse/services/NFSeXmlService.js +12 -12
- package/src/nfse/types/NFSeDocument.js +54 -54
- package/src/queue/NFCeQueue.js +63 -63
- package/src/queue/NFSeQueue.js +59 -59
- package/src/queue/NFeQueue.js +76 -76
- package/src/reforma/ibscbs.js +175 -175
- package/src/sefaz/SefazEndpointResolver.js +95 -95
- package/src/sefaz/ca/icp-brasil-raiz-v10.pem +38 -0
- package/src/sefaz/endpoints.registry.js +205 -205
- package/src/sefaz/httpsAgent.js +33 -0
- package/src/shared/fiscal.helpers.js +53 -53
- package/src/utils/certificate.utils.js +69 -47
- package/src/utils/chave.utils.js +205 -117
- package/src/utils/code128.utils.js +67 -0
- package/src/utils/date.utils.js +26 -26
- package/src/utils/xml.utils.js +44 -44
- package/src/validation/XsdValidator.js +133 -114
- package/src/validation/schemas.js +18 -14
package/src/constants/sefaz.js
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/** Códigos de UF conforme tabela IBGE (últimos 2 dígitos da chave NF-e). */
|
|
2
|
-
export const UF_IBGE = {
|
|
3
|
-
AC: '12',
|
|
4
|
-
AL: '27',
|
|
5
|
-
AM: '13',
|
|
6
|
-
AP: '16',
|
|
7
|
-
BA: '29',
|
|
8
|
-
CE: '23',
|
|
9
|
-
DF: '53',
|
|
10
|
-
ES: '32',
|
|
11
|
-
GO: '52',
|
|
12
|
-
MA: '21',
|
|
13
|
-
MG: '31',
|
|
14
|
-
MS: '50',
|
|
15
|
-
MT: '51',
|
|
16
|
-
PA: '15',
|
|
17
|
-
PB: '25',
|
|
18
|
-
PE: '26',
|
|
19
|
-
PI: '22',
|
|
20
|
-
PR: '41',
|
|
21
|
-
RJ: '33',
|
|
22
|
-
RN: '24',
|
|
23
|
-
RO: '11',
|
|
24
|
-
RR: '14',
|
|
25
|
-
RS: '43',
|
|
26
|
-
SC: '42',
|
|
27
|
-
SE: '28',
|
|
28
|
-
SP: '35',
|
|
29
|
-
TO: '17',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** 1 = Produção, 2 = Homologação. */
|
|
33
|
-
export const AMBIENTE = {
|
|
34
|
-
PRODUCAO: 1,
|
|
35
|
-
HOMOLOGACAO: 2,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/** Layout NF-e versão 4.00. */
|
|
39
|
-
export const NFE_VERSAO = '4.00';
|
|
40
|
-
|
|
41
|
-
/** Endpoints SEFAZ (homologação) — variam por UF; use como referência. */
|
|
42
|
-
export const SEFAZ_URLS = {
|
|
43
|
-
SP: {
|
|
44
|
-
autorizacao: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nfeautorizacao4.asmx',
|
|
45
|
-
retAutorizacao: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nferetautorizacao4.asmx',
|
|
46
|
-
consulta: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nfeconsultaprotocolo4.asmx',
|
|
47
|
-
evento: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nferecepcaoevento4.asmx',
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** cStat mais comuns retornados pela SEFAZ. */
|
|
52
|
-
export const SEFAZ_STATUS = {
|
|
53
|
-
AUTORIZADO: '100',
|
|
54
|
-
CANCELADO: '101',
|
|
55
|
-
LOTE_RECEBIDO: '103',
|
|
56
|
-
LOTE_PROCESSADO: '104',
|
|
57
|
-
LOTE_EM_PROCESSAMENTO: '105',
|
|
58
|
-
SERVICO_EM_OPERACAO: '107',
|
|
59
|
-
SERVICO_PARALISADO: '108',
|
|
60
|
-
SERVICO_PARALISADO_SEM_PREVISAO: '109',
|
|
61
|
-
USO_DENEGADO: '110',
|
|
62
|
-
REJEICAO_DUPLICIDADE: '204',
|
|
63
|
-
REJEICAO_CHAVE_INVALIDA: '236',
|
|
64
|
-
REJEICAO_CERTIFICADO: '280',
|
|
65
|
-
REJEICAO_SCHEMA: '225',
|
|
66
|
-
EVENTO_REGISTRADO: '135',
|
|
67
|
-
EVENTO_LOTE_PROCESSADO: '128',
|
|
68
|
-
EVENTO_REJEICAO: '491',
|
|
69
|
-
};
|
|
1
|
+
/** Códigos de UF conforme tabela IBGE (últimos 2 dígitos da chave NF-e). */
|
|
2
|
+
export const UF_IBGE = {
|
|
3
|
+
AC: '12',
|
|
4
|
+
AL: '27',
|
|
5
|
+
AM: '13',
|
|
6
|
+
AP: '16',
|
|
7
|
+
BA: '29',
|
|
8
|
+
CE: '23',
|
|
9
|
+
DF: '53',
|
|
10
|
+
ES: '32',
|
|
11
|
+
GO: '52',
|
|
12
|
+
MA: '21',
|
|
13
|
+
MG: '31',
|
|
14
|
+
MS: '50',
|
|
15
|
+
MT: '51',
|
|
16
|
+
PA: '15',
|
|
17
|
+
PB: '25',
|
|
18
|
+
PE: '26',
|
|
19
|
+
PI: '22',
|
|
20
|
+
PR: '41',
|
|
21
|
+
RJ: '33',
|
|
22
|
+
RN: '24',
|
|
23
|
+
RO: '11',
|
|
24
|
+
RR: '14',
|
|
25
|
+
RS: '43',
|
|
26
|
+
SC: '42',
|
|
27
|
+
SE: '28',
|
|
28
|
+
SP: '35',
|
|
29
|
+
TO: '17',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** 1 = Produção, 2 = Homologação. */
|
|
33
|
+
export const AMBIENTE = {
|
|
34
|
+
PRODUCAO: 1,
|
|
35
|
+
HOMOLOGACAO: 2,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Layout NF-e versão 4.00. */
|
|
39
|
+
export const NFE_VERSAO = '4.00';
|
|
40
|
+
|
|
41
|
+
/** Endpoints SEFAZ (homologação) — variam por UF; use como referência. */
|
|
42
|
+
export const SEFAZ_URLS = {
|
|
43
|
+
SP: {
|
|
44
|
+
autorizacao: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nfeautorizacao4.asmx',
|
|
45
|
+
retAutorizacao: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nferetautorizacao4.asmx',
|
|
46
|
+
consulta: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nfeconsultaprotocolo4.asmx',
|
|
47
|
+
evento: 'https://homologacao.nfe.fazenda.sp.gov.br/ws/nferecepcaoevento4.asmx',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** cStat mais comuns retornados pela SEFAZ. */
|
|
52
|
+
export const SEFAZ_STATUS = {
|
|
53
|
+
AUTORIZADO: '100',
|
|
54
|
+
CANCELADO: '101',
|
|
55
|
+
LOTE_RECEBIDO: '103',
|
|
56
|
+
LOTE_PROCESSADO: '104',
|
|
57
|
+
LOTE_EM_PROCESSAMENTO: '105',
|
|
58
|
+
SERVICO_EM_OPERACAO: '107',
|
|
59
|
+
SERVICO_PARALISADO: '108',
|
|
60
|
+
SERVICO_PARALISADO_SEM_PREVISAO: '109',
|
|
61
|
+
USO_DENEGADO: '110',
|
|
62
|
+
REJEICAO_DUPLICIDADE: '204',
|
|
63
|
+
REJEICAO_CHAVE_INVALIDA: '236',
|
|
64
|
+
REJEICAO_CERTIFICADO: '280',
|
|
65
|
+
REJEICAO_SCHEMA: '225',
|
|
66
|
+
EVENTO_REGISTRADO: '135',
|
|
67
|
+
EVENTO_LOTE_PROCESSADO: '128',
|
|
68
|
+
EVENTO_REJEICAO: '491',
|
|
69
|
+
};
|
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
import QRCode from 'qrcode';
|
|
2
|
-
import { extrairDadosDanfe, formatarChaveAcesso } from './xml-extract.js';
|
|
3
|
-
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Gera DANFE (NF-e) e DANFCE (NFC-e) em HTML imprimível.
|
|
7
|
-
* PDF: imprima o HTML ou use um conversor no sistema consumidor (puppeteer, etc.).
|
|
8
|
-
*/
|
|
9
|
-
export class DanfeService {
|
|
10
|
-
/**
|
|
11
|
-
* @param {string} xmlNFe XML autorizado (nfeProc) ou NFe assinada
|
|
12
|
-
* @param {{ protocolo?: string, titulo?: string }} [opcoes]
|
|
13
|
-
* @returns {Promise<{ html: string, dados: object, tipo: 'DANFE' }>}
|
|
14
|
-
*/
|
|
15
|
-
async gerarDanfe(xmlNFe, opcoes = {}) {
|
|
16
|
-
const dados = extrairDadosDanfe(xmlNFe);
|
|
17
|
-
if (!dados.chaveAcesso) {
|
|
18
|
-
throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFE_CHAVE_MISSING');
|
|
19
|
-
}
|
|
20
|
-
if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
|
|
21
|
-
|
|
22
|
-
const html = this.#renderDanfe(dados, opcoes.titulo ?? 'DANFE — Documento Auxiliar da NF-e');
|
|
23
|
-
return { html, dados, tipo: 'DANFE' };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param {string} xmlNFCe
|
|
28
|
-
* @param {{ protocolo?: string, qrCodeUrl?: string, titulo?: string }} [opcoes]
|
|
29
|
-
* @returns {Promise<{ html: string, dados: object, tipo: 'DANFCE', qrCodeDataUrl?: string }>}
|
|
30
|
-
*/
|
|
31
|
-
async gerarDanfce(xmlNFCe, opcoes = {}) {
|
|
32
|
-
const dados = extrairDadosDanfe(xmlNFCe);
|
|
33
|
-
if (!dados.chaveAcesso) {
|
|
34
|
-
throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFCE_CHAVE_MISSING');
|
|
35
|
-
}
|
|
36
|
-
if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
|
|
37
|
-
if (opcoes.qrCodeUrl) dados.qrCode = opcoes.qrCodeUrl;
|
|
38
|
-
|
|
39
|
-
let qrCodeDataUrl = '';
|
|
40
|
-
if (dados.qrCode) {
|
|
41
|
-
qrCodeDataUrl = await QRCode.toDataURL(dados.qrCode, { width: 180, margin: 1 });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const html = this.#renderDanfce(dados, qrCodeDataUrl, opcoes.titulo ?? 'NFC-e — Cupom Fiscal Eletrônico');
|
|
45
|
-
return { html, dados, tipo: 'DANFCE', qrCodeDataUrl };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Detecta modelo e gera o documento adequado.
|
|
50
|
-
* @param {string} xml
|
|
51
|
-
* @param {object} [opcoes]
|
|
52
|
-
*/
|
|
53
|
-
async gerar(xml, opcoes = {}) {
|
|
54
|
-
const dados = extrairDadosDanfe(xml);
|
|
55
|
-
if (dados.modelo === '65') return this.gerarDanfce(xml, opcoes);
|
|
56
|
-
return this.gerarDanfe(xml, opcoes);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#esc(s) {
|
|
60
|
-
return String(s ?? '')
|
|
61
|
-
.replace(/&/g, '&')
|
|
62
|
-
.replace(/</g, '<')
|
|
63
|
-
.replace(/>/g, '>')
|
|
64
|
-
.replace(/"/g, '"');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
#renderDanfe(d, titulo) {
|
|
68
|
-
const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
|
|
69
|
-
const linhas = d.itens.map((i, idx) => `
|
|
70
|
-
<tr>
|
|
71
|
-
<td>${idx + 1}</td>
|
|
72
|
-
<td>${this.#esc(i.codigo)}</td>
|
|
73
|
-
<td>${this.#esc(i.descricao)}</td>
|
|
74
|
-
<td>${this.#esc(i.ncm)}</td>
|
|
75
|
-
<td>${this.#esc(i.cfop)}</td>
|
|
76
|
-
<td>${this.#esc(i.unidade)}</td>
|
|
77
|
-
<td class="r">${this.#esc(i.quantidade)}</td>
|
|
78
|
-
<td class="r">${this.#esc(i.valorUnitario)}</td>
|
|
79
|
-
<td class="r">${this.#esc(i.valorTotal)}</td>
|
|
80
|
-
</tr>`).join('');
|
|
81
|
-
|
|
82
|
-
return `<!DOCTYPE html>
|
|
83
|
-
<html lang="pt-BR">
|
|
84
|
-
<head>
|
|
85
|
-
<meta charset="UTF-8"/>
|
|
86
|
-
<title>${this.#esc(titulo)}</title>
|
|
87
|
-
<style>
|
|
88
|
-
* { box-sizing: border-box; }
|
|
89
|
-
body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #111; margin: 16px; }
|
|
90
|
-
h1 { font-size: 14px; margin: 0 0 8px; text-align: center; }
|
|
91
|
-
.box { border: 1px solid #333; padding: 8px; margin-bottom: 8px; }
|
|
92
|
-
.row { display: flex; gap: 8px; }
|
|
93
|
-
.col { flex: 1; }
|
|
94
|
-
.label { color: #555; font-size: 9px; text-transform: uppercase; }
|
|
95
|
-
.val { font-weight: 600; }
|
|
96
|
-
table { width: 100%; border-collapse: collapse; margin-top: 6px; }
|
|
97
|
-
th, td { border: 1px solid #999; padding: 4px; }
|
|
98
|
-
th { background: #eee; font-size: 9px; }
|
|
99
|
-
.r { text-align: right; }
|
|
100
|
-
.chave { letter-spacing: 1px; font-size: 12px; font-weight: bold; word-break: break-all; }
|
|
101
|
-
.homolog { color: #b00; font-weight: bold; text-align: center; }
|
|
102
|
-
@media print { body { margin: 0; } }
|
|
103
|
-
</style>
|
|
104
|
-
</head>
|
|
105
|
-
<body>
|
|
106
|
-
<h1>${this.#esc(titulo)}</h1>
|
|
107
|
-
${d.ambiente === '2' ? '<p class="homolog">NF-e EMITIDA EM AMBIENTE DE HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
|
|
108
|
-
<div class="box row">
|
|
109
|
-
<div class="col">
|
|
110
|
-
<div class="label">Emitente</div>
|
|
111
|
-
<div class="val">${this.#esc(d.emitente.razaoSocial)}</div>
|
|
112
|
-
<div>CNPJ ${this.#esc(d.emitente.cnpj)} — IE ${this.#esc(d.emitente.ie)}</div>
|
|
113
|
-
<div>${this.#esc(d.emitente.municipio)} / ${this.#esc(d.emitente.uf)}</div>
|
|
114
|
-
</div>
|
|
115
|
-
<div class="col">
|
|
116
|
-
<div class="label">Documento</div>
|
|
117
|
-
<div>NF-e nº <strong>${this.#esc(d.numero)}</strong> — Série ${this.#esc(d.serie)}</div>
|
|
118
|
-
<div>Emissão: ${this.#esc(d.dhEmi)}</div>
|
|
119
|
-
<div>Nat. Op.: ${this.#esc(d.naturezaOperacao)}</div>
|
|
120
|
-
<div>Protocolo: ${this.#esc(d.protocolo || '—')}</div>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="box">
|
|
124
|
-
<div class="label">Destinatário</div>
|
|
125
|
-
<div class="val">${this.#esc(d.destinatario.razaoSocial)}</div>
|
|
126
|
-
<div>CPF/CNPJ ${this.#esc(d.destinatario.doc)}</div>
|
|
127
|
-
</div>
|
|
128
|
-
<div class="box">
|
|
129
|
-
<div class="label">Chave de acesso</div>
|
|
130
|
-
<div class="chave">${this.#esc(chaveFmt)}</div>
|
|
131
|
-
</div>
|
|
132
|
-
<table>
|
|
133
|
-
<thead>
|
|
134
|
-
<tr>
|
|
135
|
-
<th>#</th><th>Cód.</th><th>Descrição</th><th>NCM</th><th>CFOP</th>
|
|
136
|
-
<th>Un</th><th>Qtd</th><th>V. Unit</th><th>V. Total</th>
|
|
137
|
-
</tr>
|
|
138
|
-
</thead>
|
|
139
|
-
<tbody>${linhas}</tbody>
|
|
140
|
-
</table>
|
|
141
|
-
<div class="box row" style="margin-top:8px">
|
|
142
|
-
<div class="col"><div class="label">Produtos</div><div class="val">R$ ${this.#esc(d.totais.vProd)}</div></div>
|
|
143
|
-
<div class="col"><div class="label">ICMS</div><div class="val">R$ ${this.#esc(d.totais.vICMS)}</div></div>
|
|
144
|
-
<div class="col"><div class="label">Frete</div><div class="val">R$ ${this.#esc(d.totais.vFrete)}</div></div>
|
|
145
|
-
<div class="col"><div class="label">Total NF</div><div class="val">R$ ${this.#esc(d.totais.vNF)}</div></div>
|
|
146
|
-
</div>
|
|
147
|
-
${d.informacoesComplementares ? `<div class="box"><div class="label">Informações complementares</div>${this.#esc(d.informacoesComplementares)}</div>` : ''}
|
|
148
|
-
<p style="text-align:center;color:#666;margin-top:12px">Documento gerado por GHINFE — representação auxiliar</p>
|
|
149
|
-
</body>
|
|
150
|
-
</html>`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
#renderDanfce(d, qrDataUrl, titulo) {
|
|
154
|
-
const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
|
|
155
|
-
const linhas = d.itens.map((i) => `
|
|
156
|
-
<tr>
|
|
157
|
-
<td>${this.#esc(i.descricao)}</td>
|
|
158
|
-
<td class="r">${this.#esc(i.quantidade)}</td>
|
|
159
|
-
<td class="r">${this.#esc(i.valorUnitario)}</td>
|
|
160
|
-
<td class="r">${this.#esc(i.valorTotal)}</td>
|
|
161
|
-
</tr>`).join('');
|
|
162
|
-
|
|
163
|
-
return `<!DOCTYPE html>
|
|
164
|
-
<html lang="pt-BR">
|
|
165
|
-
<head>
|
|
166
|
-
<meta charset="UTF-8"/>
|
|
167
|
-
<title>${this.#esc(titulo)}</title>
|
|
168
|
-
<style>
|
|
169
|
-
body { font-family: 'Courier New', monospace; font-size: 12px; width: 302px; margin: 8px auto; color: #000; }
|
|
170
|
-
h1 { font-size: 13px; text-align: center; margin: 0 0 6px; }
|
|
171
|
-
.c { text-align: center; }
|
|
172
|
-
.r { text-align: right; }
|
|
173
|
-
hr { border: none; border-top: 1px dashed #000; margin: 8px 0; }
|
|
174
|
-
table { width: 100%; border-collapse: collapse; }
|
|
175
|
-
td { padding: 2px 0; vertical-align: top; }
|
|
176
|
-
img.qr { display: block; margin: 8px auto; }
|
|
177
|
-
.homolog { color: #b00; font-weight: bold; text-align: center; font-size: 11px; }
|
|
178
|
-
@media print { body { margin: 0; width: 80mm; } }
|
|
179
|
-
</style>
|
|
180
|
-
</head>
|
|
181
|
-
<body>
|
|
182
|
-
<h1>${this.#esc(d.emitente.razaoSocial)}</h1>
|
|
183
|
-
<div class="c">CNPJ ${this.#esc(d.emitente.cnpj)} IE ${this.#esc(d.emitente.ie)}</div>
|
|
184
|
-
<div class="c">${this.#esc(d.emitente.municipio)}/${this.#esc(d.emitente.uf)}</div>
|
|
185
|
-
<hr/>
|
|
186
|
-
<div class="c"><strong>${this.#esc(titulo)}</strong></div>
|
|
187
|
-
${d.ambiente === '2' ? '<p class="homolog">EMITIDA EM HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
|
|
188
|
-
<div class="c">NFC-e nº ${this.#esc(d.numero)} Série ${this.#esc(d.serie)}</div>
|
|
189
|
-
<div class="c">${this.#esc(d.dhEmi)}</div>
|
|
190
|
-
<hr/>
|
|
191
|
-
<table>
|
|
192
|
-
<thead><tr><td>Item</td><td class="r">Qtd</td><td class="r">Vl Un</td><td class="r">Total</td></tr></thead>
|
|
193
|
-
<tbody>${linhas}</tbody>
|
|
194
|
-
</table>
|
|
195
|
-
<hr/>
|
|
196
|
-
<div class="c"><strong>TOTAL R$ ${this.#esc(d.totais.vNF)}</strong></div>
|
|
197
|
-
<div class="c">Protocolo: ${this.#esc(d.protocolo || '—')}</div>
|
|
198
|
-
<hr/>
|
|
199
|
-
<div class="c" style="font-size:10px;word-break:break-all">${this.#esc(chaveFmt)}</div>
|
|
200
|
-
${qrDataUrl ? `<img class="qr" src="${qrDataUrl}" alt="QR Code NFC-e" width="160" height="160"/>` : '<p class="c">(QR Code indisponível — assine e adicione infNFeSupl)</p>'}
|
|
201
|
-
<p class="c" style="font-size:10px">Consulte pela chave de acesso no portal da SEFAZ</p>
|
|
202
|
-
</body>
|
|
203
|
-
</html>`;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
1
|
+
import QRCode from 'qrcode';
|
|
2
|
+
import { extrairDadosDanfe, formatarChaveAcesso } from './xml-extract.js';
|
|
3
|
+
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gera DANFE (NF-e) e DANFCE (NFC-e) em HTML imprimível.
|
|
7
|
+
* PDF: imprima o HTML ou use um conversor no sistema consumidor (puppeteer, etc.).
|
|
8
|
+
*/
|
|
9
|
+
export class DanfeService {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} xmlNFe XML autorizado (nfeProc) ou NFe assinada
|
|
12
|
+
* @param {{ protocolo?: string, titulo?: string }} [opcoes]
|
|
13
|
+
* @returns {Promise<{ html: string, dados: object, tipo: 'DANFE' }>}
|
|
14
|
+
*/
|
|
15
|
+
async gerarDanfe(xmlNFe, opcoes = {}) {
|
|
16
|
+
const dados = extrairDadosDanfe(xmlNFe);
|
|
17
|
+
if (!dados.chaveAcesso) {
|
|
18
|
+
throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFE_CHAVE_MISSING');
|
|
19
|
+
}
|
|
20
|
+
if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
|
|
21
|
+
|
|
22
|
+
const html = this.#renderDanfe(dados, opcoes.titulo ?? 'DANFE — Documento Auxiliar da NF-e');
|
|
23
|
+
return { html, dados, tipo: 'DANFE' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} xmlNFCe
|
|
28
|
+
* @param {{ protocolo?: string, qrCodeUrl?: string, titulo?: string }} [opcoes]
|
|
29
|
+
* @returns {Promise<{ html: string, dados: object, tipo: 'DANFCE', qrCodeDataUrl?: string }>}
|
|
30
|
+
*/
|
|
31
|
+
async gerarDanfce(xmlNFCe, opcoes = {}) {
|
|
32
|
+
const dados = extrairDadosDanfe(xmlNFCe);
|
|
33
|
+
if (!dados.chaveAcesso) {
|
|
34
|
+
throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFCE_CHAVE_MISSING');
|
|
35
|
+
}
|
|
36
|
+
if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
|
|
37
|
+
if (opcoes.qrCodeUrl) dados.qrCode = opcoes.qrCodeUrl;
|
|
38
|
+
|
|
39
|
+
let qrCodeDataUrl = '';
|
|
40
|
+
if (dados.qrCode) {
|
|
41
|
+
qrCodeDataUrl = await QRCode.toDataURL(dados.qrCode, { width: 180, margin: 1 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const html = this.#renderDanfce(dados, qrCodeDataUrl, opcoes.titulo ?? 'NFC-e — Cupom Fiscal Eletrônico');
|
|
45
|
+
return { html, dados, tipo: 'DANFCE', qrCodeDataUrl };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detecta modelo e gera o documento adequado.
|
|
50
|
+
* @param {string} xml
|
|
51
|
+
* @param {object} [opcoes]
|
|
52
|
+
*/
|
|
53
|
+
async gerar(xml, opcoes = {}) {
|
|
54
|
+
const dados = extrairDadosDanfe(xml);
|
|
55
|
+
if (dados.modelo === '65') return this.gerarDanfce(xml, opcoes);
|
|
56
|
+
return this.gerarDanfe(xml, opcoes);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#esc(s) {
|
|
60
|
+
return String(s ?? '')
|
|
61
|
+
.replace(/&/g, '&')
|
|
62
|
+
.replace(/</g, '<')
|
|
63
|
+
.replace(/>/g, '>')
|
|
64
|
+
.replace(/"/g, '"');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#renderDanfe(d, titulo) {
|
|
68
|
+
const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
|
|
69
|
+
const linhas = d.itens.map((i, idx) => `
|
|
70
|
+
<tr>
|
|
71
|
+
<td>${idx + 1}</td>
|
|
72
|
+
<td>${this.#esc(i.codigo)}</td>
|
|
73
|
+
<td>${this.#esc(i.descricao)}</td>
|
|
74
|
+
<td>${this.#esc(i.ncm)}</td>
|
|
75
|
+
<td>${this.#esc(i.cfop)}</td>
|
|
76
|
+
<td>${this.#esc(i.unidade)}</td>
|
|
77
|
+
<td class="r">${this.#esc(i.quantidade)}</td>
|
|
78
|
+
<td class="r">${this.#esc(i.valorUnitario)}</td>
|
|
79
|
+
<td class="r">${this.#esc(i.valorTotal)}</td>
|
|
80
|
+
</tr>`).join('');
|
|
81
|
+
|
|
82
|
+
return `<!DOCTYPE html>
|
|
83
|
+
<html lang="pt-BR">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8"/>
|
|
86
|
+
<title>${this.#esc(titulo)}</title>
|
|
87
|
+
<style>
|
|
88
|
+
* { box-sizing: border-box; }
|
|
89
|
+
body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #111; margin: 16px; }
|
|
90
|
+
h1 { font-size: 14px; margin: 0 0 8px; text-align: center; }
|
|
91
|
+
.box { border: 1px solid #333; padding: 8px; margin-bottom: 8px; }
|
|
92
|
+
.row { display: flex; gap: 8px; }
|
|
93
|
+
.col { flex: 1; }
|
|
94
|
+
.label { color: #555; font-size: 9px; text-transform: uppercase; }
|
|
95
|
+
.val { font-weight: 600; }
|
|
96
|
+
table { width: 100%; border-collapse: collapse; margin-top: 6px; }
|
|
97
|
+
th, td { border: 1px solid #999; padding: 4px; }
|
|
98
|
+
th { background: #eee; font-size: 9px; }
|
|
99
|
+
.r { text-align: right; }
|
|
100
|
+
.chave { letter-spacing: 1px; font-size: 12px; font-weight: bold; word-break: break-all; }
|
|
101
|
+
.homolog { color: #b00; font-weight: bold; text-align: center; }
|
|
102
|
+
@media print { body { margin: 0; } }
|
|
103
|
+
</style>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<h1>${this.#esc(titulo)}</h1>
|
|
107
|
+
${d.ambiente === '2' ? '<p class="homolog">NF-e EMITIDA EM AMBIENTE DE HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
|
|
108
|
+
<div class="box row">
|
|
109
|
+
<div class="col">
|
|
110
|
+
<div class="label">Emitente</div>
|
|
111
|
+
<div class="val">${this.#esc(d.emitente.razaoSocial)}</div>
|
|
112
|
+
<div>CNPJ ${this.#esc(d.emitente.cnpj)} — IE ${this.#esc(d.emitente.ie)}</div>
|
|
113
|
+
<div>${this.#esc(d.emitente.municipio)} / ${this.#esc(d.emitente.uf)}</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="col">
|
|
116
|
+
<div class="label">Documento</div>
|
|
117
|
+
<div>NF-e nº <strong>${this.#esc(d.numero)}</strong> — Série ${this.#esc(d.serie)}</div>
|
|
118
|
+
<div>Emissão: ${this.#esc(d.dhEmi)}</div>
|
|
119
|
+
<div>Nat. Op.: ${this.#esc(d.naturezaOperacao)}</div>
|
|
120
|
+
<div>Protocolo: ${this.#esc(d.protocolo || '—')}</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="box">
|
|
124
|
+
<div class="label">Destinatário</div>
|
|
125
|
+
<div class="val">${this.#esc(d.destinatario.razaoSocial)}</div>
|
|
126
|
+
<div>CPF/CNPJ ${this.#esc(d.destinatario.doc)}</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="box">
|
|
129
|
+
<div class="label">Chave de acesso</div>
|
|
130
|
+
<div class="chave">${this.#esc(chaveFmt)}</div>
|
|
131
|
+
</div>
|
|
132
|
+
<table>
|
|
133
|
+
<thead>
|
|
134
|
+
<tr>
|
|
135
|
+
<th>#</th><th>Cód.</th><th>Descrição</th><th>NCM</th><th>CFOP</th>
|
|
136
|
+
<th>Un</th><th>Qtd</th><th>V. Unit</th><th>V. Total</th>
|
|
137
|
+
</tr>
|
|
138
|
+
</thead>
|
|
139
|
+
<tbody>${linhas}</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
<div class="box row" style="margin-top:8px">
|
|
142
|
+
<div class="col"><div class="label">Produtos</div><div class="val">R$ ${this.#esc(d.totais.vProd)}</div></div>
|
|
143
|
+
<div class="col"><div class="label">ICMS</div><div class="val">R$ ${this.#esc(d.totais.vICMS)}</div></div>
|
|
144
|
+
<div class="col"><div class="label">Frete</div><div class="val">R$ ${this.#esc(d.totais.vFrete)}</div></div>
|
|
145
|
+
<div class="col"><div class="label">Total NF</div><div class="val">R$ ${this.#esc(d.totais.vNF)}</div></div>
|
|
146
|
+
</div>
|
|
147
|
+
${d.informacoesComplementares ? `<div class="box"><div class="label">Informações complementares</div>${this.#esc(d.informacoesComplementares)}</div>` : ''}
|
|
148
|
+
<p style="text-align:center;color:#666;margin-top:12px">Documento gerado por GHINFE — representação auxiliar</p>
|
|
149
|
+
</body>
|
|
150
|
+
</html>`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#renderDanfce(d, qrDataUrl, titulo) {
|
|
154
|
+
const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
|
|
155
|
+
const linhas = d.itens.map((i) => `
|
|
156
|
+
<tr>
|
|
157
|
+
<td>${this.#esc(i.descricao)}</td>
|
|
158
|
+
<td class="r">${this.#esc(i.quantidade)}</td>
|
|
159
|
+
<td class="r">${this.#esc(i.valorUnitario)}</td>
|
|
160
|
+
<td class="r">${this.#esc(i.valorTotal)}</td>
|
|
161
|
+
</tr>`).join('');
|
|
162
|
+
|
|
163
|
+
return `<!DOCTYPE html>
|
|
164
|
+
<html lang="pt-BR">
|
|
165
|
+
<head>
|
|
166
|
+
<meta charset="UTF-8"/>
|
|
167
|
+
<title>${this.#esc(titulo)}</title>
|
|
168
|
+
<style>
|
|
169
|
+
body { font-family: 'Courier New', monospace; font-size: 12px; width: 302px; margin: 8px auto; color: #000; }
|
|
170
|
+
h1 { font-size: 13px; text-align: center; margin: 0 0 6px; }
|
|
171
|
+
.c { text-align: center; }
|
|
172
|
+
.r { text-align: right; }
|
|
173
|
+
hr { border: none; border-top: 1px dashed #000; margin: 8px 0; }
|
|
174
|
+
table { width: 100%; border-collapse: collapse; }
|
|
175
|
+
td { padding: 2px 0; vertical-align: top; }
|
|
176
|
+
img.qr { display: block; margin: 8px auto; }
|
|
177
|
+
.homolog { color: #b00; font-weight: bold; text-align: center; font-size: 11px; }
|
|
178
|
+
@media print { body { margin: 0; width: 80mm; } }
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<h1>${this.#esc(d.emitente.razaoSocial)}</h1>
|
|
183
|
+
<div class="c">CNPJ ${this.#esc(d.emitente.cnpj)} IE ${this.#esc(d.emitente.ie)}</div>
|
|
184
|
+
<div class="c">${this.#esc(d.emitente.municipio)}/${this.#esc(d.emitente.uf)}</div>
|
|
185
|
+
<hr/>
|
|
186
|
+
<div class="c"><strong>${this.#esc(titulo)}</strong></div>
|
|
187
|
+
${d.ambiente === '2' ? '<p class="homolog">EMITIDA EM HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
|
|
188
|
+
<div class="c">NFC-e nº ${this.#esc(d.numero)} Série ${this.#esc(d.serie)}</div>
|
|
189
|
+
<div class="c">${this.#esc(d.dhEmi)}</div>
|
|
190
|
+
<hr/>
|
|
191
|
+
<table>
|
|
192
|
+
<thead><tr><td>Item</td><td class="r">Qtd</td><td class="r">Vl Un</td><td class="r">Total</td></tr></thead>
|
|
193
|
+
<tbody>${linhas}</tbody>
|
|
194
|
+
</table>
|
|
195
|
+
<hr/>
|
|
196
|
+
<div class="c"><strong>TOTAL R$ ${this.#esc(d.totais.vNF)}</strong></div>
|
|
197
|
+
<div class="c">Protocolo: ${this.#esc(d.protocolo || '—')}</div>
|
|
198
|
+
<hr/>
|
|
199
|
+
<div class="c" style="font-size:10px;word-break:break-all">${this.#esc(chaveFmt)}</div>
|
|
200
|
+
${qrDataUrl ? `<img class="qr" src="${qrDataUrl}" alt="QR Code NFC-e" width="160" height="160"/>` : '<p class="c">(QR Code indisponível — assine e adicione infNFeSupl)</p>'}
|
|
201
|
+
<p class="c" style="font-size:10px">Consulte pela chave de acesso no portal da SEFAZ</p>
|
|
202
|
+
</body>
|
|
203
|
+
</html>`;
|
|
204
|
+
}
|
|
205
|
+
}
|