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,247 lines (1,215 loc) • 91.8 kB
JavaScript
export * from '@nfewizard/types/shared';
export * from '@nfewizard/types/nfe';
import { BaseNFE, logger, getCodIBGE, XmlParser, ValidaCPFCNPJ, mountICMS, mountPIS, mountCOFINS, Environment, Utility, SaveFiles, XmlBuilder, GerarConsulta, NFE_SchemaValidate } from '@nfewizard/shared';
export { AxiosHttpClient, BaseNFE, Environment, GerarConsulta, HttpClientBuilder, LoadCertificate, NFE_SchemaValidate, SaveFiles, Utility, ValidaCPFCNPJ, ValidateEnvironment, XmlBuilder, XmlParser, getCodIBGE, getDesTipoPag, getSchema, logger, mountCOFINS, mountICMS, mountPIS } from '@nfewizard/shared';
import nodemailer from 'nodemailer';
import { format } from 'date-fns';
import pako from 'pako';
import xml2js from '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 BaseNFE {
constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) {
super(environment, utility, xmlBuilder, METHOD_NAME, axios, saveFiles, gerarConsulta);
}
gerarXml() {
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: 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 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 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 ? `-${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 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 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) {
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 = mountICMS(det.imposto.ICMS.dadosICMS);
det.imposto.ICMS = icms;
}
if (det.imposto.PIS?.dadosPIS) {
const pis = mountPIS(det.imposto.PIS.dadosPIS);
det.imposto.PIS = pis;
}
if (det.imposto.COFINS?.dadosCOFINS) {
const cofins = 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,
};
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;
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 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
});
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 BaseNFE {
constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) {
super(environment, utility, xmlBuilder, 'NFEConsultaProtocolo', axios, saveFiles, gerarConsulta);
}
gerarXml(chave) {
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 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) {
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) {
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,
};
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;
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) {
logger.info('Descomprimindo XML', {
context: 'DistribuicaoHandler',
});
const json = new 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) {
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 BaseNFE {
constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) {
super(environment, utility, xmlBuilder, 'NFeDistribuicaoDFe', axios, saveFiles, gerarConsulta);
}
gerarXml(data) {
return this.gerarXmlNFeDistribuicaoDFe(data);
}
gerarXmlNFeDistribuicaoDFe(data) {
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,
};
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;
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 = '';