Browse Source

Merge branch 'master3' into feature/nfce

pull/117/head
Danimar Ribeiro 8 years ago
committed by GitHub
parent
commit
644de0ddd7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      pytrustnfe/Servidores.py
  2. 9
      pytrustnfe/client.py
  3. 26
      pytrustnfe/nfe/__init__.py
  4. 4
      pytrustnfe/nfe/comunicacao.py
  5. 105
      pytrustnfe/nfe/danfe.py
  6. 4
      pytrustnfe/nfe/templates/RecepcaoEventoCarta.xml
  7. 82
      pytrustnfe/nfse/bh/__init__.py
  8. 44
      pytrustnfe/nfse/bh/assinatura.py
  9. 13
      pytrustnfe/nfse/bh/templates/CancelarNfse.xml
  10. 11
      pytrustnfe/nfse/bh/templates/GerarNfse.xml
  11. 91
      pytrustnfe/nfse/bh/templates/Rps.xml
  12. 3
      pytrustnfe/nfse/floripa/templates/processar_nota.xml
  13. 35
      pytrustnfe/nfse/ginfes/__init__.py
  14. 2
      pytrustnfe/nfse/imperial/__init__.py
  15. 14
      pytrustnfe/utils.py
  16. 2
      requirements.txt
  17. 8
      setup.py

8
pytrustnfe/Servidores.py

