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.
Files changed (80) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +413 -0
  4. package/docs/developer-guide.html +421 -0
  5. package/docs/openapi.yaml +428 -0
  6. package/examples/emitir-nfce.js +51 -0
  7. package/examples/emitir-nfe.js +83 -0
  8. package/examples/emitir-nfse.js +46 -0
  9. package/examples/endpoints-sefaz.js +14 -0
  10. package/examples/eventos-nfe.js +49 -0
  11. package/examples/gerar-chave.js +25 -0
  12. package/examples/gerar-danfe.js +75 -0
  13. package/examples/layouts-nfse.js +51 -0
  14. package/examples/mock-sefaz.js +52 -0
  15. package/examples/nfse-eventos.js +57 -0
  16. package/examples/reforma-ibscbs.js +27 -0
  17. package/examples/reforma-nfe-xml.js +52 -0
  18. package/examples/status-sefaz.js +12 -0
  19. package/examples/validar-xsd.js +48 -0
  20. package/package.json +68 -0
  21. package/schemas/nfe/PL_009_V4/consReciNFe_v4.00.xsd +9 -0
  22. package/schemas/nfe/PL_009_V4/consSitNFe_v4.00.xsd +9 -0
  23. package/schemas/nfe/PL_009_V4/consStatServ_v4.00.xsd +9 -0
  24. package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +9 -0
  25. package/schemas/nfe/PL_009_V4/enviNFe_v4.00.xsd +9 -0
  26. package/schemas/nfe/PL_009_V4/inutNFe_v4.00.xsd +9 -0
  27. package/schemas/nfe/PL_009_V4/leiauteNFe_v4.00.xsd +7339 -0
  28. package/schemas/nfe/PL_009_V4/nfe_v4.00.xsd +9 -0
  29. package/schemas/nfe/PL_009_V4/tiposBasico_v4.00.xsd +598 -0
  30. package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +98 -0
  31. package/scripts/download-schemas.js +39 -0
  32. package/scripts/test-xsd.js +34 -0
  33. package/src/api/server.js +316 -0
  34. package/src/constants/sefaz.js +69 -0
  35. package/src/danfe/DanfeService.js +205 -0
  36. package/src/danfe/xml-extract.js +69 -0
  37. package/src/errors/GhinfeError.js +13 -0
  38. package/src/errors/SefazError.js +25 -0
  39. package/src/errors/XsdValidationError.js +15 -0
  40. package/src/index.js +60 -0
  41. package/src/nfce/builders/NFCeXmlBuilder.js +178 -0
  42. package/src/nfce/constants/qrcode.js +26 -0
  43. package/src/nfce/services/NFCeService.js +188 -0
  44. package/src/nfce/services/NFCeXmlService.js +38 -0
  45. package/src/nfce/types/NFCeDocument.js +42 -0
  46. package/src/nfce/utils/qrcode.utils.js +92 -0
  47. package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -0
  48. package/src/nfe/builders/NFeXmlBuilder.js +215 -0
  49. package/src/nfe/mocks/sefaz-responses.js +195 -0
  50. package/src/nfe/services/NFeEventoService.js +95 -0
  51. package/src/nfe/services/NFeService.js +196 -0
  52. package/src/nfe/services/NFeSignService.js +111 -0
  53. package/src/nfe/services/NFeSoapService.js +437 -0
  54. package/src/nfe/services/NFeXmlService.js +41 -0
  55. package/src/nfe/services/NFeXsdService.js +56 -0
  56. package/src/nfe/types/NFeDocument.js +92 -0
  57. package/src/nfe/types/NFeEvento.js +44 -0
  58. package/src/nfse/NFSeMunicipioResolver.js +42 -0
  59. package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -0
  60. package/src/nfse/builders/NFSeXmlBuilder.js +16 -0
  61. package/src/nfse/constants/municipios.js +82 -0
  62. package/src/nfse/layouts/adapters.js +202 -0
  63. package/src/nfse/mocks/nfse-responses.js +142 -0
  64. package/src/nfse/services/NFSeService.js +191 -0
  65. package/src/nfse/services/NFSeSoapService.js +232 -0
  66. package/src/nfse/services/NFSeXmlService.js +12 -0
  67. package/src/nfse/types/NFSeDocument.js +54 -0
  68. package/src/queue/NFCeQueue.js +63 -0
  69. package/src/queue/NFSeQueue.js +59 -0
  70. package/src/queue/NFeQueue.js +76 -0
  71. package/src/reforma/ibscbs.js +175 -0
  72. package/src/sefaz/SefazEndpointResolver.js +95 -0
  73. package/src/sefaz/endpoints.registry.js +205 -0
  74. package/src/shared/fiscal.helpers.js +53 -0
  75. package/src/utils/certificate.utils.js +47 -0
  76. package/src/utils/chave.utils.js +117 -0
  77. package/src/utils/date.utils.js +26 -0
  78. package/src/utils/xml.utils.js +44 -0
  79. package/src/validation/XsdValidator.js +114 -0
  80. package/src/validation/schemas.js +14 -0
