From 38874665b9df092697d378f43a67f520101d8c2c Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Fri, 13 Apr 2018 21:21:43 -0300 Subject: [PATCH 1/2] =?UTF-8?q?WIP=20-=20Implementa=C3=A7=C3=A3o=20da=20no?= =?UTF-8?q?ta=20de=20BH,=20tentativa=20de=20usar=20o=20zeep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytrustnfe/nfse/bh/__init__.py | 78 +++++++++++++++++++++++ pytrustnfe/nfse/bh/templates/CancelarNfse.xml | 13 ++++ pytrustnfe/nfse/bh/templates/GerarNfse.xml | 11 ++++ pytrustnfe/nfse/bh/templates/Rps.xml | 91 +++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 pytrustnfe/nfse/bh/__init__.py create mode 100644 pytrustnfe/nfse/bh/templates/CancelarNfse.xml create mode 100644 pytrustnfe/nfse/bh/templates/GerarNfse.xml create mode 100644 pytrustnfe/nfse/bh/templates/Rps.xml diff --git a/pytrustnfe/nfse/bh/__init__.py b/pytrustnfe/nfse/bh/__init__.py new file mode 100644 index 0000000..4c9fd3f --- /dev/null +++ b/pytrustnfe/nfse/bh/__init__.py @@ -0,0 +1,78 @@ +# © 2018 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import os + +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.nfe.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 = 'r%s' % kwargs['rps']['numero'] + 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) + 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 = '' + + 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) + 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) diff --git a/pytrustnfe/nfse/bh/templates/CancelarNfse.xml b/pytrustnfe/nfse/bh/templates/CancelarNfse.xml new file mode 100644 index 0000000..137897e --- /dev/null +++ b/pytrustnfe/nfse/bh/templates/CancelarNfse.xml @@ -0,0 +1,13 @@ + + + + + {{ cancelamento.numero_nfse }} + {{ cancelamento.cnpj_prestador }} + {{ cancelamento.inscricao_municipal }} + {{ cancelamento.cidade }} + + 1 + + + diff --git a/pytrustnfe/nfse/bh/templates/GerarNfse.xml b/pytrustnfe/nfse/bh/templates/GerarNfse.xml new file mode 100644 index 0000000..c6ead33 --- /dev/null +++ b/pytrustnfe/nfse/bh/templates/GerarNfse.xml @@ -0,0 +1,11 @@ + + + {{ nfse.numero_lote }} + {{ nfse.cnpj_prestador }} + {{ nfse.inscricao_municipal }} + 1 + + {% include 'Rps.xml' %} + + + diff --git a/pytrustnfe/nfse/bh/templates/Rps.xml b/pytrustnfe/nfse/bh/templates/Rps.xml new file mode 100644 index 0000000..7b883af --- /dev/null +++ b/pytrustnfe/nfse/bh/templates/Rps.xml @@ -0,0 +1,91 @@ + + + + {{ rps.numero }} + {{ rps.serie }} + {{ rps.tipo_rps }} + + {{ rps.data_emissao }} + {{ rps.natureza_operacao }} + {{ rps.regime_tributacao }} + {{ rps.optante_simples }} + {{ rps.incentivador_cultural }} + {{ rps.status }} + + {{ rps.numero_substituido }} + {{ rps.serie_substituido }} + {{ rps.tipo_substituido }} + + + + {{ rps.valor_servico }} + {{ rps.valor_deducao }} + {{ rps.valor_pis }} + {{ rps.valor_cofins }} + {{ rps.valor_inss }} + {{ rps.valor_ir }} + {{ rps.valor_csll }} + {{ rps.iss_retido }} + {{ rps.valor_iss }} + {{ rps.valor_iss_retido }} + {{ rps.outras_retencoes }} + {{ rps.base_calculo }} + {{ rps.aliquota_issqn }} + {{ rps.valor_liquido_nfse }} + {{ rps.desconto_incondicionado }} + {{ rps.desconto_condicionado }} + + {{ rps.codigo_servico }} + {{ rps.cnae_servico }} + {{ rps.codigo_tributacao_municipio }} + {{ rps.descricao }} + {{ rps.codigo_municipio }} + + + {{ rps.prestador.cnpj }} + {{ rps.prestador.inscricao_municipal }} + + + + + {% if rps.tomador.cnpj_cpf|length == 14 %} + {{ rps.tomador.cnpj_cpf }} + {% endif %} + {% if rps.tomador.cnpj_cpf|length == 11 %} + {{ rps.tomador.cnpj_cpf }} + {% endif %} + + {{ rps.tomador.inscricao_municipal }} + + {{ rps.tomador.razao_social }} + + {{ rps.tomador.logradouro }} + {{ rps.tomador.numero }} + {{ rps.tomador.complemento }} + {{ rps.tomador.bairro }} + {{ rps.tomador.cidade }} + {{ rps.tomador.uf }} + {{ rps.tomador.cep }} + + + {{ rps.tomador.telefone }} + {{ rps.tomador.email }} + + + {% if rps.intermediario is defined -%} + + {{ rps.intermediario.razao_social }} + + {{ rps.intermediario.cnpj }} + + {{ rps.intermediario.inscricao_municipal }} + + {% endif %} + {% if rps.construcao_civil is defined -%} + + {{ rps.construcao_civil.codigo_obra }} + {{ rps.construcao_civil.art }} + + {% endif %} + + From 9a1f406c623d19a112c75c7fe12d0cad54164bda Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Sat, 14 Apr 2018 12:28:18 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Corre=C3=A7=C3=A3o=20no=20envio=20de=20NFe?= =?UTF-8?q?=20para=20o=20estado=20do=20Cear=C3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytrustnfe/Servidores.py | 8 ++++---- pytrustnfe/client.py | 9 +++++---- pytrustnfe/nfe/__init__.py | 26 ++++++++++++++------------ pytrustnfe/nfe/comunicacao.py | 4 ++-- pytrustnfe/utils.py | 1 + setup.py | 2 +- 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/pytrustnfe/Servidores.py b/pytrustnfe/Servidores.py index 0b3e28d..8a340fe 100644 --- a/pytrustnfe/Servidores.py +++ b/pytrustnfe/Servidores.py @@ -352,8 +352,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', @@ -363,8 +363,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', diff --git a/pytrustnfe/client.py b/pytrustnfe/client.py index 03adbd2..e6e67e3 100644 --- a/pytrustnfe/client.py +++ b/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 diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index bf87674..c903014 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/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) diff --git a/pytrustnfe/nfe/comunicacao.py b/pytrustnfe/nfe/comunicacao.py index df6ba53..34c550e 100644 --- a/pytrustnfe/nfe/comunicacao.py +++ b/pytrustnfe/nfe/comunicacao.py @@ -12,9 +12,9 @@ from ..xml import sanitize_response def _soap_xml(body, cabecalho): xml = '' xml += '' - xml += '' + xml += '' xml += '' + cabecalho.estado + '' + cabecalho.versao + '' - xml += '' + xml += '' xml += body xml += '' return xml.rstrip('\n') diff --git a/pytrustnfe/utils.py b/pytrustnfe/utils.py index c494496..b06fb6e 100644 --- a/pytrustnfe/utils.py +++ b/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', '') diff --git a/setup.py b/setup.py index 4f68dcf..8b6eabe 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = "0.9.18" +VERSION = "0.9.19" setup(