Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
Refatora service de código de barras (#28)
Browse files Browse the repository at this point in the history
* adiciona nix e poetry

* termina configuração do nix e poetry

* adiciona numeração do código de barras em baixo da imagem

* corrige nix e poetry

* adiciona documento cnab240

* altera testes

* adiciona mais documentos

* adiciona serviço de cobrança

* documenta melhor

* altera testes

* cria teste

* finaliza BarcodeCobrancaService

* flake8

* implementa testes

* refatora BarcodeService

* cria models para os códigos de barra

* fmt

* ajusta alguns detalhes

* Refatora para DACService

* refatora DAC e MOD 10 e 11...

* altera barcode_cobranca para utilizar DAC e MOD

* cria barcode_tributo

* altera testes

* cria testes de tributo

* corrige docstring e conversão de tributos

* conversão utilizando slices

* fmt

* validação da linha digitável do tributo!

* incremental model BarcodeTributo

* adiciona identificação de tributo

* altera docstring

* Update bb_wrapper/services/barcode_tributo.py

Co-authored-by: PedroRegisPOAR <pedroalencarregis@hotmail.com>

* Update flake.nix

Co-authored-by: PedroRegisPOAR <pedroalencarregis@hotmail.com>

* remove arquivos obsoletos

Co-authored-by: PedroRegisPOAR <pedroalencarregis@hotmail.com>
  • Loading branch information
rodrigondec and PedroRegisPOAR authored Aug 26, 2021
1 parent a78b8d0 commit 1839169
Show file tree
Hide file tree
Showing 35 changed files with 1,978 additions and 619 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ pip.install:
pip.install.build:
pip install --upgrade --requirement requirements-build.txt

poetry.install:
poetry config virtualenvs.in-project true
poetry config virtualenvs.path .
poetry install

config.env:
cp .env.sample .env

Expand Down
80 changes: 80 additions & 0 deletions bb_wrapper/models/barcode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import Optional

from pydantic import BaseModel, constr, validator, root_validator

from ..services.barcode_cobranca import BarcodeCobrancaService
from ..services.barcode_tributo import BarcodeTributoService


class BarcodeCobranca(BaseModel):
code_line: Optional[constr(min_length=47, max_length=47, regex=r"^\d+$")]
barcode: Optional[constr(min_length=44, max_length=44, regex=r"^\d+$")]
barcode_image: Optional[str]

# noinspection PyMethodParameters
@validator("code_line")
def _code_line_must_be_valid(cls, code_line):
BarcodeCobrancaService().validate_code_line(code_line)
return code_line

# noinspection PyMethodParameters
@validator("barcode")
def _barcode_must_be_valid(cls, barcode):
BarcodeCobrancaService().validate_barcode(barcode)
return barcode

# noinspection PyMethodParameters
@root_validator
def _set_data(cls, values):
from ..services.barcode import BarcodeService

code_line, barcode = values.get("code_line"), values.get("barcode")

if code_line:
values["barcode"] = BarcodeCobrancaService().code_line_to_barcode(code_line)
elif barcode:
values["code_line"] = BarcodeCobrancaService().barcode_to_code_line(barcode)
else:
raise ValueError("Informe a linha digitável ou código de barras!")

values["barcode_image"] = BarcodeService().generate_barcode_b64image(
values["barcode"]
)
return values


class BarcodeTributo(BaseModel):
code_line: Optional[constr(min_length=48, max_length=48, regex=r"^\d+$")]
barcode: Optional[constr(min_length=44, max_length=44, regex=r"^\d+$")]
barcode_image: Optional[str]

# noinspection PyMethodParameters
@validator("code_line")
def _code_line_must_be_valid(cls, code_line):
BarcodeTributoService().validate_code_line(code_line)
return code_line

# noinspection PyMethodParameters
@validator("barcode")
def _barcode_must_be_valid(cls, barcode):
BarcodeTributoService().validate_barcode(barcode)
return barcode

# noinspection PyMethodParameters
@root_validator
def _set_data(cls, values):
from ..services.barcode import BarcodeService

code_line, barcode = values.get("code_line"), values.get("barcode")

if code_line:
values["barcode"] = BarcodeTributoService().code_line_to_barcode(code_line)
elif barcode:
values["code_line"] = BarcodeTributoService().barcode_to_code_line(barcode)
else:
raise ValueError("Informe a linha digitável ou código de barras!")

values["barcode_image"] = BarcodeService().generate_barcode_b64image(
values["barcode"]
)
return values
5 changes: 2 additions & 3 deletions bb_wrapper/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from .b64 import Base64Service # noqa: F401
from .unicode import UnicodeService
from .qrcode import QRCodeService
from .barcode import BarCodeService
from .barcode import BarcodeService
from .mod import ModService # noqa: F401
from .febrabran import FebrabranService # noqa: F401
from .pixcode import PixCodeService # noqa: F401


parse_unicode_to_alphanumeric = UnicodeService().parse_unicode_to_alphanumeric
generate_qrcode_b64image = QRCodeService().generate_qrcode_b64image
generate_barcode_b64image = BarCodeService().generate_barcode_b64image
generate_barcode_b64image = BarcodeService().generate_barcode_b64image
187 changes: 31 additions & 156 deletions bb_wrapper/services/barcode.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import io
from typing import Union

from barcode import generate as generate_barcode
from barcode.writer import SVGWriter

from pydantic import ValidationError

from .b64 import Base64Service
from .febrabran import FebrabranService
from ..models.barcode import BarcodeCobranca, BarcodeTributo


class BarCodeService:
def generate_barcode_b64image(self, barcode, text=""):
class BarcodeService:
def generate_barcode_b64image(self, barcode: str) -> str:
"""
Método para gerar uma imagem base46 a partir de um código de barras numérico.
"""
Expand All @@ -17,7 +20,7 @@ def generate_barcode_b64image(self, barcode, text=""):
name="itf",
code=barcode,
output=buffer,
text=text,
text=barcode,
writer=SVGWriter(),
writer_options={
"quiet_zone": 0, # margin esquerda e direita (sem margem pois nosso template tem espaço!) # noqa
Expand All @@ -31,155 +34,27 @@ def generate_barcode_b64image(self, barcode, text=""):
)
return Base64Service().generate_b64image_from_buffer(buffer)

def codeline_to_barcode(self, codeline: str):
"""
Método para converter uma linha digitável em código de barras!
A linha digitável segue a seguinte especificação:
Posição 0:3 (3) = Identificação do banco (exemplo: 001 = Banco do Brasil)
Posição 3 (1) = Código de moeda (exemplo: 9 = Real)
Posição 4:9 (5) = 5 primeiras posições do campo livre (posição 19:24 do código de barras) # noqa
Posição 9 (1) = Dígito verificador do primeiro campo
Posição 10:20 (10) = 6ª a 15ª posições do campo livre (posição 24:34 do código de barras) # noqa
Posição 20 (1) = Dígito verificador do segundo campo
Posição 21:31 (10) = 16ª a 25ª posições do campo livre (posição 34:44 do código de barras) # noqa
Posição 31 (1) = Dígito verificador do terceiro campo
Posição 32 (1) = Dígito verificador geral (posição 4 do código de barras)
Posição 33:37 (4) = Fator de vencimento (posição 5:9 do código de barras)
Posição 37:47 (10) = Valor nominal do título (posição 9:19 do código de barras) # noqa
http://www.meusutilitarios.com.br/2015/05/boleto-bancario-validacao-do-codigo-de.html
"""

barcode = ""
barcode += codeline[0:3] # banco
barcode += codeline[3] # modeda
barcode += codeline[32] # dígito verificador
barcode += codeline[33:37] # fator de vencimento
barcode += codeline[37:47] # valor do título
barcode += codeline[4:9] # 1ª parte campo livre
barcode += codeline[10:20] # 2ª parte campo livre
barcode += codeline[21:31] # 3ª parte campo livre
return barcode

def barcode_to_codeline(self, barcode):
"""
Método para converter um código de barras em linha digitável!
O código de barras segue a seguinte especificação:
Posição 0:3 (3) = Número do banco
Posição 3 (1) = Código da Moeda - 9 para Real
Posição 4 (1) = Digito verificador do Código de Barras
Posição 5:9 (4) = Data de vencimento em dias partir de 07/10/1997
Posição 9:19 (10) = Valor do boleto (8 inteiros e 2 decimais)
Posição 19:44 (25) = Campo Livre definido por cada banco
https://github.com/eduardocereto/pyboleto/blob/1fed215eac2c974efc6f03a16b94406c2bb55cc2/pyboleto/data.py#L180 # noqa
"""
codeline = ""

first_number = ""
first_number += barcode[0:3] # banco
first_number += barcode[3] # moeda
first_number += barcode[19:24] # 5 primeiras posições do campo livre
first_dv = self.calculate_codeline_dv(first_number)

codeline += first_number + str(first_dv)

second_number = barcode[24:34] # 10 seguintes posições do campo livre
second_dv = self.calculate_codeline_dv(second_number)

codeline += second_number + str(second_dv)

third_number = barcode[34:44] # 10 seguintes posições do campo livre
third_dv = self.calculate_codeline_dv(third_number)

codeline += third_number + str(third_dv)

codeline += barcode[4] # dígito verificador do código de barras
codeline += barcode[5:9] # fator de vencimento
codeline += barcode[9:19] # valor do boleto

return codeline

def calculate_barcode_dv(self, number):
"""
Método para calcular o DV geral do código de barras
"""
return FebrabranService().dac_11(number)

def calculate_codeline_dv(self, number):
"""
Método para calcular o DV de um segmento da linha digitável
.. note:
Foi presumido que é utilizado o modulo 10 e deu
certo para o cenário de teste.
O que não significa que é uma regra universal!
"""
return FebrabranService().dac_10(number)

def validate_barcode(self, barcode):
"""
Método para validar um código de barras
O código de barras segue a seguinte especificação:
Posição 0:2 (3) = Número do banco
Posição 3 (1) = Código da Moeda - 9 para Real
Posição 4 (1) = Digito verificador do Código de Barras
Posição 5:9 (4) = Data de vencimento em dias partir de 07/10/1997
Posição 9:19 (10) = Valor do boleto (8 inteiros e 2 decimais)
Posição 19:44 (25) = Campo Livre definido por cada banco
https://github.com/eduardocereto/pyboleto/blob/1fed215eac2c974efc6f03a16b94406c2bb55cc2/pyboleto/data.py#L180 # noqa
"""
dv = int(barcode[4])

number = barcode[:4] + barcode[5:]

calculated_dv = self.calculate_barcode_dv(number)

return dv == calculated_dv

def validate_codeline(self, codeline):
"""
Método para validar uma linha digitável
A linha digitável segue a seguinte especificação:
Posição 0:3 (3) = Identificação do banco (exemplo: 001 = Banco do Brasil)
Posição 3 (1) = Código de moeda (exemplo: 9 = Real)
Posição 4:9 (5) = 5 primeiras posições do campo livre (posições 20 a 24 do código de barras) # noqa
Posição 9 (1) = Dígito verificador do primeiro campo
Posição 10:20 (10) = 6ª a 15ª posições do campo livre (posições 25 a 34 do código de barras) # noqa
Posição 20 (1) = Dígito verificador do segundo campo
Posição 21:31 (10) = 16ª a 25ª posições do campo livre (posições 35 a 44 do código de barras) # noqa
Posição 31 (1) = Dígito verificador do terceiro campo
Posição 32 (1) = Dígito verificador geral (posição 5 do código de barras)
Posição 33:37 (4) = Fator de vencimento (posições 6 a 9 do código de barras)
Posição 37:47 (10) = Valor nominal do título (posições 10 a 19 do código de barras) # noqa
http://www.meusutilitarios.com.br/2015/05/boleto-bancario-validacao-do-codigo-de.html # noqa
"""
valid_barcode = self.validate_barcode(self.codeline_to_barcode(codeline))

first_number = codeline[:9]
first_dv = int(codeline[9])
first_calculated_dv = self.calculate_codeline_dv(first_number)
first_bool = first_dv == first_calculated_dv

second_number = codeline[10:20]
second_dv = int(codeline[20])
second_calculated_dv = self.calculate_codeline_dv(second_number)
second_bool = second_dv == second_calculated_dv

third_number = codeline[21:31]
third_dv = int(codeline[31])
third_calculated_dv = self.calculate_codeline_dv(third_number)
third_bool = third_dv == third_calculated_dv

return valid_barcode and first_bool and second_bool and third_bool
def identify(self, number: str) -> Union[BarcodeCobranca, BarcodeTributo]:
""""""
length = len(number)

if 47 <= length <= 48:
data = {"code_line": number}
elif length == 44:
data = {"barcode": number}
else:
raise ValueError("Tipo não identificado!")

try:
instance = BarcodeCobranca(**data)
return instance
except ValidationError:
pass

try:
instance = BarcodeTributo(**data)
return instance
except ValidationError:
pass

raise ValueError("Tipo não identificado!")
Loading

0 comments on commit 1839169

Please # to comment.