From 4d59eda36271373b2554e354e72492f68d579b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Coelho?= Date: Tue, 25 Nov 2014 16:33:55 -0200 Subject: [PATCH] =?UTF-8?q?FIX=20=20#11=20-=20Agora=20o=20ano=20=C3=A9=20d?= =?UTF-8?q?efinido=20conforme=20a=20data=20de=20vencimento=20da=20fatura.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Também traduzi todos os comentários evitando a macarronada portinglês e facilitando a vida de outros brasileiros que queiram contribuir. No caso ficou, código em inglês documentação em português. --- bbvisa2ofx/bbvisa2ofx.py | 9 +- bbvisa2ofx/test/exemploFaturaCartao.txt.ofx | 64 +++++------ bbvisa2ofx/test/txtparser.py | 36 +++++- bbvisa2ofx/txtparser.py | 118 ++++++++++++++------ bin/bbvisa2ofx | 4 +- bin/bbvisa2ofx_cli | 6 +- 6 files changed, 161 insertions(+), 76 deletions(-) mode change 100755 => 100644 bin/bbvisa2ofx_cli diff --git a/bbvisa2ofx/bbvisa2ofx.py b/bbvisa2ofx/bbvisa2ofx.py index 5bfc248..100132d 100644 --- a/bbvisa2ofx/bbvisa2ofx.py +++ b/bbvisa2ofx/bbvisa2ofx.py @@ -4,9 +4,10 @@ def convert ( fileTxt, fileOfx, closeOfxFile=True): """ - fileTxt: python loaded file of txt to be convertedCaminho para o arquivo txt disponibilizado pelo banco do brasil - fileOfx: python loaded file with path to ofx that will be generated - closeOfxFile: if false do not close fileOfx on the end of convertion (workaroud to use an StringIO class instead of default file class) + fileTxt: python file para o arquivo txt disponibilizado pelo banco do brasil + fileOfx: python file com o caminho para o ofx que sera gerado + closeOfxFile: se false nao fecha fileOfx (workaroud a classe para usar StringIO ao inves da classe file do python) + Em um arquivo OFX temos um banco, que possui contas (neste caso apenas uma) que por sua vez possuem transacoes. Os itens retornados pelo parser do txt, representam transacoes de uma conta @@ -104,6 +105,6 @@ def convert ( fileTxt, fileOfx, closeOfxFile=True): if(closeOfxFile): out.close() - print "Success converted" + print "Convertido com sucesso!" diff --git a/bbvisa2ofx/test/exemploFaturaCartao.txt.ofx b/bbvisa2ofx/test/exemploFaturaCartao.txt.ofx index de35f9f..4d69b86 100644 --- a/bbvisa2ofx/test/exemploFaturaCartao.txt.ofx +++ b/bbvisa2ofx/test/exemploFaturaCartao.txt.ofx @@ -15,7 +15,7 @@ NEWFILEUID:NON 0 INFO - 20140901 + 20141125 POR Banco do Brasil @@ -38,111 +38,111 @@ NEWFILEUID:NON CHECKING - 20140901 - 20140901 + 20141125 + 20141125 OTHER - 19000811 + 20140811 1000.0 - 190008111000.0PGTODEBITOCONTA3333000006037200 + 201408111000.0PGTODEBITOCONTA3333000006037200 PGTO DEBITO CONTA 3333 000006037 200 OTHER - 19000725 + 20140725 -46.86 - 19000725-46.86AtlassianSydney + 20140725-46.86AtlassianSydney Atlassian Sydney OTHER - 19000728 + 20140728 -2.99904 - 19000728-2.99904IOF-COMPRANOEXTERIOR + 20140728-2.99904IOF-COMPRANOEXTERIOR IOF - COMPRA NO EXTERIOR OTHER - 19000728 + 20140728 -57.85 - 19000728-57.85PAYPALCACHORROQUENTE4029357733 + 20140728-57.85PAYPALCACHORROQUENTE4029357733 PAYPAL CACHORRO QUENTE 4029357733 OTHER - 19000729 + 20140729 -3.69 - 19000729-3.69IOF-COMPRANOEXTERIOR + 20140729-3.69IOF-COMPRANOEXTERIOR IOF - COMPRA NO EXTERIOR OTHER - 19000730 + 20140730 -16.9 - 19000730-16.9NETFLIX.COMSAOPAULO + 20140730-16.9NETFLIX.COMSAOPAULO NETFLIX.COM SAO PAULO OTHER - 19000730 + 20140730 -65.0 - 19000730-65.0AGROPECUARIAVERDESCA + 20140730-65.0AGROPECUARIAVERDESCA AGROPECUARIA VERDES CA OTHER - 19000807 + 20140807 -29.9 - 19000807-29.9CONTAAZULJOINVILLE + 20140807-29.9CONTAAZULJOINVILLE CONTA AZUL JOINVILLE OTHER - 19000626 + 20140626 -352.0 - 19000626-352.0LLLLLLLLLLLLPARC02/03FLORIANOPOLI + 20140626-352.0LLLLLLLLLLLLPARC02/03FLORIANOPOLI LLLLLLLLLLLL PARC 02/03 FLORIANOPOLI OTHER - 19001004 + 20131004 -141.58 - 19001004-141.58ASDASDASDASDAPARC11/12FARROUPILHA + 20131004-141.58ASDASDASDASDAPARC11/12FARROUPILHA ASDASDASDASDA PARC 11/12 FARROUPILHA OTHER - 19000719 + 20140719 -163.9 - 19000719-163.9GOLTRANSPPARC02/02SAOPAULO + 20140719-163.9GOLTRANSPPARC02/02SAOPAULO GOL TRAN SP PARC 02/02 SAO PAULO OTHER - 19000826 + 20140826 -3.0 - 19000826-3.0PROTECAOOUROSET/2014 + 20140826-3.0PROTECAOOUROSET/2014 PROTECAO OURO SET/2014 OTHER - 19000815 + 20140815 -3.95 - 19000815-3.95AJUSTEADEBITOVAR.CAMBIALCOMPRAEX + 20140815-3.95AJUSTEADEBITOVAR.CAMBIALCOMPRAEX AJUSTE A DEBITO VAR.CAMBIAL COMPRA EX OTHER - 19000826 + 20140826 -15.0 - 19000826-15.0ANUIDADEDIFERENCIADATIT-PARC03/06 + 20140826-15.0ANUIDADEDIFERENCIADATIT-PARC03/06 ANUIDADE DIFERENCIADA TIT-PARC 03/06 0 - 20140901 + 20141125 diff --git a/bbvisa2ofx/test/txtparser.py b/bbvisa2ofx/test/txtparser.py index 8689a75..906bb3e 100644 --- a/bbvisa2ofx/test/txtparser.py +++ b/bbvisa2ofx/test/txtparser.py @@ -6,7 +6,7 @@ import unittest from bbvisa2ofx.txtparser import TxtParser from bbvisa2ofx.bbvisa2ofx import convert -import os +import os, datetime class Test(unittest.TestCase): file_path = os.path.dirname(os.path.abspath(__file__))+"/exemploFaturaCartao.txt"; @@ -18,12 +18,42 @@ def setUp(self): self.parser = TxtParser(fTxt); def testParseTransactionLine(self): + self.parser.dueDate = datetime.datetime(2011,12,25) parsedLine = self.parser.parseTransactionLine('11/08 PGTO DEBITO CONTA 3333 000006037 200 211 -1.000,00 0,00') - self.assertEquals(parsedLine['date'],'19000811') - self.assertEquals(parsedLine['fitid'],'190008111000.0PGTODEBITOCONTA3333000006037200') + self.assertEquals(parsedLine['date'],'20110811') + self.assertEquals(parsedLine['fitid'],'201108111000.0PGTODEBITOCONTA3333000006037200') self.assertEquals(parsedLine['value'],1000.00) self.assertEquals(parsedLine['desc'],'PGTO DEBITO CONTA 3333 000006037 200 ') + def testParseValueFromTransactionLine(self): + line = '30/07 NETFLIX.COM SAO PAULO BR 16,90 0,00' + value = self.parser.parseValueFromTransactionLine(line) + + self.assertEquals(value, -16.90) + + def testParseValueFromTransactionLine_ConvertingDollarToReal(self): + line = '25/07 Atlassian Sydney AU 0,00 20,00' + self.parser.exchangeRate = 2 #force exchangeRate + value = self.parser.parseValueFromTransactionLine(line) + + self.assertEquals(value, -40.00) + + def testParseDateFromTransactionLine_fromSameYear(self): + line = '25/02 Atlassian Sydney AU 0,00 20,00' + self.parser.dueDate = datetime.datetime(2011,07,25) + + date = self.parser.parseDateFromTransactionLine(line) + self.assertEquals(date,'20110225') + + def testParseDateFromTransactionLine_guessingYearBefore(self): + ''' + mesmo mes do vencimento, sugerindo ano anterior + ''' + line = '25/07 Atlassian Sydney AU 0,00 20,00' + self.parser.dueDate = datetime.datetime(2011,07,25) + + date = self.parser.parseDateFromTransactionLine(line) + self.assertEquals(date,'20100725') def testParse(self): self.parser.parse() diff --git a/bbvisa2ofx/txtparser.py b/bbvisa2ofx/txtparser.py index 9207081..f0121f8 100644 --- a/bbvisa2ofx/txtparser.py +++ b/bbvisa2ofx/txtparser.py @@ -17,15 +17,16 @@ class TxtParser: data no formato dd/mm no inicio. Para cada linha contendo este padrao, vamos extrair as informacoes da seguinte forma: - date: primeiros 8 caracteres - desc: do caracter 10 ao 49 + date: primeiros 5 caracteres + desc: do caracter 8 ao 49 value: split no final da linha do 51 caracter em diante, primeiro item ''' items = None - cardTitle = None #name of card, defined by "Modalidade" on txt file - cardNumber = None #number of card, defined by Nr.Cartao on txt file + cardTitle = None #titulo do cartao, definido por "Modalidade" no txt + cardNumber = None #numero do cartao, definido por Nr.Cartao no txt txtFile = None + dueDate = None #data de vencimento, definido por "Vencimento" no txt def __init__(self,txtFile): ''' @@ -39,17 +40,31 @@ def parse(self): f = self.txtFile lines = f.readlines() - #before parse transaction lines, we need to load the exchange rate to convert dollar values to real correctly - #fix issue #5 for line in lines: + self.parseDueDate(line) self.parseExchangeRateLine(line) self.parseCardTitleLine(line) self.parseCardNumberLine(line) - #now with the exangeRate populated, we can parse all transaction lines + #now with the exangeRate and dueDate populated, we can parse all transaction lines for line in lines: self.parseTransactionLine(line) + def parseDueDate(self, line): + ''' + popula dueDate se for a linha que representa o vencimento da fatura, + esta informacao eh utilizada para adivinharmos o ano da compra + FIXME: ainda pode gerar problemas quando temos uma compra realizada no ano anterior, mas que aparenta ser do ano atual + + ''' + + if(line.lstrip().startswith("Vencimento")): + print "Due date line found. %s" % line + + self.dueDate = datetime.strptime(line.split(":")[1].strip(),'%d.%m.%Y') + print "Due date is: %s" % self.dueDate + + def parseExchangeRateLine(self, line): ''' popula exchangeRate se for a linha que apresenta o resumo dos gastos @@ -70,9 +85,9 @@ def parseExchangeRateLine(self, line): return 0.0 - def parseCardTitleLine(self,line): + def parseCardTitleLine(self, line): ''' - Card title line starts with "Modalidade" + Titulo do cartao inicia com "Modalidade" ''' if(line.lstrip().startswith("Modalidade")): @@ -82,9 +97,9 @@ def parseCardTitleLine(self,line): self.cardTitle = line.split(":")[1].strip() print "The card title is: %s" % self.cardTitle - def parseCardNumberLine(self,line): + def parseCardNumberLine(self, line): ''' - Card number line starts with "Nr.Cart" + Numero do cartao inicia com "Nr.Cart" ''' if(line.lstrip().startswith("Nr.Cart")): @@ -95,16 +110,16 @@ def parseCardNumberLine(self,line): - def parseTransactionLine(self,line): + def parseTransactionLine(self, line): ''' - transction lines starts with a date in the format "dd/mm" " - (we must check with the end space because dates on dd/mm/yyyy format are not a transaction line) - - if that's a transaction line, an parsed object will be append on self.items list, - this object will contain these fields: - date: date of transaction - desc: description - value: value as BRL + Linhas de transacao inicial com uma data no formato "dd/mm " + (devemos verificar o espaco no final pois existem linhas no formato dd/mm/yyyy que nao sao tansacoes) + + caso for uma linha de transacao, um objeto sera adicionado na lista self.items + este objeto contem os seguintes campos: + date: data da transacao + desc: descricao + value: valor em BRL ''' if(re.match("^\d\d\/\d\d\ $", line[:6]) != None): @@ -112,21 +127,13 @@ def parseTransactionLine(self,line): usdValue = '' obj = {} - obj['date'] = datetime.strptime(line[:5],'%d/%m').strftime('%Y%m%d') - obj['desc'] = line[9:48].lstrip().replace('*','') - # LCARD - Start (bugfix issue 2 - country code can have 3 chars, like "BRA" instead of "BR" - # arr = line[50:].split() - arr = line[51:].split() - # LCARD - End (bugfix issue 2 - country code can have 3 chars, like "BRA" instead of "BR" + obj['value'] = self.parseValueFromTransactionLine(line) + obj['date'] = self.parseDateFromTransactionLine(line) - brlValue = float(arr[0].replace('.','').replace(',','.')) * -1 #inverte valor - usdValue = float(arr[1].replace('.','').replace(',','.')) * -1 #inverte valor + obj['desc'] = line[9:48].lstrip().replace('*','') - if brlValue != -0.0: - obj['value'] = brlValue - else: - obj['value'] = usdValue * self.exchangeRate + obj['value'] = self.parseValueFromTransactionLine(line) obj['fitid'] = (obj['date'] + str(obj['value']) + obj['desc']).replace(' ','') @@ -134,3 +141,50 @@ def parseTransactionLine(self,line): self.items.append(obj) return obj + def parseValueFromTransactionLine(self, line): + ''' + Extraindo valor da linha, a partir do caracter 51 + Caso esteja em dolar, converter para real utilizando a taxa de cambio. + + Agradecimento especial para Rodrigo que contribuiu pelo code.google + ''' + value = 0.0 + + arr = line[51:].split() + + brlValue = float(arr[0].replace('.','').replace(',','.')) + usdValue = float(arr[1].replace('.','').replace(',','.')) + + if brlValue != 0.0: + value = brlValue + else: + value = usdValue * self.exchangeRate + + value = value * -1 #inverte valor + + return value + + def parseDateFromTransactionLine(self, line): + ''' + Extraindo data da linha de transacao + Como o BB removeu o ano das datas, vamos precisar "adivinhar" o ano correto, + conforme a data de vencimento + + definimos o ano do vencimento como padrao, porem caso a data da transacao + fique maior que o vencimento, assumimos o ano anterior como ano correto. + + FIXME transacoes feitas a mais de 12 meses terao o ano definido incorretamente, mas + nao consegui pensar em outra solucao no momento + + Agradecimento especial a Leonardo F. Cardoso que fez esta contruicao por email. :) + ''' + + transactionDate = datetime.strptime(line[:5],'%d/%m') + transactionDate = transactionDate.replace(self.dueDate.year) + if transactionDate >= self.dueDate: + transactionDate = transactionDate.replace(transactionDate.year-1) + + return transactionDate.strftime('%Y%m%d') + + + diff --git a/bin/bbvisa2ofx b/bin/bbvisa2ofx index 51f1573..fa4ed9e 100755 --- a/bin/bbvisa2ofx +++ b/bin/bbvisa2ofx @@ -7,10 +7,10 @@ from tkMessageBox import * try: - # attempt to use the installed python package + # tentando usar o pacote instalado from bbvisa2ofx import bbvisa2ofx except: - # attempt to run the package from the source directory + # nao rolou, entao tenta usar o source pois esta rodando direto pelo codigo sys.path.insert (0,os.path.dirname(os.path.abspath(__file__))+"/..") from bbvisa2ofx import bbvisa2ofx diff --git a/bin/bbvisa2ofx_cli b/bin/bbvisa2ofx_cli old mode 100755 new mode 100644 index 1534ff1..1e6c511 --- a/bin/bbvisa2ofx_cli +++ b/bin/bbvisa2ofx_cli @@ -5,10 +5,10 @@ import os try: - # attempt to use the installed python package + # tentando usar o pacote instalado from bbvisa2ofx import bbvisa2ofx except: - # attempt to run the package from the source directory + # nao rolou, tentando carregar o pacote do src sys.path.insert (0,os.path.dirname(os.path.abspath(__file__))+"/..") from bbvisa2ofx import bbvisa2ofx @@ -23,7 +23,7 @@ def main(): fileOfx = open(pathTxt + ".ofx",'w') bbvisa2ofx.convert(fileTxt,fileOfx) else: - print "USAGE: bbvisa2ofx " + print "USO: bbvisa2ofx "