diff --git a/.gitignore b/.gitignore
index 93c111b..b477ac8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,4 @@ venv
.cache
.pytest_cache
+.settings
\ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000..0ec7c7d
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ boleto
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..ad74947
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+
+/${PROJECT_DIR_NAME}
+
+python interpreter
+Default
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..9480eef
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,13 @@
+eclipse.preferences.version=1
+encoding//build/lib/pyboleto/bank/bs2.py=utf-8
+encoding//pyboleto/bank/__init__.py=utf-8
+encoding//pyboleto/bank/bancodobrasil.py=utf-8
+encoding//pyboleto/bank/banrisul.py=utf-8
+encoding//pyboleto/bank/bs2.py=utf-8
+encoding//pyboleto/bank/itau.py=utf-8
+encoding//pyboleto/bank/sicredi.py=utf-8
+encoding//pyboleto/data.py=utf-8
+encoding//pyboleto/pdf.py=utf-8
+encoding//tests/test_banco_bs2.py=utf-8
+encoding//tests/testutils.py=utf-8
+encoding/setup.py=utf-8
diff --git a/README.rst b/README.rst
index 6d39b05..ee585f0 100644
--- a/README.rst
+++ b/README.rst
@@ -65,6 +65,8 @@ Por enquanto, são essas que temos.
+----------------------+----------------+-----------------+------------+
| **Cecred** | 1 | Yes | Yes |
+----------------------+----------------+-----------------+------------+
+ | **BS2** | 1 | Yes | Yes |
+ +----------------------+----------------+-----------------+------------+
.. _pyboleto-docs:
diff --git a/pyboleto/bank/__init__.py b/pyboleto/bank/__init__.py
index 48dc5e1..66c18e1 100644
--- a/pyboleto/bank/__init__.py
+++ b/pyboleto/bank/__init__.py
@@ -2,13 +2,14 @@
from ..data import BoletoException
BANCOS_IMPLEMENTADOS = {
'001': 'bancodobrasil.BoletoBB',
+ '033': 'santander.BoletoSantander',
'041': 'banrisul.BoletoBanrisul',
- '237': 'bradesco.BoletoBradesco',
'104': 'caixa.BoletoCaixa',
- '399': 'hsbc.BoletoHsbc',
+ '218': 'bs2.BoletoBs2',
+ '237': 'bradesco.BoletoBradesco',
'341': 'itau.BoletoItau',
'356': 'real.BoletoReal',
- '033': 'santander.BoletoSantander',
+ '399': 'hsbc.BoletoHsbc',
'748': 'sicredi.BoletoSicredi',
'756': 'sicoob.BoletoSicoob',
'0851': 'cecred.BoletoCecred',
diff --git a/pyboleto/bank/bs2.py b/pyboleto/bank/bs2.py
new file mode 100644
index 0000000..4e9aa10
--- /dev/null
+++ b/pyboleto/bank/bs2.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+from pyboleto.data import BoletoData, CustomProperty
+
+
+class BoletoBs2(BoletoData):
+ '''Implementa Boleto bs2
+
+ Gera Dados necessários para criação de boleto para o banco Bs2
+
+ '''
+
+ # Nosso numero (sem dv) com 8 digitos
+ nosso_numero = "9%s" % CustomProperty('nosso_numero', 9)
+ # Conta (sem dv) com 5 digitos
+ conta_cedente = CustomProperty('conta_cedente', 5)
+ # Agência (sem dv) com 4 digitos
+ agencia_cedente = CustomProperty('agencia_cedente', 4)
+
+ carteira = 21
+
+ data_limite = False
+
+ def __init__(self):
+ super(BoletoBs2, self).__init__()
+
+ self.codigo_banco = "218"
+ self.logo_image = "logo_bs2.png"
+ self.especie_documento = 'Outro'
+ self.local_pagamento = 'Pagável em qualquer banco até a data limite de %s'
+
+ @property
+ def dv_nosso_numero(self):
+ return self.modulo11("9"+self.nosso_numero.zfill(9))
+
+ @property
+ def dv_agencia_conta_cedente(self):
+ agencia_conta = "%s%s" % (self.agencia_cedente, self.conta_cedente)
+ return self.modulo11(agencia_conta)
+
+ @property
+ def agencia_conta_cedente(self):
+ return "%s/%s-%s" % (self.agencia_cedente, self.conta_cedente,
+ self.conta_cedente_dv)
+
+ def format_nosso_numero(self):
+ return "%9s%1s" % ("9"+self.nosso_numero.zfill(9),self.dv_nosso_numero)
+
+ @property
+ def campo_livre(self):
+ content = "%3s%9s%1s%10s%1s8" % (self.agencia_cedente.zfill(4)[-3:],
+ self.conta_cedente.zfill(9),
+ self.conta_cedente_dv,
+ '9'+self.nosso_numero.zfill(9),
+ self.dv_nosso_numero)
+ return content
diff --git a/pyboleto/data.py b/pyboleto/data.py
index 1b1b0bd..40c1739 100644
--- a/pyboleto/data.py
+++ b/pyboleto/data.py
@@ -100,6 +100,7 @@ class BoletoData(object):
:param cedente_documento: CPF ou CNPJ do Cedente.
:param conta_cedente: Conta do Cedente sem o dígito verificador.
:param data_documento:
+ :param data_limite:
:type data_documento: `datetime.date`
:param data_processamento:
:type data_processamento: `datetime.date`
@@ -129,6 +130,7 @@ class BoletoData(object):
def __init__(self, **kwargs):
# otherwise the printed value might diffent from the value in
# the barcode.
+ self.data_limite = kwargs.pop('data_limite', "")
self.aceite = kwargs.pop('aceite', "N")
self.agencia_cedente = kwargs.pop('agencia_cedente', "")
self.carteira = kwargs.pop('carteira', "")
@@ -143,13 +145,11 @@ def __init__(self, **kwargs):
self.conta_cedente = kwargs.pop('conta_cedente', "")
self.conta_cedente_dv = kwargs.pop('conta_cedente_dv', "")
self.data_documento = kwargs.pop('data_documento', "")
- self.data_processamento = kwargs.pop('data_processamento',
- datetime.date.today())
+ self.data_processamento = kwargs.pop('data_processamento',datetime.date.today())
self.data_vencimento = kwargs.pop('data_vencimento', "")
self.especie = kwargs.pop('especie', "R$")
self.especie_documento = kwargs.pop('especie_documento', "")
- self.local_pagamento = kwargs.pop(
- 'local_pagamento', "Pagável em qualquer banco até o vencimento")
+ self.local_pagamento = kwargs.pop('local_pagamento', "Pagável em qualquer banco até o vencimento")
self.logo_image = kwargs.pop('logo_image', "")
self.moeda = kwargs.pop('moeda', "9")
self.numero_documento = kwargs.pop('numero_do_documento', "")
@@ -161,6 +161,7 @@ def __init__(self, **kwargs):
self.sacado_endereco = kwargs.pop('sacado_endereco', "")
self.sacado_bairro = kwargs.pop('sacado_bairro', "")
self.sacado_cep = kwargs.pop('sacado_cep', "")
+ self.data_limite = kwargs.pop('data_limite', "")
if kwargs:
raise TypeError("Paramêtro(s) desconhecido: %r" % (kwargs, ))
self._cedente_endereco = None
diff --git a/pyboleto/media/logo_bs2.png b/pyboleto/media/logo_bs2.png
new file mode 100644
index 0000000..6703957
Binary files /dev/null and b/pyboleto/media/logo_bs2.png differ
diff --git a/pyboleto/pdf.py b/pyboleto/pdf.py
index c6fdc37..9f6089c 100644
--- a/pyboleto/pdf.py
+++ b/pyboleto/pdf.py
@@ -253,6 +253,12 @@ def _drawReciboSacado(self, boleto_dados, x, y):
self.pdf_canvas.setFont('Helvetica', 6)
self.delta_title = self.height_line - (6 + 1)
+ self.pdf_canvas.drawString(
+ 0,
+ self.height_line,
+ 'Sacador / Avalista'
+ )
+
self.pdf_canvas.drawRightString(
self.width,
self.height_line,
@@ -387,6 +393,14 @@ def _drawReciboSacado(self, boleto_dados, x, y):
valor_documento
)
+ if boleto_dados.sacador_documento and boleto_dados.sacador_nome:
+ self.pdf_canvas.setFont('Helvetica', 6)
+ self.pdf_canvas.drawString(
+ 0,
+ self.height_line - 8,
+ boleto_dados.sacador_documento + ' - ' + boleto_dados.sacador_nome
+ )
+
self.pdf_canvas.setFont('Courier', 9)
demonstrativo = boleto_dados.demonstrativo[0:25]
for i in range(len(demonstrativo)):
@@ -440,7 +454,7 @@ def _drawReciboCaixa(self, boleto_dados, x, y):
y = 1.5 * self.height_line
self.pdf_canvas.drawRightString(
self.width,
- (1.5 * self.height_line) + self.delta_title - 1,
+ y + self.delta_title - 1,
'Autenticação Mecânica / Ficha de Compensação'
)
@@ -452,9 +466,19 @@ def _drawReciboCaixa(self, boleto_dados, x, y):
self.width - (45 * mm) + self.space,
y + self.space, 'Código de baixa'
)
+
self.pdf_canvas.drawString(0, y + self.space, 'Sacador / Avalista')
+ if boleto_dados.sacador_documento and boleto_dados.sacador_nome:
+ self.pdf_canvas.setFont('Helvetica-Bold', self.font_size_title-0.3)
+ self.pdf_canvas.drawString(
+ (20 * mm),
+ y + self.space,
+ boleto_dados.sacador_documento + ' / ' + boleto_dados.sacador_nome
+ )
- y += self.height_line
+ self.pdf_canvas.setFont('Helvetica', self.font_size_title)
+
+ y += self.height_line + 7
self.pdf_canvas.drawString(0, y + self.delta_title, 'Pagador')
sacado = boleto_dados.sacado
@@ -515,7 +539,7 @@ def _drawReciboCaixa(self, boleto_dados, x, y):
self.pdf_canvas.drawString(
0,
y + self.delta_title,
- 'Instruções'
+ 'Informações de Responsabilidade do Beneficiário'
)
self.pdf_canvas.setFont('Helvetica', self.font_size_value)
@@ -526,6 +550,26 @@ def _drawReciboCaixa(self, boleto_dados, x, y):
y - (i * self.delta_font),
instrucoes[i]
)
+
+ ln = 6
+ if hasattr(boleto_dados, 'tx_multa'):
+ mt_txt = "Multa para pagamento após o vencimento: {:.2f}%.".format(boleto_dados.tx_multa)
+ self.pdf_canvas.drawString(
+ 2 * self.space,
+ y - (ln * self.delta_font),
+ mt_txt
+ )
+ ln += 1
+
+ if hasattr(boleto_dados, 'tx_juros'):
+ jr_txt = "Juros para pagamento após o vencimento: {:.2f}% ao mês.".format(boleto_dados.tx_juros)
+ self.pdf_canvas.drawString(
+ 2 * self.space,
+ y - (ln * self.delta_font),
+ jr_txt
+ )
+
+
self.pdf_canvas.setFont('Helvetica', self.font_size_title)
# Linha horizontal com primeiro campo Uso do Banco
@@ -817,7 +861,7 @@ def drawBoleto(self, boleto_dados):
y = 10 * mm # margem inferior
self._drawHorizontalCorteLine(x, y, self.width)
- y += 4 * mm # distancia entre linha de corte e barcode
+ y += 3 * mm # distancia entre linha de corte e barcode
d = self._drawReciboCaixa(boleto_dados, x, y)
y += d[1] + (12 * mm) # distancia entre Recibo caixa e linha de corte
diff --git a/tests/test_banco_bs2.py b/tests/test_banco_bs2.py
new file mode 100644
index 0000000..81b5a73
--- /dev/null
+++ b/tests/test_banco_bs2.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+import unittest
+import datetime
+import difflib
+import fnmatch
+import os
+import re
+import subprocess
+import tempfile
+from xml.etree.ElementTree import fromstring, tostring
+
+from pyboleto.bank.bs2 import BoletoBs2
+import pyboleto
+from pyboleto.pdf import BoletoPDF
+from pyboleto.html import BoletoHTML
+
+def indent(elem, level=0):
+ i = "\n" + level * " "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ indent(elem, level + 1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+
+def pdftoxml(filename, output):
+ p = subprocess.Popen(['pdftohtml',
+ '-stdout',
+ '-xml',
+ '-noframes',
+ '-i',
+ '-q',
+ filename],
+ stdout=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if stderr:
+ raise SystemExit("Error while runnig pdftohtml: %s" % (stderr, ))
+
+ root = fromstring(stdout)
+ indent(root)
+ with open(output, 'wb') as f:
+ f.write(tostring(root))
+
+def _diff(orig, new, short, verbose):
+ lines = difflib.unified_diff(orig, new)
+ if not lines:
+ return ''
+
+ return ''.join('%s: %s' % (short, line) for line in lines)
+
+def diff_files(orig, new, verbose=False):
+ with open(orig) as f_orig:
+ with open(new) as f_new:
+ return _diff(f_orig.readlines(),
+ f_new.readlines(),
+ short=os.path.basename(orig),
+ verbose=verbose)
+
+def _get_expected(bank, generated, f_type='xml'):
+ fname = os.path.join(os.path.dirname(pyboleto.__file__),"..", "tests", f_type, bank + '-expected.' + f_type)
+ if not os.path.exists(fname):
+ with open(fname, 'wb') as f:
+ with open(generated) as g:
+ f.write(g.read())
+ return fname
+
+def diff_pdf_htmls(original_filename, filename):
+ # REPLACE all generated dates with %%DATE%%
+ for fname in [original_filename, filename]:
+ with open(fname) as f:
+ data = f.read()
+ data = re.sub(r'name="date" content="(.*)"',
+ r'name="date" content="%%DATE%%"', data)
+ data = re.sub(r']+>', r'', data)
+ with open(fname, 'w') as f:
+ f.write(data)
+
+ return diff_files(original_filename, filename)
+
+dados = []
+for i in range(3):
+ d = BoletoBs2()
+ d.carteira = '21'
+ d.agencia_cedente = '001'
+ d.conta_cedente = '892700'
+ d.conta_cedente_dv = '6'
+ d.cedente = "M&D Com. E Man. DE Equip. para Inform. Ltda"
+ d.cedente_cidade = "Fazenda Rio Grande"
+ d.cedente_uf = "PR"
+ d.cedente_logradouro = "Rua Aruba"
+ d.cedente_bairro = "96"
+ d.cedente_cep = "88888-888"
+ d.cedente_documento = "123"
+ d.data_vencimento = datetime.date(2021, 6, 30)
+ d.data_documento = datetime.date(2021, 5, 25)
+ d.data_processamento = datetime.date(2021, 5, 20)
+ #d.self.data_limite = datetime.date(2009, 11, 19)
+ d.valor_documento = 123.45
+ d.nosso_numero = str(2399)
+ d.numero_documento = str(456)
+ d.sacador_documento = ""
+ d.sacador_nome = ""
+ d.instrucoes = ['Após o vencimento cobrar R$ 1,00 multa e ', 'R$ 0,25 ao dia juros por atraso']
+ dados.append(d)
+
+dados = dados[0]
+bank = type(dados).__name__
+filename = tempfile.mktemp(prefix="pyboleto-", suffix=".pdf")
+boleto = BoletoPDF(filename, True)
+boleto.drawBoleto(dados)
+boleto.nextPage()
+boleto.save()
+
+generated = filename + '.xml'
+pdftoxml(filename, generated)
+# expected = _get_expected(bank, generated)
+# diff = diff_pdf_htmls(expected, generated)
+# os.unlink(generated)
+# os.unlink(filename)
+# if diff:
+# print("Error while checking xml for %r:\n%s" % (bank, diff))
diff --git a/tests/xml/BoletoBs2-expected.xml b/tests/xml/BoletoBs2-expected.xml
new file mode 100644
index 0000000..e69de29