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
|
@@ -1,47 +1,69 @@
|
|
|
1
|
-
import forge from 'node-forge';
|
|
2
|
-
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param {string}
|
|
8
|
-
* @
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const p12 = forge.pkcs12.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
import forge from 'node-forge';
|
|
2
|
+
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Monta um PFX em memória a partir de certificado e chave PEM (ex.: cert.pem + key.pem),
|
|
6
|
+
* para uso nas APIs que recebem { pfx, senha }.
|
|
7
|
+
* @param {string} certificatePem
|
|
8
|
+
* @param {string} privateKeyPem Chave sem senha
|
|
9
|
+
* @param {string} senha Senha do PFX gerado
|
|
10
|
+
* @returns {Buffer}
|
|
11
|
+
*/
|
|
12
|
+
export function criarPfxDePem(certificatePem, privateKeyPem, senha) {
|
|
13
|
+
try {
|
|
14
|
+
const p12 = forge.pkcs12.toPkcs12Asn1(
|
|
15
|
+
forge.pki.privateKeyFromPem(privateKeyPem),
|
|
16
|
+
[forge.pki.certificateFromPem(certificatePem)],
|
|
17
|
+
senha,
|
|
18
|
+
{ algorithm: '3des' }
|
|
19
|
+
);
|
|
20
|
+
return Buffer.from(forge.asn1.toDer(p12).getBytes(), 'binary');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new GhinfeError('Falha ao converter certificado/chave PEM em PFX', 'CERT_PEM_ERROR', error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Carrega certificado A1 (.pfx / .p12) e retorna chave privada + certificado PEM.
|
|
28
|
+
* @param {Buffer|string} pfxBuffer
|
|
29
|
+
* @param {string} password
|
|
30
|
+
* @returns {{ privateKeyPem: string, certificatePem: string, certificate: forge.pki.Certificate }}
|
|
31
|
+
*/
|
|
32
|
+
export function loadPfxCertificate(pfxBuffer, password) {
|
|
33
|
+
try {
|
|
34
|
+
const buffer = Buffer.isBuffer(pfxBuffer) ? pfxBuffer : Buffer.from(pfxBuffer);
|
|
35
|
+
const p12Asn1 = forge.asn1.fromDer(buffer.toString('binary'));
|
|
36
|
+
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
|
|
37
|
+
|
|
38
|
+
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
|
39
|
+
const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
|
|
40
|
+
|
|
41
|
+
const certBag = certBags[forge.pki.oids.certBag]?.[0];
|
|
42
|
+
const keyBag =
|
|
43
|
+
keyBags[forge.pki.oids.pkcs8ShroudedKeyBag]?.[0] ??
|
|
44
|
+
p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag]?.[0];
|
|
45
|
+
|
|
46
|
+
if (!certBag?.cert || !keyBag?.key) {
|
|
47
|
+
throw new GhinfeError(
|
|
48
|
+
'Certificado ou chave privada não encontrados no arquivo PFX',
|
|
49
|
+
'CERT_NOT_FOUND'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const certificate = certBag.cert;
|
|
54
|
+
const privateKey = keyBag.key;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
privateKeyPem: forge.pki.privateKeyToPem(privateKey),
|
|
58
|
+
certificatePem: forge.pki.certificateToPem(certificate),
|
|
59
|
+
certificate,
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof GhinfeError) throw error;
|
|
63
|
+
throw new GhinfeError(
|
|
64
|
+
'Falha ao carregar certificado PFX. Verifique o arquivo e a senha.',
|
|
65
|
+
'CERT_LOAD_ERROR',
|
|
66
|
+
error
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/utils/chave.utils.js
CHANGED
|
@@ -1,117 +1,205 @@
|
|
|
1
|
-
import { UF_IBGE } from '../constants/sefaz.js';
|
|
2
|
-
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Gera código numérico aleatório de 8 dígitos (cNF).
|
|
6
|
-
* @returns {string}
|
|
7
|
-
*/
|
|
8
|
-
export function gerarCodigoNumerico() {
|
|
9
|
-
const valor = Math.floor(Math.random() * 99_999_999) + 1;
|
|
10
|
-
return String(valor).padStart(8, '0');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Calcula dígito verificador da chave NF-e (módulo 11).
|
|
15
|
-
* @param {string} chave43 Primeiros 43 dígitos da chave (sem DV)
|
|
16
|
-
* @returns {string} Dígito verificador (0-9)
|
|
17
|
-
*/
|
|
18
|
-
export function calcularDigitoVerificador(chave43) {
|
|
19
|
-
if (!/^\d{43}$/.test(chave43)) {
|
|
20
|
-
throw new GhinfeError('Chave deve conter exatamente 43 dígitos para cálculo do DV', 'CHAVE_INVALID_LENGTH');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const pesos = [2, 3, 4, 5, 6, 7, 8, 9];
|
|
24
|
-
let soma = 0;
|
|
25
|
-
let pesoIndex = 0;
|
|
26
|
-
|
|
27
|
-
for (let i = chave43.length - 1; i >= 0; i--) {
|
|
28
|
-
soma += parseInt(chave43[i], 10) * pesos[pesoIndex];
|
|
29
|
-
pesoIndex = (pesoIndex + 1) % pesos.length;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const resto = soma % 11;
|
|
33
|
-
return String(resto < 2 ? 0 : 11 - resto);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Valida se a chave de acesso possui DV correto.
|
|
38
|
-
* @param {string} chaveAcesso 44 dígitos
|
|
39
|
-
* @returns {boolean}
|
|
40
|
-
*/
|
|
41
|
-
export function validarChaveAcesso(chaveAcesso) {
|
|
42
|
-
const chave = chaveAcesso.replace(/\D/g, '');
|
|
43
|
-
if (chave.length !== 44) return false;
|
|
44
|
-
const dvCalculado = calcularDigitoVerificador(chave.substring(0, 43));
|
|
45
|
-
return dvCalculado === chave[43];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Gera chave de acesso NF-e/NFC-e (44 dígitos) conforme layout SEFAZ.
|
|
50
|
-
*
|
|
51
|
-
* Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nNF(9) + tpEmis(1) + cNF(8) + cDV(1)
|
|
52
|
-
*
|
|
53
|
-
* @param {{
|
|
54
|
-
* uf: string,
|
|
55
|
-
* cnpj: string,
|
|
56
|
-
* modelo?: string|number,
|
|
57
|
-
* serie: number,
|
|
58
|
-
* numero: number,
|
|
59
|
-
* tipoEmissao?: number,
|
|
60
|
-
* codigoNumerico?: string,
|
|
61
|
-
* dataEmissao?: Date,
|
|
62
|
-
* }} params
|
|
63
|
-
* @returns {string}
|
|
64
|
-
*/
|
|
65
|
-
export function gerarChaveAcesso(params) {
|
|
66
|
-
const uf = params.uf?.toUpperCase();
|
|
67
|
-
const cUF = UF_IBGE[uf];
|
|
68
|
-
|
|
69
|
-
if (!cUF) {
|
|
70
|
-
throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const data = params.dataEmissao ?? new Date();
|
|
74
|
-
const ano = String(data.getFullYear()).slice(-2);
|
|
75
|
-
const mes = String(data.getMonth() + 1).padStart(2, '0');
|
|
76
|
-
const aamm = `${ano}${mes}`;
|
|
77
|
-
|
|
78
|
-
const cnpj = params.cnpj.replace(/\D/g, '');
|
|
79
|
-
if (cnpj.length !== 14) {
|
|
80
|
-
throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const modelo = String(params.modelo ?? '55').padStart(2, '0');
|
|
84
|
-
const serie = String(params.serie).padStart(3, '0');
|
|
85
|
-
const numero = String(params.numero).padStart(9, '0');
|
|
86
|
-
const tpEmis = String(params.tipoEmissao ?? 1);
|
|
87
|
-
const cNF = (params.codigoNumerico ?? gerarCodigoNumerico()).padStart(8, '0');
|
|
88
|
-
|
|
89
|
-
const chave43 = `${cUF}${aamm}${cnpj}${modelo}${serie}${numero}${tpEmis}${cNF}`;
|
|
90
|
-
const cDV = calcularDigitoVerificador(chave43);
|
|
91
|
-
|
|
92
|
-
return chave43 + cDV;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1
|
+
import { UF_IBGE } from '../constants/sefaz.js';
|
|
2
|
+
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gera código numérico aleatório de 8 dígitos (cNF).
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function gerarCodigoNumerico() {
|
|
9
|
+
const valor = Math.floor(Math.random() * 99_999_999) + 1;
|
|
10
|
+
return String(valor).padStart(8, '0');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calcula dígito verificador da chave NF-e (módulo 11).
|
|
15
|
+
* @param {string} chave43 Primeiros 43 dígitos da chave (sem DV)
|
|
16
|
+
* @returns {string} Dígito verificador (0-9)
|
|
17
|
+
*/
|
|
18
|
+
export function calcularDigitoVerificador(chave43) {
|
|
19
|
+
if (!/^\d{43}$/.test(chave43)) {
|
|
20
|
+
throw new GhinfeError('Chave deve conter exatamente 43 dígitos para cálculo do DV', 'CHAVE_INVALID_LENGTH');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pesos = [2, 3, 4, 5, 6, 7, 8, 9];
|
|
24
|
+
let soma = 0;
|
|
25
|
+
let pesoIndex = 0;
|
|
26
|
+
|
|
27
|
+
for (let i = chave43.length - 1; i >= 0; i--) {
|
|
28
|
+
soma += parseInt(chave43[i], 10) * pesos[pesoIndex];
|
|
29
|
+
pesoIndex = (pesoIndex + 1) % pesos.length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const resto = soma % 11;
|
|
33
|
+
return String(resto < 2 ? 0 : 11 - resto);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Valida se a chave de acesso possui DV correto.
|
|
38
|
+
* @param {string} chaveAcesso 44 dígitos
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function validarChaveAcesso(chaveAcesso) {
|
|
42
|
+
const chave = chaveAcesso.replace(/\D/g, '');
|
|
43
|
+
if (chave.length !== 44) return false;
|
|
44
|
+
const dvCalculado = calcularDigitoVerificador(chave.substring(0, 43));
|
|
45
|
+
return dvCalculado === chave[43];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gera chave de acesso NF-e/NFC-e (44 dígitos) conforme layout SEFAZ.
|
|
50
|
+
*
|
|
51
|
+
* Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nNF(9) + tpEmis(1) + cNF(8) + cDV(1)
|
|
52
|
+
*
|
|
53
|
+
* @param {{
|
|
54
|
+
* uf: string,
|
|
55
|
+
* cnpj: string,
|
|
56
|
+
* modelo?: string|number,
|
|
57
|
+
* serie: number,
|
|
58
|
+
* numero: number,
|
|
59
|
+
* tipoEmissao?: number,
|
|
60
|
+
* codigoNumerico?: string,
|
|
61
|
+
* dataEmissao?: Date,
|
|
62
|
+
* }} params
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function gerarChaveAcesso(params) {
|
|
66
|
+
const uf = params.uf?.toUpperCase();
|
|
67
|
+
const cUF = UF_IBGE[uf];
|
|
68
|
+
|
|
69
|
+
if (!cUF) {
|
|
70
|
+
throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = params.dataEmissao ?? new Date();
|
|
74
|
+
const ano = String(data.getFullYear()).slice(-2);
|
|
75
|
+
const mes = String(data.getMonth() + 1).padStart(2, '0');
|
|
76
|
+
const aamm = `${ano}${mes}`;
|
|
77
|
+
|
|
78
|
+
const cnpj = params.cnpj.replace(/\D/g, '');
|
|
79
|
+
if (cnpj.length !== 14) {
|
|
80
|
+
throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const modelo = String(params.modelo ?? '55').padStart(2, '0');
|
|
84
|
+
const serie = String(params.serie).padStart(3, '0');
|
|
85
|
+
const numero = String(params.numero).padStart(9, '0');
|
|
86
|
+
const tpEmis = String(params.tipoEmissao ?? 1);
|
|
87
|
+
const cNF = (params.codigoNumerico ?? gerarCodigoNumerico()).padStart(8, '0');
|
|
88
|
+
|
|
89
|
+
const chave43 = `${cUF}${aamm}${cnpj}${modelo}${serie}${numero}${tpEmis}${cNF}`;
|
|
90
|
+
const cDV = calcularDigitoVerificador(chave43);
|
|
91
|
+
|
|
92
|
+
return chave43 + cDV;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gera código numérico da DC-e (cDC, 6 dígitos).
|
|
97
|
+
* A SEFAZ rejeita (897) sequências repetidas (000000, 111111...) e cDC igual a nDC.
|
|
98
|
+
* @param {number} [numero] nDC da declaração
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
export function gerarCodigoNumericoDCe(numero) {
|
|
102
|
+
for (;;) {
|
|
103
|
+
const cDC = String(Math.floor(Math.random() * 1_000_000)).padStart(6, '0');
|
|
104
|
+
if (/^(\d)\1{5}$/.test(cDC)) continue;
|
|
105
|
+
if (numero !== undefined && Number(cDC) === Number(numero)) continue;
|
|
106
|
+
return cDC;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Gera chave de acesso da DC-e (modelo 99, 44 dígitos).
|
|
112
|
+
*
|
|
113
|
+
* Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nDC(9) + tpEmis(1)
|
|
114
|
+
* + tpEmit(1) + nSiteAutoriz(1) + cDC(6) + cDV(1)
|
|
115
|
+
*
|
|
116
|
+
* O CNPJ é o de quem emite/assina: Fisco, Marketplace, Transportadora, ECT
|
|
117
|
+
* ou o próprio usuário emitente (emissão própria).
|
|
118
|
+
*
|
|
119
|
+
* @param {{
|
|
120
|
+
* uf: string,
|
|
121
|
+
* cnpj: string,
|
|
122
|
+
* serie: number,
|
|
123
|
+
* numero: number,
|
|
124
|
+
* tipoEmitente: number,
|
|
125
|
+
* tipoEmissao?: number,
|
|
126
|
+
* siteAutorizador?: number,
|
|
127
|
+
* codigoNumerico?: string,
|
|
128
|
+
* dataEmissao?: Date,
|
|
129
|
+
* }} params
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
export function gerarChaveAcessoDCe(params) {
|
|
133
|
+
const cUF = UF_IBGE[params.uf?.toUpperCase()];
|
|
134
|
+
if (!cUF) {
|
|
135
|
+
throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cnpj = params.cnpj.replace(/\D/g, '');
|
|
139
|
+
if (cnpj.length !== 14) {
|
|
140
|
+
throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const data = params.dataEmissao ?? new Date();
|
|
144
|
+
const aamm = `${String(data.getFullYear()).slice(-2)}${String(data.getMonth() + 1).padStart(2, '0')}`;
|
|
145
|
+
|
|
146
|
+
const serie = String(params.serie).padStart(3, '0');
|
|
147
|
+
const numero = String(params.numero).padStart(9, '0');
|
|
148
|
+
const tpEmis = String(params.tipoEmissao ?? 1);
|
|
149
|
+
const tpEmit = String(params.tipoEmitente);
|
|
150
|
+
const nSiteAutoriz = String(params.siteAutorizador ?? 0);
|
|
151
|
+
const cDC = (params.codigoNumerico ?? gerarCodigoNumericoDCe(params.numero)).padStart(6, '0');
|
|
152
|
+
|
|
153
|
+
const chave43 = `${cUF}${aamm}${cnpj}99${serie}${numero}${tpEmis}${tpEmit}${nSiteAutoriz}${cDC}`;
|
|
154
|
+
return chave43 + calcularDigitoVerificador(chave43);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extrai campos da chave de acesso da DC-e.
|
|
159
|
+
* @param {string} chaveAcesso
|
|
160
|
+
*/
|
|
161
|
+
export function parseChaveAcessoDCe(chaveAcesso) {
|
|
162
|
+
const chave = chaveAcesso.replace(/\D/g, '');
|
|
163
|
+
if (chave.length !== 44) {
|
|
164
|
+
throw new GhinfeError('Chave de acesso deve ter 44 dígitos', 'CHAVE_INVALID_LENGTH');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
cUF: chave.substring(0, 2),
|
|
169
|
+
aamm: chave.substring(2, 6),
|
|
170
|
+
cnpj: chave.substring(6, 20),
|
|
171
|
+
modelo: chave.substring(20, 22),
|
|
172
|
+
serie: parseInt(chave.substring(22, 25), 10),
|
|
173
|
+
numero: parseInt(chave.substring(25, 34), 10),
|
|
174
|
+
tipoEmissao: parseInt(chave[34], 10),
|
|
175
|
+
tipoEmitente: parseInt(chave[35], 10),
|
|
176
|
+
siteAutorizador: parseInt(chave[36], 10),
|
|
177
|
+
codigoNumerico: chave.substring(37, 43),
|
|
178
|
+
digitoVerificador: chave[43],
|
|
179
|
+
valida: validarChaveAcesso(chave),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extrai campos da chave de acesso para conferência.
|
|
185
|
+
* @param {string} chaveAcesso
|
|
186
|
+
*/
|
|
187
|
+
export function parseChaveAcesso(chaveAcesso) {
|
|
188
|
+
const chave = chaveAcesso.replace(/\D/g, '');
|
|
189
|
+
if (chave.length !== 44) {
|
|
190
|
+
throw new GhinfeError('Chave de acesso deve ter 44 dígitos', 'CHAVE_INVALID_LENGTH');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
cUF: chave.substring(0, 2),
|
|
195
|
+
aamm: chave.substring(2, 6),
|
|
196
|
+
cnpj: chave.substring(6, 20),
|
|
197
|
+
modelo: chave.substring(20, 22),
|
|
198
|
+
serie: parseInt(chave.substring(22, 25), 10),
|
|
199
|
+
numero: parseInt(chave.substring(25, 34), 10),
|
|
200
|
+
tipoEmissao: parseInt(chave[34], 10),
|
|
201
|
+
codigoNumerico: chave.substring(35, 43),
|
|
202
|
+
digitoVerificador: chave[43],
|
|
203
|
+
valida: validarChaveAcesso(chave),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Larguras (barra, espaço, barra, ...) de cada símbolo Code 128, em módulos.
|
|
5
|
+
* Índice = valor do símbolo; 105 = Start C, 106 = Stop.
|
|
6
|
+
*/
|
|
7
|
+
const PADROES = [
|
|
8
|
+
'212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213',
|
|
9
|
+
'221312', '231212', '112232', '122132', '122231', '113222', '123122', '123221', '223211', '221132',
|
|
10
|
+
'221231', '213212', '223112', '312131', '311222', '321122', '321221', '312212', '322112', '322211',
|
|
11
|
+
'212123', '212321', '232121', '111323', '131123', '131321', '112313', '132113', '132311', '211313',
|
|
12
|
+
'231113', '231311', '112133', '112331', '132131', '113123', '113321', '133121', '313121', '211331',
|
|
13
|
+
'231131', '213113', '213311', '213131', '311123', '311321', '331121', '312113', '312311', '332111',
|
|
14
|
+
'314111', '221411', '431111', '111224', '111422', '121124', '121421', '141122', '141221', '112214',
|
|
15
|
+
'112412', '122114', '122411', '142112', '142211', '241211', '221114', '413111', '241112', '134111',
|
|
16
|
+
'111242', '121142', '121241', '114212', '124112', '124211', '411212', '421112', '421211', '212141',
|
|
17
|
+
'214121', '412121', '111143', '111341', '131141', '114113', '114311', '411113', '411311', '113141',
|
|
18
|
+
'114131', '311141', '411131', '211412', '211214', '211232', '2331112',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const START_C = 105;
|
|
22
|
+
const STOP = 106;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Codifica dígitos em Code 128 subconjunto C (pares numéricos), com DV módulo 103.
|
|
26
|
+
* @param {string} digitos Quantidade par de dígitos (ex.: chave de acesso, 44)
|
|
27
|
+
* @returns {{ simbolos: number[], dv: number, larguras: number[] }}
|
|
28
|
+
*/
|
|
29
|
+
export function codificarCode128C(digitos) {
|
|
30
|
+
if (!/^\d+$/.test(digitos) || digitos.length % 2 !== 0) {
|
|
31
|
+
throw new GhinfeError('Code 128C exige quantidade par de dígitos', 'CODE128C_INVALIDO');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const dados = digitos.match(/\d{2}/g).map(Number);
|
|
35
|
+
const soma = dados.reduce((acc, valor, i) => acc + valor * (i + 1), START_C);
|
|
36
|
+
const dv = soma % 103;
|
|
37
|
+
const simbolos = [START_C, ...dados, dv, STOP];
|
|
38
|
+
const larguras = simbolos.flatMap((s) => [...PADROES[s]].map(Number));
|
|
39
|
+
|
|
40
|
+
return { simbolos, dv, larguras };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gera SVG do código de barras Code 128C (barras em unidades de módulo, escalável via CSS).
|
|
45
|
+
* @param {string} digitos
|
|
46
|
+
* @param {{ altura?: number, margem?: number }} [opcoes] altura e margem clara em módulos (margem mínima: 10)
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function gerarSvgCode128C(digitos, opcoes = {}) {
|
|
50
|
+
const altura = opcoes.altura ?? 50;
|
|
51
|
+
const margem = Math.max(opcoes.margem ?? 10, 10);
|
|
52
|
+
const { larguras } = codificarCode128C(digitos);
|
|
53
|
+
|
|
54
|
+
let x = margem;
|
|
55
|
+
const barras = [];
|
|
56
|
+
larguras.forEach((largura, i) => {
|
|
57
|
+
if (i % 2 === 0) barras.push(`<rect x="${x}" y="0" width="${largura}" height="${altura}"/>`);
|
|
58
|
+
x += largura;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const total = x + margem;
|
|
62
|
+
return (
|
|
63
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${altura}" preserveAspectRatio="none" ` +
|
|
64
|
+
`shape-rendering="crispEdges" role="img" aria-label="${digitos}">` +
|
|
65
|
+
`<rect width="${total}" height="${altura}" fill="#fff"/><g fill="#000">${barras.join('')}</g></svg>`
|
|
66
|
+
);
|
|
67
|
+
}
|
package/src/utils/date.utils.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Formata data para padrão SEFAZ: AAAA-MM-DDTHH:mm:ss-03:00
|
|
3
|
-
* @param {Date} [date]
|
|
4
|
-
* @returns {string}
|
|
5
|
-
*/
|
|
6
|
-
export function formatSefazDateTime(date = new Date()) {
|
|
7
|
-
const pad = (n) => String(n).padStart(2, '0');
|
|
8
|
-
const offset = -date.getTimezoneOffset();
|
|
9
|
-
const sign = offset >= 0 ? '+' : '-';
|
|
10
|
-
const abs = Math.abs(offset);
|
|
11
|
-
const tz = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
|
15
|
-
`T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${tz}`
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {Date} [date]
|
|
21
|
-
* @returns {string} AAAA-MM-DD
|
|
22
|
-
*/
|
|
23
|
-
export function formatSefazDate(date = new Date()) {
|
|
24
|
-
const pad = (n) => String(n).padStart(2, '0');
|
|
25
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
26
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Formata data para padrão SEFAZ: AAAA-MM-DDTHH:mm:ss-03:00
|
|
3
|
+
* @param {Date} [date]
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function formatSefazDateTime(date = new Date()) {
|
|
7
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
8
|
+
const offset = -date.getTimezoneOffset();
|
|
9
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
10
|
+
const abs = Math.abs(offset);
|
|
11
|
+
const tz = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
|
15
|
+
`T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${tz}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Date} [date]
|
|
21
|
+
* @returns {string} AAAA-MM-DD
|
|
22
|
+
*/
|
|
23
|
+
export function formatSefazDate(date = new Date()) {
|
|
24
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
25
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
26
|
+
}
|
package/src/utils/xml.utils.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @param {string} xml
|
|
5
|
-
* @returns {string}
|
|
6
|
-
*/
|
|
7
|
-
export function minifyXml(xml) {
|
|
8
|
-
return xml.replace(/>\s+</g, '><').trim();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {string} xml
|
|
13
|
-
* @returns {Document}
|
|
14
|
-
*/
|
|
15
|
-
export function parseXml(xml) {
|
|
16
|
-
const parser = new DOMParser();
|
|
17
|
-
const doc = parser.parseFromString(xml, 'text/xml');
|
|
18
|
-
const parseError = doc.getElementsByTagName('parsererror')[0];
|
|
19
|
-
if (parseError) {
|
|
20
|
-
throw new Error(`XML inválido: ${parseError.textContent}`);
|
|
21
|
-
}
|
|
22
|
-
return doc;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {Document|Element} node
|
|
27
|
-
* @returns {string}
|
|
28
|
-
*/
|
|
29
|
-
export function serializeXml(node) {
|
|
30
|
-
return new XMLSerializer().serializeToString(node);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {string} value
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
export function escapeXml(value) {
|
|
38
|
-
return String(value)
|
|
39
|
-
.replace(/&/g, '&')
|
|
40
|
-
.replace(/</g, '<')
|
|
41
|
-
.replace(/>/g, '>')
|
|
42
|
-
.replace(/"/g, '"')
|
|
43
|
-
.replace(/'/g, ''');
|
|
44
|
-
}
|
|
1
|
+
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} xml
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function minifyXml(xml) {
|
|
8
|
+
return xml.replace(/>\s+</g, '><').trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} xml
|
|
13
|
+
* @returns {Document}
|
|
14
|
+
*/
|
|
15
|
+
export function parseXml(xml) {
|
|
16
|
+
const parser = new DOMParser();
|
|
17
|
+
const doc = parser.parseFromString(xml, 'text/xml');
|
|
18
|
+
const parseError = doc.getElementsByTagName('parsererror')[0];
|
|
19
|
+
if (parseError) {
|
|
20
|
+
throw new Error(`XML inválido: ${parseError.textContent}`);
|
|
21
|
+
}
|
|
22
|
+
return doc;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Document|Element} node
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
export function serializeXml(node) {
|
|
30
|
+
return new XMLSerializer().serializeToString(node);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} value
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function escapeXml(value) {
|
|
38
|
+
return String(value)
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/</g, '<')
|
|
41
|
+
.replace(/>/g, '>')
|
|
42
|
+
.replace(/"/g, '"')
|
|
43
|
+
.replace(/'/g, ''');
|
|
44
|
+
}
|