Example #1
0
class AccountMoveLine(models.Model):

    _inherit = 'account.move.line'

    pdf_boleto_id = fields.Many2one(comodel_name='ir.attachment',
                                    string='PDF Boleto',
                                    ondelete='cascade')

    own_number = fields.Integer(string='Nosso número', help="Nosso número")

    def generate_pdf_boleto(self):
        """
        Creates a new attachment with the Boleto PDF
        """
        if self.own_number and self.pdf_boleto_id:
            return

        order_id = self.payment_line_ids[0].order_id
        with ArquivoCertificado(order_id.journal_id, 'w') as (key, cert):
            partner_bank_id = self.journal_id.bank_account_id
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=(
                    order_id.company_partner_bank_id.acc_number +
                    order_id.company_partner_bank_id.acc_number_dig))
            datas = self.api.boleto_pdf(self.own_number)
            self.pdf_boleto_id = self.env['ir.attachment'].create({
                'name': ("Boleto %s" % self.bank_payment_line_id.display_name),
                'datas':
                datas,
                'datas_fname':
                ("boleto_%s.pdf" % self.bank_payment_line_id.display_name),
                'type':
                'binary'
            })

    def print_pdf_boleto(self):
        """
        Generates and downloads Boletos PDFs
        :return: actions.act_url
        """

        self.generate_pdf_boleto()

        if self.own_number:
            boleto_id = self.pdf_boleto_id
            base_url = self.env['ir.config_parameter'].get_param(
                'web.base.url')
            download_url = '/web/content/%s/%s?download=True' % (str(
                boleto_id.id), boleto_id.name)

            return {
                "type": "ir.actions.act_url",
                "url": str(base_url) + str(download_url),
                "target": "new",
            }
Example #2
0
class PaymentTransaction(models.Model):
    _inherit = "payment.transaction"

    transaction_url = fields.Char(string="Url de Pagamento", size=256)
    origin_move_line_id = fields.Many2one("account.move.line")
    date_maturity = fields.Date(string="Data de Vencimento")

    pdf_boleto_id = fields.Many2one(comodel_name="ir.attachment",
                                    string="PDF Boleto",
                                    ondelete="cascade")

    def _isBase64(self, sb):
        try:
            if isinstance(sb, str):
                # If there's any unicode here, an exception will be thrown and the function will return false
                sb_bytes = bytes(sb, "ascii")
                # _logger.error("my sb_bytes : %r", sb_bytes)
            elif isinstance(sb, bytes):
                sb_bytes = sb
                # _logger.error("my sb_bytes : %r", sb_bytes)
            else:
                raise ValidationError(
                    "Cannot download invalid base64 (.pdf) file")
            return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes
        except Exception:
            raise ValidationError("Cannot download invalid base64 (.pdf) file")
            return False

    def generate_pdf_boleto(self):
        """
        Creates a new attachment with the Boleto PDF
        """

        # _logger.error("my acquirer_reference : %r", self.acquirer_reference)

        if self.acquirer_reference and self.pdf_boleto_id:
            return

        with ArquivoCertificado(self.acquirer_id, "w") as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=(
                    self.acquirer_id.journal_id.bank_account_id.acc_number +
                    self.acquirer_id.journal_id.bank_account_id.acc_number_dig
                ),
            )
            datas = self.api.boleto_pdf(self.acquirer_reference)
            if self._isBase64(datas):
                self.pdf_boleto_id = self.env["ir.attachment"].create({
                    # "res_model": "payment.transaction",
                    "name": ("Boleto %s.pdf" % self.display_name),
                    "datas":
                    datas,
                    "type":
                    "binary",
                })
            else:
                raise ValidationError(
                    "Cannot download invalid base64 (.pdf) file")

    def print_pdf_boleto(self):
        """
        Generates and downloads Boletos PDFs
        :return: actions.act_url
        """

        self.generate_pdf_boleto()

        if self.acquirer_reference:
            boleto_id = self.pdf_boleto_id
            iddoboleto = boleto_id.id
            base_url = self.env["ir.config_parameter"].get_param(
                "web.base.url")
            download_url = "/web/content/%s/%s?download=True" % (
                str(boleto_id.id),
                boleto_id.name.replace("/", "_"),
            )

            return {
                "type": "ir.actions.act_url",
                "url": str(base_url) + str(download_url),
                "target": "new",
            }

    def cron_verify_transaction(self):
        documents = self.search(
            [
                ("state", "in", ["draft", "pending"]),
            ],
            limit=50,
        )
        for doc in documents:
            try:
                doc.action_verify_transaction()
                self.env.cr.commit()
            except Exception as e:
                self.env.cr.rollback()
                _logger.exception(
                    "Payment Transaction ID {}: {}.".format(doc.id, str(e)),
                    exc_info=True,
                )

    def action_verify_transaction(self):
        if self.acquirer_id.provider != "apiboletointer":
            return
        if not self.acquirer_reference:
            raise UserError(
                "Esta transação não foi enviada a nenhum gateway de pagamento")

        with ArquivoCertificado(self.acquirer_id, "w") as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=(
                    self.acquirer_id.journal_id.bank_account_id.acc_number +
                    self.acquirer_id.journal_id.bank_account_id.acc_number_dig
                ),
            )
            data = self.api.boleto_recupera(self.acquirer_reference)

        # EMABERTO, BAIXADO e VENCIDO e PAGO
        if "errors" in data or not data:
            raise UserError(data)
        if data["situacao"] == "EMABERTO" and self.state in ("draft"):
            self._set_transaction_pending()

        if data["situacao"] == "PAGO" and self.state not in ("done",
                                                             "authorized"):
            self._set_transaction_done()
            self._post_process_after_done()
            # if self.origin_move_line_id:
            # self.origin_move_line_id._create_bank_tax_move(
            #    (data.get('taxes_paid_cents') or 0) / 100)
        # else:
        # self.iugu_status = data['status']

    def cancel_transaction_in_inter(self):
        if not self.acquirer_reference:
            raise UserError(
                "Esta transação não foi enviada a nenhum gateway de pagamento")
        with ArquivoCertificado(self.acquirer_id, "w") as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=(
                    self.acquirer_id.journal_id.bank_account_id.acc_number +
                    self.acquirer_id.journal_id.bank_account_id.acc_number_dig
                ),
            )
            data = self.api.boleto_baixa(self.acquirer_reference,
                                         "SUBISTITUICAO")

    def action_cancel_transaction(self):
        self._set_transaction_cancel()
        if self.acquirer_id.provider == "apiboletointer":
            self.cancel_transaction_in_inter()
