Odoo18-Base/addons/l10n_br/models/res_partner_bank.py
2025-01-06 10:57:38 +07:00

128 lines
5.5 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import models, fields, api, _
from odoo.addons.mail.tools.mail_validation import mail_validate
from odoo.exceptions import ValidationError
from odoo.tools import float_repr
class ResPartnerBank(models.Model):
_inherit = "res.partner.bank"
proxy_type = fields.Selection(
selection_add=[
("email", "Email Address"),
("mobile", "Mobile Number"),
("br_cpf_cnpj", "CPF/CNPJ (BR)"),
("br_random", "Random Key (BR)"),
],
ondelete={
"email": "set default",
"mobile": "set default",
"br_cpf_cnpj": "set default",
"br_random": "set default",
},
)
@api.constrains("proxy_type", "proxy_value", "partner_id")
def _check_br_proxy(self):
for bank in self.filtered(lambda bank: bank.country_code == "BR" and bank.proxy_type != "none"):
if bank.proxy_type not in ("email", "mobile", "br_cpf_cnpj", "br_random"):
raise ValidationError(
_(
"The proxy type must be Email Address, Mobile Number, CPF/CNPJ (BR) or Random Key (BR) for Pix code generation."
)
)
value = bank.proxy_value
if bank.proxy_type == "email" and not mail_validate(value):
raise ValidationError(_("%s is not a valid email.", value))
if bank.proxy_type == "br_cpf_cnpj" and (
not self.partner_id.check_vat_br(value) or any(not char.isdecimal() for char in value)
):
raise ValidationError(_("%s is not a valid CPF or CNPJ (don't include periods or dashes).", value))
if bank.proxy_type == "mobile" and (not value or not value.startswith("+55") or len(value) != 14):
raise ValidationError(
_(
"The mobile number %s is invalid. It must start with +55, contain a 2 digit territory or state code followed by a 9 digit number.",
value,
)
)
regex = r"%(char)s{8}-%(char)s{4}-%(char)s{4}-%(char)s{4}-%(char)s{12}" % {"char": "[a-fA-F0-9]"}
if bank.proxy_type == "br_random" and not re.fullmatch(regex, bank.proxy_value):
raise ValidationError(
_(
"The random key %s is invalid, the format looks like this: 71d6c6e1-64ea-4a11-9560-a10870c40ca2",
value,
)
)
@api.depends("country_code")
def _compute_display_qr_setting(self):
"""Override."""
bank_br = self.filtered(lambda b: b.country_code == "BR")
bank_br.display_qr_setting = True
super(ResPartnerBank, self - bank_br)._compute_display_qr_setting()
def _get_additional_data_field(self, comment):
"""Override."""
if self.country_code == "BR":
# Only include characters allowed by the Pix spec.
return self._serialize(5, re.sub(r"[^a-zA-Z0-9*]", "", comment))
return super()._get_additional_data_field(comment)
def _get_qr_code_vals_list(self, *args, **kwargs):
"""Override. Force the amount field to always have two decimals. Uppercase the merchant name and merchant city.
Although not specified explicitly in the spec, not uppercasing causes errors when scanning the code. Also ensure
there is always some comment set."""
res = super()._get_qr_code_vals_list(*args, **kwargs)
if self.country_code == "BR":
res[5] = (res[5][0], float_repr(res[5][1], 2) if res[5][1] else None) # amount
res[7] = (res[7][0], res[7][1].upper()) # merchant_name
res[8] = (res[8][0], res[8][1].upper()) # merchant_city
if not res[9][1]:
res[9] = (res[9][0], self._get_additional_data_field("***")) # default comment if none is set
return res
def _get_merchant_account_info(self):
"""Override."""
if self.country_code == "BR":
merchant_account_info_data = (
(0, "br.gov.bcb.pix"), # GUI
(1, self.proxy_value), # key
)
return 26, "".join(self._serialize(*val) for val in merchant_account_info_data)
return super()._get_merchant_account_info()
def _get_error_messages_for_qr(self, qr_method, debtor_partner, currency):
"""Override."""
if qr_method == "emv_qr" and self.country_code == "BR":
if currency.name != "BRL":
return _("Can't generate a Pix QR code with a currency other than BRL.")
return None
return super()._get_error_messages_for_qr(qr_method, debtor_partner, currency)
def _check_for_qr_code_errors(
self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication
):
"""Override."""
if (
qr_method == "emv_qr"
and self.country_code == "BR"
and self.proxy_type not in ("email", "mobile", "br_cpf_cnpj", "br_random")
):
return _(
"To generate a Pix code the proxy type for %s must be Email Address, Mobile Number, CPF/CNPJ (BR) or Random Key (BR).",
self.display_name,
)
return super()._check_for_qr_code_errors(
qr_method, amount, currency, debtor_partner, free_communication, structured_communication
)