diff --git a/pynfe/entidades/__init__.py b/pynfe/entidades/__init__.py index 43fa430..a9c865a 100644 --- a/pynfe/entidades/__init__.py +++ b/pynfe/entidades/__init__.py @@ -4,5 +4,6 @@ from cliente import Cliente from transportadora import Transportadora from notafiscal import NotaFiscal from lotes import LoteNotaFiscal -from fontes_dados import FonteDados +from fonte_dados import _fonte_dados +from certificado import CertificadoA1 diff --git a/pynfe/entidades/fontes_dados.py b/pynfe/entidades/fontes_dados.py deleted file mode 100644 index 8db5ddf..0000000 --- a/pynfe/entidades/fontes_dados.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -from pynfe.excecoes import NenhumObjetoEncontrado, MuitosObjetosEncontrados - -class FonteDados(object): - u"""Classe responsável por ser o repositório dos objetos em memória e que - pode ser extendida para persistir esses objetos. Também tem a função de - memorizar os objetos redundantes como um só e assim otimizar o desempenho.""" - - _objetos = None - - def __init__(self, objetos=None): - # Inicializa variável que armazena os objetos contidos na Fonte de Dados - if objetos: - self._objetos = objetos - else: - self._objetos = [] - - def carregar_objetos(self, **kwargs): - u"""Método responsavel por retornar os objetos que casem com os atributos - informados no argumento **kwargs (argumentos nomeados). - - Um argumento especial é o '_classe', que representa a classe da entidade - desejada. - - FIXME: Este algoritimo pode ser melhorado pra fazer pesquisas melhores, - mas por enquanto vamos nos focar no processo em geral para só depois nos - preocupar com otimizações e desempenho.""" - - # Função de filtro - def filtrar(obj): - ret = True - - for k,v in kwargs.items(): - # Filtra pela classe e pelos atributos - ret = (k == '_classe' and isinstance(obj, v)) or\ - (k != '_classe' and getattr(obj, k, None) == v) - - if not ret: - break - - return ret - - # Filtra a lista de objetos - lista = filter(filtrar, self._objetos) - - return lista - - def adicionar_objeto(self, _objeto): - u"""Método responsável por adicionar o(s) objeto(s) informado(s) ao - repositorio de objetos da fonte de dados.""" - - from base import Entidade - - # Adiciona _objeto como objeto - if isinstance(_objeto, Entidade): - self._objetos.append(_objeto) - - # Adiciona _objeto como lista - elif isinstance(_objeto, (list, tuple)): - self._objetos += _objeto - - else: - raise Exception('Objeto informado e invalido!') - - def remover_objeto(self, _objeto=None, **kwargs): - u"""Método responsavel por remover os objetos que casem com os atributos - informados no argumento **kwargs (argumentos nomeados). - - Um argumento especial é o '_classe', que representa a classe da entidade - desejada. - - Outro argumetno especial é o '_objeto', que representa o objeto a ser - removido. Caso o argumento _objeto seja uma lista de objetos, eles serão - removidos também.""" - - from base import Entidade - - lista = None - - # Remove objetos - if not _objeto: - lista = self.carregar_objetos(**kwargs) - - # Remove _objeto como objeto - elif isinstance(_objeto, Entidade): - lista = [_objeto] - - # Remove _objeto como objeto - elif isinstance(_objeto, (list, tuple)): - lista = _objeto - - else: - raise Exception('Objeto informado e invalido!') - - # Efetiva a remoção - for obj in lista: - self._objetos.remove(obj) - - def obter_objeto(self, **kwargs): - u"""Faz a ponte para o método 'carregar_objetos' mas obriga o retorno de - apenas um objeto, levantando exceção se nenhum for encontrado ou se forem - encontrados mais de um.""" - - lista = self.carregar_objetos(**kwargs) - - if len(lista) == 0: - raise NenhumObjetoEncontrado('Nenhum objeto foi encontrado!') - elif len(lista) > 1: - raise MuitosObjetosEncontrados('Muitos objetos foram encontrados!') - - return lista[0] - - def obter_lista(self, **kwargs): - u"""Método de proxy, que somente repassa a chamada ao metodo 'carregar_objetos'""" - return self.carregar_objetos(**kwargs) - - def contar_objetos(self, **kwargs): - u"""Método que repassa a chamada ao metodo 'carregar_objetos' mas retorna - somente a quantidade de objetos encontrados.""" - - if kwargs: - return len(self.carregar_objetos(**kwargs)) - else: - return len(self._objetos) - -# Instancia da fonte de dados default -_fonte_dados = FonteDados() - diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index 45d2be9..42c48fe 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -291,7 +291,7 @@ class NotaFiscal(Entidade): super(NotaFiscal, self).__init__(*args, **kwargs) def __str__(self): - return ' '.join([self.modelo, self.serie, self.numero_nf]) + return ' '.join([str(self.modelo), self.serie, self.numero_nf]) def adicionar_nota_fiscal_referenciada(self, **kwargs): u"""Adiciona uma instancia de Nota Fisca referenciada""" diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index fc6928c..22040c3 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -64,32 +64,24 @@ class Serializacao(object): raise Exception('Metodo nao implementado') class SerializacaoXML(Serializacao): - def exportar(self, destino, **kwargs): + def exportar(self, destino=None, retorna_string=False, **kwargs): """Gera o(s) arquivo(s) de Nofa Fiscal eletronica no padrao oficial da SEFAZ e Receita Federal, para ser(em) enviado(s) para o webservice ou para ser(em) armazenado(s) em cache local.""" + # No raiz do XML de saida + raiz = etree.Element('NFe', xmlns="http://www.portalfiscal.inf.br/nfe") + # Carrega lista de Notas Fiscais notas_fiscais = self._fonte_dados.obter_lista(_classe=NotaFiscal, **kwargs) - saida = [] - - # Dados do emitente - saida.append(self._serializar_emitente(self._obter_emitente_de_notas_fiscais(notas_fiscais))) - - # Certificado Digital? XXX - - # Clientes - #saida.append(self._serializar_clientes(**kwargs)) - - # Transportadoras - #saida.append(self._serializar_transportadoras(**kwargs)) - - # Produtos - #saida.append(self._serializar_produtos(**kwargs)) + for nf in notas_fiscais: + raiz.append(self._serializar_notas_fiscal(nf, retorna_string=False)) - # Lote de Notas Fiscais - #saida.append(self._serializar_notas_fiscais(**kwargs)) + if retorna_string: + return etree.tostring(raiz, pretty_print=True) + else: + return raiz def importar(self, origem): """Cria as instancias do PyNFe a partir de arquivos XML no formato padrao da @@ -97,16 +89,6 @@ class SerializacaoXML(Serializacao): raise Exception('Metodo nao implementado') - def _obter_emitente_de_notas_fiscais(self, notas_fiscais): - lista = list(set([nf.emitente for nf in notas_fiscais if nf.emitente])) - - if len(lista) == 0: - raise NenhumObjetoEncontrado('Nenhum objeto foi encontrado!') - elif len(lista) > 1: - raise MuitosObjetosEncontrados('Muitos objetos foram encontrados!') - - return lista[0] - def _serializar_emitente(self, emitente, tag_raiz='emit', retorna_string=True): raiz = etree.Element(tag_raiz) @@ -335,7 +317,10 @@ class SerializacaoXML(Serializacao): etree.SubElement(transp, 'modFrete').text = str(nota_fiscal.transporte_modalidade_frete) # Transportadora - transp.append(self._serializar_transportadora(nota_fiscal.transporte_transportadora, retorna_string=False)) + transp.append(self._serializar_transportadora( + nota_fiscal.transporte_transportadora, + retorna_string=False, + )) # Veículo veiculo = etree.SubElement(transp, 'veicTransp') diff --git a/tests/01-basico.txt b/tests/01-basico.txt index 0aff2bb..8b6aaca 100644 --- a/tests/01-basico.txt +++ b/tests/01-basico.txt @@ -41,24 +41,26 @@ modelo: --------------------------------------------------------------------- - -------------------------------------------------------------------------- - | PROCESSAMENTO | - -------------------------------------------------------------------------- - | | - | ------------------- -------------- -------------------------------- | - | | SerializacaoXML | | Assinatura | | Comunicacao | | - | ------------------- -------------- -------------------------------- | - | | exportar() | | assinar() | | transmitir() | | - | | importar() | -------------- | cancelar() | | - | ------------------- | situacao_nfe() | | - | ------------- | status_servico() | | - | -------------- | Validacao | | consultar_cadastro() | | - | | DANFE | ------------- | inutilizar_faixa_numeracao() | | - | -------------- | validar() | -------------------------------- | - | | imprimir() | ------------- | - | -------------- | - | | - -------------------------------------------------------------------------- + ---------------------------------------------------------------------------- + | PROCESSAMENTO | + ---------------------------------------------------------------------------- + | | + | ------------------- -------------- -------------------------------- | + | | SerializacaoXML | | Assinatura | | Comunicacao | | + | ------------------- -------------- -------------------------------- | + | | exportar() | | assinar() | | transmitir() | | + | | importar() | -------------- | cancelar() | | + | ------------------- | situacao_nfe() | | + | ---------------------- | status_servico() | | + | -------------- | Validacao | | consultar_cadastro() | | + | | DANFE | ---------------------- | inutilizar_faixa_numeracao() | | + | -------------- | validar_arquivos() | -------------------------------- | + | | imprimir() | | validar_xml() | | + | -------------- | validar_etree() | | + | | validar_objetos() | | + | ---------------------- | + | | + ---------------------------------------------------------------------------- Os pacotes da biblioteca sao: @@ -75,7 +77,8 @@ Os pacotes da biblioteca sao: >>> set([attr for attr in dir(entidades) if not attr.startswith('__')]) == set([ ... 'Cliente', 'Emitente', 'LoteNotaFiscal', 'NotaFiscal', 'Produto', ... 'Transportadora', 'base', 'cliente', 'emitente', 'lotes', 'notafiscal', - ... 'produto', 'transportadora', 'fontes_dados', 'FonteDados']) + ... 'produto', 'transportadora', 'fonte_dados', '_fonte_dados','certificado', + ... 'CertificadoA1']) True - processamento (contem todas as funcionalidades de processamento da diff --git a/tests/02-modelo-00-definicoes-gerais.txt b/tests/02-modelo-00-definicoes-gerais.txt index b2d02b8..03b2e4c 100644 --- a/tests/02-modelo-00-definicoes-gerais.txt +++ b/tests/02-modelo-00-definicoes-gerais.txt @@ -9,7 +9,7 @@ Todas as entidades devem referenciar uma Fonte de Dados, de forma a evitar redundancia de dados (com o objetivo de melhorar o desempenho e possibilitar o uso de cache de persistencia de dados serializados). - >>> from pynfe.entidades import FonteDados + >>> from pynfe.entidades.fonte_dados import FonteDados >>> fonte_dados = FonteDados() Nao eh da funcao do PyNFe efetuar a persistencia dos objetos, mas a classe diff --git a/tests/02-modelo-06-certificado.txt b/tests/02-modelo-06-certificado.txt index 411be3d..4e8d862 100644 --- a/tests/02-modelo-06-certificado.txt +++ b/tests/02-modelo-06-certificado.txt @@ -1,9 +1,24 @@ MODELO - CERTIFICADO DIGITAL ============================ -Modelo das entidades e como elas se relacionam. + >>> from pynfe.entidades.certificado import Certificado, CertificadoA1 -Nenhum dos campos deve permitir acentos e/ou cedilhas. +A classe 'Certificado' eh abstrata e eh responsavel por definir o modelo +padrao para instancias de certificados. + + >>> try: + ... Certificado() + ... except Exception, e: + ... print e.message + Esta classe nao pode ser instanciada diretamente! + +Num primeiro momento teremos apenas a classe 'CertificadoA1', responsavel +pelo certificado digital do tipo A1, mas num segundo momento teremos +outros modelos de certificados (como o A3, por exemplo) que vao herdar da +classe 'Certificado'. + + >>> issubclass(CertificadoA1, Certificado) + True - Caminho do arquivo - Windows diff --git a/tests/03-processamento-00-definicoes-gerais.txt b/tests/03-processamento-00-definicoes-gerais.txt index 5893456..6c63264 100644 --- a/tests/03-processamento-00-definicoes-gerais.txt +++ b/tests/03-processamento-00-definicoes-gerais.txt @@ -7,11 +7,6 @@ Validar NF-e - Efetuar validacoes dos XSD no(s) XML(s) gerado(s) - Validar nota_fiscal.dv_codigo_numerico_aleatorio (ver pagina 85 do manual de integracao) -Assinar NF-e ------------- - -- Na hora de assinar, selecionar um Certificado Digital - Transmitir NF-e (ou lote de NF-e`s) ----------------------------------- diff --git a/tests/03-processamento-01-serializacao-xml.txt b/tests/03-processamento-01-serializacao-xml.txt index 9b54069..6665c44 100644 --- a/tests/03-processamento-01-serializacao-xml.txt +++ b/tests/03-processamento-01-serializacao-xml.txt @@ -492,6 +492,11 @@ Serializando por partes +Exportacao completa +------------------- + + >>> xml = serializador.exportar(modelo=55) + - Quando gerados me lote, apenas o primeiro arquivo deve ter o cabecalho padrao do XML 1.0 - diff --git a/tests/saida/README b/tests/saida/README new file mode 100644 index 0000000..90c4ec9 --- /dev/null +++ b/tests/saida/README @@ -0,0 +1,2 @@ +Esta pasta provavelmente vai permanecer vazia, e foi criada apenas para armazenar alguns +arquivos de saida da exportacao do serializador.