ghinfe 0.6.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/.env.example +15 -0
- package/LICENSE +21 -0
- package/README.md +413 -0
- package/docs/developer-guide.html +421 -0
- package/docs/openapi.yaml +428 -0
- package/examples/emitir-nfce.js +51 -0
- package/examples/emitir-nfe.js +83 -0
- package/examples/emitir-nfse.js +46 -0
- package/examples/endpoints-sefaz.js +14 -0
- package/examples/eventos-nfe.js +49 -0
- package/examples/gerar-chave.js +25 -0
- package/examples/gerar-danfe.js +75 -0
- package/examples/layouts-nfse.js +51 -0
- package/examples/mock-sefaz.js +52 -0
- package/examples/nfse-eventos.js +57 -0
- package/examples/reforma-ibscbs.js +27 -0
- package/examples/reforma-nfe-xml.js +52 -0
- package/examples/status-sefaz.js +12 -0
- package/examples/validar-xsd.js +48 -0
- package/package.json +68 -0
- package/schemas/nfe/PL_009_V4/consReciNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/consSitNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/consStatServ_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +9 -0
- package/schemas/nfe/PL_009_V4/enviNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/inutNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/leiauteNFe_v4.00.xsd +7339 -0
- package/schemas/nfe/PL_009_V4/nfe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/tiposBasico_v4.00.xsd +598 -0
- package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +98 -0
- package/scripts/download-schemas.js +39 -0
- package/scripts/test-xsd.js +34 -0
- package/src/api/server.js +316 -0
- package/src/constants/sefaz.js +69 -0
- package/src/danfe/DanfeService.js +205 -0
- package/src/danfe/xml-extract.js +69 -0
- package/src/errors/GhinfeError.js +13 -0
- package/src/errors/SefazError.js +25 -0
- package/src/errors/XsdValidationError.js +15 -0
- package/src/index.js +60 -0
- package/src/nfce/builders/NFCeXmlBuilder.js +178 -0
- package/src/nfce/constants/qrcode.js +26 -0
- package/src/nfce/services/NFCeService.js +188 -0
- package/src/nfce/services/NFCeXmlService.js +38 -0
- package/src/nfce/types/NFCeDocument.js +42 -0
- package/src/nfce/utils/qrcode.utils.js +92 -0
- package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -0
- package/src/nfe/builders/NFeXmlBuilder.js +215 -0
- package/src/nfe/mocks/sefaz-responses.js +195 -0
- package/src/nfe/services/NFeEventoService.js +95 -0
- package/src/nfe/services/NFeService.js +196 -0
- package/src/nfe/services/NFeSignService.js +111 -0
- package/src/nfe/services/NFeSoapService.js +437 -0
- package/src/nfe/services/NFeXmlService.js +41 -0
- package/src/nfe/services/NFeXsdService.js +56 -0
- package/src/nfe/types/NFeDocument.js +92 -0
- package/src/nfe/types/NFeEvento.js +44 -0
- package/src/nfse/NFSeMunicipioResolver.js +42 -0
- package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -0
- package/src/nfse/builders/NFSeXmlBuilder.js +16 -0
- package/src/nfse/constants/municipios.js +82 -0
- package/src/nfse/layouts/adapters.js +202 -0
- package/src/nfse/mocks/nfse-responses.js +142 -0
- package/src/nfse/services/NFSeService.js +191 -0
- package/src/nfse/services/NFSeSoapService.js +232 -0
- package/src/nfse/services/NFSeXmlService.js +12 -0
- package/src/nfse/types/NFSeDocument.js +54 -0
- package/src/queue/NFCeQueue.js +63 -0
- package/src/queue/NFSeQueue.js +59 -0
- package/src/queue/NFeQueue.js +76 -0
- package/src/reforma/ibscbs.js +175 -0
- package/src/sefaz/SefazEndpointResolver.js +95 -0
- package/src/sefaz/endpoints.registry.js +205 -0
- package/src/shared/fiscal.helpers.js +53 -0
- package/src/utils/certificate.utils.js +47 -0
- package/src/utils/chave.utils.js +117 -0
- package/src/utils/date.utils.js +26 -0
- package/src/utils/xml.utils.js +44 -0
- package/src/validation/XsdValidator.js +114 -0
- package/src/validation/schemas.js +14 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NFSE_MUNICIPIOS } from './constants/municipios.js';
|
|
2
|
+
import { GhinfeError } from '../errors/GhinfeError.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve endpoint NFS-e por código IBGE do município.
|
|
6
|
+
*/
|
|
7
|
+
export class NFSeMunicipioResolver {
|
|
8
|
+
/** @type {Record<string, object>} */
|
|
9
|
+
static #custom = {};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registra endpoint customizado para um município.
|
|
13
|
+
* @param {string} codigoMunicipio IBGE 7 dígitos
|
|
14
|
+
* @param {{ nome?: string, homologacao: string, producao: string, layout?: string }} config
|
|
15
|
+
*/
|
|
16
|
+
static registrar(codigoMunicipio, config) {
|
|
17
|
+
this.#custom[codigoMunicipio] = config;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} codigoMunicipio
|
|
22
|
+
* @param {number} [ambiente] 1=Produção, 2=Homologação
|
|
23
|
+
*/
|
|
24
|
+
static resolver(codigoMunicipio, ambiente = 2) {
|
|
25
|
+
const config = this.#custom[codigoMunicipio] ?? NFSE_MUNICIPIOS[codigoMunicipio];
|
|
26
|
+
|
|
27
|
+
if (!config) {
|
|
28
|
+
throw new GhinfeError(
|
|
29
|
+
`Endpoint NFS-e não configurado para município ${codigoMunicipio}. Use NFSeMunicipioResolver.registrar()`,
|
|
30
|
+
'NFSE_MUNICIPIO_NOT_FOUND'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const url = ambiente === 1 ? config.producao : config.homologacao;
|
|
35
|
+
return { url, layout: config.layout ?? 'abrasf', nome: config.nome };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @returns {string[]} */
|
|
39
|
+
static listarMunicipios() {
|
|
40
|
+
return [...new Set([...Object.keys(NFSE_MUNICIPIOS), ...Object.keys(this.#custom)])];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { create } from 'xmlbuilder2';
|
|
2
|
+
import { NFSE_NAMESPACE } from '../constants/municipios.js';
|
|
3
|
+
import { GhinfeError } from '../../errors/GhinfeError.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* XML de cancelamento e substituição de NFS-e (padrão ABRASF 2.04).
|
|
7
|
+
*/
|
|
8
|
+
export class NFSeEventoXmlBuilder {
|
|
9
|
+
/**
|
|
10
|
+
* @param {{
|
|
11
|
+
* numeroNfse: string,
|
|
12
|
+
* cnpjPrestador: string,
|
|
13
|
+
* inscricaoMunicipal: string,
|
|
14
|
+
* codigoMunicipio: string,
|
|
15
|
+
* codigoCancelamento?: string,
|
|
16
|
+
* }} dados
|
|
17
|
+
*/
|
|
18
|
+
buildCancelamento(dados) {
|
|
19
|
+
if (!dados.numeroNfse) throw new GhinfeError('numeroNfse é obrigatório', 'NFSE_CANCEL_NUMERO');
|
|
20
|
+
if (!dados.cnpjPrestador) throw new GhinfeError('cnpjPrestador é obrigatório', 'NFSE_CANCEL_CNPJ');
|
|
21
|
+
|
|
22
|
+
const id = `Cancelamento_${dados.numeroNfse}`;
|
|
23
|
+
const doc = create({ version: '1.0', encoding: 'UTF-8' })
|
|
24
|
+
.ele('CancelarNfseEnvio', { xmlns: NFSE_NAMESPACE })
|
|
25
|
+
.ele('Pedido')
|
|
26
|
+
.ele('InfPedidoCancelamento', { Id: id });
|
|
27
|
+
|
|
28
|
+
const inf = doc;
|
|
29
|
+
const ident = inf.ele('IdentificacaoNfse');
|
|
30
|
+
ident.ele('Numero').txt(String(dados.numeroNfse)).up();
|
|
31
|
+
ident.ele('Cnpj').txt(dados.cnpjPrestador.replace(/\D/g, '')).up();
|
|
32
|
+
ident.ele('InscricaoMunicipal').txt(dados.inscricaoMunicipal).up();
|
|
33
|
+
ident.ele('CodigoMunicipio').txt(dados.codigoMunicipio).up();
|
|
34
|
+
ident.up();
|
|
35
|
+
inf.ele('CodigoCancelamento').txt(dados.codigoCancelamento ?? '2').up();
|
|
36
|
+
|
|
37
|
+
return doc.end({ prettyPrint: false, headless: false });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Substituição = pedido de cancelamento da NFS-e substituída + novo RPS.
|
|
42
|
+
* O XML do novo RPS deve ser passado já montado (GerarNfseEnvio / lote).
|
|
43
|
+
*
|
|
44
|
+
* @param {{
|
|
45
|
+
* numeroNfseSubstituida: string,
|
|
46
|
+
* cnpjPrestador: string,
|
|
47
|
+
* inscricaoMunicipal: string,
|
|
48
|
+
* codigoMunicipio: string,
|
|
49
|
+
* codigoCancelamento?: string,
|
|
50
|
+
* xmlRpsSubstituto: string,
|
|
51
|
+
* }} dados
|
|
52
|
+
*/
|
|
53
|
+
buildSubstituicao(dados) {
|
|
54
|
+
if (!dados.numeroNfseSubstituida) {
|
|
55
|
+
throw new GhinfeError('numeroNfseSubstituida é obrigatório', 'NFSE_SUBST_NUMERO');
|
|
56
|
+
}
|
|
57
|
+
if (!dados.xmlRpsSubstituto) {
|
|
58
|
+
throw new GhinfeError('xmlRpsSubstituto é obrigatório', 'NFSE_SUBST_RPS');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const cancelamento = this.buildCancelamento({
|
|
62
|
+
numeroNfse: dados.numeroNfseSubstituida,
|
|
63
|
+
cnpjPrestador: dados.cnpjPrestador,
|
|
64
|
+
inscricaoMunicipal: dados.inscricaoMunicipal,
|
|
65
|
+
codigoMunicipio: dados.codigoMunicipio,
|
|
66
|
+
codigoCancelamento: dados.codigoCancelamento ?? '1',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Extrai InfPedidoCancelamento e Rps interno do RPS substituto
|
|
70
|
+
const pedidoMatch = cancelamento.match(/<Pedido>[\s\S]*?<\/Pedido>/);
|
|
71
|
+
const rpsInner = this.#extrairRps(dados.xmlRpsSubstituto);
|
|
72
|
+
|
|
73
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
74
|
+
<SubstituirNfseEnvio xmlns="${NFSE_NAMESPACE}">
|
|
75
|
+
<SubstituicaoNfse Id="Substituicao_${dados.numeroNfseSubstituida}">
|
|
76
|
+
${pedidoMatch?.[0] ?? ''}
|
|
77
|
+
<Rps>${rpsInner}</Rps>
|
|
78
|
+
</SubstituicaoNfse>
|
|
79
|
+
</SubstituirNfseEnvio>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @param {string} xmlRps */
|
|
83
|
+
#extrairRps(xmlRps) {
|
|
84
|
+
const inf = xmlRps.match(/<(InfRps|InfDeclaracaoPrestacaoServico)[\s\S]*?<\/\1>/);
|
|
85
|
+
if (inf) return inf[0];
|
|
86
|
+
const rps = xmlRps.match(/<Rps[\s\S]*?<\/Rps>/);
|
|
87
|
+
if (rps) {
|
|
88
|
+
return rps[0].replace(/^<Rps[^>]*>/, '').replace(/<\/Rps>$/, '');
|
|
89
|
+
}
|
|
90
|
+
return xmlRps.replace(/^<\?xml[^?]*\?>/, '').trim();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { obterAdapter } from '../layouts/adapters.js';
|
|
2
|
+
import { NFSE_LAYOUTS } from '../constants/municipios.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gera XML RPS conforme layout municipal (ABRASF, GINFES, Betha, ISSNet).
|
|
6
|
+
*/
|
|
7
|
+
export class NFSeXmlBuilder {
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
|
|
10
|
+
* @param {string} [layout] abrasf | ginfes | betha | issnet
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
buildGerarNfse(documento, layout = NFSE_LAYOUTS.ABRASF) {
|
|
14
|
+
return obterAdapter(layout).build(documento);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Layouts NFS-e suportados pela biblioteca. */
|
|
2
|
+
export const NFSE_LAYOUTS = {
|
|
3
|
+
ABRASF: 'abrasf',
|
|
4
|
+
GINFES: 'ginfes',
|
|
5
|
+
BETHA: 'betha',
|
|
6
|
+
ISSNET: 'issnet',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Layout ABRASF 2.04 — padrão mais adotado pelos municípios. */
|
|
10
|
+
export const NFSE_LAYOUT = '2.04';
|
|
11
|
+
export const NFSE_NAMESPACE = 'http://www.abrasf.org.br/nfse.xsd';
|
|
12
|
+
export const GINFES_NAMESPACE = 'http://www.ginfes.com.br/servico_enviar_lote_rps_envio';
|
|
13
|
+
export const BETHA_NAMESPACE = 'http://www.betha.com.br/e-nota-contribuinte-ws';
|
|
14
|
+
export const ISSNET_NAMESPACE = 'http://www.issnetonline.com.br/webserviceabrasf/vsd/servico_enviar_lote_rps_envio.xsd';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Endpoints NFS-e por município (IBGE).
|
|
18
|
+
* layout: abrasf | ginfes | betha | issnet
|
|
19
|
+
* URLs são exemplos — valide com a prefeitura / provedor.
|
|
20
|
+
*/
|
|
21
|
+
export const NFSE_MUNICIPIOS = {
|
|
22
|
+
'3550308': {
|
|
23
|
+
nome: 'São Paulo/SP',
|
|
24
|
+
layout: NFSE_LAYOUTS.ABRASF,
|
|
25
|
+
homologacao: 'https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx',
|
|
26
|
+
producao: 'https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx',
|
|
27
|
+
},
|
|
28
|
+
'3304557': {
|
|
29
|
+
nome: 'Rio de Janeiro/RJ',
|
|
30
|
+
layout: NFSE_LAYOUTS.ABRASF,
|
|
31
|
+
homologacao: 'https://notacarioca.rio.gov.br/WSNacional/nfse.asmx',
|
|
32
|
+
producao: 'https://notacarioca.rio.gov.br/WSNacional/nfse.asmx',
|
|
33
|
+
},
|
|
34
|
+
'3106200': {
|
|
35
|
+
nome: 'Belo Horizonte/MG',
|
|
36
|
+
layout: NFSE_LAYOUTS.ABRASF,
|
|
37
|
+
homologacao: 'https://bhissdigital.pbh.gov.br/bhiss-ws/nfse',
|
|
38
|
+
producao: 'https://bhissdigital.pbh.gov.br/bhiss-ws/nfse',
|
|
39
|
+
},
|
|
40
|
+
'4106902': {
|
|
41
|
+
nome: 'Curitiba/PR',
|
|
42
|
+
layout: NFSE_LAYOUTS.ABRASF,
|
|
43
|
+
homologacao: 'https://isscuritiba.curitiba.pr.gov.br/Iss.NfseWebService/NfseService.svc',
|
|
44
|
+
producao: 'https://isscuritiba.curitiba.pr.gov.br/Iss.NfseWebService/NfseService.svc',
|
|
45
|
+
},
|
|
46
|
+
'4314902': {
|
|
47
|
+
nome: 'Porto Alegre/RS',
|
|
48
|
+
layout: NFSE_LAYOUTS.BETHA,
|
|
49
|
+
homologacao: 'https://e-gov.betha.com.br/e-nota-contribuinte-ws/nfseWS',
|
|
50
|
+
producao: 'https://e-gov.betha.com.br/e-nota-contribuinte-ws/nfseWS',
|
|
51
|
+
},
|
|
52
|
+
'4205407': {
|
|
53
|
+
nome: 'Florianópolis/SC',
|
|
54
|
+
layout: NFSE_LAYOUTS.BETHA,
|
|
55
|
+
homologacao: 'https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/nfseWS',
|
|
56
|
+
producao: 'https://e-gov.betha.com.br/e-nota-contribuinte-ws/nfseWS',
|
|
57
|
+
},
|
|
58
|
+
'2927408': {
|
|
59
|
+
nome: 'Salvador/BA',
|
|
60
|
+
layout: NFSE_LAYOUTS.GINFES,
|
|
61
|
+
homologacao: 'https://homologacao.ginfes.com.br/ServiceGinfesImpl',
|
|
62
|
+
producao: 'https://producao.ginfes.com.br/ServiceGinfesImpl',
|
|
63
|
+
},
|
|
64
|
+
'2611606': {
|
|
65
|
+
nome: 'Recife/PE',
|
|
66
|
+
layout: NFSE_LAYOUTS.ISSNET,
|
|
67
|
+
homologacao: 'https://www.issnetonline.com.br/webserviceabrasf/homologacao/servicos.asmx',
|
|
68
|
+
producao: 'https://www.issnetonline.com.br/webserviceabrasf/producao/servicos.asmx',
|
|
69
|
+
},
|
|
70
|
+
'2304400': {
|
|
71
|
+
nome: 'Fortaleza/CE',
|
|
72
|
+
layout: NFSE_LAYOUTS.ISSNET,
|
|
73
|
+
homologacao: 'https://iss.fortaleza.ce.gov.br/group/guest/nfe',
|
|
74
|
+
producao: 'https://iss.fortaleza.ce.gov.br/group/guest/nfe',
|
|
75
|
+
},
|
|
76
|
+
mock: {
|
|
77
|
+
nome: 'Mock GHINFE',
|
|
78
|
+
layout: NFSE_LAYOUTS.ABRASF,
|
|
79
|
+
homologacao: 'http://localhost/mock/nfse',
|
|
80
|
+
producao: 'http://localhost/mock/nfse',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { create } from 'xmlbuilder2';
|
|
2
|
+
import {
|
|
3
|
+
NFSE_NAMESPACE,
|
|
4
|
+
GINFES_NAMESPACE,
|
|
5
|
+
BETHA_NAMESPACE,
|
|
6
|
+
ISSNET_NAMESPACE,
|
|
7
|
+
NFSE_LAYOUTS,
|
|
8
|
+
} from '../constants/municipios.js';
|
|
9
|
+
import { formatSefazDateTime } from '../../utils/date.utils.js';
|
|
10
|
+
import { GhinfeError } from '../../errors/GhinfeError.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Monta o bloco InfRps / InfDeclaracaoPrestacaoServico comum aos layouts.
|
|
14
|
+
* @param {import('xmlbuilder2').XMLBuilder} parent
|
|
15
|
+
* @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
|
|
16
|
+
* @param {string} idAttr
|
|
17
|
+
*/
|
|
18
|
+
function appendInfRps(parent, documento, idAttr = 'InfRps') {
|
|
19
|
+
const dataEmissao = formatSefazDateTime();
|
|
20
|
+
const idRps = `RPS${documento.numero}`;
|
|
21
|
+
const valorServicos = documento.servico.valorServicos;
|
|
22
|
+
const valorIss = documento.servico.valorIss ?? 0;
|
|
23
|
+
const baseCalculo = valorServicos - (documento.servico.valorDeducoes ?? 0);
|
|
24
|
+
const valorLiquido =
|
|
25
|
+
valorServicos -
|
|
26
|
+
(documento.servico.descontoIncondicionado ?? 0) -
|
|
27
|
+
(documento.servico.descontoCondicionado ?? 0);
|
|
28
|
+
|
|
29
|
+
const inf = parent.ele(idAttr, { Id: idRps });
|
|
30
|
+
|
|
31
|
+
const idRpsNode = inf.ele('IdentificacaoRps');
|
|
32
|
+
idRpsNode.ele('Numero').txt(String(documento.numero)).up();
|
|
33
|
+
idRpsNode.ele('Serie').txt(documento.serie ?? 'NF').up();
|
|
34
|
+
idRpsNode.ele('Tipo').txt(String(documento.tipo ?? 1)).up();
|
|
35
|
+
idRpsNode.up();
|
|
36
|
+
|
|
37
|
+
inf
|
|
38
|
+
.ele('DataEmissao').txt(dataEmissao).up()
|
|
39
|
+
.ele('NaturezaOperacao').txt(String(documento.naturezaOperacao ?? 1)).up()
|
|
40
|
+
.ele('OptanteSimplesNacional').txt(String(documento.optanteSimplesNacional ?? 1)).up()
|
|
41
|
+
.ele('IncentivadorCultural').txt(String(documento.incentivadorCultural ?? 2)).up()
|
|
42
|
+
.ele('Status').txt('1').up();
|
|
43
|
+
|
|
44
|
+
const servico = inf.ele('Servico');
|
|
45
|
+
const valores = servico.ele('Valores');
|
|
46
|
+
valores
|
|
47
|
+
.ele('ValorServicos').txt(dec(valorServicos)).up()
|
|
48
|
+
.ele('ValorDeducoes').txt(dec(documento.servico.valorDeducoes ?? 0)).up()
|
|
49
|
+
.ele('ValorPis').txt(dec(documento.servico.valorPis ?? 0)).up()
|
|
50
|
+
.ele('ValorCofins').txt(dec(documento.servico.valorCofins ?? 0)).up()
|
|
51
|
+
.ele('ValorInss').txt(dec(documento.servico.valorInss ?? 0)).up()
|
|
52
|
+
.ele('ValorIr').txt(dec(documento.servico.valorIr ?? 0)).up()
|
|
53
|
+
.ele('ValorCsll').txt(dec(documento.servico.valorCsll ?? 0)).up()
|
|
54
|
+
.ele('IssRetido').txt(String(documento.servico.issRetido ?? 2)).up()
|
|
55
|
+
.ele('ValorIss').txt(dec(valorIss)).up()
|
|
56
|
+
.ele('BaseCalculo').txt(dec(baseCalculo)).up()
|
|
57
|
+
.ele('Aliquota').txt(dec(documento.servico.aliquotaIss ?? 0, 4)).up()
|
|
58
|
+
.ele('ValorLiquidoNfse').txt(dec(valorLiquido)).up()
|
|
59
|
+
.ele('DescontoIncondicionado').txt(dec(documento.servico.descontoIncondicionado ?? 0)).up()
|
|
60
|
+
.ele('DescontoCondicionado').txt(dec(documento.servico.descontoCondicionado ?? 0)).up();
|
|
61
|
+
valores.up();
|
|
62
|
+
|
|
63
|
+
servico
|
|
64
|
+
.ele('ItemListaServico').txt(documento.servico.itemListaServico).up()
|
|
65
|
+
.ele('Discriminacao').txt(documento.servico.discriminacao).up()
|
|
66
|
+
.ele('CodigoMunicipio').txt(documento.servico.codigoMunicipio).up();
|
|
67
|
+
servico.up();
|
|
68
|
+
|
|
69
|
+
const prestador = inf.ele('Prestador');
|
|
70
|
+
prestador
|
|
71
|
+
.ele('Cnpj').txt(documento.cnpjPrestador.replace(/\D/g, '')).up()
|
|
72
|
+
.ele('InscricaoMunicipal').txt(documento.inscricaoMunicipal).up();
|
|
73
|
+
prestador.up();
|
|
74
|
+
|
|
75
|
+
const tomador = inf.ele('Tomador');
|
|
76
|
+
const identTomador = tomador.ele('IdentificacaoTomador').ele('CpfCnpj');
|
|
77
|
+
const docTomador = (documento.tomador.cnpj ?? documento.tomador.cpf ?? '').replace(/\D/g, '');
|
|
78
|
+
if (docTomador.length === 14) identTomador.ele('Cnpj').txt(docTomador).up();
|
|
79
|
+
else if (docTomador.length === 11) identTomador.ele('Cpf').txt(docTomador).up();
|
|
80
|
+
identTomador.up().up();
|
|
81
|
+
tomador.ele('RazaoSocial').txt(documento.tomador.razaoSocial).up();
|
|
82
|
+
|
|
83
|
+
if (documento.tomador.endereco) {
|
|
84
|
+
const end = tomador.ele('Endereco');
|
|
85
|
+
const e = documento.tomador.endereco;
|
|
86
|
+
end.ele('Endereco').txt(e.logradouro).up();
|
|
87
|
+
end.ele('Numero').txt(e.numero).up();
|
|
88
|
+
if (e.complemento) end.ele('Complemento').txt(e.complemento).up();
|
|
89
|
+
end.ele('Bairro').txt(e.bairro).up();
|
|
90
|
+
end.ele('CodigoMunicipio').txt(e.codigoMunicipio).up();
|
|
91
|
+
end.ele('Uf').txt(e.uf).up();
|
|
92
|
+
end.ele('Cep').txt(e.cep.replace(/\D/g, '')).up();
|
|
93
|
+
end.up();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (documento.tomador.email) {
|
|
97
|
+
tomador.ele('Contato').ele('Email').txt(documento.tomador.email).up().up();
|
|
98
|
+
}
|
|
99
|
+
tomador.up();
|
|
100
|
+
|
|
101
|
+
if (documento.informacoesComplementares) {
|
|
102
|
+
inf.ele('Observacao').txt(documento.informacoesComplementares).up();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return inf;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @param {number} v @param {number} [d] */
|
|
109
|
+
function dec(v, d = 2) {
|
|
110
|
+
return Number(v).toFixed(d);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
|
|
115
|
+
*/
|
|
116
|
+
function validarDocumento(documento) {
|
|
117
|
+
if (!documento.numero) throw new GhinfeError('numero do RPS é obrigatório', 'NFSE_NUMERO_REQUIRED');
|
|
118
|
+
if (!documento.cnpjPrestador) throw new GhinfeError('cnpjPrestador é obrigatório', 'NFSE_CNPJ_REQUIRED');
|
|
119
|
+
if (!documento.inscricaoMunicipal) throw new GhinfeError('inscricaoMunicipal é obrigatória', 'NFSE_IM_REQUIRED');
|
|
120
|
+
if (!documento.servico) throw new GhinfeError('servico é obrigatório', 'NFSE_SERVICO_REQUIRED');
|
|
121
|
+
if (!documento.tomador) throw new GhinfeError('tomador é obrigatório', 'NFSE_TOMADOR_REQUIRED');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Adapters de layout municipal.
|
|
126
|
+
* Mantêm o mesmo objeto de domínio e trocam o wrapper/namespace XML.
|
|
127
|
+
*/
|
|
128
|
+
export const NFSeLayoutAdapters = {
|
|
129
|
+
[NFSE_LAYOUTS.ABRASF]: {
|
|
130
|
+
nome: 'ABRASF 2.04',
|
|
131
|
+
build(documento) {
|
|
132
|
+
validarDocumento(documento);
|
|
133
|
+
const doc = create({ version: '1.0', encoding: 'UTF-8' })
|
|
134
|
+
.ele('GerarNfseEnvio', { xmlns: NFSE_NAMESPACE });
|
|
135
|
+
const rps = doc.ele('Rps');
|
|
136
|
+
appendInfRps(rps, documento, 'InfRps');
|
|
137
|
+
return doc.end({ prettyPrint: false, headless: false });
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
[NFSE_LAYOUTS.GINFES]: {
|
|
142
|
+
nome: 'GINFES',
|
|
143
|
+
build(documento) {
|
|
144
|
+
validarDocumento(documento);
|
|
145
|
+
const doc = create({ version: '1.0', encoding: 'UTF-8' })
|
|
146
|
+
.ele('EnviarLoteRpsEnvio', { xmlns: GINFES_NAMESPACE });
|
|
147
|
+
const lote = doc.ele('LoteRps', { Id: `Lote${documento.numero}`, versao: '3.00' });
|
|
148
|
+
lote.ele('NumeroLote').txt(String(documento.numero)).up();
|
|
149
|
+
lote.ele('Cnpj').txt(documento.cnpjPrestador.replace(/\D/g, '')).up();
|
|
150
|
+
lote.ele('InscricaoMunicipal').txt(documento.inscricaoMunicipal).up();
|
|
151
|
+
lote.ele('QuantidadeRps').txt('1').up();
|
|
152
|
+
const lista = lote.ele('ListaRps').ele('Rps');
|
|
153
|
+
appendInfRps(lista, documento, 'InfRps');
|
|
154
|
+
return doc.end({ prettyPrint: false, headless: false });
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
[NFSE_LAYOUTS.BETHA]: {
|
|
159
|
+
nome: 'Betha',
|
|
160
|
+
build(documento) {
|
|
161
|
+
validarDocumento(documento);
|
|
162
|
+
const doc = create({ version: '1.0', encoding: 'UTF-8' })
|
|
163
|
+
.ele('GerarNfseEnvio', { xmlns: BETHA_NAMESPACE });
|
|
164
|
+
const rps = doc.ele('Rps');
|
|
165
|
+
appendInfRps(rps, documento, 'InfDeclaracaoPrestacaoServico');
|
|
166
|
+
return doc.end({ prettyPrint: false, headless: false });
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
[NFSE_LAYOUTS.ISSNET]: {
|
|
171
|
+
nome: 'ISSNet',
|
|
172
|
+
build(documento) {
|
|
173
|
+
validarDocumento(documento);
|
|
174
|
+
const doc = create({ version: '1.0', encoding: 'UTF-8' })
|
|
175
|
+
.ele('EnviarLoteRpsEnvio', { xmlns: ISSNET_NAMESPACE });
|
|
176
|
+
const lote = doc.ele('LoteRps', { Id: `Lote${documento.numero}`, versao: '2.04' });
|
|
177
|
+
lote.ele('NumeroLote').txt(String(documento.numero)).up();
|
|
178
|
+
lote.ele('CpfCnpj').ele('Cnpj').txt(documento.cnpjPrestador.replace(/\D/g, '')).up().up();
|
|
179
|
+
lote.ele('InscricaoMunicipal').txt(documento.inscricaoMunicipal).up();
|
|
180
|
+
lote.ele('QuantidadeRps').txt('1').up();
|
|
181
|
+
const lista = lote.ele('ListaRps').ele('Rps');
|
|
182
|
+
appendInfRps(lista, documento, 'InfDeclaracaoPrestacaoServico');
|
|
183
|
+
return doc.end({ prettyPrint: false, headless: false });
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {string} layout
|
|
190
|
+
* @returns {typeof NFSeLayoutAdapters[string]}
|
|
191
|
+
*/
|
|
192
|
+
export function obterAdapter(layout) {
|
|
193
|
+
const key = (layout ?? NFSE_LAYOUTS.ABRASF).toLowerCase();
|
|
194
|
+
const adapter = NFSeLayoutAdapters[key];
|
|
195
|
+
if (!adapter) {
|
|
196
|
+
throw new GhinfeError(
|
|
197
|
+
`Layout NFS-e não suportado: ${layout}. Use: ${Object.keys(NFSE_LAYOUTS).join(', ')}`,
|
|
198
|
+
'NFSE_LAYOUT_UNSUPPORTED'
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return adapter;
|
|
202
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Respostas mock NFS-e para desenvolvimento.
|
|
3
|
+
*/
|
|
4
|
+
export const NFSE_MOCK_RESPONSES = {
|
|
5
|
+
autorizada: {
|
|
6
|
+
sucesso: true,
|
|
7
|
+
codigo: 'E000',
|
|
8
|
+
mensagem: 'NFS-e autorizada com sucesso',
|
|
9
|
+
numeroNfse: '2026000001',
|
|
10
|
+
codigoVerificacao: 'ABC1-DEF2',
|
|
11
|
+
},
|
|
12
|
+
rejeicao: {
|
|
13
|
+
sucesso: false,
|
|
14
|
+
codigo: 'E001',
|
|
15
|
+
mensagem: 'Rejeição: RPS já convertido em NFS-e',
|
|
16
|
+
},
|
|
17
|
+
cancelada: {
|
|
18
|
+
sucesso: true,
|
|
19
|
+
codigo: 'E000',
|
|
20
|
+
mensagem: 'NFS-e cancelada com sucesso',
|
|
21
|
+
numeroNfse: '2026000001',
|
|
22
|
+
},
|
|
23
|
+
substituida: {
|
|
24
|
+
sucesso: true,
|
|
25
|
+
codigo: 'E000',
|
|
26
|
+
mensagem: 'NFS-e substituída com sucesso',
|
|
27
|
+
numeroNfse: '2026000002',
|
|
28
|
+
codigoVerificacao: 'XYZ9-SUB1',
|
|
29
|
+
numeroNfseSubstituida: '2026000001',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} numeroRps
|
|
35
|
+
* @param {{ codigo: string, mensagem: string, numeroNfse?: string, codigoVerificacao?: string }} resultado
|
|
36
|
+
*/
|
|
37
|
+
export function buildMockGerarNfseResponse(numeroRps, resultado) {
|
|
38
|
+
const dh = new Date().toISOString();
|
|
39
|
+
|
|
40
|
+
if (resultado.codigo === 'E000') {
|
|
41
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
42
|
+
<GerarNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
43
|
+
<ListaNfse>
|
|
44
|
+
<CompNfse>
|
|
45
|
+
<Nfse versao="2.04">
|
|
46
|
+
<InfNfse Id="NFS${resultado.numeroNfse}">
|
|
47
|
+
<Numero>${resultado.numeroNfse}</Numero>
|
|
48
|
+
<CodigoVerificacao>${resultado.codigoVerificacao}</CodigoVerificacao>
|
|
49
|
+
<DataEmissao>${dh}</DataEmissao>
|
|
50
|
+
<IdentificacaoRps>
|
|
51
|
+
<Numero>${numeroRps}</Numero>
|
|
52
|
+
<Serie>NF</Serie>
|
|
53
|
+
<Tipo>1</Tipo>
|
|
54
|
+
</IdentificacaoRps>
|
|
55
|
+
</InfNfse>
|
|
56
|
+
</Nfse>
|
|
57
|
+
</CompNfse>
|
|
58
|
+
</ListaNfse>
|
|
59
|
+
</GerarNfseResposta>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
63
|
+
<GerarNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
64
|
+
<ListaMensagemRetorno>
|
|
65
|
+
<MensagemRetorno>
|
|
66
|
+
<Codigo>${resultado.codigo}</Codigo>
|
|
67
|
+
<Mensagem>${resultado.mensagem}</Mensagem>
|
|
68
|
+
</MensagemRetorno>
|
|
69
|
+
</ListaMensagemRetorno>
|
|
70
|
+
</GerarNfseResposta>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} numeroNfse
|
|
75
|
+
* @param {{ codigo: string, mensagem: string }} resultado
|
|
76
|
+
*/
|
|
77
|
+
export function buildMockCancelarNfseResponse(numeroNfse, resultado) {
|
|
78
|
+
const dh = new Date().toISOString();
|
|
79
|
+
if (resultado.codigo === 'E000') {
|
|
80
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
81
|
+
<CancelarNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
82
|
+
<RetCancelamento>
|
|
83
|
+
<NfseCancelamento>
|
|
84
|
+
<Confirmacao>
|
|
85
|
+
<Pedido>
|
|
86
|
+
<InfPedidoCancelamento>
|
|
87
|
+
<IdentificacaoNfse>
|
|
88
|
+
<Numero>${numeroNfse}</Numero>
|
|
89
|
+
</IdentificacaoNfse>
|
|
90
|
+
</InfPedidoCancelamento>
|
|
91
|
+
</Pedido>
|
|
92
|
+
<DataHora>${dh}</DataHora>
|
|
93
|
+
</Confirmacao>
|
|
94
|
+
</NfseCancelamento>
|
|
95
|
+
</RetCancelamento>
|
|
96
|
+
</CancelarNfseResposta>`;
|
|
97
|
+
}
|
|
98
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
99
|
+
<CancelarNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
100
|
+
<ListaMensagemRetorno>
|
|
101
|
+
<MensagemRetorno>
|
|
102
|
+
<Codigo>${resultado.codigo}</Codigo>
|
|
103
|
+
<Mensagem>${resultado.mensagem}</Mensagem>
|
|
104
|
+
</MensagemRetorno>
|
|
105
|
+
</ListaMensagemRetorno>
|
|
106
|
+
</CancelarNfseResposta>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {{ codigo: string, mensagem: string, numeroNfse?: string, codigoVerificacao?: string, numeroNfseSubstituida?: string }} resultado
|
|
111
|
+
*/
|
|
112
|
+
export function buildMockSubstituirNfseResponse(resultado) {
|
|
113
|
+
const dh = new Date().toISOString();
|
|
114
|
+
if (resultado.codigo === 'E000') {
|
|
115
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
116
|
+
<SubstituirNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
117
|
+
<RetSubstituicao>
|
|
118
|
+
<NfseSubstituida>
|
|
119
|
+
<InfNfse>
|
|
120
|
+
<Numero>${resultado.numeroNfseSubstituida ?? '2026000001'}</Numero>
|
|
121
|
+
</InfNfse>
|
|
122
|
+
</NfseSubstituida>
|
|
123
|
+
<NfseSubstituidora>
|
|
124
|
+
<InfNfse Id="NFS${resultado.numeroNfse}">
|
|
125
|
+
<Numero>${resultado.numeroNfse}</Numero>
|
|
126
|
+
<CodigoVerificacao>${resultado.codigoVerificacao}</CodigoVerificacao>
|
|
127
|
+
<DataEmissao>${dh}</DataEmissao>
|
|
128
|
+
</InfNfse>
|
|
129
|
+
</NfseSubstituidora>
|
|
130
|
+
</RetSubstituicao>
|
|
131
|
+
</SubstituirNfseResposta>`;
|
|
132
|
+
}
|
|
133
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
134
|
+
<SubstituirNfseResposta xmlns="http://www.abrasf.org.br/nfse.xsd">
|
|
135
|
+
<ListaMensagemRetorno>
|
|
136
|
+
<MensagemRetorno>
|
|
137
|
+
<Codigo>${resultado.codigo}</Codigo>
|
|
138
|
+
<Mensagem>${resultado.mensagem}</Mensagem>
|
|
139
|
+
</MensagemRetorno>
|
|
140
|
+
</ListaMensagemRetorno>
|
|
141
|
+
</SubstituirNfseResposta>`;
|
|
142
|
+
}
|