class GenerateBoletoInvoice(models.Model):

    _inherit = 'account.invoice'

    nossonumero = fields.Char('Nosso Numero', size=30)  # Armazena o nosso numero do boleto
    boleto_codigo_baixa = fields.Selection([
        ('ACERTOS', 'ACERTOS'),
        ('PROTESTADO', 'PROTESTADO'),
        ('DEVOLUCAO', 'DEVOLUCAO'),
        ('PROTESTOAPOSBAIXA', 'PROTESTOAPOSBAIXA'),
        ('PAGODIRETOAOCLIENTE', 'PAGODIRETOAOCLIENTE'),
        ('SUBISTITUICAO', 'SUBISTITUICAO'),
        ('FALTADESOLUCAO', 'FALTADESOLUCAO'),
        ('APEDIDODOCLIENTE', 'APEDIDODOCLIENTE')],
        string='Codigo da Baixa')  # Em caso de baixas no boleto, armazena o codigo da baixa.

    def dados_boleto(self):
        journal = self.env['account.journal'].search([('code', '=', 'Inter')])
        dados = []
        myself = User(
            name=self.company_id.legal_name,
            identifier=misc.punctuation_rm(self.company_id.cnpj_cpf),
            bank=UserBank(
                bankId=journal.bank_account_id.bank_id.bic,
                branchCode=journal.bank_account_id.bra_number,
                accountNumber=journal.bank_account_id.acc_number,
                accountVerifier=journal.bank_account_id.acc_number_dig,
                bankName=journal.bank_account_id.bank_id.name
            ),
        )
        payer = User(
            name=self.partner_id.name,
            identifier=misc.punctuation_rm(self.partner_id.cnpj_cpf),
            email=self.partner_id.email,
            personType='FISICA' if self.partner_id.company_type == 'person' else 'JURIDICA',
            phone=caracteres_rm(self.partner_id.phone),
            address=UserAddress(
                streetLine1=self.partner_id.street,
                district=self.partner_id.district,
                city=self.partner_id.city_id.name,
                stateCode=self.partner_id.state_id.code,
                zipCode=misc.punctuation_rm(self.partner_id.zip),
                streetNumber=self.partner_id.number,
            )
        )

        slip = BoletoInter(
            sender=myself,
            amount=self.amount_total,
            payer=payer,
            issue_date=datetime.now(),
            identifier=misc.punctuation_rm(self.partner_id.cnpj_cpf),
            instructions=[
                journal.instrucao1,
                journal.instrucao2,
                journal.instrucao3,
                journal.instrucao4,
            ],
            multa=dict(
                codigoMulta=journal.codigo_multa,
                data=str(self.date_due + timedelta(days=journal.dia_carencia_multa)),
                valor=0 if journal.codigo_multa == 'PERCENTUAL' else journal.valor_multa,
                taxa=0 if journal.codigo_multa == 'VALORFIXO' else journal.taxa_multa
            ),
            mora=dict(
                codigoMora=journal.codigo_mora,
                data=str(self.date_due + timedelta(days=journal.dia_carencia_mora)),
                valor=0 if journal.codigo_mora == 'TAXAMENSAL' else journal.valor_mora,
                taxa=0 if journal.codigo_mora == 'VALORFIXO' else journal.taxa_mora
            ),
            due_date=self.date_due,
        )
        dados.append(slip)
        return dados

    def action_gerar_boleto(self):
        journal = self.env['account.journal'].search([('code', '=', 'Inter')])
        with ArquivoCertificado(journal, 'w') as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig
            )

            if self.pdf_boletos_id:
                raise exceptions.UserError(
                    'Já existe um boleto dessa fatura. Você pode utilizar a opção Download ou enviar por email. '
                    'Caso deseje de fato gerar um novo boleto, você pode usar a opção Atualizar Boleto, ele fará a baixa'
                    ' no banco Inter (Cancelamento) e emitirá um novo boleto!')

            if self.state == 'draft':
                raise exceptions.UserError(
                    'O status da fatura ainda é provisório. Valide a fatura para que então você '
                    'consiga gerar o boleto. Impossível gerar boletos para fatura com status Provisório!')

            data = self.dados_boleto()
            for item in data:
                # Se a fatura ainda não tiver boleto (Nosso numero), então gera.
                if not self.pdf_boletos_id:

                    if self.date_due < datetime.now().date():
                        raise exceptions.UserError(
                            'A data de vencimento da fatura é menor que a data de emissão do boleto'
                            ' (Data atual). Impossível gerar um boleto com a data de vencimento menor'
                            ' que a data de emissão!')

                    # Inclusão do boleto e envio para a API do Inter
                    # os dados do emitente e do pagador estão no dicionarios no inicio do codigo
                    resposta = self.api.boleto_inclui(item._emissao_data())

                    # Salva o nosso numero do boleto na respectiva fatura
                    self.nossonumero = resposta['nossoNumero']

                    # Seta a flag para true, indicando que foi emitido boleto dessa fatura
                    self.boleto_emitido = True

                    # Consultar a API do Inter para localizar e baixar o PDF do boleto gerado
                    # A pesquisa é feita pelo nossonumero e o boleto é codificado em base64
                    boleto_base64 = self.api.boleto_pdf(self.nossonumero)

                    self.pdf_boletos_id = self.env['ir.attachment'].create(
                        {
                            'name': (
                                    "Boleto %s" % self.display_name.replace('/', '-')),
                            'datas': boleto_base64,
                            'datas_fname': ("boleto_%s.pdf" %
                                            self.display_name.replace('/', '-')),
                            'type': 'binary'
                        }
                    )

    def action_consulta_boleto(self):
        """A implementar para ser usado na futura view do painel do boleto"""
        pass

    def action_baixa_boleto(self):
        """
        Essa função realiza operações de Baixa de boleto na API do banco inter. Ela disponibiliza uma janela com todas
        as opções de baixa. Ai escolher o codigo da baixa e clicar em salvar, a API do inter é chamada e enviado a
        requisição.
        """

        journal = self.env['account.journal'].search([('code', '=', 'Inter')])
        with ArquivoCertificado(journal, 'w') as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig
            )

            if not self.pdf_boletos_id:
                raise exceptions.UserError(
                    'Ainda não existe um boleto gerado dessa fatura. Impossível imprimir/baixar pdf '
                    'de um boleto inexistente!')

            if abs((self.date_invoice - datetime.now().date()).days) <= 2:
                raise exceptions.UserError(
                    'O boleto em questão tem menos de dois dias que foi emitido. É provavel que '
                    'ainda não esteja registrado. Aguarde pelo menos dois dias para realizar a baixa')

            # resposta = self.api.boleto_baixa(self.nossonumero, self.boleto_codigo_baixa)

            view_id = self.env.ref('sismais_account_payment_inter_boleto.baixa_boleto_invoice_form').id
            context = self._context.copy()
            return {
                'name': 'Baixa Boleto Inter',
                'view_type': 'form',
                'view_mode': 'tree',
                'views': [(view_id, 'form')],
                'res_model': 'account.invoice',
                'view_id': view_id,
                'type': 'ir.actions.act_window',
                'res_id': self.id,
                'target': 'new',
                'context': context,
            }

    def action_pdf_boleto(self):

        if not self.pdf_boletos_id:
            raise exceptions.UserError(
                'Ainda não existe um boleto gerado dessa fatura. Impossível imprimir/baixar pdf '
                'de um boleto inexistente!')

        boleto_id = self.pdf_boletos_id
        base_url = self.env['ir.config_parameter'].get_param(
            'web.base.url')
        download_url = '/web/content/%s/%s?download=True' % (
            str(boleto_id.id), boleto_id.name)

        return {
            "type": "ir.actions.act_url",
            "url": str(base_url) + str(download_url),
            "target": "new",
        }

    def baixa_recorrente_boleto(self):
        """
        O Odoo possui um proprio sistema de Cron, usarei ele para acionar essa função diariamente.
        O cron pode ser feito na propria interface do Odoo ou definida por XML, nesse caso, usuario um XML,
        Assim que o modulo for instalado, o cron será criado.
        """

        journal = self.env['account.journal'].search([('code', '=', 'Inter')])
        with ArquivoCertificado(journal, 'w') as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig
            )

            #  Data inicial usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD)
            #  Uma data bem antiga para que qualquer boleto não pago, independente do tempo, venha no filtro.
            dt_inicial = '1990-01-01'

            #  Data final usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD)
            dt_final = str(datetime.now().date()).replace('.', '-')

            resposta = self.api.boleto_consulta('PAGOS', dt_inicial, dt_final)
            for i in range(len(resposta['content'])):
                invoice = self.env['account.invoice'].search([('nossonumero', '=', resposta['content'][i]['nossoNumero'])])
                # invoice = self.env['account.invoice'].search([('nossonumero', '=', '00663999953')]) # usada para teste
                if invoice and invoice.state == 'open':
                    Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, invoice.id, False)])
                    payment = Payment.create({
                        'payment_date': datetime.now(),
                        'payment_method_id': 1,
                        'payment_type': 'inbound',
                        'partner_type': 'customer',
                        'partner_id': invoice.partner_id.id,
                        'amount': invoice.amount_total,
                        'journal_id': journal.id,
                        'company_id': invoice.company_id,
                        'currency_id': 6,
                        'payment_difference_handling': 'reconcile'
                    })
                    payment.post()

    def action_atualiza_boleto(self):
        """
        Essa função faz a atualização de boleto. Basicamente ela cancela o boleto atual e gera um novo boleto atualizado.
        Provavelmente existe formas melhores de realizar essa operação, porém na API do Banco Inter existe apenas 4 endpoint
        (Emissão, consulta, baixa e PDF), então o que encontrei no momento foi cancelar com a baixa e emitir um novo boleto.

        Essa operação pode ser cobrada por boletos gerados.
        """
        journal = self.env['account.journal'].search([('code', '=', 'Inter')])
        with ArquivoCertificado(journal, 'w') as (key, cert):
            self.api = ApiInter(
                cert=(cert, key),
                conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig
            )

            if not self.pdf_boletos_id:
                raise exceptions.UserError(
                    'Ainda não existe um boleto gerado dessa fatura. Impossível atualizar um '
                    'boleto inexistente!')

            if self.date < datetime.now().date() + timedelta(days=2):
                raise exceptions.UserError(
                    'O boleto em questão tem menos de dois dias que foi emitido. É provavel que '
                    'ainda não esteja registrado. Aguarde pelo menos dois dias para atualizá-lo')

            # Realiza a baixa do boleto atual
            resposta_baixa = self.api.boleto_baixa(self.nossonumero, 'SUBISTITUICAO')

            # Depois que realizo a baixo lá no Inter, eu apago a justificativa do banco de dados, porque la vai ta o nosso numero
            # do novo boleto, e ele nao foi baixado. Sei que não é a melhor forma, mas futuramente podemos criar uma tabela
            # somente para armazenar os boletos, inclusive para consultas, podemos fazer isso quando for fazer o painel de boleto.
            self.boleto_codigo_baixa = None

            # Na função de de geração de boleto, só é gerado, se não tiver pdf_boletos_id. Esse tratamento existe para
            # a pessoa não gerar duas vezes. POrém, só nosso caso de atualização, precisaremos, então seto como Null
            # e abaixo quando a função for chamada, será gerado normalmente. Eu poderia excluir o registro do boleot,
            # mas futuramente, pode ser que queremos exibi-los em consultas no peinel do boleto.
            self.pdf_boletos_id = None

            # Realiza a emissão do novo boleto
            self.action_gerar_boleto()
