diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index 9c7d0f7..f51d03b 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -68,8 +68,20 @@ class NotaFiscal(Entidade): cliente_final = int() # - Indica se a compra foi feita presencialmente, telefone, internet, etc + """ + 0=Não se aplica (por exemplo, Nota Fiscal complementar ou deajuste); + 1=Operação presencial; + 2=Operação não presencial, pela Internet; + 3=Operação não presencial, Teleatendimento; + 4=NFC-e em operação com entrega a domicílio; + 9=Operação não presencial, outros. + """ indicador_presencial = int() + """ nfce suporta apenas operação interna + Identificador de local de destino da operação 1=Operação interna;2=Operação interestadual;3=Operação com exterior. + """ + indicador_destino = int() # - UF - converter para codigos em CODIGOS_ESTADOS uf = str() @@ -351,8 +363,8 @@ class NotaFiscal(Entidade): return obj def _codigo_numerico_aleatorio(self): - codigo_numerico_aleatorio = str(random.randint(0, 99999999)).zfill(8) - return codigo_numerico_aleatorio + self.codigo_numerico_aleatorio = str(random.randint(0, 99999999)).zfill(8) + return self.codigo_numerico_aleatorio def _dv_codigo_numerico(self, key): assert len(key) == 43 @@ -371,8 +383,8 @@ class NotaFiscal(Entidade): remainder = key_sum % 11 if remainder == 0 or remainder == 1: return '0' - dv_codigo_numerico_aleatorio = str(11 - remainder) - return str(dv_codigo_numerico_aleatorio) + self.dv_codigo_numerico_aleatorio = str(11 - remainder) + return str(self.dv_codigo_numerico_aleatorio) @property # @memoize @@ -390,6 +402,8 @@ class NotaFiscal(Entidade): 'tpEmis': str(self.forma_emissao), 'cNF': self._codigo_numerico_aleatorio(), } + print (self.codigo_numerico_aleatorio) + print (key) return "NFe%(uf)s%(ano)s%(mes)s%(cnpj)s%(mod)s%(serie)s%(nNF)s%(tpEmis)s%(cNF)s%(cDV)s"%{ 'uf': CODIGOS_ESTADOS[self.uf], 'ano': self.data_emissao.strftime('%y'), diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 5d1ee85..b14d133 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -17,15 +17,15 @@ class Assinatura(object): self.certificado = certificado self.senha = senha - def assinar_nfe(self, xml): - """Efetua a assinatura da nfe""" + def assinar(self, xml): + """Efetua a assinatura da nota""" pass class AssinaturaA1(Assinatura): """Classe responsavel por efetuar a assinatura do certificado digital no XML informado. Passar XML como string.""" - def assinar_nfe(self, xml): + def assinar(self, xml): arquivo_cert = CertificadoA1(self.certificado) #key, cert = arquivo_cert.separar_arquivo(self.senha) cert = open("cert.pem").read() diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index efae46c..fd82342 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -28,8 +28,17 @@ class ComunicacaoSefaz(Comunicacao): _assinatura = AssinaturaA1 def autorizacao(self, nota_fiscal): - pass + # tipo de nota, nfce ou nfe para pegar url correta + tipo = 'nfce' if nota_fiscal.modelo == 65 else 'nfe' + url = self._get_url(tipo=tipo, consulta='AUTORIZACAO') + # Monta XML do corpo da requisição + raiz = etree.Element('enviNFe') + etree.SubElement(raiz, 'versaoDados').text = self._versao + etree.SubElement(raiz, 'idLote').text = str(1) # numero autoincremental gerado pelo sistema + etree.SubElement(raiz, 'indSinc').text = str(1) # 0 para assincrono, 1 para sincrono + etree.SubElement(raiz, 'NFe').text = nota_fiscal # conjunto de nfe tramistidas (max 50) + def cancelar(self, nota_fiscal): pass @@ -41,19 +50,7 @@ class ComunicacaoSefaz(Comunicacao): """ tipo é a string com tipo de serviço que deseja consultar Ex: nfe ou nfce """ - if self._ambiente == 1: - ambiente = 'https://' - else: - ambiente = 'https://homologacao.' - if tipo == 'nfe': - # nfe Ex: https://nfe.fazenda.pr.gov.br/nfe/NFeStatusServico3 - url = ambiente + NFE[self.uf.upper()]['STATUS'] - elif tipo == 'nfce': - # nfce Ex: https://homologacao.nfce.fazenda.pr.gov.br/nfce/NFeStatusServico3 - url = ambiente + NFCE[self.uf.upper()]['STATUS'] - else: - # TODO implementar outros tipos de notas como NFS-e - pass + url = self._get_url(tipo=tipo, consulta='STATUS') # Monta XML do corpo da requisição raiz = etree.Element('consStatServ', versao='3.10', xmlns=NAMESPACE_NFE) @@ -62,8 +59,10 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(raiz, 'xServ').text = 'STATUS' dados = etree.tostring(raiz, encoding='UTF-8') # Monta XML para envio da requisição - xml = self._construir_xml_soap(cabecalho=self._cabecalho_soap(), dados=dados, url=url) - + if self.uf.upper() == 'PR': + xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(), dados=dados, url=url) + else: + xml = self._construir_xml_soap(cabecalho=self._cabecalho_soap(), metodo='nfeRecepcao2', tag_metodo='nfeStatusServicoNF2', dados=dados) # Chama método que efetua a requisição POST no servidor SOAP return self._post(url, xml, self._post_header()) @@ -71,8 +70,7 @@ class ComunicacaoSefaz(Comunicacao): #post = '/nfeweb/services/cadconsultacadastro.asmx' post = '/nfeweb/services/nfeconsulta.asmx' - def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, certificado, - senha, ano=None, serie='1', justificativa=''): + def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, certificado, senha, ano=None, serie='1', justificativa=''): post = '/nfeweb/services/nfestatusservico.asmx' # Valores default @@ -127,6 +125,22 @@ class ComunicacaoSefaz(Comunicacao): return retorno + def _get_url(self, tipo, consulta): + if self._ambiente == 1: + ambiente = 'https://' + else: + ambiente = 'https://homologacao.' + if tipo == 'nfe': + # nfe Ex: https://nfe.fazenda.pr.gov.br/nfe/NFeStatusServico3 + url = ambiente + NFE[self.uf.upper()][consulta] + elif tipo == 'nfce': + # nfce Ex: https://homologacao.nfce.fazenda.pr.gov.br/nfce/NFeStatusServico3 + url = ambiente + NFCE[self.uf.upper()][consulta] + else: + # TODO implementar outros tipos de notas como NFS-e + pass + return url + def _cabecalho_soap(self): u"""Monta o XML do cabeçalho da requisição SOAP""" @@ -136,7 +150,22 @@ class ComunicacaoSefaz(Comunicacao): return etree.tostring(raiz, encoding='UTF-8') - def _construir_xml_soap(self, cabecalho, dados, url): + def _construir_xml_soap(self, cabecalho, metodo, tag_metodo, dados): + """Mota o XML para o envio via SOAP""" + + raiz = etree.Element('{%s}Envelope'%NAMESPACE_SOAP, nsmap={'soap': NAMESPACE_SOAP}) + + body = etree.SubElement(raiz, '{%s}Body'%NAMESPACE_SOAP) + met = etree.SubElement( + body, tag_metodo, xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/%s"%metodo, + ) + + etree.SubElement(met, 'nfeCabecMsg').text = cabecalho + etree.SubElement(met, 'nfeDadosMsg').text = dados + + return etree.tostring(raiz, encoding='utf-8', xml_declaration=True) + + def _construir_xml_status_pr(self, cabecalho, dados, url): u"""Mota o XML para o envio via SOAP""" raiz = etree.Element('{%s}Envelope'%NAMESPACE_SOAP, nsmap={'soap': NAMESPACE_SOAP}, xmlns=url) @@ -155,7 +184,7 @@ class ComunicacaoSefaz(Comunicacao): u'Accept': u'application/soap+xml; charset=utf-8', } - def _post(self, post, xml, header): + def _post(self, url, xml, header): # Separa arquivos de certificado para chave e certificado (sozinho) #caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha) caminho_chave = '/home/junior/Documentos/Certificados/key.pem' @@ -167,7 +196,7 @@ class ComunicacaoSefaz(Comunicacao): #headers = {'content-type': 'text/xml'} try: - r = requests.post(post, s, headers=self._post_header(), cert=cert, verify=False) + r = requests.post(url, s, headers=self._post_header(), cert=cert, verify=False) print (r.content) if r == 200: return r.text diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index 16ee83e..c3dc580 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -1,12 +1,8 @@ # -*- coding: utf-8 -*- + import time -try: - set -except: - from sets import Set as set -from pynfe.entidades import Emitente, Cliente, Produto, Transportadora, NotaFiscal -from pynfe.excecoes import NenhumObjetoEncontrado, MuitosObjetosEncontrados +from pynfe.entidades import NotaFiscal from pynfe.utils import etree, so_numeros, obter_municipio_por_codigo, \ obter_pais_por_codigo, obter_municipio_e_codigo, \ formatar_decimal, safe_str, obter_uf_por_codigo, obter_codigo_por_municipio @@ -101,14 +97,15 @@ class SerializacaoXML(Serializacao): else: return raiz - def _serializar_cliente(self, cliente, tag_raiz='dest', retorna_string=True): + def _serializar_cliente(self, cliente, modelo, tag_raiz='dest', retorna_string=True): raiz = etree.Element(tag_raiz) - # Dados do cliente + # Dados do cliente (distinatario) etree.SubElement(raiz, cliente.tipo_documento).text = so_numeros(cliente.numero_documento) etree.SubElement(raiz, 'xNome').text = cliente.razao_social - etree.SubElement(raiz, 'IE').text = cliente.inscricao_estadual - + # nfc-e nao possui IE mesmo que seja uma empresa + if modelo == 55: + etree.SubElement(raiz, 'IE').text = cliente.inscricao_estadual # Endereço endereco = etree.SubElement(raiz, 'enderDest') etree.SubElement(endereco, 'xLgr').text = cliente.endereco_logradouro @@ -250,7 +247,17 @@ class SerializacaoXML(Serializacao): horário de verão serão -01:00, -02:00 e -03:00. Exemplo: "2010-08-19T13:00:15-03:00". """ etree.SubElement(ide, 'tpNF').text = str(nota_fiscal.tipo_documento) # 0=entrada 1=saida - etree.SubElement(ide, 'idDest').text = str(1) # Identificador de local de destino da operação 1=Operação interna;2=Operação interestadual;3=Operação com exterior. + """ nfce suporta apenas operação interna + Identificador de local de destino da operação 1=Operação interna;2=Operação interestadual;3=Operação com exterior. + """ + if nota_fiscal.modelo == 65: + etree.SubElement(ide, 'idDest').text = str(1) + etree.SubElement(ide, 'indPres').text = str(1) + etree.SubElement(ide, 'indFinal').text = str(1) + else: + etree.SubElement(ide, 'idDest').text = str(nota_fiscal.indicador_destino) + etree.SubElement(ide, 'indPres').text = str(nota_fiscal.indicador_presencial) + etree.SubElement(ide, 'indFinal').text = str(nota_fiscal.cliente_final) etree.SubElement(ide, 'cMunFG').text = nota_fiscal.municipio etree.SubElement(ide, 'tpImp').text = str(nota_fiscal.tipo_impressao_danfe) etree.SubElement(ide, 'tpEmis').text = str(nota_fiscal.forma_emissao) @@ -258,14 +265,16 @@ class SerializacaoXML(Serializacao): etree.SubElement(ide, 'tpAmb').text = str(self._ambiente) etree.SubElement(ide, 'finNFe').text = str(nota_fiscal.finalidade_emissao) etree.SubElement(ide, 'procEmi').text = str(nota_fiscal.processo_emissao) - etree.SubElement(ide, 'verProc').text = '%s %s'%(self._nome_aplicacao, - nota_fiscal.versao_processo_emissao) + etree.SubElement(ide, 'verProc').text = '%s %s'%(self._nome_aplicacao, nota_fiscal.versao_processo_emissao) + ### CONTINGENCIA ### + #etree.SubElement(ide, 'dhCont').text = '' # Data e Hora da entrada em contingência AAAA-MM-DDThh:mm:ssTZD + #etree.SubElement(ide, 'xJust').text = '' # Justificativa da entrada em contingência (min 20, max 256 caracteres) # Emitente raiz.append(self._serializar_emitente(nota_fiscal.emitente, retorna_string=False)) # Destinatário - raiz.append(self._serializar_cliente(nota_fiscal.cliente, retorna_string=False)) + raiz.append(self._serializar_cliente(nota_fiscal.cliente, modelo=nota_fiscal.modelo, retorna_string=False)) # Retirada if nota_fiscal.retirada: @@ -301,50 +310,55 @@ class SerializacaoXML(Serializacao): etree.SubElement(icms_total, 'vFrete').text = str(nota_fiscal.totais_icms_total_frete) etree.SubElement(icms_total, 'vSeg').text = str(nota_fiscal.totais_icms_total_seguro) etree.SubElement(icms_total, 'vDesc').text = str(nota_fiscal.totais_icms_total_desconto) - etree.SubElement(icms_total, 'vII').text = str(nota_fiscal.totais_icms_total_ii) - etree.SubElement(icms_total, 'vIPI').text = str(nota_fiscal.totais_icms_total_ipi) - etree.SubElement(icms_total, 'vPIS').text = str(nota_fiscal.totais_icms_pis) - etree.SubElement(icms_total, 'vCOFINS').text = str(nota_fiscal.totais_icms_cofins) etree.SubElement(icms_total, 'vOutro').text = str(nota_fiscal.totais_icms_outras_despesas_acessorias) etree.SubElement(icms_total, 'vNF').text = str(nota_fiscal.totais_icms_total_nota) - # Transporte - transp = etree.SubElement(raiz, 'transp') - etree.SubElement(transp, 'modFrete').text = str(nota_fiscal.transporte_modalidade_frete) - - # Transportadora - if nota_fiscal.transporte_transportadora: - transp.append(self._serializar_transportadora( - nota_fiscal.transporte_transportadora, - retorna_string=False, - )) - # Veículo - veiculo = etree.SubElement(transp, 'veicTransp') - etree.SubElement(veiculo, 'placa').text = nota_fiscal.transporte_veiculo_placa - etree.SubElement(veiculo, 'UF').text = nota_fiscal.transporte_veiculo_uf - etree.SubElement(veiculo, 'RNTC').text = nota_fiscal.transporte_veiculo_rntc - - # Reboque - reboque = etree.SubElement(transp, 'reboque') - etree.SubElement(reboque, 'placa').text = nota_fiscal.transporte_reboque_placa - etree.SubElement(reboque, 'UF').text = nota_fiscal.transporte_reboque_uf - etree.SubElement(reboque, 'RNTC').text = nota_fiscal.transporte_reboque_rntc - - # Volumes - for volume in nota_fiscal.transporte_volumes: - vol = etree.SubElement(transp, 'vol') - etree.SubElement(vol, 'qVol').text = str(volume.quantidade) - etree.SubElement(vol, 'esp').text = volume.especie - etree.SubElement(vol, 'marca').text = volume.marca - etree.SubElement(vol, 'nVol').text = volume.numeracao - etree.SubElement(vol, 'pesoL').text = str(volume.peso_liquido) - etree.SubElement(vol, 'pesoB').text = str(volume.peso_bruto) - - # Lacres - lacres = etree.SubElement(vol, 'lacres') - for lacre in volume.lacres: - etree.SubElement(lacres, 'nLacre').text = lacre.numero_lacre + # Apenas NF-e + if nota_fiscal.modelo == 55: + # Tributos + etree.SubElement(icms_total, 'vIPI').text = str(nota_fiscal.totais_icms_total_ipi) + etree.SubElement(icms_total, 'vPIS').text = str(nota_fiscal.totais_icms_pis) + etree.SubElement(icms_total, 'vCOFINS').text = str(nota_fiscal.totais_icms_cofins) + etree.SubElement(icms_total, 'vII').text = str(nota_fiscal.totais_icms_total_ii) + + # Transporte + transp = etree.SubElement(raiz, 'transp') + etree.SubElement(transp, 'modFrete').text = str(nota_fiscal.transporte_modalidade_frete) + + # Transportadora + if nota_fiscal.transporte_transportadora: + transp.append(self._serializar_transportadora( + nota_fiscal.transporte_transportadora, + retorna_string=False, + )) + + # Veículo + veiculo = etree.SubElement(transp, 'veicTransp') + etree.SubElement(veiculo, 'placa').text = nota_fiscal.transporte_veiculo_placa + etree.SubElement(veiculo, 'UF').text = nota_fiscal.transporte_veiculo_uf + etree.SubElement(veiculo, 'RNTC').text = nota_fiscal.transporte_veiculo_rntc + + # Reboque + reboque = etree.SubElement(transp, 'reboque') + etree.SubElement(reboque, 'placa').text = nota_fiscal.transporte_reboque_placa + etree.SubElement(reboque, 'UF').text = nota_fiscal.transporte_reboque_uf + etree.SubElement(reboque, 'RNTC').text = nota_fiscal.transporte_reboque_rntc + + # Volumes + for volume in nota_fiscal.transporte_volumes: + vol = etree.SubElement(transp, 'vol') + etree.SubElement(vol, 'qVol').text = str(volume.quantidade) + etree.SubElement(vol, 'esp').text = volume.especie + etree.SubElement(vol, 'marca').text = volume.marca + etree.SubElement(vol, 'nVol').text = volume.numeracao + etree.SubElement(vol, 'pesoL').text = str(volume.peso_liquido) + etree.SubElement(vol, 'pesoB').text = str(volume.peso_bruto) + + # Lacres + lacres = etree.SubElement(vol, 'lacres') + for lacre in volume.lacres: + etree.SubElement(lacres, 'nLacre').text = lacre.numero_lacre # Informações adicionais info_ad = etree.SubElement(raiz, 'infAdic') diff --git a/pynfe/utils/webservices.py b/pynfe/utils/webservices.py index 096aab6..1d7fff0 100644 --- a/pynfe/utils/webservices.py +++ b/pynfe/utils/webservices.py @@ -1,4 +1,8 @@ +""" + @author: Junior Tada, Leonardo Tada +""" + # Nfc-e NFCE = { 'RO': { diff --git a/test.py b/test.py index da8aca6..49a859a 100644 --- a/test.py +++ b/test.py @@ -8,8 +8,7 @@ from pynfe.entidades.cliente import Cliente from pynfe.entidades.emitente import Emitente from pynfe.entidades.notafiscal import NotaFiscal, NotaFiscalProduto from pynfe.entidades.fonte_dados import _fonte_dados -from pynfe.processamento.serializacao import SerializacaoPipes, SerializacaoXML -from pynfe.processamento.validacao import Validacao +from pynfe.processamento.serializacao import SerializacaoXML from pynfe.processamento.assinatura import AssinaturaA1 from pynfe.utils.flags import CODIGO_BRASIL import datetime @@ -60,7 +59,7 @@ nota_fiscal = NotaFiscal( codigo_numerico_aleatorio='66998237', natureza_operacao='VENDA ASSINATURAS', forma_pagamento='1', - modelo=55, + modelo=65, serie='1', numero_nf='1', data_emissao=datetime.datetime.now(),