@@ -0,0 +1,191 @@
1
+ import { NFSeXmlBuilder } from '../builders/NFSeXmlBuilder.js';
2
+ import { NFSeEventoXmlBuilder } from '../builders/NFSeEventoXmlBuilder.js';
3
+ import { NFSeSoapService } from './NFSeSoapService.js';
4
+ import { NFeSignService } from '../../nfe/services/NFeSignService.js';
5
+ import { NFSeMunicipioResolver } from '../NFSeMunicipioResolver.js';
6
+ import { NFSE_LAYOUTS } from '../constants/municipios.js';
7
+ import { GhinfeError } from '../../errors/GhinfeError.js';
8
+
9
+ /**
10
+ * Orquestrador NFS-e — emissão, cancelamento e substituição.
11
+ */
12
+ export class NFSeService {
13
+ /**
14
+ * @param {{
15
+ * mock?: boolean,
16
+ * endpoint?: string,
17
+ * mockScenario?: string,
18
+ * layout?: string,
19
+ * }} [options]
20
+ */
21
+ constructor(options = {}) {
22
+ this.options = options;
23
+ this.builder = new NFSeXmlBuilder();
24
+ this.eventoBuilder = new NFSeEventoXmlBuilder();
25
+ this.signService = new NFeSignService();
26
+ this.soapService = new NFSeSoapService(options);
27
+ }
28
+
29
+ /** @param {import('../types/NFSeDocument.js').NFSeDocumento} documento */
30
+ resolverLayout(documento) {
31
+ if (this.options.layout) return this.options.layout;
32
+ try {
33
+ const info = NFSeMunicipioResolver.resolver(
34
+ documento.codigoMunicipio,
35
+ documento.ambiente ?? 2
36
+ );
37
+ return info.layout ?? NFSE_LAYOUTS.ABRASF;
38
+ } catch {
39
+ return NFSE_LAYOUTS.ABRASF;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
45
+ * @param {string} [layout]
46
+ */
47
+ gerarXml(documento, layout) {
48
+ return this.builder.buildGerarNfse(documento, layout ?? this.resolverLayout(documento));
49
+ }
50
+
51
+ /**
52
+ * @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
53
+ * @param {{ pfx: Buffer|string, senha: string }} certificado
54
+ * @param {string} [layout]
55
+ */
56
+ gerarXmlAssinado(documento, certificado, layout) {
57
+ const xml = this.gerarXml(documento, layout);
58
+ return this.signService.assinarRps(xml, certificado.pfx, certificado.senha);
59
+ }
60
+
61
+ /**
62
+ * @param {{
63
+ * numeroNfse: string,
64
+ * cnpjPrestador: string,
65
+ * inscricaoMunicipal: string,
66
+ * codigoMunicipio: string,
67
+ * codigoCancelamento?: string,
68
+ * }} dados
69
+ */
70
+ gerarXmlCancelamento(dados) {
71
+ return this.eventoBuilder.buildCancelamento(dados);
72
+ }
73
+
74
+ /**
75
+ * @param {{
76
+ * numeroNfseSubstituida: string,
77
+ * cnpjPrestador: string,
78
+ * inscricaoMunicipal: string,
79
+ * codigoMunicipio: string,
80
+ * codigoCancelamento?: string,
81
+ * documentoSubstituto: import('../types/NFSeDocument.js').NFSeDocumento,
82
+ * }} dados
83
+ */
84
+ gerarXmlSubstituicao(dados) {
85
+ const xmlRps = this.gerarXml(dados.documentoSubstituto);
86
+ return this.eventoBuilder.buildSubstituicao({
87
+ ...dados,
88
+ xmlRpsSubstituto: xmlRps,
89
+ });
90
+ }
91
+
92
+ /**
93
+ * @param {import('../types/NFSeDocument.js').NFSeDocumento} documento
94
+ * @param {{ pfx?: Buffer|string, senha?: string }} [certificado]
95
+ */
96
+ async emitir(documento, certificado) {
97
+ const layout = this.resolverLayout(documento);
98
+ let xml = this.gerarXml(documento, layout);
99
+ let xmlAssinado = null;
100
+
101
+ if (certificado?.pfx && certificado?.senha) {
102
+ xmlAssinado = this.signService.assinarRps(xml, certificado.pfx, certificado.senha);
103
+ xml = xmlAssinado;
104
+ }
105
+
106
+ const retorno = await this.soapService.gerarNfse(
107
+ xml,
108
+ documento.codigoMunicipio,
109
+ documento.ambiente ?? 2
110
+ );
111
+
112
+ return {
113
+ layout,
114
+ xmlGerado: xmlAssinado ? this.gerarXml(documento, layout) : xml,
115
+ xmlAssinado,
116
+ retorno,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Cancela NFS-e já autorizada.
122
+ * @param {{
123
+ * numeroNfse: string,
124
+ * cnpjPrestador: string,
125
+ * inscricaoMunicipal: string,
126
+ * codigoMunicipio: string,
127
+ * codigoCancelamento?: string,
128
+ * ambiente?: number,
129
+ * }} dados
130
+ */
131
+ async cancelar(dados) {
132
+ const xml = this.gerarXmlCancelamento(dados);
133
+ const retorno = await this.soapService.cancelarNfse(
134
+ xml,
135
+ dados.codigoMunicipio,
136
+ dados.ambiente ?? 2
137
+ );
138
+ return { tipo: 'cancelamento', xmlGerado: xml, retorno };
139
+ }
140
+
141
+ /**
142
+ * Substitui NFS-e por um novo RPS.
143
+ * @param {{
144
+ * numeroNfseSubstituida: string,
145
+ * cnpjPrestador: string,
146
+ * inscricaoMunicipal: string,
147
+ * codigoMunicipio: string,
148
+ * codigoCancelamento?: string,
149
+ * documentoSubstituto: import('../types/NFSeDocument.js').NFSeDocumento,
150
+ * ambiente?: number,
151
+ * }} dados
152
+ * @param {{ pfx?: Buffer|string, senha?: string }} [certificado]
153
+ */
154
+ async substituir(dados, certificado) {
155
+ let xmlEnvio = this.gerarXmlSubstituicao(dados);
156
+
157
+ if (certificado?.pfx && certificado?.senha) {
158
+ const xmlRps = this.gerarXmlAssinado(dados.documentoSubstituto, certificado);
159
+ xmlEnvio = this.eventoBuilder.buildSubstituicao({
160
+ ...dados,
161
+ xmlRpsSubstituto: xmlRps,
162
+ });
163
+ }
164
+
165
+ const retorno = await this.soapService.substituirNfse(
166
+ xmlEnvio,
167
+ dados.codigoMunicipio,
168
+ dados.ambiente ?? 2
169
+ );
170
+
171
+ return { tipo: 'substituicao', xmlGerado: xmlEnvio, retorno };
172
+ }
173
+
174
+ /**
175
+ * @param {Awaited<ReturnType<NFSeService['emitir']>>} resultado
176
+ */
177
+ toPersistencePayload(resultado) {
178
+ if (!resultado.retorno.autorizado) {
179
+ throw new GhinfeError('NFS-e não autorizada', 'NFSE_NOT_AUTHORIZED', resultado.retorno);
180
+ }
181
+
182
+ return {
183
+ numero_nfse: resultado.retorno.numeroNfse,
184
+ codigo_verificacao: resultado.retorno.codigoVerificacao,
185
+ layout: resultado.layout,
186
+ xml_rps: resultado.xmlAssinado ?? resultado.xmlGerado,
187
+ xml_retorno: resultado.retorno.xmlRetorno,
188
+ autorizado_em: new Date(),
189
+ };
190
+ }
191
+ }
@@ -0,0 +1,232 @@
1
+ import axios from 'axios';
2
+ import { GhinfeError } from '../../errors/GhinfeError.js';
3
+ import { NFSeMunicipioResolver } from '../NFSeMunicipioResolver.js';
4
+ import {
5
+ buildMockGerarNfseResponse,
6
+ buildMockCancelarNfseResponse,
7
+ buildMockSubstituirNfseResponse,
8
+ NFSE_MOCK_RESPONSES,
9
+ } from '../mocks/nfse-responses.js';
10
+
11
+ export class NFSeSoapError extends GhinfeError {
12
+ /**
13
+ * @param {string} codigo
14
+ * @param {string} mensagem
15
+ * @param {Record<string, unknown>} [details]
16
+ */
17
+ constructor(codigo, mensagem, details = {}) {
18
+ super(`NFS-e [${codigo}]: ${mensagem}`, `NFSE_${codigo}`, { codigo, mensagem, ...details });
19
+ this.name = 'NFSeSoapError';
20
+ this.codigo = codigo;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Comunicação SOAP com prefeituras (NFS-e ABRASF).
26
+ */
27
+ export class NFSeSoapService {
28
+ /**
29
+ * @param {{ mock?: boolean, endpoint?: string, timeout?: number, mockScenario?: string }} [options]
30
+ */
31
+ constructor(options = {}) {
32
+ this.mock = options.mock ?? true;
33
+ this.endpoint = options.endpoint;
34
+ this.timeout = options.timeout ?? 30000;
35
+ this.mockScenario = options.mockScenario ?? 'autorizada';
36
+ }
37
+
38
+ /**
39
+ * @param {string} xmlRps
40
+ * @param {string} codigoMunicipio
41
+ * @param {number} [ambiente]
42
+ */
43
+ async gerarNfse(xmlRps, codigoMunicipio, ambiente = 2) {
44
+ if (this.mock) {
45
+ return this.#mockGerarNfse(xmlRps);
46
+ }
47
+
48
+ const { url } = this.endpoint
49
+ ? { url: this.endpoint }
50
+ : NFSeMunicipioResolver.resolver(codigoMunicipio, ambiente);
51
+
52
+ const envelope = this.#buildEnvelope('GerarNfse', xmlRps);
53
+
54
+ try {
55
+ const response = await axios.post(url, envelope, {
56
+ headers: { 'Content-Type': 'text/xml; charset=utf-8', SOAPAction: 'GerarNfse' },
57
+ timeout: this.timeout,
58
+ responseType: 'text',
59
+ });
60
+
61
+ return this.#processarRetorno(response.data);
62
+ } catch (error) {
63
+ if (axios.isAxiosError(error)) {
64
+ throw new GhinfeError(`Falha SOAP NFS-e: ${error.message}`, 'NFSE_HTTP_ERROR', {
65
+ status: error.response?.status,
66
+ });
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ /** @param {string} metodo @param {string} xml */
73
+ #buildEnvelope(metodo, xml) {
74
+ const escaped = xml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
75
+ return `<?xml version="1.0" encoding="utf-8"?>
76
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
77
+ <soap:Body>
78
+ <${metodo} xmlns="http://www.abrasf.org.br/nfse.xsd">
79
+ <nfseDadosMsg>${escaped}</nfseDadosMsg>
80
+ </${metodo}>
81
+ </soap:Body>
82
+ </soap:Envelope>`;
83
+ }
84
+
85
+ /**
86
+ * @param {string} xmlCancelamento
87
+ * @param {string} codigoMunicipio
88
+ * @param {number} [ambiente]
89
+ */
90
+ async cancelarNfse(xmlCancelamento, codigoMunicipio, ambiente = 2) {
91
+ if (this.mock) {
92
+ await new Promise((r) => setTimeout(r, 150));
93
+ const numero = xmlCancelamento.match(/<Numero>(\d+)<\/Numero>/)?.[1] ?? '';
94
+ const resultado = this.mockScenario === 'rejeicao'
95
+ ? NFSE_MOCK_RESPONSES.rejeicao
96
+ : NFSE_MOCK_RESPONSES.cancelada;
97
+ const xml = buildMockCancelarNfseResponse(numero, resultado);
98
+ return this.#processarRetornoEvento(xml, { mock: true, operacao: 'cancelamento' });
99
+ }
100
+
101
+ return this.#postMetodo('CancelarNfse', xmlCancelamento, codigoMunicipio, ambiente, 'cancelamento');
102
+ }
103
+
104
+ /**
105
+ * @param {string} xmlSubstituicao
106
+ * @param {string} codigoMunicipio
107
+ * @param {number} [ambiente]
108
+ */
109
+ async substituirNfse(xmlSubstituicao, codigoMunicipio, ambiente = 2) {
110
+ if (this.mock) {
111
+ await new Promise((r) => setTimeout(r, 150));
112
+ const resultado = this.mockScenario === 'rejeicao'
113
+ ? NFSE_MOCK_RESPONSES.rejeicao
114
+ : NFSE_MOCK_RESPONSES.substituida;
115
+ const xml = buildMockSubstituirNfseResponse(resultado);
116
+ return this.#processarRetornoEvento(xml, { mock: true, operacao: 'substituicao' });
117
+ }
118
+
119
+ return this.#postMetodo('SubstituirNfse', xmlSubstituicao, codigoMunicipio, ambiente, 'substituicao');
120
+ }
121
+
122
+ /**
123
+ * @param {string} metodo
124
+ * @param {string} xml
125
+ * @param {string} codigoMunicipio
126
+ * @param {number} ambiente
127
+ * @param {string} operacao
128
+ */
129
+ async #postMetodo(metodo, xml, codigoMunicipio, ambiente, operacao) {
130
+ const { url } = this.endpoint
131
+ ? { url: this.endpoint }
132
+ : NFSeMunicipioResolver.resolver(codigoMunicipio, ambiente);
133
+
134
+ const envelope = this.#buildEnvelope(metodo, xml);
135
+
136
+ try {
137
+ const response = await axios.post(url, envelope, {
138
+ headers: { 'Content-Type': 'text/xml; charset=utf-8', SOAPAction: metodo },
139
+ timeout: this.timeout,
140
+ responseType: 'text',
141
+ });
142
+ return this.#processarRetornoEvento(response.data, { operacao });
143
+ } catch (error) {
144
+ if (axios.isAxiosError(error)) {
145
+ throw new GhinfeError(`Falha SOAP NFS-e (${operacao}): ${error.message}`, 'NFSE_HTTP_ERROR', {
146
+ status: error.response?.status,
147
+ });
148
+ }
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ /** @param {string} xmlRps */
154
+ async #mockGerarNfse(xmlRps) {
155
+ await new Promise((r) => setTimeout(r, 200));
156
+
157
+ const numeroMatch = xmlRps.match(/<Numero>(\d+)<\/Numero>/);
158
+ const numeroRps = numeroMatch?.[1] ?? '1';
159
+
160
+ const scenario = this.mockScenario === 'rejeicao'
161
+ ? NFSE_MOCK_RESPONSES.rejeicao
162
+ : NFSE_MOCK_RESPONSES.autorizada;
163
+
164
+ const innerXml = buildMockGerarNfseResponse(numeroRps, scenario);
165
+ return this.#processarRetorno(innerXml, { mock: true });
166
+ }
167
+
168
+ /** @param {string} xml @param {Record<string, unknown>} [meta] */
169
+ #processarRetorno(xml, meta = {}) {
170
+ const codigoMatch = xml.match(/<Codigo>([^<]+)<\/Codigo>/);
171
+ const mensagemMatch = xml.match(/<Mensagem>([^<]+)<\/Mensagem>/);
172
+ const numeroNfseMatch = xml.match(/<InfNfse[\s\S]*?<Numero>(\d+)<\/Numero>/);
173
+ const codVerifMatch = xml.match(/<CodigoVerificacao>([^<]+)<\/CodigoVerificacao>/);
174
+
175
+ const temNfse = xml.includes('<ListaNfse>') || xml.includes('<InfNfse>');
176
+
177
+ if (temNfse && !xml.includes('ListaMensagemRetorno')) {
178
+ return {
179
+ sucesso: true,
180
+ autorizado: true,
181
+ numeroNfse: numeroNfseMatch?.[1],
182
+ codigoVerificacao: codVerifMatch?.[1],
183
+ xmlRetorno: xml,
184
+ ...meta,
185
+ };
186
+ }
187
+
188
+ const codigo = codigoMatch?.[1] ?? 'E999';
189
+ const mensagem = mensagemMatch?.[1] ?? 'Retorno não identificado';
190
+ throw new NFSeSoapError(codigo, mensagem, { xmlRetorno: xml, ...meta });
191
+ }
192
+
193
+ /**
194
+ * @param {string} xml
195
+ * @param {Record<string, unknown>} [meta]
196
+ */
197
+ #processarRetornoEvento(xml, meta = {}) {
198
+ if (xml.includes('ListaMensagemRetorno') && !xml.includes('RetCancelamento') && !xml.includes('RetSubstituicao')) {
199
+ const codigo = xml.match(/<Codigo>([^<]+)<\/Codigo>/)?.[1] ?? 'E999';
200
+ const mensagem = xml.match(/<Mensagem>([^<]+)<\/Mensagem>/)?.[1] ?? 'Retorno não identificado';
201
+ throw new NFSeSoapError(codigo, mensagem, { xmlRetorno: xml, ...meta });
202
+ }
203
+
204
+ if (xml.includes('RetCancelamento') || xml.includes('Confirmacao')) {
205
+ const numero = xml.match(/<Numero>(\d+)<\/Numero>/)?.[1];
206
+ return {
207
+ sucesso: true,
208
+ cancelado: true,
209
+ numeroNfse: numero,
210
+ xmlRetorno: xml,
211
+ ...meta,
212
+ };
213
+ }
214
+
215
+ if (xml.includes('RetSubstituicao') || xml.includes('NfseSubstituidora')) {
216
+ const nova = xml.match(/<NfseSubstituidora>[\s\S]*?<Numero>(\d+)<\/Numero>/)?.[1];
217
+ const antiga = xml.match(/<NfseSubstituida>[\s\S]*?<Numero>(\d+)<\/Numero>/)?.[1];
218
+ const codVerif = xml.match(/<CodigoVerificacao>([^<]+)<\/CodigoVerificacao>/)?.[1];
219
+ return {
220
+ sucesso: true,
221
+ substituido: true,
222
+ numeroNfse: nova,
223
+ numeroNfseSubstituida: antiga,
224
+ codigoVerificacao: codVerif,
225
+ xmlRetorno: xml,
226
+ ...meta,
227
+ };
228
+ }
229
+
230
+ return this.#processarRetorno(xml, meta);
231
+ }
232
+ }
@@ -0,0 +1,12 @@
1
+ import { NFSeXmlBuilder } from '../builders/NFSeXmlBuilder.js';
2
+
3
+ export class NFSeXmlService {
4
+ constructor() {
5
+ this.builder = new NFSeXmlBuilder();
6
+ }
7
+
8
+ /** @param {import('../types/NFSeDocument.js').NFSeDocumento} documento @param {string} [layout] */
9
+ gerarXml(documento, layout) {
10
+ return this.builder.buildGerarNfse(documento, layout);
11
+ }
12
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @typedef {Object} NFSeTomador
3
+ * @property {string} [cnpj]
4
+ * @property {string} [cpf]
5
+ * @property {string} razaoSocial
6
+ * @property {string} [email]
7
+ * @property {string} [telefone]
8
+ * @property {Object} [endereco]
9
+ * @property {string} endereco.logradouro
10
+ * @property {string} endereco.numero
11
+ * @property {string} [endereco.complemento]
12
+ * @property {string} endereco.bairro
13
+ * @property {string} endereco.codigoMunicipio IBGE
14
+ * @property {string} endereco.uf
15
+ * @property {string} endereco.cep
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} NFSeServico
20
+ * @property {string} itemListaServico LC 116 (ex: "01.01")
21
+ * @property {string} discriminacao
22
+ * @property {string} codigoMunicipio IBGE prestação
23
+ * @property {number} valorServicos
24
+ * @property {number} [valorDeducoes]
25
+ * @property {number} [valorPis]
26
+ * @property {number} [valorCofins]
27
+ * @property {number} [valorInss]
28
+ * @property {number} [valorIr]
29
+ * @property {number} [valorCsll]
30
+ * @property {number} [aliquotaIss]
31
+ * @property {number} [valorIss]
32
+ * @property {number} [descontoIncondicionado]
33
+ * @property {number} [descontoCondicionado]
34
+ * @property {number} [issRetido] 1=Sim, 2=Não
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} NFSeDocumento
39
+ * @property {number} numero RPS
40
+ * @property {string} [serie]
41
+ * @property {number} [tipo] 1=RPS
42
+ * @property {number} [ambiente] 1=Produção, 2=Homologação
43
+ * @property {string} codigoMunicipio IBGE do prestador
44
+ * @property {string} cnpjPrestador
45
+ * @property {string} inscricaoMunicipal
46
+ * @property {number} [naturezaOperacao] 1=Tributação no município
47
+ * @property {number} [optanteSimplesNacional] 1=Sim, 2=Não
48
+ * @property {number} [incentivadorCultural] 1=Sim, 2=Não
49
+ * @property {NFSeServico} servico
50
+ * @property {NFSeTomador} tomador
51
+ * @property {string} [informacoesComplementares]
52
+ */
53
+
54
+ export {};
@@ -0,0 +1,63 @@
1
+ import Bull from 'bull';
2
+ import { NFCeService } from '../nfce/services/NFCeService.js';
3
+
4
+ /**
5
+ * Fila Bull/Redis para processamento assíncrono de NFC-e.
6
+ */
7
+ export class NFCeQueue {
8
+ /**
9
+ * @param {{
10
+ * redis?: { host?: string, port?: number, password?: string },
11
+ * queueName?: string,
12
+ * nfceOptions?: ConstructorParameters<typeof NFCeService>[0],
13
+ * }} [options]
14
+ */
15
+ constructor(options = {}) {
16
+ const redis = options.redis ?? { host: '127.0.0.1', port: 6379 };
17
+ const queueName = options.queueName ?? 'ghinfe:nfce:emissao';
18
+
19
+ this.queue = new Bull(queueName, { redis });
20
+ this.nfceService = new NFCeService(options.nfceOptions ?? { mock: true });
21
+ this.#registerProcessor();
22
+ }
23
+
24
+ #registerProcessor() {
25
+ this.queue.process(async (job) => {
26
+ const { documento, certificado } = job.data;
27
+ return this.nfceService.emitir(documento, certificado);
28
+ });
29
+ }
30
+
31
+ /**
32
+ * @param {{
33
+ * documento: import('../nfce/types/NFCeDocument.js').NFCeDocumento,
34
+ * certificado: { pfx: Buffer|string, senha: string },
35
+ * }} payload
36
+ */
37
+ async adicionarEmissao(payload) {
38
+ return this.queue.add(payload, {
39
+ attempts: 3,
40
+ backoff: { type: 'exponential', delay: 5000 },
41
+ removeOnComplete: 100,
42
+ removeOnFail: 500,
43
+ });
44
+ }
45
+
46
+ /** @param {(resultado: Awaited<ReturnType<NFCeService['emitir']>>, job: Bull.Job) => Promise<void>} handler */
47
+ onCompleted(handler) {
48
+ this.queue.on('completed', async (job, resultado) => {
49
+ await handler(resultado, job);
50
+ });
51
+ }
52
+
53
+ /** @param {(error: Error, job: Bull.Job) => Promise<void>} handler */
54
+ onFailed(handler) {
55
+ this.queue.on('failed', async (job, error) => {
56
+ await handler(error, job);
57
+ });
58
+ }
59
+
60
+ async close() {
61
+ await this.queue.close();
62
+ }
63
+ }
@@ -0,0 +1,59 @@
1
+ import Bull from 'bull';
2
+ import { NFSeService } from '../nfse/services/NFSeService.js';
3
+
4
+ /**
5
+ * Fila Bull/Redis para processamento assíncrono de NFS-e.
6
+ */
7
+ export class NFSeQueue {
8
+ /**
9
+ * @param {{
10
+ * redis?: { host?: string, port?: number, password?: string },
11
+ * queueName?: string,
12
+ * nfseOptions?: ConstructorParameters<typeof NFSeService>[0],
13
+ * }} [options]
14
+ */
15
+ constructor(options = {}) {
16
+ const redis = options.redis ?? { host: '127.0.0.1', port: 6379 };
17
+ const queueName = options.queueName ?? 'ghinfe:nfse:emissao';
18
+
19
+ this.queue = new Bull(queueName, { redis });
20
+ this.nfseService = new NFSeService(options.nfseOptions ?? { mock: true });
21
+ this.#registerProcessor();
22
+ }
23
+
24
+ #registerProcessor() {
25
+ this.queue.process(async (job) => {
26
+ const { documento } = job.data;
27
+ return this.nfseService.emitir(documento);
28
+ });
29
+ }
30
+
31
+ /**
32
+ * @param {{ documento: import('../nfse/types/NFSeDocument.js').NFSeDocumento }} payload
33
+ */
34
+ async adicionarEmissao(payload) {
35
+ return this.queue.add(payload, {
36
+ attempts: 3,
37
+ backoff: { type: 'exponential', delay: 5000 },
38
+ removeOnComplete: 100,
39
+ removeOnFail: 500,
40
+ });
41
+ }
42
+
43
+ /** @param {(resultado: Awaited<ReturnType<NFSeService['emitir']>>, job: Bull.Job) => Promise<void>} handler */
44
+ onCompleted(handler) {
45
+ this.queue.on('completed', async (job, resultado) => {
46
+ await handler(resultado, job);
47
+ });
48
+ }
49
+
50
+ onFailed(handler) {
51
+ this.queue.on('failed', async (job, error) => {
52
+ await handler(error, job);
53
+ });
54
+ }
55
+
56
+ async close() {
57
+ await this.queue.close();
58
+ }
59
+ }
@@ -0,0 +1,76 @@
1
+ import Bull from 'bull';
2
+ import { NFeService } from '../nfe/services/NFeService.js';
3
+
4
+ /**
5
+ * Fila Bull/Redis para processamento assíncrono de NF-e.
6
+ * Integra com Sequelize no worker: persista o retorno após autorização.
7
+ *
8
+ * @example
9
+ * const queue = new NFeQueue({ redis: { host: '127.0.0.1', port: 6379 } });
10
+ * await queue.adicionarEmissao({ documento, certificado });
11
+ * queue.processar(async (resultado) => { await NotaFiscal.create(...); });
12
+ */
13
+ export class NFeQueue {
14
+ /**
15
+ * @param {{
16
+ * redis?: { host?: string, port?: number, password?: string },
17
+ * queueName?: string,
18
+ * nfeOptions?: ConstructorParameters<typeof NFeService>[0],
19
+ * }} [options]
20
+ */
21
+ constructor(options = {}) {
22
+ const redis = options.redis ?? { host: '127.0.0.1', port: 6379 };
23
+ const queueName = options.queueName ?? 'ghinfe:nfe:emissao';
24
+
25
+ this.queue = new Bull(queueName, { redis });
26
+ this.nfeService = new NFeService(options.nfeOptions ?? { mock: true });
27
+ this.#registerProcessor();
28
+ }
29
+
30
+ #registerProcessor() {
31
+ this.queue.process(async (job) => {
32
+ const { documento, certificado, idLote } = job.data;
33
+ const resultado = await this.nfeService.emitir(documento, certificado, idLote);
34
+ return resultado;
35
+ });
36
+ }
37
+
38
+ /**
39
+ * @param {{
40
+ * documento: import('../nfe/types/NFeDocument.js').NFeDocument,
41
+ * certificado: { pfx: Buffer|string, senha: string },
42
+ * idLote?: string,
43
+ * }} payload
44
+ */
45
+ async adicionarEmissao(payload) {
46
+ return this.queue.add(payload, {
47
+ attempts: 3,
48
+ backoff: { type: 'exponential', delay: 5000 },
49
+ removeOnComplete: 100,
50
+ removeOnFail: 500,
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Callback executado após emissão bem-sucedida (ex: salvar no Sequelize).
56
+ * @param {(resultado: Awaited<ReturnType<NFeService['emitir']>>, job: Bull.Job) => Promise<void>} handler
57
+ */
58
+ onCompleted(handler) {
59
+ this.queue.on('completed', async (job, resultado) => {
60
+ await handler(resultado, job);
61
+ });
62
+ }
63
+
64
+ /**
65
+ * @param {(error: Error, job: Bull.Job) => Promise<void>} handler
66
+ */
67
+ onFailed(handler) {
68
+ this.queue.on('failed', async (job, error) => {
69
+ await handler(error, job);
70
+ });
71
+ }
72
+
73
+ async close() {
74
+ await this.queue.close();
75
+ }
76
+ }