diff --git a/pytrustnfe/nfe/templates/NfeAutorizacao.xml b/pytrustnfe/nfe/templates/NfeAutorizacao.xml index d2d82e4..f7c6395 100644 --- a/pytrustnfe/nfe/templates/NfeAutorizacao.xml +++ b/pytrustnfe/nfe/templates/NfeAutorizacao.xml @@ -608,7 +608,7 @@ {{ imposto.ICMSUFDest.vBCUFDest }} {{ imposto.ICMSUFDest.pFCPUFDest }} - {{ imposto.ICMSUFDest.pICMSInter }} + {{ imposto.ICMSUFDest.pICMSUFDest }} {{ imposto.ICMSUFDest.pICMSInter }} {{ imposto.ICMSUFDest.pICMSInterPart }} {{ imposto.ICMSUFDest.vFCPUFDest }} diff --git a/pytrustnfe/nfse/mga/__init__.py b/pytrustnfe/nfse/mga/__init__.py new file mode 100644 index 0000000..a3d207f --- /dev/null +++ b/pytrustnfe/nfse/mga/__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.mga.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://isse.maringa.gov.br/ws/?wsdl' + else: + base_url = 'https://isseteste.maringa.gov.br/ws/?wsdl' + + xml_send = kwargs["xml"].decode('utf-8') + xml_cabecalho = '\ + \ + 1.00' + + 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) diff --git a/pytrustnfe/nfse/mga/assinatura.py b/pytrustnfe/nfse/mga/assinatura.py new file mode 100644 index 0000000..1831379 --- /dev/null +++ b/pytrustnfe/nfse/mga/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) diff --git a/pytrustnfe/nfse/mga/templates/CancelarNfse.xml b/pytrustnfe/nfse/mga/templates/CancelarNfse.xml new file mode 100644 index 0000000..137897e --- /dev/null +++ b/pytrustnfe/nfse/mga/templates/CancelarNfse.xml @@ -0,0 +1,13 @@ + + + + + {{ cancelamento.numero_nfse }} + {{ cancelamento.cnpj_prestador }} + {{ cancelamento.inscricao_municipal }} + {{ cancelamento.cidade }} + + 1 + + + diff --git a/pytrustnfe/nfse/mga/templates/GerarNfse.xml b/pytrustnfe/nfse/mga/templates/GerarNfse.xml new file mode 100644 index 0000000..6b35d4d --- /dev/null +++ b/pytrustnfe/nfse/mga/templates/GerarNfse.xml @@ -0,0 +1,11 @@ + + + {{ rps.numero_lote }} + {{ rps.prestador.cnpj }} + {{ rps.prestador.inscricao_municipal }} + 1 + + {% include 'Rps.xml' %} + + + diff --git a/pytrustnfe/nfse/mga/templates/Rps.xml b/pytrustnfe/nfse/mga/templates/Rps.xml new file mode 100644 index 0000000..0dfd0cf --- /dev/null +++ b/pytrustnfe/nfse/mga/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 %} + + diff --git a/setup.py b/setup.py index a9a30f7..9337653 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = "0.9.23" +VERSION = "0.9.24" setup( @@ -38,6 +38,7 @@ later (LGPLv2+)', 'nfse/floripa/templates/*xml', 'nfse/carioca/templates/*xml', 'nfse/bh/templates/*xml', + 'nfse/mga/templates/*xml', 'xml/schemas/*xsd', ]}, url='https://github.com/danimaribeiro/PyTrustNFe',