@ -374,8 +374,8 @@ UFBA = {
UFCE = {
NFE_AMBIENTE_PRODUCAO: {
'servidor': 'nfe.sefaz.ce.gov.br',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeRecepcao2',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetRecepcao2',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeAutorizacao',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetAutorizacao',
WS_NFE_INUTILIZACAO: 'nfe2/services/NfeInutilizacao2',
WS_NFE_CONSULTA: 'nfe2/services/NfeConsulta2',
WS_NFE_SITUACAO: 'nfe2/services/NfeStatusServico2',
@ -385,8 +385,8 @@ UFCE = {
},
NFE_AMBIENTE_HOMOLOGACAO: {
'servidor': 'nfeh.sefaz.ce.gov.br',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeRecepcao2',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetRecepcao2',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeAutorizacao',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetAutorizacao',
WS_NFE_INUTILIZACAO: 'nfe2/services/NfeInutilizacao2',
WS_NFE_CONSULTA: 'nfe2/services/NfeConsulta2',
WS_NFE_SITUACAO: 'nfe2/services/NfeStatusServico2',

9
pytrustnfe/client.py

@ -45,19 +45,20 @@ class HttpClient(object):
def _headers(self, action, send_raw):
if send_raw:
return {
'Content-type': 'text/xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action,
'Content-type': 'text/xml; charset=utf-8;',
'SOAPAction': "http://www.portalfiscal.inf.br/nfe/wsdl/%s" % action,
'Accept': 'application/soap+xml; charset=utf-8',
}
return {
'Content-type': 'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action,
'Accept': 'application/soap+xml; charset=utf-8',
'Content-type': 'application/soap+xml; charset=utf-8;',
'SOAPAction': 'http://www.portalfiscal.inf.br/nfe/wsdl/%s' % action,
}
def post_soap(self, xml_soap, cabecalho, send_raw):
header = self._headers(cabecalho.soap_action, send_raw)
urllib3.disable_warnings(category=InsecureRequestWarning)
res = requests.post(self.url, data=xml_soap,
res = requests.post(self.url, data=xml_soap.encode('utf-8'),
cert=(self.cert_path, self.key_path),
verify=False, headers=header)
return res.text

26
pytrustnfe/nfe/__init__.py

@ -19,19 +19,21 @@ from pytrustnfe.exceptions import NFeValidationException
def _build_header(method, **kwargs):
action = {
'NfeAutorizacao': ('NfeAutorizacao', '3.10'),
'NfeRetAutorizacao': ('NfeRetAutorizacao', '3.10'),
'NfeConsultaCadastro': ('CadConsultaCadastro2', '2.00'),
'NfeInutilizacao': ('NfeInutilizacao2', '3.10'),
'RecepcaoEventoCancelamento': ('RecepcaoEvento', '1.00'),
'RecepcaoEventoCarta': ('RecepcaoEvento', '1.00'),
'NFeDistribuicaoDFe': ('NFeDistribuicaoDFe/nfeDistDFeInteresse',
'1.00'),
'RecepcaoEventoManifesto': ('RecepcaoEvento', '1.00'),
'NfeAutorizacao': ('NfeAutorizacao', '3.10', 'NfeAutorizacao/nfeAutorizacaoLote'),
'NfeRetAutorizacao': ('NfeRetAutorizacao', '3.10', 'NfeRetAutorizacao/nfeRetAutorizacaoLote'),
'NfeConsultaCadastro': ('CadConsultaCadastro2', '2.00', 'CadConsultaCadastro2/consultaCadastro2'),
'NfeInutilizacao': ('NfeInutilizacao2', '3.10', 'NfeInutilizacao2/nfeInutilizacaoNF2'),
'RecepcaoEventoCancelamento': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
'RecepcaoEventoCarta': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
'NFeDistribuicaoDFe': ('NFeDistribuicaoDFe/nfeDistDFeInteresse', '1.00', 'NFeDistribuicaoDFe/nfeDistDFeInteresse'),
'RecepcaoEventoManifesto': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
}
vals = {
'estado': kwargs['estado'],
'method': action[method][0],
'soap_action': action[method][2],
'versao': action[method][1]
}
vals = {'estado': kwargs['estado'],
'soap_action': action[method][0],
'versao': action[method][1]}
return CabecalhoSoap(**vals)

4
pytrustnfe/nfe/comunicacao.py

@ -12,9 +12,9 @@ from ..xml import sanitize_response
def _soap_xml(body, cabecalho):
xml = '<?xml version="1.0" encoding="utf-8"?>'
xml += '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Header>'
xml += '<nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.soap_action + '">'
xml += '<nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.method + '">'
xml += '<cUF>' + cabecalho.estado + '</cUF><versaoDados>' + cabecalho.versao + '</versaoDados></nfeCabecMsg></soap:Header><soap:Body>'
xml += '<nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.soap_action + '">'
xml += '<nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.method + '">'
xml += body
xml += '</nfeDadosMsg></soap:Body></soap:Envelope>'
return xml.rstrip('\n')

105
pytrustnfe/nfe/danfe.py

@ -20,6 +20,9 @@ from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import pytz
from datetime import datetime, timedelta
def chunks(cString, nLen):
for start in range(0, len(cString), nLen):
@ -36,10 +39,43 @@ def format_cnpj_cpf(value):
return cValue
def getdateUTC(cDateUTC):
cDt = cDateUTC[0:10].split('-')
def getdateByTimezone(cDateUTC, timezone=None):
'''
Esse método trata a data recebida de acordo com o timezone do
usuário. O seu retorno é dividido em duas partes:
1) A data em si;
2) As horas;
:param cDateUTC: string contendo as informações da data
:param timezone: timezone do usuário do sistema
:return: data e hora convertidos para a timezone do usuário
'''
# Aqui cortamos a informação do timezone da string (+03:00)
dt = cDateUTC[0:19]
# Verificamos se a string está completa (data + hora + timezone)
if timezone and len(cDateUTC) == 25:
# tz irá conter informações da timezone contida em cDateUTC
tz = cDateUTC[19:25]
tz = int(tz.split(':')[0])
dt = datetime.strptime(dt, '%Y-%m-%dT%H:%M:%S')
# dt agora será convertido para o horario em UTC
dt = dt - timedelta(hours=tz)
# tzinfo passará a apontar para <UTC>
dt = pytz.utc.localize(dt)
# valor de dt é convertido para a timezone do usuário
dt = timezone.normalize(dt)
dt = dt.strftime('%Y-%m-%dT%H:%M:%S')
cDt = dt[0:10].split('-')
cDt.reverse()
return '/'.join(cDt), cDateUTC[11:16]
return '/'.join(cDt), dt[11:16]
def format_number(cNumber):
@ -74,7 +110,8 @@ def get_image(path, width=1 * cm):
class danfe(object):
def __init__(self, sizepage=A4, list_xml=None, recibo=True,
orientation='portrait', logo=None, cce_xml=None):
orientation='portrait', logo=None, cce_xml=None,
timezone=None):
path = os.path.join(os.path.dirname(__file__), 'fonts')
pdfmetrics.registerFont(
@ -114,8 +151,8 @@ class danfe(object):
self.Page = 1
# Calculando total linhas usadas para descrições dos itens
# Com bloco fatura, apenas 29 linhas para itens na primeira folha
nNr_Lin_Pg_1 = 34 if oXML_cobr is None else 30
# Com bloco fatura, apenas 25 linhas para itens na primeira folha
nNr_Lin_Pg_1 = 30 if oXML_cobr is None else 26
# [ rec_ini , rec_fim , lines , limit_lines ]
oPaginator = [[0, 0, 0, nNr_Lin_Pg_1]]
el_det = oXML.findall(".//{http://www.portalfiscal.inf.br/nfe}det")
@ -154,13 +191,13 @@ class danfe(object):
self.NrPages = len(oPaginator) # Calculando nr. páginas
if recibo:
self.recibo_entrega(oXML=oXML)
self.recibo_entrega(oXML=oXML, timezone=timezone)
self.ide_emit(oXML=oXML)
self.destinatario(oXML=oXML)
self.ide_emit(oXML=oXML, timezone=timezone)
self.destinatario(oXML=oXML, timezone=timezone)
if oXML_cobr is not None:
self.faturas(oXML=oXML_cobr)
self.faturas(oXML=oXML_cobr, timezone=timezone)
self.impostos(oXML=oXML)
self.transportes(oXML=oXML)
@ -172,7 +209,7 @@ class danfe(object):
# Gera o restante das páginas do XML
for oPag in oPaginator[1:]:
self.newpage()
self.ide_emit(oXML=oXML)
self.ide_emit(oXML=oXML, timezone=timezone)
self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag,
list_desc=list_desc, nHeight=77,
list_cod_prod=list_cod_prod)
@ -180,17 +217,19 @@ class danfe(object):
self.newpage()
if cce_xml:
for xml in cce_xml:
self._generate_cce(cce_xml=xml, oXML=oXML)
self._generate_cce(cce_xml=xml, oXML=oXML, timezone=timezone)
self.newpage()
self.canvas.save()
def ide_emit(self, oXML=None):
def ide_emit(self, oXML=None, timezone=None):
elem_infNFe = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infNFe")
elem_protNFe = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}protNFe")
elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit")
elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
elem_evento = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infEvento")
cChave = elem_infNFe.attrib.get('Id')[3:]
barcode128 = code128.Code128(
@ -260,7 +299,8 @@ class danfe(object):
self.stringcenter(self.nLeft + 116.5 + nW_Rect, self.nlin + 19.5,
' '.join(chunks(cChave, 4))) # Chave
self.canvas.setFont('NimbusSanL-Regu', 8)
cDt, cHr = getdateUTC(tagtext(oNode=elem_protNFe, cTag='dhRecbto'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=elem_protNFe, cTag='dhRecbto'), timezone)
cProtocolo = tagtext(oNode=elem_protNFe, cTag='nProt')
cDt = cProtocolo + ' - ' + cDt + ' ' + cHr
nW_Rect = (self.width - self.nLeft - self.nRight - 110) / 2
@ -281,9 +321,9 @@ class danfe(object):
# Razão Social emitente
P = Paragraph(tagtext(oNode=elem_emit, cTag='xNome'), styleN)
w, h = P.wrap(55 * mm, 50 * mm)
w, h = P.wrap(55 * mm, 40 * mm)
P.drawOn(self.canvas, (self.nLeft + 30) * mm,
(self.height - self.nlin - 12) * mm)
(self.height - self.nlin - ((5*h + 12)/12)) * mm)
if self.logo:
img = get_image(self.logo, width=2 * cm)
@ -318,9 +358,18 @@ class danfe(object):
self.string(self.nLeft + 65, 449, 'SEM VALOR FISCAL')
self.canvas.restoreState()
# Cancelado
if tagtext(oNode=elem_evento, cTag='cStat') == '135':
self.canvas.saveState()
self.canvas.rotate(45)
self.canvas.setFont('NimbusSanL-Bold', 60)
self.canvas.setFillColorRGB(1, 0.2, 0.2)
self.string(self.nLeft + 80, 275, 'CANCELADO')
self.canvas.restoreState()
self.nlin += 48
def destinatario(self, oXML=None):
def destinatario(self, oXML=None, timezone=None):
elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
elem_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
nMr = self.width - self.nRight
@ -364,9 +413,11 @@ class danfe(object):
else:
cnpj_cpf = format_cnpj_cpf(tagtext(oNode=elem_dest, cTag='CPF'))
self.string(nMr - 69, self.nlin + 7.5, cnpj_cpf)
cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhEmi'))
cDt, cHr = getdateByTimezone(tagtext(oNode=elem_ide, cTag='dhEmi'),
timezone)
self.string(nMr - 24, self.nlin + 7.7, cDt + ' ' + cHr)
cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhSaiEnt'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=elem_ide, cTag='dhSaiEnt'), timezone)
self.string(nMr - 24, self.nlin + 14.3, cDt + ' ' + cHr) # Dt saída
cEnd = tagtext(oNode=elem_dest, cTag='xLgr') + ', ' + tagtext(
oNode=elem_dest, cTag='nro')
@ -386,7 +437,7 @@ class danfe(object):
self.nlin += 24 # Nr linhas ocupadas pelo bloco
def faturas(self, oXML=None):
def faturas(self, oXML=None, timezone=None):
nMr = self.width - self.nRight
@ -419,7 +470,8 @@ class danfe(object):
line_iter = iter(oXML[1:10]) # Salta elemt 1 e considera os próximos 9
for oXML_dup in line_iter:
cDt, cHr = getdateUTC(tagtext(oNode=oXML_dup, cTag='dVenc'))
cDt, cHr = getdateByTimezone(tagtext(oNode=oXML_dup, cTag='dVenc'),
timezone)
self.string(self.nLeft + nCol + 1, self.nlin + nLin,
tagtext(oNode=oXML_dup, cTag='nDup'))
self.string(self.nLeft + nCol + 17, self.nlin + nLin, cDt)
@ -747,7 +799,7 @@ obsCont[@xCampo='NomeVendedor']")
P.drawOn(self.canvas, (self.nLeft + 1) * mm, altura - h)
self.nlin += 36
def recibo_entrega(self, oXML=None):
def recibo_entrega(self, oXML=None, timezone=None):
el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
el_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total")
@ -779,7 +831,8 @@ obsCont[@xCampo='NomeVendedor']")
self.string(self.width - self.nRight - nW + 2, self.nlin + 14,
"SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie')))
cDt, cHr = getdateUTC(tagtext(oNode=el_ide, cTag='dhEmi'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=el_ide, cTag='dhEmi'), timezone)
cTotal = format_number(tagtext(oNode=el_total, cTag='vNF'))
cEnd = tagtext(oNode=el_dest, cTag='xNome') + ' - '
@ -846,7 +899,7 @@ obsCont[@xCampo='NomeVendedor']")
self.oPDF_IO.close()
fileObj.write(pdf_out)
def _generate_cce(self, cce_xml=None, oXML=None):
def _generate_cce(self, cce_xml=None, oXML=None, timezone=None):
self.canvas.setLineWidth(.2)
# labels
@ -885,8 +938,8 @@ obsCont[@xCampo='NomeVendedor']")
self.string(82, 24, cnpj)
chave_acesso = tagtext(oNode=elem_infNFe, cTag='chNFe')
self.string(82, 30, chave_acesso)
data_correcao = getdateUTC(tagtext(
oNode=elem_infNFe, cTag='dhEvento'))
data_correcao = getdateByTimezone(tagtext(
oNode=elem_infNFe, cTag='dhEvento'), timezone)
data_correcao = data_correcao[0] + " " + data_correcao[1]
self.string(82, 36, data_correcao)
cce_id = elem_infNFe.values()[0]

4
pytrustnfe/nfe/templates/RecepcaoEventoCarta.xml

@ -12,9 +12,9 @@
<nSeqEvento>{{ nSeqEvento }}</nSeqEvento>
<verEvento>1.00</verEvento>
<detEvento versao="1.00">
<descEvento>Carta de Correção</descEvento>
<descEvento>Carta de Correcao</descEvento>
<xCorrecao>{{ xCorrecao|normalize|escape }}</xCorrecao>
<xCondUso>A Carta de Correção é disciplinada pelo § 1º-A do art. 7º do Convênio S/N, de 15 de dezembro de 1970 e pode ser utilizada para regularização de erro ocorrido na emissão de documento fiscal, desde que o erro não esteja relacionado com: I - as variáveis que determinam o valor do imposto tais como: base de cálculo, alíquota, diferença de preço, quantidade, valor da operação ou da prestação; II - a correção de dados cadastrais que implique mudança do remetente ou do destinatário; III - a data de emissão ou de saída.</xCondUso>
<xCondUso>A Carta de Correcao e disciplinada pelo paragrafo 1o-A do art. 7o do Convenio S/N, de 15 de dezembro de 1970 e pode ser utilizada para regularizacao de erro ocorrido na emissao de documento fiscal, desde que o erro nao esteja relacionado com: I - as variaveis que determinam o valor do imposto tais como: base de calculo, aliquota, diferenca de preco, quantidade, valor da operacao ou da prestacao; II - a correcao de dados cadastrais que implique mudanca do remetente ou do destinatario; III - a data de emissao ou de saida.</xCondUso>
</detEvento>
</infEvento>
</evento>

82
pytrustnfe/nfse/bh/__init__.py

@ -0,0 +1,82 @@
# © 2018 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
from lxml import etree
from requests import Session
from zeep import Client
from zeep.transports import Transport
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.nfse.bh.assinatura import Assinatura
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
xml_send = render_xml(path, '%s.xml' % method, True, **kwargs)
reference = ''
if method == 'GerarNfse':
reference = 'rps:%s' % kwargs['rps']['numero']
ref_lote = 'lote%s' % kwargs['rps']['numero_lote']
elif method == 'CancelarNfse':
reference = 'Cancelamento_NF%s' % kwargs['cancelamento']['numero_nfse']
signer = Assinatura(certificado.pfx, certificado.password)
xml_send = signer.assina_xml(xml_send, reference)
xml_send = signer.assina_xml(etree.fromstring(xml_send), ref_lote)
return xml_send.encode('utf-8')
def _send(certificado, method, **kwargs):
base_url = ''
if kwargs['ambiente'] == 'producao':
base_url = 'https://bhissdigital.pbh.gov.br/bhiss-ws/nfse?wsdl'
else:
base_url = 'https://bhisshomologa.pbh.gov.br/bhiss-ws/nfse?wsdl'
xml_send = kwargs["xml"].decode('utf-8')
xml_cabecalho = '<?xml version="1.0" encoding="UTF-8"?>\
<cabecalho xmlns="http://www.abrasf.org.br/nfse.xsd" versao="1.00">\
<versaoDados>1.00</versaoDados></cabecalho>'
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
session = Session()
session.cert = (cert, key)
session.verify = False
transport = Transport(session=session)
client = Client(base_url, transport=transport)
response = client.service[method](xml_cabecalho, xml_send)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': str(xml_send),
'received_xml': str(response),
'object': obj
}
def xml_gerar_nfse(certificado, **kwargs):
return _render(certificado, 'GerarNfse', **kwargs)
def gerar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_gerar_nfse(certificado, **kwargs)
return _send(certificado, 'GerarNfse', **kwargs)
def xml_cancelar_nfse(certificado, **kwargs):
return _render(certificado, 'CancelarNfse', **kwargs)
def cancelar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_cancelar_nfse(certificado, **kwargs)
return _send(certificado, 'CancelarNfse', **kwargs)

44
pytrustnfe/nfse/bh/assinatura.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import signxml
from lxml import etree
from pytrustnfe.certificado import extract_cert_and_key_from_pfx
from signxml import XMLSigner
class Assinatura(object):
def __init__(self, arquivo, senha):
self.arquivo = arquivo
self.senha = senha
def assina_xml(self, xml_element, reference):
cert, key = extract_cert_and_key_from_pfx(self.arquivo, self.senha)
for element in xml_element.iter("*"):
if element.text is not None and not element.text.strip():
element.text = None
signer = XMLSigner(
method=signxml.methods.enveloped, signature_algorithm="rsa-sha1",
digest_algorithm='sha1',
c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
ns = {}
ns[None] = signer.namespaces['ds']
signer.namespaces = ns
ref_uri = ('#%s' % reference) if reference else None
signed_root = signer.sign(
xml_element, key=key.encode(), cert=cert.encode(),
reference_uri=ref_uri)
if reference:
element_signed = signed_root.find(".//*[@Id='%s']" % reference)
signature = signed_root.find(".//*[@URI='#%s']" % reference).getparent().getparent()
if element_signed is not None and signature is not None:
parent = element_signed.getparent()
parent.append(signature)
return etree.tostring(signed_root, encoding=str)

13
pytrustnfe/nfse/bh/templates/CancelarNfse.xml

@ -0,0 +1,13 @@
<CancelarNfseEnvio xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
<Pedido xmlns="http://www.abrasf.org.br/nfse.xsd">
<InfPedidoCancelamento Id="pedidoCancelamento_{{ cancelamento.numero_nfse }}">
<IdentificacaoNfse>
<Numero>{{ cancelamento.numero_nfse }}</Numero>
<Cnpj>{{ cancelamento.cnpj_prestador }}</Cnpj>
<InscricaoMunicipal>{{ cancelamento.inscricao_municipal }}</InscricaoMunicipal>
<CodigoMunicipio>{{ cancelamento.cidade }}</CodigoMunicipio>
</IdentificacaoNfse>
<CodigoCancelamento>1</CodigoCancelamento>
</InfPedidoCancelamento>
</Pedido>
</CancelarNfseEnvio>

11
pytrustnfe/nfse/bh/templates/GerarNfse.xml

@ -0,0 +1,11 @@
<GerarNfseEnvio xmlns="http://www.abrasf.org.br/nfse.xsd">
<LoteRps Id="lote{{ rps.numero_lote }}" versao="1.00">
<NumeroLote>{{ rps.numero_lote }}</NumeroLote>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
<QuantidadeRps>1</QuantidadeRps>
<ListaRps xmlns="http://www.abrasf.org.br/nfse.xsd">
{% include 'Rps.xml' %}
</ListaRps>
</LoteRps>
</GerarNfseEnvio>

91
pytrustnfe/nfse/bh/templates/Rps.xml

@ -0,0 +1,91 @@
<Rps>
<InfRps xmlns="http://www.abrasf.org.br/nfse.xsd" Id="rps:{{ rps.numero }}">
<IdentificacaoRps>
<Numero>{{ rps.numero }}</Numero>
<Serie>{{ rps.serie }}</Serie>
<Tipo>{{ rps.tipo_rps }}</Tipo>
</IdentificacaoRps>
<DataEmissao>{{ rps.data_emissao }}</DataEmissao>
<NaturezaOperacao>{{ rps.natureza_operacao }}</NaturezaOperacao>
<RegimeEspecialTributacao>{{ rps.regime_tributacao }}</RegimeEspecialTributacao>
<OptanteSimplesNacional>{{ rps.optante_simples }}</OptanteSimplesNacional>
<IncentivadorCultural>{{ rps.incentivador_cultural }}</IncentivadorCultural>
<Status>{{ rps.status }}</Status>
<RpsSubstituido>
<Numero>{{ rps.numero_substituido }}</Numero>
<Serie>{{ rps.serie_substituido }}</Serie>
<Tipo>{{ rps.tipo_substituido }}</Tipo>
</RpsSubstituido>
<Servico>
<Valores>
<ValorServicos>{{ rps.valor_servico }}</ValorServicos>
<ValorDeducoes>{{ rps.valor_deducao }}</ValorDeducoes>
<ValorPis>{{ rps.valor_pis }}</ValorPis>
<ValorCofins>{{ rps.valor_cofins }}</ValorCofins>
<ValorInss>{{ rps.valor_inss }}</ValorInss>
<ValorIr>{{ rps.valor_ir }}</ValorIr>
<ValorCsll>{{ rps.valor_csll }}</ValorCsll>
<IssRetido>{{ rps.iss_retido }}</IssRetido>
<ValorIss>{{ rps.valor_iss }}</ValorIss>
<ValorIssRetido>{{ rps.valor_iss_retido }}</ValorIssRetido>
<OutrasRetencoes>{{ rps.outras_retencoes }}</OutrasRetencoes>
<BaseCalculo>{{ rps.base_calculo }}</BaseCalculo>
<Aliquota>{{ rps.aliquota_issqn }}</Aliquota>
<ValorLiquidoNfse>{{ rps.valor_liquido_nfse }}</ValorLiquidoNfse>
<DescontoIncondicionado>{{ rps.desconto_incondicionado }}</DescontoIncondicionado>
<DescontoCondicionado>{{ rps.desconto_condicionado }}</DescontoCondicionado>
</Valores>
<ItemListaServico>{{ rps.codigo_servico }}</ItemListaServico>
<CodigoCnae>{{ rps.cnae_servico }}</CodigoCnae>
<CodigoTributacaoMunicipio>{{ rps.codigo_tributacao_municipio }}</CodigoTributacaoMunicipio>
<Discriminacao>{{ rps.descricao }}</Discriminacao>
<CodigoMunicipio>{{ rps.codigo_municipio }}</CodigoMunicipio>
</Servico>
<Prestador>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
</Prestador>
<Tomador>
<IdentificacaoTomador>
<CpfCnpj>
{% if rps.tomador.cnpj_cpf|length == 14 %}
<Cnpj>{{ rps.tomador.cnpj_cpf }}</Cnpj>
{% endif %}
{% if rps.tomador.cnpj_cpf|length == 11 %}
<Cpf>{{ rps.tomador.cnpj_cpf }}</Cpf>
{% endif %}
</CpfCnpj>
<InscricaoMunicipal>{{ rps.tomador.inscricao_municipal }}</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>{{ rps.tomador.razao_social }}</RazaoSocial>
<Endereco>
<Endereco>{{ rps.tomador.logradouro }}</Endereco>
<Numero>{{ rps.tomador.numero }}</Numero>
<Complemento>{{ rps.tomador.complemento }}</Complemento>
<Bairro>{{ rps.tomador.bairro }}</Bairro>
<CodigoMunicipio>{{ rps.tomador.cidade }}</CodigoMunicipio>
<Uf>{{ rps.tomador.uf }}</Uf>
<Cep>{{ rps.tomador.cep }}</Cep>
</Endereco>
<Contato>
<Telefone>{{ rps.tomador.telefone }}</Telefone>
<Email>{{ rps.tomador.email }}</Email>
</Contato>
</Tomador>
{% if rps.intermediario is defined -%}
<IntermediarioServico>
<RazaoSocial>{{ rps.intermediario.razao_social }}</RazaoSocial>
<CpfCnpj>
<Cnpj>{{ rps.intermediario.cnpj }}</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>{{ rps.intermediario.inscricao_municipal }}</InscricaoMunicipal>
</IntermediarioServico>
{% endif %}
{% if rps.construcao_civil is defined -%}
<ContrucaoCivil>
<CodigoObra>{{ rps.construcao_civil.codigo_obra }}</CodigoObra>
<Art>{{ rps.construcao_civil.art }}</Art>
</ContrucaoCivil>
{% endif %}
</InfRps>
</Rps>

3
pytrustnfe/nfse/floripa/templates/processar_nota.xml

@ -17,8 +17,9 @@
{% for item in rps.itens_servico -%}
<itemServico>
<aliquota>{{ item.aliquota }}</aliquota>
<baseCalculo>{{ item.base_calculo }}</baseCalculo>
<cst>{{ item.cst_servico }}</cst>
<descricaoServico>{{ item.descricao|normalize|escape }}</descricaoServico>
<descricaoServico>{{ item.name|normalize|escape }}</descricaoServico>
<idCNAE>{{ item.cnae }}</idCNAE>
<quantidade>{{ item.quantidade }}</quantidade>
<valorTotal>{{ item.valor_total }}</valorTotal>

35
pytrustnfe/nfse/ginfes/__init__.py

@ -3,9 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import suds
from requests import Session
from zeep import Client
from zeep.transports import Transport
from requests.packages.urllib3 import disable_warnings
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.client import get_authenticated_client
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.nfe.assinatura import Assinatura
@ -33,19 +36,21 @@ def _send(certificado, method, **kwargs):
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
client = get_authenticated_client(base_url, cert, key)
try:
xml_send = kwargs['xml']
header = '<ns2:cabecalho xmlns:ns2="http://www.ginfes.com.br/cabecalho_v03.xsd" versao="3"><versaoDados>3</versaoDados></ns2:cabecalho>' #noqa
response = getattr(client.service, method)(header, xml_send)
except suds.WebFault as e:
return {
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,
'object': None
}
response, obj = sanitize_response(response)
header = '<ns2:cabecalho xmlns:ns2="http://www.ginfes.com.br/cabecalho_v03.xsd" versao="3"><versaoDados>3</versaoDados></ns2:cabecalho>' #noqa
disable_warnings()
session = Session()
session.cert = (cert, key)
session.verify = False
transport = Transport(session=session)
client = Client(base_url, transport=transport)
xml_send = kwargs['xml']
response = client.service[method](header, xml_send)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response,

2
pytrustnfe/nfse/imperial/__init__.py

@ -27,7 +27,7 @@ def _send(certificado, method, **kwargs):
response = client.post_soap(soap, 'NFeaction/AWS_NFE.%s' % method)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'sent_xml': xml_send.decode(),
'received_xml': response.decode(),
'object': obj
}

14
pytrustnfe/utils.py

@ -13,6 +13,7 @@ class CabecalhoSoap(object):
def __init__(self, **kwargs):
self.versao = kwargs.pop('versao', '')
self.estado = kwargs.pop('estado', '')
self.method = kwargs.pop('method', '')
self.soap_action = kwargs.pop('soap_action', '')
@ -92,7 +93,18 @@ def gerar_nfeproc(envio, recibo):
nfe = _find_node(docEnvio, "NFe")
protocolo = _find_node(docRecibo, "protNFe")
if nfe is None or protocolo is None:
return ''
return b''
root.append(nfe)
root.append(protocolo)
return ET.tostring(root)
def gerar_nfeproc_cancel(nfe_proc, cancelamento):
docEnvio = ET.fromstring(nfe_proc)
docCancel = ET.fromstring(cancelamento)
ev_cancelamento = _find_node(docCancel, "retEvento")
if ev_cancelamento is None:
return b''
docEnvio.append(ev_cancelamento)
return ET.tostring(docEnvio)

2
requirements.txt

@ -14,3 +14,5 @@ xmlsec >= 1.3.3
reportlab
pytest
pytest-cov
pytz
zeep

8
setup.py

@ -2,7 +2,7 @@
from setuptools import setup, find_packages
VERSION = "0.9.12"
VERSION = "0.9.21"
setup(
@ -37,6 +37,7 @@ later (LGPLv2+)',
'nfse/imperial/templates/*xml',
'nfse/floripa/templates/*xml',
'nfse/carioca/templates/*xml',
'nfse/bh/templates/*xml',
'xml/schemas/*xsd',
]},
url='https://github.com/danimaribeiro/PyTrustNFe',
@ -45,11 +46,14 @@ later (LGPLv2+)',
long_description=open('README.md', 'r').read(),
install_requires=[
'Jinja2 >= 2.8',
'pyOpenSSL >= 16.0.0, < 18',
'signxml >= 2.4.0',
'lxml >= 3.5.0, < 5',
'suds-jurko >= 0.6',
'suds-jurko-requests >= 1.2',
'reportlab'
'reportlab',
'pytz',
'zeep',
],
tests_require=[
'pytest',

Loading…
Cancel
Save