<commit_before># @ 2020 Akretion - www.akretion.com.br -
#   <NAME> <<EMAIL>>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)

import base64
import datetime
import json
import logging

import requests

from odoo.exceptions import UserError

from odoo.addons.account_move_base_import.parser.file_parser import FileParser

from ..constants.br_cobranca import get_brcobranca_api_url

logger = logging.getLogger(__name__)

dict_brcobranca_bank = {
    "001": "banco_brasil",
    "041": "banrisul",
    "085": "ailos",
    "237": "bradesco",
    "104": "caixa",
    "399": "hsbc",
    "341": "itau",
    "033": "santander",
    "748": "sicred",
    "004": "banco_nordeste",
    "021": "banestes",
    "756": "sicoob",
    "136": "unicred",
}


class CNABFileParser(FileParser):
    """CNAB parser that use a define format in CNAB to import
    bank statement.
    """

    def __init__(self, journal, *args, **kwargs):
        # The name of the parser as it will be called
        self.parser_name = journal.import_type
        # The result as a list of row. One row per line of data in the file,
        # but not the commission one!
        self.result_row_list = None
        # The file buffer on which to work on
        self.filebuffer = None
        # The profile record to access its parameters in any parser method
        self.journal = journal
        self.move_date = None
        self.move_name = None
        self.move_ref = None
        self.support_multi_moves = None
        self.commission_field = None
        self.env = journal.env
        self.bank = self.journal.bank_account_id.bank_id
        self.cnab_return_events = []

    @classmethod
    def parser_for(cls, parser_name):
        if parser_name == "cnab400":
            return parser_name == "cnab400"
        elif parser_name == "cnab240":
            return parser_name == "cnab240"

    def parse(self, filebuffer):

        files = {"data": base64.b64decode(filebuffer)}

        data = self._get_brcobranca_retorno(files)

        self.result_row_list = self.process_return_file(data)

        yield self.result_row_list

    def _get_brcobranca_retorno(self, files):

        bank_name_brcobranca = dict_brcobranca_bank[self.bank.code_bc]
        brcobranca_api_url = get_brcobranca_api_url(self.env)
        # Ex.: "http://boleto_cnab_api:9292/api/retorno"
        brcobranca_service_url = brcobranca_api_url + "/api/retorno"
        logger.info(
            "Connecting to %s to get CNAB-RETORNO of file name %s",
            brcobranca_service_url,
            self.env.context.get("file_name"),
        )
        res = requests.post(
            brcobranca_service_url,
            data={
                "type": self.journal.import_type,
                "bank": bank_name_brcobranca,
            },
            files=files,
        )

        if res.status_code != 201:
            raise UserError(res.text)

        string_result = res.json()
        data = json.loads(string_result)

        return data

    def _get_date_format(self, bank_name_brcobranca):
        # TODO: Idealmente o JSON de Retorno do BRCobranca deveria vir
        #  padronizado para não ser necessário ser feito esse tratamento aqui
        if bank_name_brcobranca == "ailos":
            # No Banco AILOS o formato da Data é completo com os 4 digitos.
            zeros_date = "00000000"
            date_format = "%d%m%Y"
        else:
            # Outros Bancos mapeados é apenas 2 digitos.
            zeros_date = "000000"
            date_format = "%d%m%y"

        return zeros_date, date_format

    def process_return_file(self, data):

        #          Forma de Lançamento do Retorno
        # Em caso de Pagamento/Liquidação é feita a criação de uma Entrada de
        # Diário para cada linha do arquivo CNAB com os valores de desconto,
        # juros/mora, tarifa bancaria, abatimento, valor total
        #
        # Quando marcada a opção de Reconciliação Automatica no Diário
        # a Fatura será Reconciliada e a Entrada de Diário será movida
        # para o status Lançado

        #          Valor Recebido diferente do Valor no Odoo
        # Para que a Fatura/Nota Fiscal no Odoo seja completamente reconciliada
        # e o seu status seja alterado de Aberto para Pago é preciso que
        # a soma dos lançamentos(account.move.line) criados sejam iguais ao
        # valor da account_move_line em aberto.
        #
        #                      Valor Menor
        # Os valores de Desconto e Abatimento estão sendo adicionados no
        # lançamento referente ao pagamento para que o valor total fique
        # igual, exemplo:
        #        Odoo               Valores arquivo CNAB
        #    Valor Odoo   | Valor Recebido | Valor Abatimento |Valor Desconto
        #       100       |     80         |       10         |     10
        #
        # O Valor Recebido a ser lançado será 100.
        #
        # Caso o valor ainda seja menor nada pode ser feito aqui, pois
        # significa que o cliente pagou um valor menor sem relação com o
        # Abatimento e ou Desconto e que ainda está em Aberto junto ao banco.
        # TODO: Existe a possibilidade de Pagar um valor menor junto ao banco ?
        # Se for preciso Baixar esse valor o processo deverá ser utilizar o
        # wizard de atualizações do CNAB, na account.move.line referente,
        # pedindo a Baixa do Título o que irá gerar um nova Linha/Instrução
        # CNAB a ser enviada ao Banco.
        #
        #                   Valor Maior
        # Caso comum onde o devido o valor de Juros Mora/Multa faz com que
        # os valores fiquem diferentes, exemplo:
        #        Odoo               Valores arquivo CNAB
        #    Valor Odoo   | Valor Recebido | Juros Mora + Multa*
        #       100       |     110        |       10
        #
        # O Valor Recebido a ser lançado será 100.
        # *valores vem somados no CNAB Unicred 400, seria um padrão ?
        #
        # Não é possível relacionar esse lançamento contabil diretamente isso
        # causa erro, seria preciso "Cancelar o Lançamento" da account.move(
        # referente a invoice que gerou o CNAB) é criar uma nova linha
        # assim aumentando o valor original da Fatura, na primeira linha que
        # fosse recebida esse processo até poderia funcionar mas no caso de uma
        # segunda, terceira, etc acredito que o programa já não permitiria mais
        # Cancelar o Lançamento existindo reconciliações parciais.
        # Por isso Nesse caso é preciso alterar o Valor Recebido
        # ( Valor Recebido - Valor Juros Mora/Multa )
        # TODO: Deveria existir uma forma de mostrar o Valor de Juros/Multa na
        #  tela da Fatura/Invoice, e assim o usuário poder visualizar isso ?
        # Porque não vai existir um relacionamento direto de conciliação,
        # apenas a referencia no bank_payment_line_id da account.move.line,
        # e caso se queira saber os detalhes será preciso olhar a Entrada de
        # Diário referente.

        # Lista com os dados q poderão ser usados
        # na criação das account move line
        result_row_list = []

        bank_name_brcobranca = dict_brcobranca_bank[self.bank.code_bc]

        if bank_name_brcobranca == "ailos":
            # No AILOS o código de registro onde ficam as linhas CNAB é o 3.
            registration_code_allowed = 3
        else:
            registration_code_allowed = 1

        for linha_cnab in data:

            if int(linha_cnab["codigo_registro"]) != registration_code_allowed:
                # Bradesco
                # Existe o codigo de registro 9 que eh um totalizador
                # porem os campos estao colocados em outras posicoes<commit_msg>[FIX] brcobranca: add check for banco_brasil in cnab parser<commit_after># @ 2020 Akretion - www.akretion.com.br -
