Marcelo Salhab Brogliato 5 years ago
parent
commit
b02e2dba4b
  1. 3
      .gitignore
  2. 2
      MANIFEST.in
  3. 5
      README.md
  4. 16
      pynfe/entidades/certificado.py
  5. 19
      pynfe/entidades/notafiscal.py
  6. 2
      pynfe/entidades/produto.py
  7. 15
      pynfe/processamento/comunicacao.py
  8. 70
      pynfe/processamento/serializacao.py
  9. 12
      pynfe/utils/webservices.py
  10. 2
      requirements-nfse.txt
  11. 29
      setup.py

3
.gitignore

@ -1,6 +1,9 @@
# Pycharm
.idea/*
# VsCode
.vscode/*
# Apple OS X
.DS_Store

2
MANIFEST.in

@ -0,0 +1,2 @@
graft tests
include AUTHORS PLANEJAMENTO LICENCE *.py *.sh

5
README.md

@ -90,8 +90,3 @@ Documentação
-----------
- https://github.com/leotada/PyNFe/wiki
- http://pynfe.readthedocs.org/pt/latest/
backlog:
- renomeado metodo serializar_evento (_serializar_evento)
- removido metoco con.cancelar (utilizar con.evento)
- add evento carta de correção (con.evento)

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

19
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)
@ -468,7 +477,7 @@ class NotaFiscal(Entidade):
'uf': CODIGOS_ESTADOS[self.uf],
'ano': self.data_emissao.strftime('%y'),
'mes': self.data_emissao.strftime('%m'),
'cnpj': so_numeros(self.emitente.cnpj),
'cnpj': so_numeros(self.emitente.cnpj).zfill(14),
'mod': self.modelo,
'serie': str(self.serie).zfill(3),
'nNF': str(self.numero_nf).zfill(9),
@ -479,7 +488,7 @@ class NotaFiscal(Entidade):
'uf': CODIGOS_ESTADOS[self.uf],
'ano': self.data_emissao.strftime('%y'),
'mes': self.data_emissao.strftime('%m'),
'cnpj': so_numeros(self.emitente.cnpj),
'cnpj': so_numeros(self.emitente.cnpj).zfill(14),
'mod': self.modelo,
'serie': str(self.serie).zfill(3),
'nNF': str(self.numero_nf).zfill(9),
@ -550,6 +559,9 @@ class NotaFiscalProduto(Entidade):
# - Unidade Tributavel (obrigatorio)
unidade_tributavel = str()
# - cBenef
cbenef = str()
# - Quantidade Tributavel (obrigatorio)
quantidade_tributavel = Decimal()
@ -1023,3 +1035,6 @@ class NotaFiscalResponsavelTecnico(Entidade):
email = str()
fone = str()
csrt = str()
class AutorizadosBaixarXML(Entidade):
CPFCNPJ = str()

2
pynfe/entidades/produto.py

@ -34,6 +34,8 @@ class Produto(Entidade):
# Tabela https://www.confaz.fazenda.gov.br/anexo-i.pdf
cest = str()
cbenef = str()
# - Unid. Com.
unidade_comercial = str()

15
pynfe/processamento/comunicacao.py

@ -201,9 +201,11 @@ class ComunicacaoSefaz(Comunicacao):
:return:
"""
# UF que utilizam a SVRS - Sefaz Virtual do RS: Para serviço de Consulta Cadastro: AC, RN, PB, SC
lista_svrs = ['AC', 'RJ', 'RN', 'PB', 'SC', 'PI']
lista_svrs = ['AC', 'RN', 'PB', 'SC', 'PA']
# RS implementa um método diferente na consulta de cadastro
# usa o mesmo url para produção e homologação
# não tem url para NFCE
if self.uf.upper() == 'RS':
url = NFE['RS']['CADASTRO']
elif self.uf.upper() in lista_svrs:
@ -359,8 +361,7 @@ class ComunicacaoSefaz(Comunicacao):
raise Exception('Modelo não encontrado! Defina modelo="nfe" ou "nfce"')
# Estados que utilizam outros ambientes
else:
lista_svrs = ['AC', 'RJ', 'RN', 'PB', 'SC', 'SE', 'PI', 'DF', 'ES']
lista_svan = ['MA','PA']
lista_svrs = ['AC', 'AL', 'AP', 'DF', 'ES', 'PB', 'PI', 'RJ', 'RN', 'RO', 'RR', 'SC', 'SE', 'TO', 'PA']
if self.uf.upper() in lista_svrs:
if self._ambiente == 1:
ambiente = 'HTTPS'
@ -374,7 +375,9 @@ class ComunicacaoSefaz(Comunicacao):
self.url = NFCE['SVRS'][ambiente] + NFCE['SVRS'][consulta]
else:
raise Exception('Modelo não encontrado! Defina modelo="nfe" ou "nfce"')
elif self.uf.upper() in lista_svan:
# unico UF que utiliza SVAN ainda para NF-e
# SVRS para NFC-e
elif self.uf.upper() == 'MA':
if self._ambiente == 1:
ambiente = 'HTTPS'
else:
@ -384,9 +387,11 @@ class ComunicacaoSefaz(Comunicacao):
self.url = NFE['SVAN'][ambiente] + NFE['SVAN'][consulta]
elif modelo == 'nfce':
# nfce Ex: https://homologacao.nfce.fazenda.pr.gov.br/nfce/NFeStatusServico3
self.url = NFCE['SVAN'][ambiente] + NFCE['SVAN'][consulta]
self.url = NFCE['SVRS'][ambiente] + NFCE['SVRS'][consulta]
else:
raise Exception('Modelo não encontrado! Defina modelo="nfe" ou "nfce"')
else:
raise Exception(f"Url não encontrada para {modelo} e {consulta} {self.uf.upper()}")
return self.url
def _construir_xml_soap(self, metodo, dados, cabecalho=False):

70
pynfe/processamento/serializacao.py

@ -92,6 +92,9 @@ class SerializacaoXML(Serializacao):
raiz = etree.Element(tag_raiz)
# Dados do emitente
if len(so_numeros(emitente.cnpj)) == 11:
etree.SubElement(raiz, 'CPF').text = so_numeros(emitente.cnpj)
else:
etree.SubElement(raiz, 'CNPJ').text = so_numeros(emitente.cnpj)
etree.SubElement(raiz, 'xNome').text = emitente.razao_social
etree.SubElement(raiz, 'xFant').text = emitente.nome_fantasia
@ -213,6 +216,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)
@ -224,7 +240,10 @@ class SerializacaoXML(Serializacao):
etree.SubElement(prod, 'NCM').text = produto_servico.ncm
# Codificação opcional que detalha alguns NCM. Formato: duas letras maiúsculas e 4 algarismos.
# Se a mercadoria se enquadrar em mais de uma codificação, informar até 8 codificações principais.
#etree.SubElement(prod, 'NVE').text = ''
# etree.SubElement(prod, 'NVE').text = ''
# etree.SubElement(prod, 'CEST').text = produto_service.cest
if produto_servico.cbenef:
etree.SubElement(prod, 'cBenef').text = produto_servico.cbenef
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)
@ -239,8 +258,18 @@ class SerializacaoXML(Serializacao):
etree.SubElement(prod, 'qTrib').text = str(produto_servico.quantidade_tributavel)
etree.SubElement(prod, 'vUnTrib').text = '{:.4f}'.format(produto_servico.valor_unitario_tributavel or 0)
# frete
if produto_servico.total_frete:
etree.SubElement(prod, 'vFrete').text = '{:.2f}'.format(produto_servico.total_frete)
# seguro
if produto_servico.total_seguro:
etree.SubElement(prod, 'vSeg').text = '{:.2f}'.format(produto_servico.total_seguro)
# desconto
if produto_servico.desconto:
etree.SubElement(prod, 'vDesc').text = '{:.2f}'.format(produto_servico.desconto)
# outras despesas acessórias
if produto_servico.outras_despesas_acessorias:
etree.SubElement(prod, 'vOutro').text = '{:.2f}'.format(produto_servico.outras_despesas_acessorias)
""" Indica se valor do Item (vProd) entra no valor total da NF-e (vProd)
0=Valor do item (vProd) não compõe o valor total da NF-e
@ -289,6 +318,11 @@ 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 == '51':
icms_item = etree.SubElement(icms, 'ICMS'+produto_servico.icms_modalidade)
etree.SubElement(icms_item, 'orig').text = str(produto_servico.icms_origem)
etree.SubElement(icms_item, 'CST').text = '51'
etree.SubElement(icms_item, 'modBC').text = str(produto_servico.icms_modalidade_determinacao_bc)
else:
### OUTROS TIPOS DE ICMS (00,10,20)
icms_item = etree.SubElement(icms, 'ICMS'+produto_servico.icms_modalidade)
@ -331,12 +365,18 @@ class SerializacaoXML(Serializacao):
else:
raise NotImplementedError
# ipi
# ipi = etree.SubElement(imposto, 'IPI')
# etree.SubElement(ipi, 'clEnq') = produto_servico.ipi_classe_enquadramento # Preenchimento conforme Atos Normativos editados pela Receita Federal (Observação 2)
# ipint = etree.SubElement(ipi, 'IPINT')
# # 01=Entrada tributada com alíquota zero 02=Entrada isenta 03=Entrada não-tributada 04=Entrada imune 05=Entrada com suspensão
# # 51=Saída tributada com alíquota zero 52=Saída isenta 53=Saída não-tributada 54=Saída imune 55=Saída com suspensão
# etree.SubElement(ipint, 'CST') = produto_servico.ipi_codigo_enquadramento
ipint_lista = ('01','02','03','04','05','51','52','53','54','55')
if produto_servico.ipi_codigo_enquadramento in ipint_lista:
ipi = etree.SubElement(imposto, 'IPI')
# Preenchimento conforme Atos Normativos editados pela Receita Federal (Observação 2)
etree.SubElement(ipi, 'cEnq').text = produto_servico.ipi_classe_enquadramento
if produto_servico.ipi_classe_enquadramento == '':
etree.SubElement(ipi, 'cEnq').text = '999'
ipint = etree.SubElement(ipi, 'IPINT')
# 01=Entrada tributada com alíquota zero 02=Entrada isenta 03=Entrada não-tributada 04=Entrada imune 05=Entrada com suspensão
# 51=Saída tributada com alíquota zero 52=Saída isenta 53=Saída não-tributada 54=Saída imune 55=Saída com suspensão
etree.SubElement(ipint, 'CST').text = produto_servico.ipi_codigo_enquadramento
# apenas nfe
if modelo == 55:
@ -356,7 +396,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')
@ -365,7 +405,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
@ -400,7 +440,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
@ -531,6 +571,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)
@ -674,8 +718,10 @@ class SerializacaoXML(Serializacao):
e = etree.SubElement(raiz, 'infEvento', Id=evento.identificador)
etree.SubElement(e, 'cOrgao').text = CODIGOS_ESTADOS[evento.uf.upper()]
etree.SubElement(e, 'tpAmb').text = str(self._ambiente)
etree.SubElement(e, 'CNPJ').text = evento.cnpj # Empresas somente terão CNPJ
#etree.SubElement(e, 'CPF').text = ''
if len(so_numeros(evento.cnpj)) == 11:
etree.SubElement(e, 'CPF').text = evento.cnpj
else:
etree.SubElement(e, 'CNPJ').text = evento.cnpj
etree.SubElement(e, 'chNFe').text = evento.chave
etree.SubElement(e, 'dhEvento').text = evento.data_emissao.strftime('%Y-%m-%dT%H:%M:%S') + tz
etree.SubElement(e, 'tpEvento').text = evento.tp_evento

12
pynfe/utils/webservices.py

@ -322,7 +322,7 @@ NFE = {
'CHAVE': 'sefaz.ce.gov.br/nfe4/services/NFeConsultaProtocolo4?WSDL',
'INUTILIZACAO': 'sefaz.ce.gov.br/nfe4/services/NFeInutilizacao4?WSDL',
'EVENTOS': 'sefaz.ce.gov.br/nfe4/services/NFeRecepcaoEvento4?WSDL',
'CADASTRO': 'nfe.sefaz.ce.gov.br/nfe4/services/CadConsultaCadastro4?wsdl',
'CADASTRO': 'sefaz.ce.gov.br/nfe4/services/CadConsultaCadastro4?wsdl',
'DOWNLOAD': 'sefaz.ce.gov.br/nfe2/services/NfeDownloadNF?wsdl',
'HTTPS': 'https://nfe.',
'HOMOLOGACAO': 'https://nfeh.'
@ -334,7 +334,7 @@ NFE = {
'CHAVE': 'sefaz.pe.gov.br/nfe-service/services/NFeConsultaProtocolo4',
'INUTILIZACAO': 'sefaz.pe.gov.br/nfe-service/services/NFeInutilizacao4',
'EVENTOS': 'sefaz.pe.gov.br/nfe-service/services/NFeRecepcaoEvento4',
# 'CADASTRO': 'sefaz.pe.gov.br/nfe-service/services/CadConsultaCadastro2',
'CADASTRO': 'sefaz.pe.gov.br/nfe-service/services/CadConsultaCadastro4?wsdl',
'HTTPS': 'https://nfe.',
'HOMOLOGACAO': 'https://nfehomolog.'
},
@ -353,10 +353,10 @@ NFE = {
'STATUS': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeStatusServico4',
'AUTORIZACAO': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeAutorizacao4',
'RECIBO': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeRetAutorizacao4',
'CHAVE': 'nfe.fazenda.mg.gov.br/nfe2/services/NFeConsulta4',
'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'
},
@ -390,7 +390,7 @@ NFE = {
'CHAVE': 'sefazrs.rs.gov.br/ws/NfeConsulta/NfeConsulta4.asmx',
'INUTILIZACAO': 'sefazrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx',
'EVENTOS': 'sefazrs.rs.gov.br/ws/recepcaoevento/recepcaoevento4.asmx',
'CADASTRO': 'cad.sefazrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro2.asmx',
'CADASTRO': 'https://cad.sefazrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro4.asmx',
'DOWNLOAD': 'sefazrs.rs.gov.br/ws/nfeDownloadNF/nfeDownloadNF.asmx',
'DESTINADAS': 'sefazrs.rs.gov.br/ws/nfeConsultaDest/nfeConsultaDest.asmx',
'HTTPS': 'https://nfe.',
@ -447,7 +447,7 @@ NFE = {
'CHAVE': 'svrs.rs.gov.br/ws/NfeConsulta/NfeConsulta4.asmx',
'INUTILIZACAO': 'svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx',
'EVENTOS': 'svrs.rs.gov.br/ws/recepcaoevento/recepcaoevento4.asmx',
'CADASTRO': 'https://cad.svrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro2.asmx',
'CADASTRO': 'https://cad.svrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro4.asmx',
'HTTPS': 'https://nfe.',
'HOMOLOGACAO': 'https://nfe-homologacao.'
},

2
requirements-nfse.txt

@ -1,3 +1,3 @@
# Opcional para NFS-e
suds-jurko
pyxb=1.2.4
pyxb==1.2.4

29
setup.py

@ -1,19 +1,28 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
try: # for pip >= 10
from pip._internal.req import parse_requirements as parse
except ImportError: # for pip <= 9.0.3
from pip.req import parse_requirements as parse
import setuptools
requirements = lambda f: [str(i.req) for i in parse(f, session=False)]
setup(
setuptools.setup(
name='PyNFe',
version='0.4',
packages=find_packages(),
author='TadaSoftware',
author_email='tadasoftware@gmail.com',
url='https://github.com/TadaSoftware',
packages=setuptools.find_packages(exclude=['tests', 'tests.*']),
package_data={
'pynfe': ['data/**/*.txt', 'data/**/*.xsd', 'danfe/**/*.ttf'],
},
install_requires=requirements('requirements.txt'),
install_requires=[
'pyopenssl',
'requests',
'lxml',
'signxml',
],
extras_require={
'nfse': [
'suds-jurko',
'pyxb==1.2.4',
],
},
zip_safe=False,
python_requires='>=3.6',
)
Loading…
Cancel
Save