class TestBancoApiInter(unittest.TestCase):
    def setUp(self):
        certificado_cert = os.environ.get('certificado_inter_cert')
        certificado_key = os.environ.get('certificado_inter_key')

        self.api = ApiInter(cert=(certificado_cert, certificado_key),
                            conta_corrente='14054310')
        self.dados = []

        myself = User(
            name='KMEE INFORMATICA LTDA',
            identifier='23130935000198',
            bank=UserBank(bankId="341",
                          branchCode="1234",
                          accountNumber="33333",
                          accountVerifier="4",
                          bankName="BANCO ITAU SA"),
        )
        now = datetime.now()
        for i in range(3):
            payer = User(name="Sacado Teste",
                         identifier="26103754097",
                         email="*****@*****.**",
                         personType="FISICA",
                         phone="35988763663",
                         address=UserAddress(
                             streetLine1="Rua dos TESTES",
                             district="CENTRO",
                             city="SAO PAULO",
                             stateCode="SP",
                             zipCode="31327130",
                             streetNumber="15",
                         ))
            slip = BoletoInter(sender=myself,
                               amount_in_cents="100.00",
                               payer=payer,
                               issue_date=now,
                               due_date=now,
                               identifier="456" + str(i),
                               instructions=[
                                   'TESTE 1',
                                   'TESTE 2',
                                   'TESTE 3',
                                   'TESTE 4',
                               ])
            self.dados.append(slip)

    def test_data(self):
        for item in self.dados:
            self.assertTrue(item._emissao_data())

    def test_boleto_api(self):
        for item in self.dados:
            resposta = self.api.boleto_inclui(item._emissao_data())
            item.nosso_numero = resposta['nossoNumero']
            item.seu_numero = resposta['seuNumero']
            item.linha_digitavel = resposta['linhaDigitavel']
            item.barcode = resposta['codigoBarras']

            self.assertListEqual(
                list(resposta.keys()),
                ['seuNumero', 'nossoNumero', 'codigoBarras', 'linhaDigitavel'],
                'Erro ao registrar boleto')

        resposta = self.api.boleto_consulta(data_inicial='2020-01-01',
                                            data_final='2020-12-01',
                                            ordenar_por='SEUNUMERO')
        self.assertTrue(resposta, 'Falha ao consultar boletos')

        for item in self.dados:
            resposta = self.api.boleto_pdf(nosso_numero=item.nosso_numero)
            self.assertTrue(resposta, 'Falha ao imprimir boleto')

        for item in self.dados:
            resposta = self.api.boleto_baixa(
                nosso_numero=item.nosso_numero,
                codigo_baixa='SUBISTITUICAO',
            )
            self.assertTrue(resposta, 'Falha ao Baixar boletos')