diff --git a/.gitignore b/.gitignore index cf2be46..11179d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Pycharm .idea/* +# VsCode +.vscode/* + # Apple OS X .DS_Store diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4894d86 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +graft tests +include AUTHORS PLANEJAMENTO LICENCE *.py *.sh diff --git a/pynfe/entidades/certificado.py b/pynfe/entidades/certificado.py index f2a560c..dc430b4 100644 --- a/pynfe/entidades/certificado.py +++ b/pynfe/entidades/certificado.py @@ -35,11 +35,21 @@ class CertificadoA1(Certificado): e retorna a string. Se caminho for True grava na pasta temporaria e retorna o caminho dos arquivos, senao retorna o objeto. Apos o uso devem ser excluidos com o metodo excluir.""" + try: + with open(self.caminho_arquivo, "rb") as cert_arquivo: + cert_conteudo = cert_arquivo.read() + except (PermissionError, FileNotFoundError) as exc: + raise Exception('Falha ao abrir arquivo do certificado digital A1. Verifique local e permissoes do arquivo.') from exc + except Exception as exc: + raise Exception('Falha ao abrir arquivo do certificado digital A1. Causa desconhecida.') from exc + # Carrega o arquivo .pfx, erro pode ocorrer se a senha estiver errada ou formato invalido. try: - pkcs12 = crypto.load_pkcs12(open(self.caminho_arquivo, "rb").read(), senha) - except Exception as e: - raise Exception('Falha ao carregar certificado digital A1. Verifique local e senha.') + pkcs12 = crypto.load_pkcs12(cert_conteudo, senha) + except crypto.Error as exc: + raise Exception('Falha ao carregar certificado digital A1. Verifique a senha do certificado.') from exc + except Exception as exc: + raise Exception('Falha ao carregar certificado digital A1. Causa desconhecida.') from exc if caminho: cert = crypto.dump_certificate(crypto.FILETYPE_PEM, pkcs12.get_certificate()) diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index 0036160..4edd2e2 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -145,6 +145,9 @@ class NotaFiscal(Entidade): # - Local de entrega diferente do destinatario (Sim/Nao) local_entrega_diferente_destinatario = False + # - Autorizados a baixar XML (lista 1 para * / ManyToManyField) + autorizados_baixar_xml = None + # - Produtos e Servicos (lista 1 para * / ManyToManyField) produtos_e_servicos = None @@ -349,6 +352,7 @@ class NotaFiscal(Entidade): processos_referenciados = None def __init__(self, *args, **kwargs): + self.autorizados_baixar_xml = [] self.notas_fiscais_referenciadas = [] self.produtos_e_servicos = [] self.transporte_volumes = [] @@ -362,6 +366,11 @@ class NotaFiscal(Entidade): def __str__(self): return ' '.join([str(self.modelo), self.serie, self.numero_nf]) + def adicionar_autorizados_baixar_xml(self, **kwargs): + obj = AutorizadosBaixarXML(**kwargs) + self.autorizados_baixar_xml.append(obj) + return obj + def adicionar_nota_fiscal_referenciada(self, **kwargs): u"""Adiciona uma instancia de Nota Fisca referenciada""" obj = NotaFiscalReferenciada(**kwargs) @@ -589,6 +598,28 @@ class NotaFiscalProduto(Entidade): # - Produto especifico (seleciona de lista) - NF_PRODUTOS_ESPECIFICOS produto_especifico = str() + # Grupo de informações de Combustível + # Código de produto da ANP + cProdANP = str() + + # Descrição do produto conforme ANP + descANP = str() + + # Percentual de Gás derivado do Petróleo + pGLP = Decimal() + + # Percentual de gás natural nacional + pGNn = Decimal() + + # Percentual do gás natural importado + pGNi = Decimal() + + # Valor de Partida (apenas para GLP) + vPart = Decimal() + + # Sigla da UF de consumo – (OBS: Deve ser a Sigla e não o Código da UF) + UFCons = str() + # - Tributos # - ICMS # - Situacao tributaria (obrigatorio - seleciona de lista) - ICMS_TIPOS_TRIBUTACAO @@ -1026,3 +1057,6 @@ class NotaFiscalResponsavelTecnico(Entidade): email = str() fone = str() csrt = str() + +class AutorizadosBaixarXML(Entidade): + CPFCNPJ = str() diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index 7950c65..5b41e18 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -230,6 +230,19 @@ class SerializacaoXML(Serializacao): else: return raiz + def _serializar_autorizados_baixar_xml(self, autorizados_baixar_xml, tag_raiz='autXML', retorna_string=True): + raiz = etree.Element(tag_raiz) + + if len(so_numeros(autorizados_baixar_xml.CPFCNPJ)) == 11: + etree.SubElement(raiz, 'CPF').text = so_numeros(autorizados_baixar_xml.CPFCNPJ) + else: + etree.SubElement(raiz, 'CNPJ').text = so_numeros(autorizados_baixar_xml.CPFCNPJ) + + if retorna_string: + return etree.tostring(raiz, encoding="unicode", pretty_print=True) + else: + return raiz + def _serializar_produto_servico(self, produto_servico, modelo, tag_raiz='det', retorna_string=True): raiz = etree.Element(tag_raiz) @@ -248,7 +261,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(prod, 'CFOP').text = produto_servico.cfop etree.SubElement(prod, 'uCom').text = produto_servico.unidade_comercial etree.SubElement(prod, 'qCom').text = str(produto_servico.quantidade_comercial or 0) - etree.SubElement(prod, 'vUnCom').text = str('{:.4f}').format(produto_servico.valor_unitario_comercial or 0) + etree.SubElement(prod, 'vUnCom').text = str('{:.10f}').format(produto_servico.valor_unitario_comercial or 0) """ Código Especificador da Substituição Tributária – CEST, que estabelece a sistemática de uniformização e identificação das mercadorias e bens passíveis de sujeição aos regimes de substituição tributária e de antecipação de recolhimento do ICMS. """ #if produto_servico.cest: @@ -257,7 +270,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(prod, 'cEANTrib').text = produto_servico.ean_tributavel etree.SubElement(prod, 'uTrib').text = produto_servico.unidade_tributavel etree.SubElement(prod, 'qTrib').text = str(produto_servico.quantidade_tributavel) - etree.SubElement(prod, 'vUnTrib').text = '{:.4f}'.format(produto_servico.valor_unitario_tributavel or 0) + etree.SubElement(prod, 'vUnTrib').text = '{:.10f}'.format(produto_servico.valor_unitario_tributavel or 0) # frete if produto_servico.total_frete: @@ -286,6 +299,17 @@ class SerializacaoXML(Serializacao): if produto_servico.numero_item: etree.SubElement(prod, 'nItemPed').text = str(produto_servico.numero_item) + # Combustível + if produto_servico.cProdANP: + combustivel = etree.SubElement(prod, 'comb') + etree.SubElement(combustivel, 'cProdANP').text = str(produto_servico.cProdANP) + etree.SubElement(combustivel, 'descANP').text = str(produto_servico.descANP) + etree.SubElement(combustivel, 'pGLP').text = '{:.4f}'.format(produto_servico.pGLP or 0) + etree.SubElement(combustivel, 'pGNn').text = '{:.4f}'.format(produto_servico.pGNn or 0) + etree.SubElement(combustivel, 'pGNi').text = '{:.4f}'.format(produto_servico.pGNi or 0) + etree.SubElement(combustivel, 'vPart').text = '{:.2f}'.format(produto_servico.vPart or 0) + etree.SubElement(combustivel, 'UFCons').text = str(produto_servico.UFCons) + # Imposto imposto = etree.SubElement(raiz, 'imposto') @@ -319,6 +343,10 @@ class SerializacaoXML(Serializacao): icms_item = etree.SubElement(icms, 'ICMSSN'+produto_servico.icms_modalidade) etree.SubElement(icms_item, 'orig').text = str(produto_servico.icms_origem) etree.SubElement(icms_item, 'CSOSN').text = produto_servico.icms_csosn + elif produto_servico.icms_modalidade in ['40', '41', '50']: + icms_item = etree.SubElement(icms, 'ICMS40') + etree.SubElement(icms_item, 'orig').text = str(produto_servico.icms_origem) + etree.SubElement(icms_item, 'CST').text = str(produto_servico.icms_modalidade) elif produto_servico.icms_modalidade == '51': icms_item = etree.SubElement(icms, 'ICMS'+produto_servico.icms_modalidade) etree.SubElement(icms_item, 'orig').text = str(produto_servico.icms_origem) @@ -362,6 +390,50 @@ class SerializacaoXML(Serializacao): etree.SubElement(icms_item, 'vBCFCP').text = '{:.2f}'.format(produto_servico.fcp_base_calculo or 0) # Base de calculo FCP etree.SubElement(icms_item, 'pFCP').text = '{:.2f}'.format(produto_servico.fcp_percentual or 0) # Percentual FCP etree.SubElement(icms_item, 'vFCP').text = '{:.2f}'.format(produto_servico.fcp_valor or 0) # Valor Fundo Combate a Pobreza + # 30=Isenta / não tributada e com cobrança do ICMS por substituição tributária + elif produto_servico.icms_modalidade == '30': + etree.SubElement(icms_item, 'modBCST').text = str(produto_servico.icms_st_modalidade_determinacao_bc) + etree.SubElement(icms_item, 'pMVAST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_adicional or 0) # Percentual da margem de valor Adicionado do ICMS ST + etree.SubElement(icms_item, 'pRedBCST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_reducao_bc or 0) # APercentual da Redução de BC do ICMS ST + etree.SubElement(icms_item, 'vBCST').text = '{:.2f}'.format(produto_servico.icms_st_valor_base_calculo or 0) + etree.SubElement(icms_item, 'pICMSST').text = '{:.2f}'.format(produto_servico.icms_st_aliquota or 0) + etree.SubElement(icms_item, 'vICMSST').text = '{:.2f}'.format(produto_servico.icms_st_valor or 0) + if produto_servico.icms_desonerado > 0: + etree.SubElement(icms_item, 'vICMSDeson').text = '{:.2f}'.format(produto_servico.icms_desonerado or 0) # Valor do ICMS Desonerado + etree.SubElement(icms_item, 'motDesICMS').text = str(produto_servico.icms_motivo_desoneracao) + # 70=Com redução da BC e cobrança do ICMS por substituição tributária + elif produto_servico.icms_modalidade == '70': + etree.SubElement(icms_item, 'modBC').text = str(produto_servico.icms_modalidade_determinacao_bc) + etree.SubElement(icms_item, 'pRedBC').text = '{:.2f}'.format(produto_servico.icms_percentual_reducao_bc or 0) # Percentual da Redução de BC + etree.SubElement(icms_item, 'vBC').text = '{:.2f}'.format(produto_servico.icms_valor_base_calculo or 0) # Valor da BC do ICMS + etree.SubElement(icms_item, 'pICMS').text = '{:.2f}'.format(produto_servico.icms_aliquota or 0) # Alíquota do imposto + etree.SubElement(icms_item, 'vICMS').text = '{:.2f}'.format(produto_servico.icms_valor or 0) # Valor do ICMS + + etree.SubElement(icms_item, 'modBCST').text = str(produto_servico.icms_st_modalidade_determinacao_bc) + etree.SubElement(icms_item, 'pMVAST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_adicional or 0) # Percentual da margem de valor Adicionado do ICMS ST + etree.SubElement(icms_item, 'pRedBCST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_reducao_bc or 0) # APercentual da Redução de BC do ICMS ST + etree.SubElement(icms_item, 'vBCST').text = '{:.2f}'.format(produto_servico.icms_st_valor_base_calculo or 0) + etree.SubElement(icms_item, 'pICMSST').text = '{:.2f}'.format(produto_servico.icms_st_aliquota or 0) + etree.SubElement(icms_item, 'vICMSST').text = '{:.2f}'.format(produto_servico.icms_st_valor or 0) + + if produto_servico.icms_desonerado > 0: + etree.SubElement(icms_item, 'vICMSDeson').text = '{:.2f}'.format(produto_servico.icms_desonerado or 0) # Valor do ICMS Desonerado + etree.SubElement(icms_item, 'motDesICMS').text = str(produto_servico.icms_motivo_desoneracao) + # 90=Outras + elif produto_servico.icms_modalidade == '90': + etree.SubElement(icms_item, 'vBC').text = '{:.2f}'.format(produto_servico.icms_valor_base_calculo or 0) # Valor da BC do ICMS + etree.SubElement(icms_item, 'pRedBC').text = '{:.2f}'.format(produto_servico.icms_percentual_reducao_bc or 0) # Percentual da Redução de BC + etree.SubElement(icms_item, 'pICMS').text = '{:.2f}'.format(produto_servico.icms_aliquota or 0) # Alíquota do imposto + etree.SubElement(icms_item, 'vICMS').text = '{:.2f}'.format(produto_servico.icms_valor or 0) # Valor do ICMS + + if (produto_servico.icms_st_valor_base_calculo > 0) and (produto_servico.icms_st_valor > 0): + etree.SubElement(icms_item, 'modBCST').text = str(produto_servico.icms_st_modalidade_determinacao_bc) + etree.SubElement(icms_item, 'pMVAST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_adicional or 0) # Percentual da margem de valor Adicionado do ICMS ST + etree.SubElement(icms_item, 'pRedBCST').text = '{:.2f}'.format(produto_servico.icms_st_percentual_reducao_bc or 0) # APercentual da Redução de BC do ICMS ST + etree.SubElement(icms_item, 'vBCST').text = '{:.2f}'.format(produto_servico.icms_st_valor_base_calculo or 0) + etree.SubElement(icms_item, 'pICMSST').text = '{:.2f}'.format(produto_servico.icms_st_aliquota or 0) + etree.SubElement(icms_item, 'vICMSST').text = '{:.2f}'.format(produto_servico.icms_st_valor or 0) + # Impostos não implementados else: raise NotImplementedError @@ -397,7 +469,7 @@ class SerializacaoXML(Serializacao): pis_item = etree.SubElement(pis, 'PISQtde') etree.SubElement(pis_item, 'CST').text = produto_servico.pis_modalidade etree.SubElement(pis_item, 'qBCProd').text = '{:.4f}'.format(produto_servico.quantidade_comercial) - etree.SubElement(pis_item, 'vAliqProd').text = produto_servico.pis_aliquota_percentual + etree.SubElement(pis_item, 'vAliqProd').text = '{:.4f}'.format(produto_servico.pis_aliquota_percentual or 0) etree.SubElement(pis_item, 'vPIS').text = '{:.2f}'.format(produto_servico.pis_valor_base_calculo or 0) else: pis_item = etree.SubElement(pis, 'PISOutr') @@ -406,7 +478,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(pis_item, 'pPIS').text = '{:.2f}'.format(produto_servico.pis_aliquota_percentual or 0) if produto_servico.pis_modalidade is not '99': etree.SubElement(pis_item, 'qBCProd').text = '{:.4f}'.format(produto_servico.quantidade_comercial) - etree.SubElement(pis_item, 'vAliqProd').text = produto_servico.pis_aliquota_percentual + etree.SubElement(pis_item, 'vAliqProd').text = '{:.4f}'.format(produto_servico.pis_aliquota_percentual or 0) etree.SubElement(pis_item, 'vPIS').text = '{:.2f}'.format(produto_servico.pis_valor_base_calculo or 0) ## PISST @@ -441,7 +513,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(cofins_item, 'vBC').text = '{:.2f}'.format(produto_servico.cofins_valor_base_calculo or 0) etree.SubElement(cofins_item, 'pCOFINS').text = '{:.2f}'.format(produto_servico.cofins_aliquota_percentual or 0) if produto_servico.cofins_modalidade is not '99': - etree.SubElement(cofins_item, 'vAliqProd').text = '{:.2f}'.format(produto_servico.cofins_aliquota_percentual or 0) + etree.SubElement(cofins_item, 'vAliqProd').text = '{:.4f}'.format(produto_servico.cofins_aliquota_percentual or 0) etree.SubElement(cofins_item, 'vCOFINS').text = '{:.2f}'.format(produto_servico.cofins_valor or 0) ## COFINSST @@ -571,6 +643,10 @@ class SerializacaoXML(Serializacao): tag_raiz='entrega', )) + # Autorizados a baixar o XML + for num, item in enumerate(nota_fiscal.autorizados_baixar_xml): + raiz.append(self._serializar_autorizados_baixar_xml(item, retorna_string=False)) + # Itens for num, item in enumerate(nota_fiscal.produtos_e_servicos): det = self._serializar_produto_servico(item, modelo=nota_fiscal.modelo, retorna_string=False) diff --git a/pynfe/utils/webservices.py b/pynfe/utils/webservices.py index 357b0ea..e1ddd77 100644 --- a/pynfe/utils/webservices.py +++ b/pynfe/utils/webservices.py @@ -189,7 +189,7 @@ NFCE = { 'INUTILIZACAO': '', 'EVENTOS': '', 'QR': 'http://www4.fazenda.rj.gov.br/consultaNFCe/QRCode?', - 'URL': 'www.nfce.fazenda.rj.gov.br/consulta' + 'URL': 'www.fazenda.rj.gov.br/nfce/consulta' }, # Os Web Services de homologação da NFC-e 4.00 são: # https://homologacao.nfce.fazenda.sp.gov.br/ws/NFeAutorizacao4.asmx @@ -230,7 +230,10 @@ NFCE = { 'CHAVE': '', 'INUTILIZACAO': '', 'EVENTOS': '', - 'QR': '' + 'QR': 'sat.sef.sc.gov.br/nfce/consulta?p=', + 'HTTPS': 'https://', + 'HOMOLOGACAO': 'https://hom.', + 'URL': 'sat.sef.sc.gov.br/nfce/consulta' }, 'RS': { 'STATUS': 'sefazrs.rs.gov.br/ws/NfeStatusServico/NfeStatusServico4.asmx', @@ -245,13 +248,16 @@ NFCE = { 'HOMOLOGACAO': 'https://nfce-homologacao.' }, 'MS': { - 'STATUS': '', - 'AUTORIZACAO': '', - 'RECIBO': '', - 'CHAVE': '', - 'INUTILIZACAO': '', - 'EVENTOS': '', - 'QR': '' + 'STATUS': 'sefaz.ms.gov.br/ws/NFeStatusServico4?wsdl', + 'AUTORIZACAO': 'sefaz.ms.gov.br/ws/NFeAutorizacao4?wsdl', + 'RECIBO': 'sefaz.ms.gov.br/ws/NFeRetAutorizacao4?wsdl', + 'CHAVE': 'sefaz.ms.gov.br/ws/NFeConsultaProtocolo4?wsdl', + 'INUTILIZACAO': 'sefaz.ms.gov.br/ws/NFeInutilizacao4?wsdl', + 'EVENTOS': 'sefaz.ms.gov.br/ws/NFeRetAutorizacao4', + 'QR': 'http://www.dfe.ms.gov.br/nfce/qrcode', + 'URL': 'http://www.dfe.ms.gov.br/nfce/consulta', + 'HTTPS': 'https://nfce.', + 'HOMOLOGACAO': 'https://hom.nfce.' }, 'MT': { 'QR': 'sefaz.mt.gov.br/nfce/consultanfce?', @@ -355,7 +361,7 @@ NFE = { 'CHAVE': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeConsultaProtocolo4', 'INUTILIZACAO': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeInutilizacao4', 'EVENTOS': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeRecepcaoEvento4', - 'CADASTRO': 'nfe.fazenda.mg.gov.br/nfe2/services/cadconsultacadastro2', + 'CADASTRO': 'nfe.fazenda.mg.gov.br/nfe2/services/CadConsultaCadastro4', 'HTTPS': 'https://', 'HOMOLOGACAO': 'https://h' }, diff --git a/requirements-nfse.txt b/requirements-nfse.txt index 582da17..15f3243 100644 --- a/requirements-nfse.txt +++ b/requirements-nfse.txt @@ -1,3 +1,3 @@ # Opcional para NFS-e suds-jurko -pyxb=1.2.4 +pyxb==1.2.4 diff --git a/setup.py b/setup.py index 4a8f870..f8a3b9b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( author='TadaSoftware', author_email='tadasoftware@gmail.com', url='https://github.com/TadaSoftware', - packages=setuptools.find_packages(), + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), package_data={ 'pynfe': ['data/**/*.txt'], }, @@ -17,6 +17,12 @@ setuptools.setup( 'lxml', 'signxml', ], + extras_require={ + 'nfse': [ + 'suds-jurko', + 'pyxb==1.2.4', + ], + }, zip_safe=False, python_requires='>=3.6', )