#   <NAME> <<EMAIL>>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)

import base64
import datetime
import json
import logging

import requests

from odoo.exceptions import UserError

from odoo.addons.account_move_base_import.parser.file_parser import FileParser

from ..constants.br_cobranca import get_brcobranca_api_url

logger = logging.getLogger(__name__)

dict_brcobranca_bank = {
    "001": "banco_brasil",
    "041": "banrisul",
    "085": "ailos",
    "237": "bradesco",
    "104": "caixa",
    "399": "hsbc",
    "341": "itau",
    "033": "santander",
    "748": "sicred",
    "004": "banco_nordeste",
    "021": "banestes",
    "756": "sicoob",
    "136": "unicred",
}


class CNABFileParser(FileParser):
    """CNAB parser that use a define format in CNAB to import
    bank statement.
    """

    def __init__(self, journal, *args, **kwargs):
        # The name of the parser as it will be called
        self.parser_name = journal.import_type
        # The result as a list of row. One row per line of data in the file,
        # but not the commission one!
        self.result_row_list = None
        # The file buffer on which to work on
        self.filebuffer = None
        # The profile record to access its parameters in any parser method
        self.journal = journal
        self.move_date = None
        self.move_name = None
        self.move_ref = None
        self.support_multi_moves = None
        self.commission_field = None
        self.env = journal.env
        self.bank = self.journal.bank_account_id.bank_id
        self.cnab_return_events = []

    @classmethod
    def parser_for(cls, parser_name):
        if parser_name == "cnab400":
            return parser_name == "cnab400"
        elif parser_name == "cnab240":
            return parser_name == "cnab240"

    def parse(self, filebuffer):

        files = {"data": base64.b64decode(filebuffer)}

        data = self._get_brcobranca_retorno(files)

        self.result_row_list = self.process_return_file(data)

        yield self.result_row_list

    def _get_brcobranca_retorno(self, files):

        bank_name_brcobranca = dict_brcobranca_bank[self.bank.code_bc]
        brcobranca_api_url = get_brcobranca_api_url(self.env)
        # Ex.: "http://boleto_cnab_api:9292/api/retorno"
        brcobranca_service_url = brcobranca_api_url + "/api/retorno"
        logger.info(
            "Connecting to %s to get CNAB-RETORNO of file name %s",
            brcobranca_service_url,
            self.env.context.get("file_name"),
        )
        res = requests.post(
            brcobranca_service_url,
            data={
                "type": self.journal.import_type,
                "bank": bank_name_brcobranca,
            },
            files=files,
        )

        if res.status_code != 201:
            raise UserError(res.text)

        string_result = res.json()
        data = json.loads(string_result)

        return data

    def _get_date_format(self, bank_name_brcobranca):
        # TODO: Idealmente o JSON de Retorno do BRCobranca deveria vir
        #  padronizado para não ser necessário ser feito esse tratamento aqui
        if bank_name_brcobranca == "ailos":
            # No Banco AILOS o formato da Data é completo com os 4 digitos.
            zeros_date = "00000000"
            date_format = "%d%m%Y"
        else:
            # Outros Bancos mapeados é apenas 2 digitos.
            zeros_date = "000000"
            date_format = "%d%m%y"

        return zeros_date, date_format

    def process_return_file(self, data):

        #          Forma de Lançamento do Retorno
        # Em caso de Pagamento/Liquidação é feita a criação de uma Entrada de
        # Diário para cada linha do arquivo CNAB com os valores de desconto,
        # juros/mora, tarifa bancaria, abatimento, valor total
        #
        # Quando marcada a opção de Reconciliação Automatica no Diário
        # a Fatura será Reconciliada e a Entrada de Diário será movida
        # para o status Lançado

        #          Valor Recebido diferente do Valor no Odoo
        # Para que a Fatura/Nota Fiscal no Odoo seja completamente reconciliada
        # e o seu status seja alterado de Aberto para Pago é preciso que
        # a soma dos lançamentos(account.move.line) criados sejam iguais ao
        # valor da account_move_line em aberto.
        #
        #                      Valor Menor
        # Os valores de Desconto e Abatimento estão sendo adicionados no
        # lançamento referente ao pagamento para que o valor total fique
        # igual, exemplo:
        #        Odoo               Valores arquivo CNAB
        #    Valor Odoo   | Valor Recebido | Valor Abatimento |Valor Desconto
        #       100       |     80         |       10         |     10
        #
        # O Valor Recebido a ser lançado será 100.
        #
        # Caso o valor ainda seja menor nada pode ser feito aqui, pois
        # significa que o cliente pagou um valor menor sem relação com o
        # Abatimento e ou Desconto e que ainda está em Aberto junto ao banco.
        # TODO: Existe a possibilidade de Pagar um valor menor junto ao banco ?
        # Se for preciso Baixar esse valor o processo deverá ser utilizar o
        # wizard de atualizações do CNAB, na account.move.line referente,
        # pedindo a Baixa do Título o que irá gerar um nova Linha/Instrução
        # CNAB a ser enviada ao Banco.
        #
        #                   Valor Maior
        # Caso comum onde o devido o valor de Juros Mora/Multa faz com que
        # os valores fiquem diferentes, exemplo:
        #        Odoo               Valores arquivo CNAB
        #    Valor Odoo   | Valor Recebido | Juros Mora + Multa*
        #       100       |     110        |       10
        #
        # O Valor Recebido a ser lançado será 100.
        # *valores vem somados no CNAB Unicred 400, seria um padrão ?
        #
        # Não é possível relacionar esse lançamento contabil diretamente isso
        # causa erro, seria preciso "Cancelar o Lançamento" da account.move(
        # referente a invoice que gerou o CNAB) é criar uma nova linha
        # assim aumentando o valor original da Fatura, na primeira linha que
        # fosse recebida esse processo até poderia funcionar mas no caso de uma
        # segunda, terceira, etc acredito que o programa já não permitiria mais
        # Cancelar o Lançamento existindo reconciliações parciais.
        # Por isso Nesse caso é preciso alterar o Valor Recebido
        # ( Valor Recebido - Valor Juros Mora/Multa )
        # TODO: Deveria existir uma forma de mostrar o Valor de Juros/Multa na
        #  tela da Fatura/Invoice, e assim o usuário poder visualizar isso ?
        # Porque não vai existir um relacionamento direto de conciliação,
        # apenas a referencia no bank_payment_line_id da account.move.line,
        # e caso se queira saber os detalhes será preciso olhar a Entrada de
        # Diário referente.

        # Lista com os dados q poderão ser usados
        # na criação das account move line
        result_row_list = []

        bank_name_brcobranca = dict_brcobranca_bank[self.bank.code_bc]

        if bank_name_brcobranca == "ailos":
            # No AILOS o código de registro onde ficam as linhas CNAB é o 3.
            registration_code_allowed = 3
        elif bank_name_brcobranca == "banco_brasil":
            # No Banco do Brasil o código do registro principal é o 7.
            # existem registros opcionais porém como não estão mapeados no BRCobrança
            # e serão ignorados aqui. Teoricamente a verificação do código do registro
            # nem deveria se feita aqui, cada dict retornado da lib era pra representar
            # um registro completo do boleto. Esse tratamento deveria estar lá.
            registration_code_allowed = 7
        else:
            registration_code_allowed = 1

        for linha_cnab in data:

            if int(linha_cnab["codigo_registro"]) != registration_code_allowed:
                # Bradesco
                # Existe o codigo de registro 9 que eh um totalizador
                # porem os campos estao colocados em outras posicoes
{'fraction_non_alphanumeric': 0.06474730479217299, 'fraction_numerical': 0.014582164890633763, 'mean_word_length': 2.1252190847127554, 'pattern_counts': {'":': 41, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '13871', 'n_tokens_mistral': 5304, 'n_tokens_neox': 5058, 'n_words': 1630}