From bbdf541ecc9a8ca7838e15cb65b523fbebe982ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Thu, 14 Jan 2010 15:33:21 -0200 Subject: [PATCH] Trabalhando na serializacao da Nota Fiscal --- pynfe/entidades/notafiscal.py | 91 ++++++++++++++++---------- pynfe/entidades/produto.py | 2 + pynfe/processamento/serializacao.py | 74 +++++++++++++++++++-- tests/02-modelo-05-notafiscal.txt | 67 ++++++++++++------- tests/03-processamento-01-serializacao-xml.txt | 91 +++++++++++++++++++------- 5 files changed, 241 insertions(+), 84 deletions(-) diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index c1f9e24..a64df18 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -9,7 +9,7 @@ from pynfe.utils.flags import NF_STATUS, NF_TIPOS_DOCUMENTO, NF_TIPOS_IMPRESSAO_ 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, CODIGO_BRASIL,\ - NF_PROCESSOS_EMISSAO, CODIGOS_ESTADOS + NF_PROCESSOS_EMISSAO, CODIGOS_ESTADOS, TIPOS_DOCUMENTO from pynfe.utils import so_numeros, memoize from decimal import Decimal @@ -97,33 +97,11 @@ class NotaFiscal(Entidade): # - Identificacao (seleciona de Clientes) destinatario_remetente = None - # - Endereco (ver se pode copiar do Cliente) - # - Logradouro (obrigatorio) - destinatario_endereco_logradouro = str() - - # - Numero (obrigatorio) - destinatario_endereco_numero = str() - - # - Complemento - destinatario_endereco_complemento = str() - - # - Bairro (obrigatorio) - destinatario_endereco_bairro = str() - - # - CEP - destinatario_endereco_cep = str() + # - Entrega (XXX sera possivel ter entrega e retirada ao mesmo tempo na NF?) + entrega = None - # - Pais (seleciona de lista) - destinatario_endereco_pais = CODIGO_BRASIL - - # - UF (obrigatorio) - destinatario_endereco_uf = str() - - # - Municipio (obrigatorio) - destinatario_endereco_municipio = str() - - # - Telefone - destinatario_endereco_telefone = str() + # - Retirada + retirada = None # - Local Retirada/Entrega # - Local de retirada diferente do emitente (Sim/Nao) @@ -317,27 +295,39 @@ class NotaFiscal(Entidade): def adicionar_nota_fiscal_referenciada(self, **kwargs): u"""Adiciona uma instancia de Nota Fisca referenciada""" - self.notas_fiscais_referenciadas.append(NotaFiscalReferenciada(**kwargs)) + obj = NotaFiscalReferenciada(**kwargs) + self.notas_fiscais_referenciadas.append(obj) + return obj def adicionar_produto_servico(self, **kwargs): u"""Adiciona uma instancia de Produto""" - self.produtos_e_servicos.append(NotaFiscalProduto(**kwargs)) + obj = NotaFiscalProduto(**kwargs) + self.produtos_e_servicos.append(obj) + return obj def adicionar_transporte_volume(self, **kwargs): u"""Adiciona uma instancia de Volume de Transporte""" - self.transporte_volumes.append(NotaFiscalTransporteVolume(**kwargs)) + obj = NotaFiscalTransporteVolume(**kwargs) + self.transporte_volumes.append(obj) + return obj def adicionar_duplicata(self, **kwargs): u"""Adiciona uma instancia de Duplicata""" - self.duplicatas.append(NotaFiscalCobrancaDuplicata(**kwargs)) + obj = NotaFiscalCobrancaDuplicata(**kwargs) + self.duplicatas.append(obj) + return obj def adicionar_observacao_contribuinte(self, **kwargs): u"""Adiciona uma instancia de Observacao do Contribuinte""" - self.observacoes_contribuinte.append(NotaFiscalObservacaoContribuinte(**kwargs)) + obj = NotaFiscalObservacaoContribuinte(**kwargs) + self.observacoes_contribuinte.append(obj) + return obj def adicionar_processo_referenciado(self, **kwargs): u"""Adiciona uma instancia de Processo Referenciado""" - self.processos_referenciados.append(NotaFiscalProcessoReferenciado(**kwargs)) + obj = NotaFiscalProcessoReferenciado(**kwargs) + self.processos_referenciados.append(obj) + return obj @property @memoize @@ -776,3 +766,38 @@ class NotaFiscalProcessoReferenciado(Entidade): # - Outros origem = str() +class NotaFiscalEntregaRetirada(Entidade): + # - Tipo de Documento (obrigatorio) - default CNPJ + tipo_documento = 'CNPJ' + + # - Numero do Documento (obrigatorio) + numero_documento = str() + + # - Endereco + # - Logradouro (obrigatorio) + endereco_logradouro = str() + + # - Numero (obrigatorio) + endereco_numero = str() + + # - Complemento + endereco_complemento = str() + + # - Bairro (obrigatorio) + endereco_bairro = str() + + # - CEP + endereco_cep = str() + + # - Pais (seleciona de lista) + endereco_pais = CODIGO_BRASIL + + # - UF (obrigatorio) + endereco_uf = str() + + # - Municipio (obrigatorio) + endereco_municipio = str() + + # - Telefone + endereco_telefone = str() + diff --git a/pynfe/entidades/produto.py b/pynfe/entidades/produto.py index 3f9a430..17a7bfe 100644 --- a/pynfe/entidades/produto.py +++ b/pynfe/entidades/produto.py @@ -5,6 +5,8 @@ from pynfe.utils.flags import ICMS_TIPOS_TRIBUTACAO, ICMS_ORIGENS, ICMS_MODALIDA from decimal import Decimal class Produto(Entidade): + """XXX: E provavel que esta entidade sera descartada.""" + # Dados do Produto # - Descricao (obrigatorio) descricao = str() diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index aea0d42..930e468 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -187,10 +187,53 @@ class SerializacaoXML(Serializacao): else: return raiz - def _serializar_produto(self, produto, tag_raiz='prod', retorna_string=True): - # Provavelmente nao vai ser feito desta forma, e sim como serializacao do produto - # na NF (NotaFiscalProduto) - return '' + def _serializar_entrega_retirada(self, entrega_retirada, tag_raiz='entrega', retorna_string=True): + raiz = etree.Element(tag_raiz) + + # Dados da entrega/retirada + etree.SubElement(raiz, entrega_retirada.tipo_documento).text = so_numeros(entrega_retirada.numero_documento) + + # Endereço + etree.SubElement(raiz, 'xLgr').text = entrega_retirada.endereco_logradouro + etree.SubElement(raiz, 'nro').text = entrega_retirada.endereco_numero + etree.SubElement(raiz, 'xCpl').text = entrega_retirada.endereco_complemento + etree.SubElement(raiz, 'xBairro').text = entrega_retirada.endereco_bairro + etree.SubElement(raiz, 'cMun').text = entrega_retirada.endereco_municipio + etree.SubElement(raiz, 'xMun').text = obter_municipio_por_codigo( + entrega_retirada.endereco_municipio, entrega_retirada.endereco_uf, + ) + etree.SubElement(raiz, 'UF').text = entrega_retirada.endereco_uf + + if retorna_string: + return etree.tostring(raiz, pretty_print=True) + else: + return raiz + + def _serializar_produto_servico(self, produto_servico, tag_raiz='det', retorna_string=True): + raiz = etree.Element(tag_raiz) + + # Produto + prod = etree.SubElement(raiz, 'prod') + etree.SubElement(prod, 'cProd').text = str(produto_servico.codigo) + etree.SubElement(prod, 'cEAN').text = produto_servico.ean + etree.SubElement(prod, 'xProd').text = produto_servico.descricao + etree.SubElement(prod, 'CFOP').text = produto_servico.cfop + etree.SubElement(prod, 'uCom').text = produto_servico.unidade_comercial + etree.SubElement(prod, 'qCom').text = '%f'%(produto_servico.quantidade_comercial or 0) + etree.SubElement(prod, 'vUnCom').text = '%f'%(produto_servico.valor_unitario_comercial or 0) + etree.SubElement(prod, 'vProd').text = '%f'%(produto_servico.valor_total_bruto or 0) + etree.SubElement(prod, 'cEANTrib').text = produto_servico.ean_tributavel + etree.SubElement(prod, 'uTrib').text = produto_servico.unidade_tributavel + etree.SubElement(prod, 'qTrib').text = '%f'%(produto_servico.quantidade_tributavel) + etree.SubElement(prod, 'vUnTrib').text = '%f'%(produto_servico.valor_unitario_tributavel) + + # Imposto + imposto = etree.SubElement(raiz, 'imposto') + + if retorna_string: + return etree.tostring(raiz, pretty_print=True) + else: + return raiz def _serializar_notas_fiscal(self, nota_fiscal, tag_raiz='infNFe', retorna_string=True): raiz = etree.Element(tag_raiz, versao="2.00") @@ -223,6 +266,29 @@ class SerializacaoXML(Serializacao): # Destinatário raiz.append(self._serializar_cliente(nota_fiscal.cliente, retorna_string=False)) + # Retirada + if nota_fiscal.retirada: + raiz.append(self._serializar_entrega_retirada( + nota_fiscal.retirada, + retorna_string=False, + tag_raiz='retirada', + )) + + # Entrega + if nota_fiscal.entrega: + raiz.append(self._serializar_entrega_retirada( + nota_fiscal.entrega, + retorna_string=False, + tag_raiz='entrega', + )) + + # Itens + for num, item in enumerate(nota_fiscal.produtos_e_servicos): + det = self._serializar_produto_servico(item, retorna_string=False) + det.attrib['nItem'] = str(num+1) + + raiz.append(det) + # Transporte transp = etree.SubElement(raiz, 'transp') transp.append(self._serializar_transportadora(nota_fiscal.transporte_transportadora, retorna_string=False)) diff --git a/tests/02-modelo-05-notafiscal.txt b/tests/02-modelo-05-notafiscal.txt index ca4b67b..a590739 100644 --- a/tests/02-modelo-05-notafiscal.txt +++ b/tests/02-modelo-05-notafiscal.txt @@ -10,7 +10,8 @@ Nenhum dos campos deve permitir acentos e/ou cedilhas. ... NotaFiscalProduto, NotaFiscalDeclaracaoImportacao,\ ... NotaFiscalDeclaracaoImportacaoAdicao, NotaFiscalTransporteVolume,\ ... NotaFiscalTransporteVolumeLacre, NotaFiscalCobrancaDuplicata,\ - ... NotaFiscalObservacaoContribuinte, NotaFiscalProcessoReferenciado + ... NotaFiscalObservacaoContribuinte, NotaFiscalProcessoReferenciado,\ + ... NotaFiscalEntregaRetirada - Dados da NF-e - Status (controle de estados da NF) @@ -208,61 +209,83 @@ Nenhum dos campos deve permitir acentos e/ou cedilhas. >>> hasattr(NotaFiscal, 'destinatario_remetente') True - - Endereco (ver se pode copiar do Cliente) +- Entrega/Retirada + - Local de retirada diferente do emitente (Sim/Nao) + + >>> hasattr(NotaFiscal, 'local_retirada_diferente_emitente') + True + + - Local de entrega diferente do destinatario (Sim/Nao) + + >>> hasattr(NotaFiscal, 'local_entrega_diferente_destinatario') + True + + - Entrega + + >>> hasattr(NotaFiscal, 'entrega') + True + + - Retirada + + >>> hasattr(NotaFiscal, 'retirada') + True + + - Campos de Entrega e Retirada + - Tipo de Documento (obrigatorio) - default CNPJ + - CNPJ + - CPF + + >>> hasattr(NotaFiscalEntregaRetirada, 'tipo_documento') + True + + - Numero do Documento (obrigatorio) + + >>> hasattr(NotaFiscalEntregaRetirada, 'numero_documento') + True + - Logradouro (obrigatorio) - >>> hasattr(NotaFiscal, 'destinatario_endereco_logradouro') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_logradouro') True - Numero (obrigatorio) - >>> hasattr(NotaFiscal, 'destinatario_endereco_numero') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_numero') True - Complemento - >>> hasattr(NotaFiscal, 'destinatario_endereco_complemento') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_complemento') True - Bairro (obrigatorio) - >>> hasattr(NotaFiscal, 'destinatario_endereco_bairro') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_bairro') True - CEP - >>> hasattr(NotaFiscal, 'destinatario_endereco_cep') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_cep') True - Pais (seleciona de lista) - >>> hasattr(NotaFiscal, 'destinatario_endereco_pais') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_pais') True - UF (obrigatorio) - >>> hasattr(NotaFiscal, 'destinatario_endereco_uf') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_uf') True - Municipio (obrigatorio) - >>> hasattr(NotaFiscal, 'destinatario_endereco_municipio') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_municipio') True - Telefone - >>> hasattr(NotaFiscal, 'destinatario_endereco_telefone') - True - - - Local Retirada/Entrega - - Local de retirada diferente do emitente (Sim/Nao) - - >>> hasattr(NotaFiscal, 'local_retirada_diferente_emitente') - True - - - Local de entrega diferente do destinatario (Sim/Nao) - - >>> hasattr(NotaFiscal, 'local_entrega_diferente_destinatario') + >>> hasattr(NotaFiscalEntregaRetirada, 'endereco_telefone') True - Produtos e Servicos (lista 1 para * / ManyToManyField) diff --git a/tests/03-processamento-01-serializacao-xml.txt b/tests/03-processamento-01-serializacao-xml.txt index 515e3af..398dd61 100644 --- a/tests/03-processamento-01-serializacao-xml.txt +++ b/tests/03-processamento-01-serializacao-xml.txt @@ -4,9 +4,9 @@ PROCESSAMENTO - SERIALIZACAO PARA XML Populando fonte de dados ------------------------ - >>> import datetime - >>> from pynfe.entidades import Emitente, Cliente, NotaFiscal, Produto,\ - ... Transportadora + >>> import datetime, decimal + >>> from pynfe.entidades import Emitente, Cliente, NotaFiscal, Transportadora + >>> from pynfe.entidades.notafiscal import NotaFiscalEntregaRetirada >>> from pynfe.entidades.fontes_dados import _fonte_dados >>> from pynfe.utils.flags import CODIGO_BRASIL @@ -43,9 +43,6 @@ Popula dependentes da NF ... 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', @@ -66,8 +63,8 @@ Instancia a NF ... modelo=55, ... serie='1', ... numero_nf='1', - ... data_emissao=datetime.date.today(), - ... data_saida_entrada=datetime.date.today(), + ... data_emissao=datetime.date(2010,1,13), + ... data_saida_entrada=datetime.date(2010,1,13), ... natureza_operacao='Venda a vista', ... forma_pagamento=0, ... tipo_impressao_danfe=1, # Retrato @@ -77,9 +74,53 @@ Instancia a NF ... codigo_numerico_aleatorio='51800512', ... dv_codigo_numerico_aleatorio='3', ... ) + >>> nota_fiscal.retirada = NotaFiscalEntregaRetirada( + ... tipo_documento='CNPJ', + ... numero_documento='99.171.171/0001-94', + ... endereco_logradouro='AV PAULISTA', + ... endereco_numero='12345', + ... endereco_complemento='TERREO', + ... endereco_bairro='CERQUEIRA CESAR', + ... endereco_municipio='3304557', # Rio de Janeiro + ... endereco_uf='RJ', + ... ) + >>> nota_fiscal.entrega = NotaFiscalEntregaRetirada( + ... tipo_documento='CNPJ', + ... numero_documento='99.299.299/0001-94', + ... endereco_logradouro='AV FARIA LIMA', + ... endereco_numero='1500', + ... endereco_complemento='15 ANDAR', + ... endereco_bairro='PINHEIROS', + ... endereco_municipio='3304557', # Rio de Janeiro + ... endereco_uf='RJ', + ... ) + >>> nf_prod1 = nota_fiscal.adicionar_produto_servico( + ... codigo='00001', + ... descricao='Agua Mineral', + ... cfop='5101', + ... unidade_comercial='dz', + ... quantidade_comercial=decimal.Decimal('2'), + ... valor_unitario_comercial=decimal.Decimal('10.0'), + ... valor_total_bruto=decimal.Decimal('20.0'), + ... unidade_tributavel='und', + ... quantidade_tributavel=decimal.Decimal('24.0'), + ... valor_unitario_tributavel=decimal.Decimal('3.00'), + ... ) + >>> nf_prod2 = nota_fiscal.adicionar_produto_servico( + ... codigo='00002', + ... descricao='Agua Mineral', + ... cfop='5101', + ... unidade_comercial='pack', + ... quantidade_comercial=decimal.Decimal('5000000'), + ... valor_unitario_comercial=decimal.Decimal('2.0'), + ... valor_total_bruto=decimal.Decimal('10000000.0'), + ... unidade_tributavel='und', + ... quantidade_tributavel=decimal.Decimal('3000000.0'), + ... valor_unitario_tributavel=decimal.Decimal('0.3333'), + ... ) >>> _fonte_dados.contar_objetos() - 6 + 8 Gerar arquivos XML ------------------ @@ -218,9 +259,9 @@ Serializando por partes 12345 TERREO CERQUEIRA CESAR - 3550308 - SAO PAULO - SP + 3304557 + Rio de Janeiro + RJ 99299299000194 @@ -228,9 +269,9 @@ Serializando por partes 1500 15 ANDAR PINHEIROS - 3550308 - SAO PAULO - SP + 3304557 + Rio de Janeiro + RJ @@ -239,13 +280,13 @@ Serializando por partes Agua Mineral 5101 dz - 1000000.0000 - 1 - 10000000.00 + 2.000000 + 10.000000 + 20.000000 und - 12000000.0000 - 1 + 24.000000 + 3.000000 @@ -283,13 +324,13 @@ Serializando por partes Agua Mineral 5101 pack - 5000000.0000 - 2 - 10000000.00 + 5000000.000000 + 2.000000 + 10000000.000000 und - 3000000.0000 - 0.3333 + 3000000.000000 + 0.333300