Created
August 29, 2024 19:49
-
-
Save GGontijo/92edf256fa1fc88cf46ac554d73b0f84 to your computer and use it in GitHub Desktop.
validar boleto e doc de arrecadação
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# AUTHOR https://github.com/viniciusccosta/ClipBarcode/blob/master/clipbarcode/digito_verificador.py | |
# ============================================================================= | |
import re | |
from abc import ABC, abstractmethod | |
from decimal import Decimal | |
from .datetime_tools import calculate_date | |
from .digito_verificador import mod10, mod11 | |
# ============================================================================= | |
DATA_BASE = "07/10/1997" | |
# ============================================================================= | |
def new_boleto(*args, **kwargs): | |
if "linha_digitavel" in kwargs: | |
linha_digitavel = kwargs.get("linha_digitavel") | |
linha_digitavel = re.sub('\D', '', linha_digitavel) | |
if len(linha_digitavel) == 47: | |
try: | |
return Cobranca(linha_digitavel=linha_digitavel) | |
except BoletoInvalidoException: | |
raise | |
elif len(linha_digitavel) == 48: | |
return Arrecadacao(linha_digitavel=linha_digitavel) | |
elif "cod_barras" in kwargs: | |
cod_barras = kwargs.get("cod_barras") | |
cod_barras = re.sub('\D', '', cod_barras) | |
if len(cod_barras) == 44: | |
if cod_barras[0] == "8": | |
return Arrecadacao(cod_barras=cod_barras) | |
else: | |
return Cobranca(cod_barras=cod_barras) | |
raise BoletoInvalidoException | |
# ============================================================================= | |
class BoletoInvalidoException(Exception): | |
pass | |
# ============================================================================= | |
class Boleto(ABC): | |
def __init__(self, *args, **kwargs): | |
self.linha_digitavel = None | |
self.cod_barras = None | |
@abstractmethod | |
def from_linha_digitavel(self, linha_digitavel): | |
pass | |
@abstractmethod | |
def from_cod_barras(self, cod_barras): | |
pass | |
class Arrecadacao(Boleto): | |
""" | |
Segmento do Produto: | |
1. Prefeituras; | |
2. Saneamento; | |
3. Energia Elétrica e Gás; | |
4. Telecomunicações; | |
5. Órgãos Governamentais; | |
6. Carnes e Assemelhados ou demais | |
Empresas / Órgãos que serão identificadas através do CNPJ. | |
7. Multas de trânsito | |
9. Uso exclusivo do banco | |
Valor Efetivo ou Referência: | |
“6”- Valor a ser cobrado efetivamente em reais com dígito verificador calculado pelo módulo 10 na quarta posição do Código de Barras e valor com 11 posições (versão 2 e posteriores) sem qualquer alteração; | |
“7”- Quantidade de moeda: | |
Zeros- somente na impossibilidade de utilizar o valor; | |
Valor a ser reajustado por um índice com dígito verificador calculado pelo módulo 10 na quarta posição do Código de Barras e valor com 11 posições (versão 2 e posteriores). | |
“8”- Valor a ser cobrado efetivamente em reais com dígito verificador calculado pelo módulo 11 na quarta posição do Código de Barras e valor com 11 posições (versão 2 e posteriores) sem qualquer alteração. | |
“9”- Quantidade de moeda | |
Zeros- somente na impossibilidade de utilizar o valor; | |
Valor a ser reajustado por um índice com dígito verificador calculado pelo módulo 11 na quarta posição do Código de Barras e valor com 11 posições (versão 2 e posteriores). | |
""" | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
# TODO: TALVEZ usar variáveis mais específicas | |
# self.id_prod = None # Constante “8” para identificar arrecadação | |
# self.seg_prod = None # 1 - Prefeitura, 2 - Saneamento, .... | |
# self.id_ve_ref = None # | |
# self.valor = None # | |
# self.id_emp = None # | |
# self.campo_livre = None # | |
self.dv_geral = None # | |
self.campo1 = None | |
self.dv1 = None | |
self.campo2 = None | |
self.dv2 = None | |
self.campo3 = None | |
self.dv3 = None | |
self.campo4 = None | |
self.dv4 = None | |
if "linha_digitavel" in kwargs: | |
self.from_linha_digitavel(kwargs.get("linha_digitavel")) | |
elif "cod_barras" in kwargs: | |
self.from_cod_barras(kwargs.get("cod_barras")) | |
else: | |
raise BoletoInvalidoException | |
def from_linha_digitavel(self, linha_digitavel): | |
def validar_linha_digitavel(): | |
if self.campo1[2] == "8" or self.campo1[2] == "9": # TODO: Ao invés desses "ifs" poderíamos fazer uma subclasse de Arrecadação | |
dv1 = mod11(f'{self.campo1}', x10=True) | |
dv2 = mod11(f'{self.campo2}', x10=True) | |
dv3 = mod11(f'{self.campo3}', x10=True) | |
dv4 = mod11(f'{self.campo4}', x10=True) | |
dv_geral = mod11(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}', x10=True) | |
else: | |
dv1 = mod10(f'{self.campo1}') | |
dv2 = mod10(f'{self.campo2}') | |
dv3 = mod10(f'{self.campo3}') | |
dv4 = mod10(f'{self.campo4}') | |
dv_geral = mod10(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}') | |
return dv1 == self.dv1 and dv2 == self.dv2 and dv3 == self.dv3 and dv4 == self.dv4 and self.dv_geral == dv_geral | |
def preencher_cod_barras(): | |
self.cod_barras = f'{self.campo1}{self.campo2}{self.campo3}{self.campo4}' | |
linha_digitavel = re.sub('\D', '', linha_digitavel) | |
if len(linha_digitavel) == 48: | |
self.linha_digitavel = linha_digitavel | |
self.campo1 = linha_digitavel[ 0: 0+11] | |
self.dv1 = linha_digitavel[11:11+ 1] | |
self.campo2 = linha_digitavel[12:12+11] | |
self.dv2 = linha_digitavel[23:23+ 1] | |
self.campo3 = linha_digitavel[24:24+11] | |
self.dv3 = linha_digitavel[35:35+ 1] | |
self.campo4 = linha_digitavel[36:36+11] | |
self.dv4 = linha_digitavel[47:47+ 1] | |
self.dv_geral = self.campo1[3] | |
if validar_linha_digitavel(): | |
preencher_cod_barras() | |
else: | |
raise BoletoInvalidoException | |
else: | |
raise BoletoInvalidoException | |
def from_cod_barras(self, cod_barras): | |
""" | |
+---------+---------+--------------------------------------------+ | |
| POSIÇÃO | TAMANHO | CONTEÚDO | | |
+---------+---------+--------------------------------------------+ | |
| 01 - 01 | 1 | Identificação do Produto | | |
+---------+---------+--------------------------------------------+ | |
| 02 - 02 | 1 | Identificação do Segmento | | |
+---------+---------+--------------------------------------------+ | |
| 03 - 03 | 1 | Identificação do valor real ou referência | | |
+---------+---------+--------------------------------------------+ | |
| 04 - 04 | 1 | Dígito verificador geral (módulo 10 ou 11) | | |
+---------+---------+--------------------------------------------+ | |
| 05 - 15 | 11 | Valor | | |
+---------+---------+--------------------------------------------+ | |
| 16 - 19 | 4 | Identificação da Empresa/Órgão | | |
+---------+---------+--------------------------------------------+ | |
| 20 - 44 | 25 | Campo livre de utilização da Empresa/Orgão | | |
+---------+---------+--------------------------------------------+ | |
| 16 - 23 | 8 | CNPJ / MF | | |
+---------+---------+--------------------------------------------+ | |
| 24 - 44 | 21 | Campo livre de utilização da Empresa/Órgão | | |
+---------+---------+--------------------------------------------+ | |
""" | |
def validar_cod_barras(): | |
if self.campo1[2] == "8" or self.campo1[2] == "9": # TODO: Ao invés desses "ifs" poderíamos fazer uma subclasse de Arrecadação | |
dv_geral = mod11(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}', x10=True) | |
else: | |
dv_geral = mod10(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}') | |
return dv_geral == self.dv_geral | |
def preencher_linha_digitavel(): | |
if self.campo1[2] == "8" or self.campo1[2] == "9": # TODO: Ao invés desses "ifs" poderíamos fazer uma subclasse de Arrecadação | |
self.dv1 = mod11(f'{self.campo1}', x10=True) | |
self.dv2 = mod11(f'{self.campo2}', x10=True) | |
self.dv3 = mod11(f'{self.campo3}', x10=True) | |
self.dv4 = mod11(f'{self.campo4}', x10=True) | |
self.dv_geral = mod11(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}', x10=True) | |
else: | |
self.dv1 = mod10(f'{self.campo1}') | |
self.dv2 = mod10(f'{self.campo2}') | |
self.dv3 = mod10(f'{self.campo3}') | |
self.dv4 = mod10(f'{self.campo4}') | |
self.dv_geral = mod10(f'{self.campo1[0:0 + 3]}{self.campo1[4:4 + 8]}{self.campo2}{self.campo3}{self.campo4}') | |
self.linha_digitavel = f'{self.campo1}{self.dv1}{self.campo2}{self.dv2}{self.campo3}{self.dv3}{self.campo4}{self.dv4}' | |
cod_barras = re.sub('\D', '', cod_barras) | |
if len(cod_barras) == 44: | |
self.cod_barras = cod_barras | |
self.campo1 = cod_barras[ 0: 0+11] | |
self.campo2 = cod_barras[11:11+11] | |
self.campo3 = cod_barras[22:22+11] | |
self.campo4 = cod_barras[33:33+11] | |
self.dv_geral = self.campo1[3] | |
if validar_cod_barras(): | |
preencher_linha_digitavel() | |
else: | |
raise BoletoInvalidoException | |
else: | |
raise BoletoInvalidoException | |
class Cobranca(Boleto): | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
# ------------------------------ | |
self.id_banco = None | |
self.cod_moeda = None | |
self.campo_livre_1 = None | |
self.dv1 = None | |
self.campo_livre_2 = None | |
self.dv2 = None | |
self.campo_livre_3 = None | |
self.dv3 = None | |
self.dv_geral = None | |
self.fator_venc = None | |
self.valor = None | |
# ------------------------------ | |
self.dv_cod_barras = None | |
self.campo_livre_cod_barras = None | |
# ------------------------------ | |
self.venc = None | |
# ------------------------------ | |
if "linha_digitavel" in kwargs: | |
self.from_linha_digitavel(kwargs.get("linha_digitavel")) | |
elif "cod_barras" in kwargs: | |
self.from_cod_barras(kwargs.get("cod_barras")) | |
else: | |
raise BoletoInvalidoException | |
def from_linha_digitavel(self, linha_digitavel): | |
""" | |
+---------+---------+--------------------------------------+ | |
| Posição | Tamanho | Conteúdo | | |
+---------+---------+--------------------------------------+ | |
| 01-03 | 3 | Identificação do Banco | | |
+---------+---------+--------------------------------------+ | |
| 04-04 | 1 | Código de Moeda (9 - Real) | | |
+---------+---------+--------------------------------------+ | |
| 05-09 | 5 | Posições 1 a 5 do campo livre | | |
+---------+---------+--------------------------------------+ | |
| 10-10 | 1 | Dígito verificador do primeiro campo | | |
+---------+---------+--------------------------------------+ | |
| 11-20 | 10 | Posições 6 a 15 do campo livre | | |
+---------+---------+--------------------------------------+ | |
| 21-21 | 1 | Dígito verificador do segundo campo | | |
+---------+---------+--------------------------------------+ | |
| 22-31 | 10 | Posições 16 a 25 do campo livre | | |
+---------+---------+--------------------------------------+ | |
| 32-32 | 1 | Dígito verificador do terceiro campo | | |
+---------+---------+--------------------------------------+ | |
| 33-33 | 1 | Dígito verificador geral | | |
+---------+---------+--------------------------------------+ | |
| 34-37 | 4 | Fator de Vencimento | | |
+---------+---------+--------------------------------------+ | |
| 38-47 | 10 | Valor nominal do título | | |
+---------+---------+--------------------------------------+ | |
""" | |
def validar_linha_digitavel(): | |
dv1 = mod10(f'{self.id_banco}{self.cod_moeda}{self.campo_livre_1}') | |
dv2 = mod10(f'{self.campo_livre_2}') | |
dv3 = mod10(f'{self.campo_livre_3}') | |
dv = mod11(f'{self.id_banco}{self.cod_moeda}{self.fator_venc}{int(round(self.valor * 100)):010}{self.campo_livre_1}{self.campo_livre_2}{self.campo_livre_3}') | |
return dv1 == self.dv1 and dv2 == self.dv2 and dv3 == self.dv3 and dv == self.dv_geral | |
def preencher_cod_barras(): | |
self.campo_livre_cod_barras = f'{self.campo_livre_1}{self.campo_livre_2}{self.campo_livre_3}' | |
self.dv_cod_barras = self.dv_geral | |
self.cod_barras = f'{self.id_banco}{self.cod_moeda}{self.dv_cod_barras}{self.fator_venc}{int(round(self.valor * 100)):010}{self.campo_livre_cod_barras}' | |
linha_digitavel = re.sub('\D', '', linha_digitavel) | |
if len(linha_digitavel) == 47: # TODO: Realizar verificações necessárias, como verificar se só possuem números e coisas similares | |
self.linha_digitavel = linha_digitavel | |
self.id_banco = linha_digitavel[ 0: 0 + 3] | |
self.cod_moeda = linha_digitavel[ 3: 3 + 1] | |
self.campo_livre_1 = linha_digitavel[ 4: 4 + 5] | |
self.dv1 = linha_digitavel[ 9: 9 + 1] | |
self.campo_livre_2 = linha_digitavel[10:10 + 10] | |
self.dv2 = linha_digitavel[20:20 + 1] | |
self.campo_livre_3 = linha_digitavel[21:21 + 10] | |
self.dv3 = linha_digitavel[31:31 + 1] | |
self.dv_geral = linha_digitavel[32:32 + 1] | |
self.fator_venc = linha_digitavel[33:33 + 4] | |
self.valor = float( Decimal(linha_digitavel[37:37 + 10]) / 100 ) | |
self.venc = calculate_date(DATA_BASE, self.fator_venc) | |
if validar_linha_digitavel(): | |
preencher_cod_barras() | |
else: | |
raise BoletoInvalidoException | |
else: | |
raise BoletoInvalidoException | |
def from_cod_barras(self, cod_barras): | |
""" | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| Posição | Tamanho | Picture | Conteúdo | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 01-03 | 3 | 9(3) | Identificação do banco | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 04-04 | 1 | 9 | Código moeda (9-Real) | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 05-05 | 1 | 9 | Dígito verificador do código de barras (DV) | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 06-09 | 4 | 9(4) | Fator de vencimento | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 10-19 | 10 | 9(08)v99 | Valor nominal do título | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
| 20-44 | 25 | 9(25) | Campo livre - utilizado de acordo com a especificação interna do banco emissor | | |
+---------+---------+----------+--------------------------------------------------------------------------------+ | |
""" | |
def validar_cod_barras(): | |
dv = mod11(f'{self.id_banco}{self.cod_moeda}{self.fator_venc}{int(round(self.valor * 100)):010}{self.campo_livre_cod_barras}') | |
return dv == self.dv_cod_barras | |
def preencher_linha_digitavel(): | |
self.campo_livre_1 = self.campo_livre_cod_barras[0:0+5] | |
self.dv1 = mod10(f'{self.id_banco}{self.cod_moeda}{self.campo_livre_1}') | |
self.campo_livre_2 = self.campo_livre_cod_barras[5:5+10] | |
self.dv2 = mod10(f'{self.campo_livre_2}') | |
self.campo_livre_3 = self.campo_livre_cod_barras[15:15+10] | |
self.dv3 = mod10(f'{self.campo_livre_3}') | |
self.dv_geral = self.dv_cod_barras | |
self.linha_digitavel = f'{self.id_banco}{self.cod_moeda}{self.campo_livre_1}{self.dv1}{self.campo_livre_2}{self.dv2}{self.campo_livre_3}{self.dv3}{self.dv_geral}{self.fator_venc}{int(round(self.valor * 100)):010}' | |
cod_barras = re.sub('\D', '', cod_barras) | |
if len(cod_barras) == 44: | |
self.cod_barras = cod_barras | |
self.id_banco = cod_barras[ 0: 0 + 3] | |
self.cod_moeda = cod_barras[ 3: 3 + 1] | |
self.dv_cod_barras = cod_barras[ 4: 4 + 1] | |
self.fator_venc = cod_barras[ 5: 5 + 4] | |
self.valor = float( Decimal(cod_barras[ 9: 9 + 10]) / 100 ) | |
self.campo_livre_cod_barras = cod_barras[19:19 + 25] | |
self.venc = calculate_date(DATA_BASE, self.fator_venc) | |
if validar_cod_barras(): | |
preencher_linha_digitavel() | |
else: | |
raise BoletoInvalidoException | |
else: | |
raise BoletoInvalidoException | |
# ============================================================================= | |
""" | |
https://www.boletobancario-codigodebarras.com/2016/04/linha-digitavel.html | |
https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20ATUALIZADO.pdf | |
https://www.macoratti.net/07/10/net_bol.htm | |
https://www.banese.com.br/wps/discovirtual/download?nmInternalFolder=/Empresa_recebimento&nmFile=Composicao%20da%20Linha%20Digitavel%20e%20do%20Codigo%20de%20Barras_05062017.pdf | |
https://demo.iprefeituras.com.br/uploads/noticia/16091/manual_cnab_400.pdf | |
https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc5175Bloqueto.pdf | |
https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc8122GuiaNaoComp.pdf | |
""" | |
# ============================================================================= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# AUTHOR https://github.com/viniciusccosta/ClipBarcode/blob/master/clipbarcode/digito_verificador.py | |
def mod10(dados): | |
fatores = [2, 1] # [2, 1] | |
multiplicador = [fatores[i % len(fatores)] for i in range(len(dados))] # [2, 1, 2, 1, 2, 1,...] só que está da esquerda para direita nesse caso | |
multiplicador = multiplicador[::-1] # Agora sim, está da direita para esquerda! | |
# --------------------------------------------------------------------------- | |
digitos = [] | |
for i in range(len(multiplicador)): | |
produto = int(dados[i]) * multiplicador[i] | |
digitos += [int(i) for i in str(produto)] # Separando dígitos do resultado da múltiplicação (resultado = 18 --> 1+8,) | |
soma = sum(digitos) | |
# --------------------------------------------------------------------------- | |
resto = soma % 10 | |
if resto == 0: # Observação: Utilizar o dígito "0" para o resto 0 (zero). Exemplo: | |
dv = 0 | |
else: | |
dv = 10 - resto | |
# --------------------------------------------------------------------------- | |
return str(dv) | |
def mod11(dados, x10=False): | |
fatores = [i for i in range(2, 10)] # [2, 3, 4, 5, 6, 7, 8, 9] | |
multiplicador = [fatores[i % len(fatores)] for i in range(len(dados))] # [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9,...] só que está da esquerda para direita nesse caso | |
multiplicador = multiplicador[::-1] # Agora sim, está da direita para esquerda! | |
# --------------------------------------------------------------------------- | |
soma = 0 | |
# print(dados, multiplicador) | |
for i in range(len(multiplicador)): | |
produto = int(dados[i]) * multiplicador[i] | |
# print(f'{i:02}) {int(dados[i])} x {multiplicador[i]} = {produto:02} --> Soma antes: {soma:03}') | |
soma += produto | |
# print(f"Soma: {soma:03}") | |
# --------------------------------------------------------------------------- | |
resto = soma % 11 | |
# print(f"RESTO: {resto}") | |
if x10: | |
dv = ((soma*10) % 11) % 10 | |
else: | |
if resto <= 1 or resto >= 10: # Observação: para o código de barras, sempre que o resto for 0, 1 ou 10, deverá ser utilizado o dígito 1 | |
dv = 1 | |
else: | |
dv = 11 - resto | |
# --------------------------------------------------------------------------- | |
return str(dv) | |
# https://www.cjdinfo.com.br/solucao-javascript-calculo-digito-modulo-11?p=34 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment