From dc54d482557f8dceb8dd77f625315aaf8b9f1ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Wed, 13 Jan 2010 18:14:53 -0200 Subject: [PATCH] Trabalhando no serializador para XML --- pynfe/data/MunIBGE/README | 1 + pynfe/entidades/base.py | 7 +- pynfe/entidades/cliente.py | 4 +- pynfe/entidades/emitente.py | 3 +- pynfe/entidades/fontes_dados.py | 2 + pynfe/entidades/notafiscal.py | 6 +- pynfe/processamento/serializacao.py | 105 ++++++++++++++++---- pynfe/utils/__init__.py | 65 +++++++++++++ pynfe/utils/flags.py | 1 + tests/01-basico.txt | 2 +- tests/03-processamento-01-serializacao-xml.txt | 128 ++++++++++++++++++++++++- 11 files changed, 291 insertions(+), 33 deletions(-) create mode 100644 pynfe/data/MunIBGE/README diff --git a/pynfe/data/MunIBGE/README b/pynfe/data/MunIBGE/README new file mode 100644 index 0000000..10a9c67 --- /dev/null +++ b/pynfe/data/MunIBGE/README @@ -0,0 +1 @@ +Origem e Creditos: set de componentes ACBr (http://acbr.sourceforge.net/drupal/?q=node/36) diff --git a/pynfe/entidades/base.py b/pynfe/entidades/base.py index a06058e..f56c745 100644 --- a/pynfe/entidades/base.py +++ b/pynfe/entidades/base.py @@ -10,8 +10,11 @@ class Entidade(object): setattr(self, k, v) # Adiciona o objeto à fonte de dados informada - if self._fonte_dados: - self._fonte_dados.adicionar_objeto(self) + if not self._fonte_dados: + from fontes_dados import _fonte_dados + self._fonte_dados = _fonte_dados + + self._fonte_dados.adicionar_objeto(self) def __repr__(self): return '<%s %s>'%(self.__class__.__name__, str(self)) diff --git a/pynfe/entidades/cliente.py b/pynfe/entidades/cliente.py index c70c03e..f18bb60 100644 --- a/pynfe/entidades/cliente.py +++ b/pynfe/entidades/cliente.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from base import Entidade -from pynfe.utils.flags import TIPOS_DOCUMENTO +from pynfe.utils.flags import TIPOS_DOCUMENTO, CODIGO_BRASIL class Cliente(Entidade): # Dados do Cliente @@ -39,7 +39,7 @@ class Cliente(Entidade): endereco_cep = str() # - Pais (seleciona de lista) - endereco_pais = 'BRASIL' + endereco_pais = CODIGO_BRASIL # - UF (obrigatorio) endereco_uf = str() diff --git a/pynfe/entidades/emitente.py b/pynfe/entidades/emitente.py index 6d5ea61..933db98 100644 --- a/pynfe/entidades/emitente.py +++ b/pynfe/entidades/emitente.py @@ -1,4 +1,5 @@ from base import Entidade +from pynfe.utils.flags import CODIGO_BRASIL class Emitente(Entidade): # Dados do Emitente @@ -40,7 +41,7 @@ class Emitente(Entidade): endereco_cep = str() # - Pais (aceita somente Brasil) - endereco_pais = 'BRASIL' + endereco_pais = CODIGO_BRASIL # - UF (obrigatorio) endereco_uf = str() diff --git a/pynfe/entidades/fontes_dados.py b/pynfe/entidades/fontes_dados.py index bb6b159..8db5ddf 100644 --- a/pynfe/entidades/fontes_dados.py +++ b/pynfe/entidades/fontes_dados.py @@ -123,4 +123,6 @@ class FonteDados(object): 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 74d7c85..036695a 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -4,7 +4,7 @@ from pynfe.utils.flags import NF_STATUS, NF_TIPOS_DOCUMENTO, NF_TIPOS_IMPRESSAO_ NF_REFERENCIADA_TIPOS, NF_PRODUTOS_ESPECIFICOS, ICMS_TIPOS_TRIBUTACAO,\ ICMS_ORIGENS, ICMS_MODALIDADES, IPI_TIPOS_TRIBUTACAO, IPI_TIPOS_CALCULO,\ PIS_TIPOS_TRIBUTACAO, PIS_TIPOS_CALCULO, COFINS_TIPOS_TRIBUTACAO,\ - COFINS_TIPOS_CALCULO, MODALIDADES_FRETE, ORIGENS_PROCESSO + COFINS_TIPOS_CALCULO, MODALIDADES_FRETE, ORIGENS_PROCESSO, CODIGO_BRASIL from decimal import Decimal @@ -13,7 +13,7 @@ class NotaFiscal(Entidade): # Nota Fisca eletronica # - Modelo (formato: NN) - modelo = str() + modelo = int() # - Serie (obrigatorio - formato: NNN) serie = str() @@ -96,7 +96,7 @@ class NotaFiscal(Entidade): destinatario_endereco_cep = str() # - Pais (seleciona de lista) - destinatario_endereco_pais = 'BRASIL' + destinatario_endereco_pais = CODIGO_BRASIL # - UF (obrigatorio) destinatario_endereco_uf = str() diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index f13e443..3c92b1d 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- try: set except: @@ -26,6 +27,7 @@ except ImportError: from pynfe.entidades import Emitente, Cliente, Produto, Transportadora, NotaFiscal from pynfe.excecoes import NenhumObjetoEncontrado, MuitosObjetosEncontrados +from pynfe.utils import so_numeros, obter_municipio_por_codigo, obter_pais_por_codigo class Serializacao(object): """Classe abstrata responsavel por fornecer as funcionalidades basicas para @@ -40,12 +42,12 @@ class Serializacao(object): if cls == Serializacao: raise Exception('Esta classe nao pode ser instanciada diretamente!') else: - return cls(*args, **kwargs) + return super(Serializacao, cls).__new__(cls, *args, **kwargs) def __init__(self, fonte_dados): self._fonte_dados = fonte_dados - def exportar(self, **kwargs): + def exportar(self, destino, **kwargs): """Gera o(s) arquivo(s) de exportacao a partir da Nofa Fiscal eletronica ou lista delas.""" @@ -58,7 +60,7 @@ class Serializacao(object): raise Exception('Metodo nao implementado') class SerializacaoXML(Serializacao): - def exportar(self, **kwargs): + def exportar(self, destino, **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.""" @@ -74,19 +76,16 @@ class SerializacaoXML(Serializacao): # Certificado Digital? XXX # Clientes - saida.append(self._serializar_clientes(**kwargs)) + #saida.append(self._serializar_clientes(**kwargs)) # Transportadoras - saida.append(self._serializar_transportadoras(**kwargs)) + #saida.append(self._serializar_transportadoras(**kwargs)) # Produtos - saida.append(self._serializar_produtos(**kwargs)) + #saida.append(self._serializar_produtos(**kwargs)) # Lote de Notas Fiscais - saida.append(self._serializar_notas_fiscais(**kwargs)) - - # FIXME - return '\n'.join(saida) + #saida.append(self._serializar_notas_fiscais(**kwargs)) def importar(self, origem): """Cria as instancias do PyNFe a partir de arquivos XML no formato padrao da @@ -95,7 +94,7 @@ class SerializacaoXML(Serializacao): raise Exception('Metodo nao implementado') def _obter_emitente_de_notas_fiscais(self, notas_fiscais): - lista = set([nf.emitente for nf in notas_fiscais if nf.emitente]) + lista = list(set([nf.emitente for nf in notas_fiscais if nf.emitente])) if len(lista) == 0: raise NenhumObjetoEncontrado('Nenhum objeto foi encontrado!') @@ -104,18 +103,82 @@ class SerializacaoXML(Serializacao): return lista[0] - def _serializar_emitente(self, emitente): - return '' - - def _serializar_clientes(self, objetos): - return '' + def _serializar_emitente(self, emitente, tag_raiz='emit'): + raiz = etree.Element(tag_raiz) - def _serializar_transportadoras(self, objetos): - return '' - - def _serializar_produtos(self, objetos): + # Dados do emitente + 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 + etree.SubElement(raiz, 'IE').text = emitente.inscricao_estadual + + # Endereço + endereco = etree.SubElement(raiz, 'enderEmit') + etree.SubElement(endereco, 'xLgr').text = emitente.endereco_logradouro + etree.SubElement(endereco, 'nro').text = emitente.endereco_numero + etree.SubElement(endereco, 'xCpl').text = emitente.endereco_complemento + etree.SubElement(endereco, 'xBairro').text = emitente.endereco_bairro + etree.SubElement(endereco, 'cMun').text = emitente.endereco_municipio + etree.SubElement(endereco, 'xMun').text = obter_municipio_por_codigo( + emitente.endereco_municipio, emitente.endereco_uf, + ) + etree.SubElement(endereco, 'UF').text = emitente.endereco_uf + etree.SubElement(endereco, 'CEP').text = so_numeros(emitente.endereco_cep) + etree.SubElement(endereco, 'cPais').text = emitente.endereco_pais + etree.SubElement(endereco, 'xPais').text = obter_pais_por_codigo(emitente.endereco_pais) + etree.SubElement(endereco, 'fone').text = emitente.endereco_telefone + + return etree.tostring(raiz, pretty_print=True) + + def _serializar_cliente(self, cliente, tag_raiz='dest'): + raiz = etree.Element(tag_raiz) + + # Dados do cliente + etree.SubElement(raiz, cliente.tipo_documento).text = so_numeros(cliente.numero_documento) + etree.SubElement(raiz, 'xNome').text = cliente.razao_social + etree.SubElement(raiz, 'IE').text = cliente.inscricao_estadual + + # Endereço + endereco = etree.SubElement(raiz, 'enderDest') + etree.SubElement(endereco, 'xLgr').text = cliente.endereco_logradouro + etree.SubElement(endereco, 'nro').text = cliente.endereco_numero + etree.SubElement(endereco, 'xCpl').text = cliente.endereco_complemento + etree.SubElement(endereco, 'xBairro').text = cliente.endereco_bairro + etree.SubElement(endereco, 'cMun').text = cliente.endereco_municipio + etree.SubElement(endereco, 'xMun').text = obter_municipio_por_codigo( + cliente.endereco_municipio, cliente.endereco_uf, + ) + etree.SubElement(endereco, 'UF').text = cliente.endereco_uf + etree.SubElement(endereco, 'CEP').text = so_numeros(cliente.endereco_cep) + etree.SubElement(endereco, 'cPais').text = cliente.endereco_pais + etree.SubElement(endereco, 'xPais').text = obter_pais_por_codigo(cliente.endereco_pais) + etree.SubElement(endereco, 'fone').text = cliente.endereco_telefone + + return etree.tostring(raiz, pretty_print=True) + + def _serializar_transportadora(self, transportadora, tag_raiz='transporta'): + raiz = etree.Element(tag_raiz) + + # Dados da transportadora + etree.SubElement(raiz, transportadora.tipo_documento).text = so_numeros(transportadora.numero_documento) + etree.SubElement(raiz, 'xNome').text = transportadora.razao_social + etree.SubElement(raiz, 'IE').text = transportadora.inscricao_estadual + + # Endereço + etree.SubElement(raiz, 'xEnder').text = transportadora.endereco_logradouro + etree.SubElement(raiz, 'cMun').text = transportadora.endereco_municipio + etree.SubElement(raiz, 'xMun').text = obter_municipio_por_codigo( + transportadora.endereco_municipio, transportadora.endereco_uf, + ) + etree.SubElement(raiz, 'UF').text = transportadora.endereco_uf + + return etree.tostring(raiz, pretty_print=True) + + def _serializar_produto(self, produto, tag_raiz='prod'): + # TODO return '' - def _serializar_notas_fiscais(self, objetos): + def _serializar_notas_fiscal(self, notas_fiscal, tag_raiz='infNFe'): + # TODO return '' diff --git a/pynfe/utils/__init__.py b/pynfe/utils/__init__.py index fd59ff0..dcbd31f 100644 --- a/pynfe/utils/__init__.py +++ b/pynfe/utils/__init__.py @@ -1,2 +1,67 @@ +import os + import flags +from geraldo.utils import memoize + +@memoize +def so_numeros(texto): + """Retorna o texto informado mas somente os numeros""" + return ''.join(filter(lambda c: ord(c) in range(48,58), texto)) + +@memoize +def obter_pais_por_codigo(codigo): + # TODO + if codigo == '1058': + return 'Brasil' + +ARQUIVOS_ESTADOS = { + 'RO': 'MunIBGE-UF11.txt', + 'AC': 'MunIBGE-UF12.txt', + 'AM': 'MunIBGE-UF13.txt', + 'RR': 'MunIBGE-UF14.txt', + 'PA': 'MunIBGE-UF15.txt', + 'AP': 'MunIBGE-UF16.txt', + 'TO': 'MunIBGE-UF17.txt', + 'MA': 'MunIBGE-UF21.txt', + 'PI': 'MunIBGE-UF22.txt', + 'CE': 'MunIBGE-UF23.txt', + 'RN': 'MunIBGE-UF24.txt', + 'PB': 'MunIBGE-UF25.txt', + 'PE': 'MunIBGE-UF26.txt', + 'AL': 'MunIBGE-UF27.txt', + 'SE': 'MunIBGE-UF28.txt', + 'BA': 'MunIBGE-UF29.txt', + 'MG': 'MunIBGE-UF31.txt', + 'ES': 'MunIBGE-UF32.txt', + 'RJ': 'MunIBGE-UF33.txt', + 'SP': 'MunIBGE-UF35.txt', + 'PR': 'MunIBGE-UF41.txt', + 'SC': 'MunIBGE-UF42.txt', + 'RS': 'MunIBGE-UF43.txt', + 'MS': 'MunIBGE-UF50.txt', + 'MT': 'MunIBGE-UF51.txt', + 'GO': 'MunIBGE-UF52.txt', + 'DF': 'MunIBGE-UF53.txt', +} + +CAMINHO_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data') +CAMINHO_MUNICIPIOS = os.path.join(CAMINHO_DATA, 'MunIBGE') + +@memoize +def carregar_arquivo_municipios(uf): + caminho_arquivo = os.path.join(CAMINHO_MUNICIPIOS, ARQUIVOS_ESTADOS[uf.upper()]) + + fp = file(caminho_arquivo) + linhas = list(fp.readlines()) + fp.close() + + return dict([(linha[:7], linha[7:].strip()) for linha in linhas]) + +@memoize +def obter_municipio_por_codigo(codigo, uf): + # TODO: fazer UF ser opcional + municipios = carregar_arquivo_municipios(uf) + + return municipios[codigo] + diff --git a/pynfe/utils/flags.py b/pynfe/utils/flags.py index 11c4d94..15a7bab 100644 --- a/pynfe/utils/flags.py +++ b/pynfe/utils/flags.py @@ -147,5 +147,6 @@ ORIGENS_PROCESSO = ( 'Outros', ) +CODIGO_BRASIL = '1058' diff --git a/tests/01-basico.txt b/tests/01-basico.txt index e7e3b70..0aff2bb 100644 --- a/tests/01-basico.txt +++ b/tests/01-basico.txt @@ -66,7 +66,7 @@ Os pacotes da biblioteca sao: biblioteca, incluindo flags e funcoes genericas) >>> from pynfe import utils - >>> set([attr for attr in dir(utils) if not attr.startswith('__')]) == set(['flags']) + >>> set([attr for attr in dir(utils) if not attr.startswith('__')]) >= set(['flags']) True - entidades (contem todas as entidades da biblioteca) diff --git a/tests/03-processamento-01-serializacao-xml.txt b/tests/03-processamento-01-serializacao-xml.txt index 9bd76ea..0c0eed9 100644 --- a/tests/03-processamento-01-serializacao-xml.txt +++ b/tests/03-processamento-01-serializacao-xml.txt @@ -4,17 +4,139 @@ PROCESSAMENTO - SERIALIZACAO PARA XML Populando fonte de dados ------------------------ - >>> from pynfe.entidades import FonteDados - >>> fonte_dados = FonteDados() + >>> import datetime + >>> from pynfe.entidades import Emitente, Cliente, NotaFiscal, Produto,\ + ... Transportadora + >>> from pynfe.entidades.fontes_dados import _fonte_dados + >>> from pynfe.utils.flags import CODIGO_BRASIL -Inicia uma NF e demais dependentes +Popula dependentes da NF + >>> emitente = Emitente( + ... cnpj='12.345.678/0001-90', + ... razao_social='Tarsila Calcados Ltda.', + ... nome_fantasia='Tarsila Calcados Ltda.', + ... inscricao_estadual='123456789012', + ... endereco_logradouro='Rua 10', + ... endereco_numero='15', + ... endereco_complemento='qd 17, lt 10', + ... endereco_bairro='Setor Oeste', + ... endereco_municipio='5208806', # Goiania + ... endereco_uf='GO', + ... endereco_cep='75370-000', + ... endereco_telefone='6242421212', + ... ) + >>> cliente = Cliente( + ... razao_social='Jose Felipe da Silva', + ... tipo_documento='CPF', + ... numero_documento='123.456.789-01', + ... inscricao_estadual='9876543210', + ... endereco_logradouro='AV DAS ROSAS', + ... endereco_numero='1777', + ... endereco_complemento='10 ANDAR', + ... endereco_bairro='PARQUE FONTES', + ... endereco_municipio='3304557', # Rio de Janeiro + ... endereco_uf='RJ', + ... endereco_pais=CODIGO_BRASIL, + ... endereco_cep='23950-000', + ... endereco_telefone='2132011234', + ... ) + + >>> produto1 = Produto(codigo=1, descricao='Tenis Adidas Cinza') + >>> produto2 = Produto(codigo=2, descricao='Sapato Ferracini Preto') + + >>> transportadora = Transportadora( + ... razao_social='WS Cargas S/A', + ... tipo_documento='CNPJ', + ... numero_documento='123.123.123/0001-12', + ... inscricao_estadual='171999999119', + ... endereco_logradouro='Rua Central 100 - Fundos - Distrito Industrial', + ... endereco_municipio='3304557', # Rio de Janeiro + ... endereco_uf='RJ', + ... ) + +Instancia a NF + + >>> nota_fiscal = NotaFiscal( + ... emitente=emitente, + ... transporte_transportadora=transportadora, + ... modelo=55, + ... serie='1', + ... numero_nf='1', + ... data_emissao=datetime.date.today(), + ... natureza_operacao='Venda no Varejo', + ... ) + + >>> _fonte_dados.contar_objetos() + 6 Gerar arquivos XML ------------------ + >>> import os + >>> CUR_DIR = '.' + >>> CAMINHO_SAIDA = os.path.join(CUR_DIR, 'tests', 'saida') + >>> from pynfe.processamento.serializacao import SerializacaoXML + >>> serializador = SerializacaoXML(_fonte_dados) + +Serializando por partes + + >>> print serializador._serializar_emitente(emitente) + + 12345678000190 + Tarsila Calcados Ltda. + Tarsila Calcados Ltda. + 123456789012 + + Rua 10 + 15 + qd 17, lt 10 + Setor Oeste + 5208806 + Goianira + GO + 75370000 + 1058 + Brasil + 6242421212 + + + + + >>> print serializador._serializar_cliente(cliente) + + 12345678901 + Jose Felipe da Silva + 9876543210 + + AV DAS ROSAS + 1777 + 10 ANDAR + PARQUE FONTES + 3304557 + Rio de Janeiro + RJ + 23950000 + 1058 + Brasil + 2132011234 + + + + + >>> print serializador._serializar_transportadora(transportadora) + + 123123123000112 + WS Cargas S/A + 171999999119 + Rua Central 100 - Fundos - Distrito Industrial + 3304557 + Rio de Janeiro + RJ + + - Gera os arquivos XML a partir dos dados das instancias da NF-e - Quando gerados me lote, apenas o primeiro arquivo deve ter o cabecalho