UNPKG

nfewizard-io

Version:

NFeWizard-io é uma biblioteca Node.js projetada para simplificar a interação com os webservices da SEFAZ, proporcionando uma solução robusta para automação de processos relacionados à Nota Fiscal Eletrônica (NF-e).

1,252 lines (1,219 loc) 95.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var shared = require('@nfewizard/types/shared'); var nfe = require('@nfewizard/types/nfe'); var shared$1 = require('@nfewizard/shared'); var nodemailer = require('nodemailer'); var dateFns = require('date-fns'); var pako = require('pako'); var xml2js = require('xml2js'); class NFEAutorizacao { constructor(nfeAutorizacaoService) { this.nfeAutorizacaoService = nfeAutorizacaoService; } async Exec(data) { return await this.nfeAutorizacaoService.Exec(data); } } class NFEStatusServico { constructor(nfeStatusServicoService) { this.nfeStatusServicoService = nfeStatusServicoService; } async Exec(data) { return await this.nfeStatusServicoService.Exec(data); } } class NFEConsultaProtocolo { constructor(nfeConsultaProtocoloService) { this.nfeConsultaProtocoloService = nfeConsultaProtocoloService; } async Exec(data) { return await this.nfeConsultaProtocoloService.Exec(data); } } class NFERetornoAutorizacao { constructor(nfeRetornoAutorizacaoService) { this.nfeRetornoAutorizacaoService = nfeRetornoAutorizacaoService; } async Exec(data) { return await this.getXmlRetorno(data); } async getXmlRetorno(data) { return await this.nfeRetornoAutorizacaoService.getXmlRetorno(data); } } class NFEInutilizacao { constructor(nfeInutilizacaoService) { this.nfeInutilizacaoService = nfeInutilizacaoService; } async Exec(data) { return await this.nfeInutilizacaoService.Exec(data); } } class NFEDistribuicaoDFe { constructor(nfeDistribuicaoDFeService) { this.nfeDistribuicaoDFeService = nfeDistribuicaoDFeService; } async Exec(data) { return await this.nfeDistribuicaoDFeService.Exec(data); } } class NFEDistribuicaoDFePorChave { constructor(nfeDistribuicaoDFePorChaveService) { this.nfeDistribuicaoDFePorChaveService = nfeDistribuicaoDFePorChaveService; } async Exec(data) { return await this.nfeDistribuicaoDFePorChaveService.Exec(data); } } class NFEDistribuicaoDFePorNSU { constructor(nfeDistribuicaoDFePorNSUService) { this.nfeDistribuicaoDFePorNSUService = nfeDistribuicaoDFePorNSUService; } async Exec(data) { return await this.nfeDistribuicaoDFePorNSUService.Exec(data); } } class NFEDistribuicaoDFePorUltNSU { constructor(nfeDistribuicaoDFePorUltNSUService) { this.nfeDistribuicaoDFePorUltNSUService = nfeDistribuicaoDFePorUltNSUService; } async Exec(data) { return await this.nfeDistribuicaoDFePorUltNSUService.Exec(data); } } class NFECancelamento { constructor(nfeCancelamentoServiceService) { this.nfeCancelamentoServiceService = nfeCancelamentoServiceService; } async Exec(data) { return await this.nfeCancelamentoServiceService.Exec(data); } } class NFECartaDeCorrecao { constructor(nfeCartaDeCorrecaoServiceService) { this.nfeCartaDeCorrecaoServiceService = nfeCartaDeCorrecaoServiceService; } async Exec(data) { return await this.nfeCartaDeCorrecaoServiceService.Exec(data); } } class NFECienciaDaOperacao { constructor(nfeCienciaDaOperacaoServiceService) { this.nfeCienciaDaOperacaoServiceService = nfeCienciaDaOperacaoServiceService; } async Exec(data) { return await this.nfeCienciaDaOperacaoServiceService.Exec(data); } } class NFEConfirmacaoDaOperacao { constructor(nfeConfirmacaoDaOperacaoServiceService) { this.nfeConfirmacaoDaOperacaoServiceService = nfeConfirmacaoDaOperacaoServiceService; } async Exec(data) { return await this.nfeConfirmacaoDaOperacaoServiceService.Exec(data); } } class NFEDesconhecimentoDaOperacao { constructor(nfeDesconhecimentoDaOperacaoServiceService) { this.nfeDesconhecimentoDaOperacaoServiceService = nfeDesconhecimentoDaOperacaoServiceService; } async Exec(data) { return await this.nfeDesconhecimentoDaOperacaoServiceService.Exec(data); } } class NFEOperacaoNaoRealizada { constructor(nfeOperacaoNaoRealizadaServiceService) { this.nfeOperacaoNaoRealizadaServiceService = nfeOperacaoNaoRealizadaServiceService; } async Exec(data) { return await this.nfeOperacaoNaoRealizadaServiceService.Exec(data); } } class NFEEpec { constructor(nfeEpecServiceService) { this.nfeEpecServiceService = nfeEpecServiceService; } async Exec(data) { return await this.nfeEpecServiceService.Exec(data); } } /* * This file is part of NFeWizard-io. * * NFeWizard-io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NFeWizard-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NFeWizard-io. If not, see <https://www.gnu.org/licenses/>. */ const METHOD_NAME = 'NFEStatusServico'; class NFEStatusServicoService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, METHOD_NAME, axios, saveFiles, gerarConsulta); } gerarXml() { shared$1.logger.info('Montando estrutuda do XML em JSON', { context: 'NFEStatusServicoService', }); const { nfe: { ambiente, versaoDF }, dfe: { UF } } = this.environment.getConfig(); const xmlObject = { $: { versao: versaoDF, xmlns: 'http://www.portalfiscal.inf.br/nfe' }, tpAmb: ambiente, cUF: shared$1.getCodIBGE(UF), xServ: 'STATUS', }; return this.xmlBuilder.gerarXml(xmlObject, 'consStatServ', this.metodo); } } class MailController { constructor(environment) { this.environment = environment; } createTransporter(email) { const { host, port, secure, auth: { user, pass } } = email; // const transporter = nodemailer.createTransport(email); const transporter = nodemailer.createTransport({ host, port, secure, auth: { user, pass, }, }); return transporter; } mountMail(email, mailParams) { const { emailParams: { from, to } } = email; const { message, subject, attachments } = mailParams; const attachmentsWithVerifiedFileName = attachments?.map((attachment) => { if (!attachment.filename) { attachment.filename = false; } return attachment; }); return { from, to, subject, html: message, attachments: attachmentsWithVerifiedFileName, }; } sendEmail(mailParams) { try { const { email } = this.environment.getConfig(); if (!email) { throw new Error('Email não configurado. Para utilizar o envio de e-mail certifique-se de passar as configurações corretas para o método "NFE_LoadEnvironment".'); } const transporter = this.createTransporter(email); const mailOptions = this.mountMail(email, mailParams); transporter.sendMail(mailOptions, (error, _info) => { if (error) { console.log(error); throw new Error(error); } }); } catch (error) { throw new Error(`Erro ao enviar e-mail: ${error.message}`); } } } /* * This file is part of NFeWizard-io. * * NFeWizard-io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NFeWizard-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NFeWizard-io. If not, see <https://www.gnu.org/licenses/>. */ class NFERetornoAutorizacaoService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, 'NFERetAutorizacao', axios, saveFiles, gerarConsulta); } gerarXml(data) { const { nfe: { ambiente } } = this.environment.getConfig(); const xmlObject = { $: { xmlns: 'http://www.portalfiscal.inf.br/nfe', versao: "4.00", }, tpAmb: ambiente, nRec: data }; return this.xmlBuilder.gerarXml(xmlObject, 'consReciNFe', this.metodo); } /** * Busca o retorno da Autorização pelo número do recibo (nRec) * * @param {string} nRec - Número do recibo retornado pela SEFAZ ao emitir uma NFe em modo assíncrono. * @param {any} xmlNFe - Array contendo as NFe do lote enviado à SEFAZ. * @returns {Promise<string>} XML completo da NFe (já com protocolo de autorização). */ async getRetornoRecibo(nRec, xmlNFe) { // try { /** * Gera o XML para consulta de acordo com o número do recibo da emissão (nRec) */ const xmlConsulta = this.gerarXml(nRec); const { xmlFormated, agent, webServiceUrl, action } = await this.gerarConsulta.gerarConsulta(xmlConsulta, this.metodo); // Salva XML de Consulta this.utility.salvaConsulta(xmlConsulta, xmlFormated, this.metodo); // Efetua requisição para o webservice NFEStatusServico const xmlRetorno = await this.axios.post(webServiceUrl, xmlFormated, { headers: { 'Content-Type': this.setContentType(), 'SOAPAction': action, }, httpsAgent: agent }); const responseInJson = this.utility.verificaRejeicao(xmlRetorno.data, this.metodo); // Salva XML de Retorno this.utility.salvaRetorno(xmlRetorno.data, responseInJson, this.metodo); const { protNFe } = this.utility.getProtNFe(xmlRetorno.data); if (!protNFe) { throw new Error(`Não foi possível encontrar a tag 'protNFe'. Talvez a NFe ainda não tenha sido processada.`); } return this.getXmlRetornoAutorizacao(protNFe, xmlNFe); // } catch (error: any) { // throw new Error(error.message) // } } /** * Agrega o protNFe ao restante da NFe gerada na emissão. * * @param {string} protNFe - Tag protNFe do XML em formato JSON. * @param {any} xmlNFe - Array contendo as NFe do lote enviado à SEFAZ. * @returns {} XML completo da NFe (já com protocolo de autorização). */ getXmlRetornoAutorizacao(protNFe, xmlNFe) { // try { /** * Cria o Obj base da NFe já processada (nfeProc) */ const XMLs = []; for (let i = 0; i < protNFe.length; i++) { const baseXML = { $: { versao: "4.00", xmlns: 'http://www.portalfiscal.inf.br/nfe' }, _: '[XML]' }; let xml = this.xmlBuilder.gerarXml(baseXML, 'nfeProc', this.metodo); /** * Converte a tag protNFe do formato JSON para XML e armazena na string protTag. * Adiciona a tag protNFe (armazenada na string protTag) ao array contendo os dados das NFe. */ // Expressão regular para capturar o valor do atributo Id const formatedProtNFe = protNFe; const xmlCompleto = xmlNFe.find((item) => item.indexOf(formatedProtNFe[i].infProt[0].chNFe[0]) !== -1); if (xmlCompleto) { const protTag = this.xmlBuilder.gerarXml(protNFe[i], 'protNFe', this.metodo); const xmlFinal = [xmlCompleto]; xmlFinal.push(protTag); /** * Substitui o "[XML]" com as tags NFe e a tag protNFe */ xml = xml.replace('[XML]', xmlFinal.join('')); xml = `<?xml version="1.0" encoding="UTF-8"?>${xml}`; XMLs.push(xml); } } return { success: true, message: 'xMotivo', data: XMLs }; // } catch (error: any) { // throw new Error(error.message) // } } /** * Retorna o XML completo da Autorização (já com o protocolo de autorização) * * @param {number} tipoEmissao - Informa se o tipo emissão foi síncrona ou assíncrona (0- Não / 1 - Sim). * @param {string | undefined} nRec - Número do recibo retornado pela SEFAZ ao emitir uma NFe em modo assíncrono. * @param {ProtNFe | undefined} protNFe - Tag protNFe do XML em formato JSON. * @param {string[]} xmlNFe - Array contendo as NFe do lote enviado à SEFAZ. * @returns {Promise<string>} XML completo da NFe (já com protocolo de autorização). */ async getXmlRetorno({ tipoEmissao, nRec, protNFe, xmlNFe }) { // try { /** * Trata retorno Síncrono */ if (tipoEmissao === 1 && protNFe) { return this.getXmlRetornoAutorizacao(protNFe, xmlNFe); } /** * Trata retorno Assíncrono */ if (tipoEmissao === 0 && nRec) { return this.getRetornoRecibo(nRec, xmlNFe); } throw new Error('Não foi possível buscar o retorno da autorização.'); // } catch (error: any) { // throw new Error(error.message) // } } } const T_MED = 5; // Tempo médio de resposta padrão em segundos class NFEAutorizacaoService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, 'NFEAutorizacao', axios, saveFiles, gerarConsulta); this.xmlNFe = []; } gerarXml(data) { return this.gerarXmlNFeAutorizacao(data); } salvaArquivos(_xmlConsulta, responseInJson, _xmlRetorno, options) { // Recupera configuração do ambiente para verificar se os arquivos gerados serão gravados em disco const config = this.environment.getConfig(); let dateAndTimeInFileName = config.dfe.incluirTimestampNoNomeDosArquivos; const createFileName = (prefix, includeMethodName) => { const dtaTime = dateAndTimeInFileName ? `-${dateFns.format(new Date(), 'dd-MM-yyyy-HHmm')}` : ''; const baseFileName = ''; const prefixPart = prefix ? `${prefix}` : ''; const nfePart = responseInJson.chNFe ? `-${responseInJson.chNFe}` : ''; const dateTimePart = dtaTime; return `${baseFileName}${prefixPart}${nfePart}${dateTimePart}`; }; const salvarArquivo = (data, prefix, path, fileType, includeMethodName) => { const fileName = createFileName(prefix); const method = fileType === 'xml' ? 'salvaXML' : 'salvaJSON'; this.utility[method]({ data: data, fileName, metodo: this.metodo, path, }); }; let xmlAutorizacaoInJson = {}; let xMotivoPorXml = []; let xmlsInJson = []; if (options) { const { xmlAutorizacao } = options; const json = new shared$1.XmlParser(); for (let i = 0; i < xmlAutorizacao.length; i++) { xmlAutorizacaoInJson = json.convertXmlToJson(xmlAutorizacao[i], 'NFEAutorizacaoFinal'); xmlsInJson.push(xmlAutorizacaoInJson); const chNFe = xmlAutorizacaoInJson.protNFe.infProt.chNFe; const xMotivo = xmlAutorizacaoInJson.protNFe.infProt.xMotivo; const cStat = xmlAutorizacaoInJson.protNFe.infProt.cStat; xMotivoPorXml.push({ chNFe, xMotivo, cStat, }); if (config.dfe.armazenarXMLAutorizacao) { salvarArquivo(xmlAutorizacao[i], chNFe, config.dfe.pathXMLAutorizacao, 'xml'); salvarArquivo(xmlAutorizacaoInJson, chNFe, config.dfe.pathXMLAutorizacao, 'json'); } } return { success: true, xMotivo: xMotivoPorXml, response: xmlsInJson, }; } return { success: true, xMotivo: xMotivoPorXml, response: xmlsInJson, }; } async trataRetorno(xmlRetorno, indSinc, responseInJson) { /** * Captura o valor nRec e protNFe */ const { nRec, protNFe } = this.utility.getProtNFe(xmlRetorno); /** * 0 - assíncrona * 1 - síncrona */ let tipoEmissao = 0; if (indSinc === 1 && protNFe) { tipoEmissao = 1; } const nfeRetornoAutService = new NFERetornoAutorizacaoService(this.environment, this.utility, this.xmlBuilder, this.axios, this.saveFiles, this.gerarConsulta); const nfeRetornoAut = new NFERetornoAutorizacao(nfeRetornoAutService); /** * Aguarda o Tempo médio de resposta do serviço (em segundos) dos últimos 5 minutos * A informação do tMed só é recebida caso o processamento for assíncrono (indSinc = 0) */ if (tipoEmissao !== 1) await new Promise(resolve => setTimeout(resolve, Number(responseInJson.infRec?.tMed || T_MED) * 1000)); const retorno = await nfeRetornoAut.getXmlRetorno({ tipoEmissao, nRec, protNFe, xmlNFe: this.xmlNFe }); return retorno; } /** * Método utilitário para criação do XML a partir de um Objeto */ anoMesEmissao(dhEmi) { // Lógica para obter o ano e mês de emissão (AAMM) const dataAtual = new Date(dhEmi); const ano = dataAtual.getFullYear().toString().slice(-2); const mes = (dataAtual.getMonth() + 1).toString().padStart(2, '0'); return ano + mes; } // private _gerarCodigoNumerico() { // // Lógica para gerar um código numérico aleatório de 8 dígitos // return Math.floor(Math.random() * 100000000).toString().padStart(8, '0'); // } calcularModulo11(sequencia) { const pesos = [4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; let somatoria = 0; for (let i = 0; i < sequencia.length; i++) { somatoria += parseInt(sequencia.charAt(i)) * pesos[i]; } const restoDivisao = somatoria % 11; const digitoVerificador = restoDivisao === 0 || restoDivisao === 1 ? 0 : 11 - restoDivisao; return digitoVerificador; } calcularDigitoVerificador(data) { const { infNFe: { Id, ide: { cUF, mod, serie, nNF, tpEmis, cNF, dhEmi }, emit: { CNPJCPF }, }, } = data; if (Id) { this.chaveNfe = Id; return { chaveAcesso: `NFe${Id}`, dv: parseInt(Id.charAt(Id.length - 1), 10), }; } const anoMes = this.anoMesEmissao(dhEmi); // Montando a sequência para o cálculo do dígito verificador const sequencia = `${cUF}${anoMes}${CNPJCPF}${mod}${String(serie).padStart(3, '0')}${String(nNF).padStart(9, '0')}${tpEmis}${cNF}`; // Calculando o dígito verificador const dv = this.calcularModulo11(sequencia); // Montando a chave de acesso const chaveAcesso = `NFe${sequencia}` + dv; this.chaveNfe = `${sequencia}${dv}`; return { chaveAcesso, dv }; } validaDocumento(doc, campo) { // Valida se CPF ou CNPJ const nfeAutorizacaoHandler = new shared$1.ValidaCPFCNPJ(); const { documentoValido, tipoDoDocumento } = nfeAutorizacaoHandler.validarCpfCnpj(doc); console.log({ documentoValido, tipoDoDocumento }); if (!documentoValido || tipoDoDocumento === 'Desconhecido') { const message = tipoDoDocumento === 'Desconhecido' ? `Documento do ${campo} ausente ou inválido` : `${tipoDoDocumento} do ${campo} é inválido`; throw new Error(message); } return tipoDoDocumento; } gerarXmlNFeAutorizacao(data) { shared$1.logger.info('Montando estrutuda do XML em JSON', { context: 'NFEAutorizacaoService', }); const createXML = (NFe) => { // Verificando se existe mais de um produto if (NFe?.infNFe?.det instanceof Array) { // Adicionando indice ao item const formatedItens = NFe.infNFe.det.map((det, index) => { if (det.imposto?.ICMS?.dadosICMS) { const icms = shared$1.mountICMS(det.imposto.ICMS.dadosICMS); det.imposto.ICMS = icms; } if (det.imposto.PIS?.dadosPIS) { const pis = shared$1.mountPIS(det.imposto.PIS.dadosPIS); det.imposto.PIS = pis; } if (det.imposto.COFINS?.dadosCOFINS) { const cofins = shared$1.mountCOFINS(det.imposto.COFINS.dadosCOFINS); det.imposto.COFINS = cofins; } return { $: { nItem: index + 1, }, ...det, }; }); NFe.infNFe.det = formatedItens; } // Cria chave da nota e grava digito verificador const { chaveAcesso, dv } = this.calcularDigitoVerificador(NFe); NFe.infNFe.ide.cDV = dv; NFe.infNFe.ide.verProc = NFe.infNFe.ide.verProc || '1.0.0.0'; delete NFe.infNFe.Id; // Gera hashCSRT automaticamente se infRespTec e idCSRT existirem if (NFe.infNFe.infRespTec?.idCSRT) { const csrt = this.environment.getConfig().nfe?.CSRT; if (csrt) { // Remove o prefixo "NFe" da chave de acesso para concatenar apenas os 44 dígitos const chaveAcessoSemPrefixo = chaveAcesso.replace('NFe', ''); NFe.infNFe.infRespTec.hashCSRT = this.utility.gerarHashCSRT(csrt, chaveAcessoSemPrefixo); } } // Valida Documento do emitente NFe.infNFe.emit = Object.assign({ [this.validaDocumento(String(NFe.infNFe.emit.CNPJCPF), 'emitente')]: NFe.infNFe.emit.CNPJCPF }, NFe.infNFe.emit); delete NFe.infNFe.emit.CNPJCPF; // Valida Documento do destinatário if (NFe.infNFe.dest) { NFe.infNFe.dest = Object.assign({ [this.validaDocumento(String(NFe.infNFe.dest?.CNPJCPF || ''), 'destinatário')]: NFe.infNFe.dest?.CNPJCPF || '' }, NFe.infNFe.dest); delete NFe.infNFe.dest.CNPJCPF; } // Valida Documento do transportador if (NFe.infNFe.transp.transporta) { NFe.infNFe.transp.transporta = Object.assign({ [this.validaDocumento(String(NFe.infNFe.transp.transporta?.CNPJCPF), 'transportador')]: NFe.infNFe.transp.transporta?.CNPJCPF }, NFe.infNFe.transp.transporta); delete NFe.infNFe.transp.transporta?.CNPJCPF; } // Valida Documento do produtor rural if (NFe.infNFe?.NFref instanceof Array) { const NFrefArray = NFe.infNFe.NFref; if (NFrefArray && NFrefArray.length > 0) { NFe.infNFe.NFref = NFrefArray.map(NFref => { if (NFref.refNFP) { NFref.refNFP = Object.assign({ [this.validaDocumento(String(NFref.refNFP.CNPJCPF), 'produtor rural')]: NFref.refNFP.CNPJCPF }, NFref.refNFP); delete NFref.refNFP.CNPJCPF; } return NFref; }); } } else { if (NFe.infNFe.NFref && NFe.infNFe.NFref.refNFP) { NFe.infNFe.NFref.refNFP = Object.assign({ [this.validaDocumento(String(NFe.infNFe.NFref.refNFP.CNPJCPF), 'produtor rural')]: NFe.infNFe.NFref.refNFP.CNPJCPF }, NFe.infNFe.NFref.refNFP); } } // Caso Seja hambiente de homologação if (NFe.infNFe.dest) { if (NFe.infNFe.ide.tpAmb === 2) { NFe.infNFe.dest.xNome = 'NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL'; } } const xmlObject = { $: { xmlns: 'http://www.portalfiscal.inf.br/nfe' }, infNFe: { $: { versao: "4.00", Id: chaveAcesso, }, ...NFe.infNFe } }; const eventoXML = this.xmlBuilder.gerarXml(xmlObject, 'NFe', this.metodo); const xmlAssinado = this.xmlBuilder.assinarXML(eventoXML, 'infNFe'); this.xmlNFe.push(xmlAssinado); }; if (data.NFe instanceof Array) { for (let i = 0; i < data.NFe.length; i++) { const NFe = data.NFe[i]; createXML(NFe); } } else { createXML(data.NFe); } // Base do XML const baseXML = { $: { versao: "4.00", xmlns: 'http://www.portalfiscal.inf.br/nfe' }, idLote: data.idLote, indSinc: data.indSinc, _: '[XML]' }; // Gera base do XML const xml = this.xmlBuilder.gerarXml(baseXML, 'enviNFe', this.metodo); return xml.replace('[XML]', this.xmlNFe.join('')); } async callWebService(xmlConsulta, webServiceUrl, ContentType, action, agent) { const startTime = Date.now(); const headers = { 'Content-Type': ContentType, }; shared$1.logger.http('Iniciando comunicação com o webservice', { context: `NFEAutorizacaoService`, method: this.metodo, url: webServiceUrl, action, headers, }); const response = await this.axios.post(webServiceUrl, xmlConsulta, { headers, httpsAgent: agent }); const duration = Date.now() - startTime; shared$1.logger.http('Comunicação concluída com sucesso', { context: `NFEAutorizacaoService`, method: this.metodo, duration: `${duration}ms`, responseSize: response.data ? JSON.stringify(response.data).length : 0 }); return response; } async Exec(data) { let xmlConsulta = ''; let xmlConsultaSoap = ''; let responseInJson = undefined; let xmlRetorno = {}; const ContentType = this.setContentType(); try { // Se receber XML em string, converte para o JSON do padrão esperado pela lib const dataAsJson = typeof data === 'string' ? new shared$1.XmlParser().convertXmlEnvioNFeToJson(data) : data; // Gerando XML para consulta de Status do Serviço xmlConsulta = this.gerarXmlNFeAutorizacao(dataAsJson); const { xmlFormated, agent, webServiceUrl, action } = await this.gerarConsulta.gerarConsulta(xmlConsulta, this.metodo); xmlConsultaSoap = xmlFormated; // Efetua requisição para o webservice NFEStatusServico const xmlRetorno = await this.callWebService(xmlFormated, webServiceUrl, ContentType, action, agent); /** * Verifica se houve rejeição no processamento do lote */ responseInJson = this.utility.verificaRejeicao(xmlRetorno.data, this.metodo); const retorno = await this.trataRetorno(xmlRetorno.data, dataAsJson.indSinc, responseInJson); const xmlFinal = this.salvaArquivos(xmlConsulta, responseInJson, xmlRetorno.data, { xmlAutorizacao: retorno.data, xMotivo: retorno.message }); shared$1.logger.info('NFe transmitida com sucesso', { context: 'NFEAutorizacaoService', }); return { success: true, xMotivo: xmlFinal.xMotivo, xmls: xmlFinal.response, }; } finally { // Salva XML de Consulta this.utility.salvaConsulta(xmlConsulta, xmlConsultaSoap, this.metodo); // Salva XML de Retorno this.utility.salvaRetorno(xmlRetorno.data, responseInJson, this.metodo); } } } class NFEconsultaProtocoloService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, 'NFEConsultaProtocolo', axios, saveFiles, gerarConsulta); } gerarXml(chave) { shared$1.logger.info('Montando estrutuda do XML em JSON', { context: 'NFEconsultaProtocoloService', }); const { nfe: { ambiente, versaoDF } } = this.environment.getConfig(); const xmlObject = { $: { versao: versaoDF, xmlns: 'http://www.portalfiscal.inf.br/nfe' }, tpAmb: ambiente, xServ: 'CONSULTAR', chNFe: chave, }; return this.xmlBuilder.gerarXml(xmlObject, 'consSitNFe', this.metodo); } } class NFERecepcaoEvento { constructor(nfeRecepcaoEventoService) { this.nfeRecepcaoEventoService = nfeRecepcaoEventoService; } async Exec(data) { return await this.nfeRecepcaoEventoService.Exec(data); } } class NFERecepcaoEventoService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, 'RecepcaoEvento', axios, saveFiles, gerarConsulta); this.tpEvento = ''; this.modelo = 'NFe'; this.xmlEventosNacionais = []; this.xmlEventosRegionais = []; this.xMotivoPorEvento = []; console.log('constructor recepcao evento service'); } /** * Método para gerar o Id do evento */ getID(evento) { const { tpEvento, chNFe, nSeqEvento } = evento; // Validação do tipo do evento (tpEvento) if (typeof tpEvento !== 'string' || !/^\d{6}$/.test(tpEvento)) { throw new Error('tpEvento deve ser uma string com 6 dígitos.'); } // Validação da chave da NF-e (chNFe) if (typeof chNFe !== 'string' || !/^\d{44}$/.test(chNFe)) { throw new Error('chNFe deve ser uma string com 44 dígitos.'); } // Validação do número sequencial do evento (nSeqEvento) if (!Number.isInteger(nSeqEvento) || nSeqEvento < 1 || nSeqEvento > 99) { throw new Error('nSeqEvento deve ser um número entre 1 e 99.'); } // Preenchendo o número sequencial do evento com zeros à esquerda const nSeqEventoPadded = nSeqEvento.toString().padStart(2, '0'); // Construção do ID const id = `ID${tpEvento}${chNFe}${nSeqEventoPadded}`; // Verificação do comprimento do ID if (id.length !== 54) { throw new Error('O ID construído não tem 54 caracteres.'); } return id; // Retorna o ID validado } /** * Verifica se o evento será disparado para o ambiente nacional ou para o estado pré-definido */ isAmbienteNacional(tpEvento) { switch (tpEvento) { case '210210': return true; case '210200': return true; case '210220': return true; case '210240': return true; case '110140': return true; case '110110': return false; default: return false; } } /** * Retorna o nome do Evento */ getTipoEventoName(tpEvento) { switch (tpEvento) { case '210210': return 'Ciência da Operação'; case '210200': return 'Confirmação da Operaçã'; case '210220': return 'Desconhecimento da Operação'; case '210240': return 'Operação não Realizada'; case '110110': return 'Carta de Correção'; case '110111': return 'Cancelamento'; case '110140': return 'EPEC'; default: return 'Desconhecido'; } } separaEventosPorAmbiente(evento) { shared$1.logger.info('Dividindo eventos por ambiente', { context: 'NFERecepcaoEventoService', }); const nacional = evento.filter(event => ['210210', '210200', '210220', '210240', '110140'].includes(event.tpEvento)); const regional = evento.filter(event => !['210210', '210200', '210220', '210240', '110140'].includes(event.tpEvento)); return { nacional, regional }; } /** * Criação do XML */ gerarXmlRecepcaoEvento(evento, idLote, ambienteNacional) { const { nfe: { ambiente } } = this.environment.getConfig(); for (let i = 0; i < evento.length; i++) { const eventoProps = evento[i]; const { cOrgao, tpEvento, chNFe, nSeqEvento, CNPJ, CPF, detEvento, dhEvento, verEvento } = eventoProps; const idEvento = this.getID(eventoProps); // const ambienteNacional = this.isAmbienteNacional(tpEvento); const orgao = ambienteNacional ? 91 : cOrgao; this.tpEvento = tpEvento; // XML parte 1 const eventoObject = { $: { versao: "1.00", xmlns: 'http://www.portalfiscal.inf.br/nfe' }, infEvento: { $: { Id: idEvento, }, cOrgao: orgao, tpAmb: ambiente, ...(CNPJ ? { CNPJ } : { CPF }), chNFe: chNFe, dhEvento: dhEvento, tpEvento: tpEvento, nSeqEvento: nSeqEvento, verEvento: verEvento, detEvento: { $: { versao: "1.00", }, ...detEvento, }, } }; // Gera primeira parte do XML const eventoXML = this.xmlBuilder.gerarXml(eventoObject, 'evento', this.metodo); const xmlAssinado = this.xmlBuilder.assinarXML(eventoXML, 'infEvento'); if (ambienteNacional) { this.xmlEventosNacionais.push(xmlAssinado); } else { this.xmlEventosRegionais.push(xmlAssinado); } } // XML parte 2 const envEvento = { $: { versao: "1.00", xmlns: 'http://www.portalfiscal.inf.br/nfe' }, idLote, _: '[XML]' }; // Gera Segunda parte do XML const xml = this.xmlBuilder.gerarXml(envEvento, 'envEvento', this.metodo); if (ambienteNacional) { return xml.replace('[XML]', this.xmlEventosNacionais.join('')); } return xml.replace('[XML]', this.xmlEventosRegionais.join('')); } trataRetorno(responseInJson) { shared$1.logger.info('Tratando retorno dos eventos', { context: 'NFERecepcaoEventoService', }); const retornoEventos = this.utility.findInObj(responseInJson, 'retEvento'); if (retornoEventos instanceof Array) { for (let i = 0; i < retornoEventos.length; i++) { const chNFe = retornoEventos[i].infEvento.chNFe; const xMotivo = retornoEventos[i].infEvento.xMotivo; const cStat = retornoEventos[i].infEvento.cStat; const tipoEvento = this.getTipoEventoName(retornoEventos[i].infEvento.tpEvento); this.xMotivoPorEvento.push({ chNFe, xMotivo, cStat, tipoEvento }); } return this.xMotivoPorEvento; } const chNFe = retornoEventos.infEvento.chNFe; const xMotivo = retornoEventos.infEvento.xMotivo; const cStat = retornoEventos.infEvento.cStat; const tipoEvento = this.getTipoEventoName(retornoEventos.infEvento.tpEvento); this.xMotivoPorEvento.push({ chNFe, xMotivo, cStat, tipoEvento }); return this.xMotivoPorEvento; } async callWebService(xmlConsulta, webServiceUrl, ContentType, action, agent) { const startTime = Date.now(); const headers = { 'Content-Type': ContentType, 'SOAPAction': action, }; shared$1.logger.http('Iniciando comunicação com o webservice', { context: `BaseNFE`, method: this.metodo, url: webServiceUrl, action, headers, }); const response = await this.axios.post(webServiceUrl, xmlConsulta, { headers, httpsAgent: agent }); const duration = Date.now() - startTime; shared$1.logger.http('Comunicação concluída com sucesso', { context: `BaseNFE`, method: this.metodo, duration: `${duration}ms`, responseSize: response.data ? JSON.stringify(response.data).length : 0 }); return response; } async enviaEvento(evento, idLote, tipoAmbiente) { let xmlConsulta = ''; let xmlConsultaSoap = ''; const ContentType = this.setContentType(); const ambienteNacional = tipoAmbiente === 0 ? true : false; try { // Gerando XML para consulta de Status do Serviço xmlConsulta = this.gerarXmlRecepcaoEvento(evento, idLote, ambienteNacional); const { xmlFormated, agent, webServiceUrl, action } = await this.gerarConsulta.gerarConsulta(xmlConsulta, this.metodo, ambienteNacional || this.isAmbienteNacional(this.tpEvento), '', this.modelo); xmlConsultaSoap = xmlFormated; const xmlRetorno = await this.callWebService(xmlFormated, webServiceUrl, ContentType, action, agent); return xmlRetorno.data; } finally { // Salva XML de Consulta const fileName = ambienteNacional ? 'RecepcaoEvento[Nacional]-consulta' : 'RecepcaoEvento[Regional]-consulta'; this.utility.salvaConsulta(xmlConsulta, xmlConsultaSoap, this.metodo, fileName); } } async Exec(data) { const { evento, idLote, modelo } = data; const { nacional, regional } = this.separaEventosPorAmbiente(evento); if (modelo === '65') this.modelo = 'NFCe'; // Enviar eventos ambiente nacional e regional separadamente let responseNacionalInJson, responseRegionalInJson = null; let finalResponseInJson = []; if (nacional.length > 0) { const retornoNacional = await this.enviaEvento(nacional, idLote, 0); responseNacionalInJson = this.utility.verificaRejeicao(retornoNacional, this.metodo); this.utility.salvaRetorno(retornoNacional, responseNacionalInJson, this.metodo, 'RecepcaoEvento[Nacional]-retorno'); this.trataRetorno(responseNacionalInJson); finalResponseInJson.push(responseNacionalInJson); } if (regional.length > 0) { const retornoRegional = await this.enviaEvento(regional, idLote, 1); responseRegionalInJson = this.utility.verificaRejeicao(retornoRegional, this.metodo); this.utility.salvaRetorno(retornoRegional, responseRegionalInJson, this.metodo, 'RecepcaoEvento[Regional]-retorno'); this.trataRetorno(responseRegionalInJson); finalResponseInJson.push(responseRegionalInJson); } return { success: true, xMotivos: this.xMotivoPorEvento, response: finalResponseInJson, }; } } class NFECancelamentoService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } class NFECartaDeCorrecaoService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } class NFECienciaDaOperacaoService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } class NFEDesconhecimentoDaOperacaoService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } class NFEEpecService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } class NFEOperacaoNaoRealizadaService extends NFERecepcaoEventoService { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta); } } /* * This file is part of NFeWizard-io. * * NFeWizard-io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NFeWizard-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NFeWizard-io. If not, see <https://www.gnu.org/licenses/>. */ class DistribuicaoHandler { constructor(environment, utility, metodo) { this.utility = utility; this.environment = environment; this.metodo = metodo; } /** * Métodos para tratativas do DistribuicaoDFe */ salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, chNFe) { const { pathXMLDistribuicao, baixarXMLDistribuicao, armazenarRetornoEmJSON } = this.environment.config.dfe; if (baixarXMLDistribuicao) { this.utility.salvaXML({ data: XMLDistribuicao, fileName: chNFe, metodo: this.metodo, path: pathXMLDistribuicao, }); if (armazenarRetornoEmJSON) { this.utility.salvaJSON({ data: XMLDistribuicaoInJson, fileName: chNFe, metodo: this.metodo, path: pathXMLDistribuicao, }); } } } deCompressDFeXML(loteDistDFeInt, metodo, _xmlConsulta) { shared$1.logger.info('Descomprimindo XML', { context: 'DistribuicaoHandler', }); const json = new shared$1.XmlParser(); const files = []; xml2js.parseString(loteDistDFeInt, (err, result) => { if (err) { throw new Error(`Erro ao descomprimir o XML: ${err}`); } const docZips = this.utility.findInObj(result, 'docZip'); docZips.forEach((docZip) => { const xmlString = this.decodeDocZip(docZip); const cleanedXml = this.removeSignatureTag(xmlString); const parsedResult = this.parseXml(cleanedXml); if (!parsedResult) return; let chNFe = this.getChNFe(parsedResult); let tipo = this.getTipo(parsedResult); const nsu = docZip['$'].NSU; if (parsedResult['procEventoNFe']) { const tpEvento = this.utility.findInObj(parsedResult, 'tpEvento'); chNFe = `${chNFe}-event-${tpEvento}`; tipo = 'event'; } const xmlDistribuicaoInJson = json.convertXmlToJson(cleanedXml, `${metodo}_${tipo}`, nsu); this.handleResponse(xmlDistribuicaoInJson, cleanedXml, chNFe); files.push(chNFe); }); }); return files; } decodeDocZip(docZip) { shared$1.logger.info('Decodificando DocZip', { context: 'DistribuicaoHandler', }); const base64String = docZip['_']; let binaryString = atob(base64String); let bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } let decompressedData = pako.inflate(bytes); return new TextDecoder("utf-8").decode(decompressedData); } removeSignatureTag(xmlString) { return xmlString.replace(/<Signature.*?<\/Signature>/gs, ''); } parseXml(xmlString) { let parsedResult; xml2js.parseString(xmlString, (err, result) => { if (err) { console.error('Erro ao parsear o XML decomprimido:', err); parsedResult = null; } else { parsedResult = result; } }); return parsedResult; } getChNFe(parsedResult) { return this.utility.findInObj(parsedResult, 'chNFe'); } getTipo(parsedResult) { if (this.utility.findInObj(parsedResult, 'resNFe')) { return 'res'; } else if (parsedResult['procEventoNFe']) { return 'event'; } return 'proc'; } handleResponse(XMLDistribuicaoInJson, XMLDistribuicao, chNFe) { this.salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, chNFe); // Gera erro em caso de Rejeição const xMotivo = this.utility.findInObj(XMLDistribuicaoInJson, 'xMotivo'); if (xMotivo && (xMotivo.includes('Rejeição') || xMotivo.includes('Rejeicao'))) { throw new Error(XMLDistribuicaoInJson.xMotivo); } } } class NFEDistribuicaoDFeService extends shared$1.BaseNFE { constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) { super(environment, utility, xmlBuilder, 'NFeDistribuicaoDFe', axios, saveFiles, gerarConsulta); } gerarXml(data) { return this.gerarXmlNFeDistribuicaoDFe(data); } gerarXmlNFeDistribuicaoDFe(data) { shared$1.logger.info('Montando estrutuda do XML em JSON', { context: 'NFEDistribuicaoDFeService', }); const { nfe: { ambiente } } = this.environment.getConfig(); // XML const xmlObject = { $: { versao: "1.01", xmlns: 'http://www.portalfiscal.inf.br/nfe' }, tpAmb: ambiente, ...data, }; return this.xmlBuilder.gerarXml(xmlObject, 'distDFeInt', this.metodo); } async callWebService(xmlConsulta, webServiceUrl, ContentType, action, agent) { const startTime = Date.now(); const headers = { 'Content-Type': ContentType, }; shared$1.logger.http('Iniciando comunicação com o webservice', { context: `NFEDistribuicaoDFeService`, method: this.metodo, url: webServiceUrl, action, headers, }); const response = await this.axios.post(webServiceUrl, xmlConsulta, { headers, httpsAgent: agent }); const duration = Date.now() - startTime; shared$1.logger.http('Comunicação concluída com sucesso', { context: `NFEDistribuicaoDFeService`, method: this.metodo, duration: `${duration}ms`, responseSize: response.data ? JSON.stringify(response.data).length : 0 }); return response; } async Exec(data) { let xmlConsulta = ''; let xmlConsultaSoap = ''; let responseInJson = undefined; let xmlRetorno = {}; const ContentType = thi