Browse Source

xml soap para envio nfe

pull/1/head
Junior Tada 11 years ago
parent
commit
3e75a2f7a7
  1. 22
      pynfe/entidades/notafiscal.py
  2. 6
      pynfe/processamento/assinatura.py
  3. 71
      pynfe/processamento/comunicacao.py
  4. 122
      pynfe/processamento/serializacao.py
  5. 4
      pynfe/utils/webservices.py
  6. 5
      test.py

22
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'),

6
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()

71
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

122
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')

4
pynfe/utils/webservices.py

@ -1,4 +1,8 @@
"""
@author: Junior Tada, Leonardo Tada
"""
# Nfc-e
NFCE = {
'RO': {

5
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(),

Loading…
Cancel
Save