Skip to content

Commit

Permalink
Merge PR #2123 into 14.0
Browse files Browse the repository at this point in the history
Signed-off-by rvalyi
  • Loading branch information
OCA-git-bot committed Sep 30, 2022
2 parents a6ea1fd + 05863d6 commit 6b760a5
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 6 deletions.
1 change: 1 addition & 0 deletions l10n_br_account/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"views/l10n_br_account_menu.xml",
# Report
# "report/account_invoice_report_view.xml",
"views/res_partner_view.xml",
],
"demo": [
"demo/res_users_demo.xml",
Expand Down
34 changes: 34 additions & 0 deletions l10n_br_account/views/res_partner_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--
Partners Extension
-->
<record id="l10n_br_account_partner_form" model="ir.ui.view">
<field name="name">l10n_br_account.res.partner.form</field>
<field name="model">res.partner</field>
<field name="priority">99</field>
<field name="inherit_id" ref="account.view_partner_property_form" />
<field name="arch" type="xml">
<group position="after" name="banks">
<div />
<group
string="Brazilian Instant Payment Keys (PIX)"
name="pix_keys"
groups="account.group_account_invoice,account.group_account_readonly"
attrs="{'invisible': [('show_l10n_br', '=', False)]}"
>
<field name="show_l10n_br" invisible="1" />
<field name="pix_key_ids" nolabel="1">
<tree editable="bottom">
<field name="partner_id" invisible="1" />
<field name="sequence" widget="handle" />
<field name="key_type" />
<field name="key" />
<field name="partner_bank_id" />
</tree>
</field>
</group>
</group>
</field>
</record>
</odoo>
5 changes: 4 additions & 1 deletion l10n_br_base/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
"demo/res_partner_demo.xml",
"demo/res_company_demo.xml",
"demo/res_users_demo.xml",
"demo/res_partner_pix_demo.xml",
],
"installable": True,
"pre_init_hook": "pre_init_hook",
"development_status": "Mature",
"external_dependencies": {"python": ["num2words", "erpbrasil.base"]},
"external_dependencies": {
"python": ["num2words", "erpbrasil.base", "phonenumbers", "email_validator"]
},
}
32 changes: 32 additions & 0 deletions l10n_br_base/demo/res_partner_pix_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<!--
Resource: res.partner.pix
Partner: AMD do Brasil
-->
<record id="res_partner_amd_pix_cnpj" model="res.partner.pix">
<field name="partner_id" ref="res_partner_amd" />
<field name="key_type">cnpj_cpf</field>
<field name="key">62228384000151</field>
</record>

<record id="res_partner_amd_pix_phone" model="res.partner.pix">
<field name="partner_id" ref="res_partner_amd" />
<field name="key_type">phone</field>
<field name="key">1144576060</field>
</record>

<record id="res_partner_amd_pix_email" model="res.partner.pix">
<field name="partner_id" ref="res_partner_amd" />
<field name="key_type">email</field>
<field name="key">[email protected]</field>
</record>

<record id="res_partner_amd_evp" model="res.partner.pix">
<field name="partner_id" ref="res_partner_amd" />
<field name="key_type">evp</field>
<field name="key">123e4567-e12b-12d1-a456-426655440000</field>
</record>

</odoo>
1 change: 1 addition & 0 deletions l10n_br_base/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from . import state_tax_numbers
from . import res_company
from . import res_config_settings
from . import res_partner_pix
22 changes: 22 additions & 0 deletions l10n_br_base/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ class Partner(models.Model):

union_entity_code = fields.Char(string="Union Entity code")

pix_key_ids = fields.One2many(
string="Pix Keys",
comodel_name="res.partner.pix",
inverse_name="partner_id",
help="Keys for Brazilian instant payment (pix)",
)

show_l10n_br = fields.Boolean(
compute="_compute_show_l10n_br",
help="Indicates if Brazilian localization fields should be displayed.",
)

@api.constrains("cnpj_cpf", "inscr_est")
def _check_cnpj_inscr_est(self):
for record in self:
Expand Down Expand Up @@ -176,3 +188,13 @@ def _address_fields(self):
@api.onchange("city_id")
def _onchange_city_id(self):
self.city = self.city_id.name

def _compute_show_l10n_br(self):
"""
Defines when Brazilian localization fields should be displayed.
"""
for rec in self:
if rec.company_id and rec.company_id.country_id != self.env.ref("base.br"):
rec.show_l10n_br = False
else:
rec.show_l10n_br = True
52 changes: 52 additions & 0 deletions l10n_br_base/models/res_partner_bank.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
("13", _("Conta depósito judicial/Depósito em consignação conjunta")),
]

TRANSACTIONAL_ACCOUNT_TYPE = [
("checking", _("Checking Account (Conta Corrente)")),
("saving", _("Saving Account (Conta Poupança)")),
("payment", _("Prepaid Payment Account (Conta Pagamento)")),
]


class ResPartnerBank(models.Model):
"""Adiciona campos necessários para o cadastramentos de contas
Expand All @@ -28,6 +34,19 @@ class ResPartnerBank(models.Model):
default="01",
)

transactional_acc_type = fields.Selection(
selection=TRANSACTIONAL_ACCOUNT_TYPE,
string="Account Type",
help="Type of transactional account, classification used in "
"the Brazilian instant payment system (PIX)",
)

partner_pix_ids = fields.One2many(
comodel_name="res.partner.pix",
inverse_name="partner_bank_id",
string="Pix Keys",
)

acc_number = fields.Char(
string="Account Number",
size=64,
Expand Down Expand Up @@ -55,9 +74,42 @@ class ResPartnerBank(models.Model):
help="Last part of BIC/Swift Code.",
)

company_country_id = fields.Many2one(
comodel_name="res.country",
string="Company Country",
related="company_id.country_id",
)

@api.constrains("bra_number")
def _check_bra_number(self):
for b in self:
if b.bank_id.code_bc:
if len(b.bra_number) > 4:
raise UserError(_("Bank branch code must be four caracteres."))

@api.constrains(
"transactional_acc_type",
"bank_id",
"acc_number",
"bra_number",
"acc_number_dig",
)
def _check_transc_acc_type(self):
for rec in self:
if rec.transactional_acc_type:
if not rec.bank_id or not rec.bank_id.code_bc or not rec.acc_number:
raise UserError(
_(
"a transactional account must contain the bank "
"information (code_bc) and the account number"
)
)
if rec.transactional_acc_type in ["checking", "saving"]:
if not rec.bra_number or not rec.acc_number_dig:
raise UserError(
_(
"A Checking Account or Saving Account transactional account "
"must contain the branch number and the account verification "
"digit."
)
)
150 changes: 150 additions & 0 deletions l10n_br_base/models/res_partner_pix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import phonenumbers
from email_validator import EmailSyntaxError, validate_email
from erpbrasil.base.fiscal import cnpj_cpf

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class PartnerPix(models.Model):
_name = "res.partner.pix"
_description = "Brazilian instant payment ecosystem (Pix)"
_order = "sequence, id"
_rec_name = "key"

_sql_constraints = [
(
"partner_pix_key_unique",
"unique(key_type, key, partner_id)",
"A Pix Key with this values already exists in this partner.",
)
]

KEY_TYPES = [
("cnpj_cpf", _("CPF or CNPJ")),
("phone", _("Phone Number")),
("email", _("E-mail")),
("evp", _("Random Key")),
]

partner_id = fields.Many2one(
comodel_name="res.partner",
string="Partner",
ondelete="cascade",
required=True,
)
sequence = fields.Integer(default=10)
key_type = fields.Selection(
selection=KEY_TYPES,
string="Type",
required=True,
)
key = fields.Char(
help="PIX Addressing key",
required=True,
)

partner_bank_id = fields.Many2one(
comodel_name="res.partner.bank",
string="Bank Account",
domain="[('partner_id', '=', partner_id)]",
)

def _normalize_email(self, email):
try:
result = validate_email(
email,
check_deliverability=False,
)
except EmailSyntaxError:
raise ValidationError(_(f"{email.strip()} is an invalid email"))
normalized_email = result["local"].lower() + "@" + result["domain_i18n"]
if len(normalized_email) > 77:
raise ValidationError(
_(
f"The email is too long, "
f"a maximum of 77 characters is allowed: \n{email.strip()}"
)
)
return normalized_email

def _normalize_phone(self, phone):
try:
phonenumber = phonenumbers.parse(phone, "BR")
except phonenumbers.phonenumberutil.NumberParseException as e:
raise ValidationError(_(f"Unable to parse {phone}: {str(e)}"))
if not phonenumbers.is_possible_number(phonenumber):
raise ValidationError(
_(f"Impossible number {phone}: probably invalid number of digits.")
)
if not phonenumbers.is_valid_number(phonenumber):
raise ValidationError(
_(f"Invalid number {phone}: probably incorrect prefix.")
)
phone = phonenumbers.format_number(
phonenumber, phonenumbers.PhoneNumberFormat.E164
)
return phone

def _normalize_cnpj_cpf(self, doc_number):
doc_number = "".join(char for char in doc_number if char.isdigit())
if not 11 <= len(doc_number) <= 14:
raise ValidationError(
_(
f"Invalid Document Number {doc_number}: "
f"\nThe CPF must have 11 digits and the CNPJ 14 digits."
)
)
is_valid = cnpj_cpf.validar(doc_number)
if not is_valid:
raise ValidationError(_(f"Invalid Document Number: {doc_number}"))
return doc_number

def _normalize_evp(self, key):
# EVP: Endereço Virtual de Pagamento (chave aleatória)
# ex: 123e4567-e12b-12d1-a456-426655440000
key = "".join(key.split())
if len(key) != 36:
raise ValidationError(
_(f"Invalid Random Key: {key}, cannot be longer than 35 characters")
)
blocks = key.split("-")
if len(blocks) != 5:
raise ValidationError(
_(f"Invalid Random Key: {key}, the key must consist of five blocks.")
)
for block in blocks:
try:
int(block, 16)
except ValueError:
raise ValidationError(
_(
f"Invalid Random Key: {key} \nthe block {block} "
f"is not a valid hexadecimal format."
)
)
return key

@api.model
def create(self, vals):
self.check_vals(vals)
return super(PartnerPix, self).create(vals)

def write(self, vals):
self.check_vals(vals)
return super(PartnerPix, self).write(vals)

def check_vals(self, vals):
key_type = vals.get("key_type") or self.key_type
key = vals.get("key") or self.key
if not key or not key_type:
return
if key_type == "email":
key = self._normalize_email(key)
elif key_type == "phone":
key = self._normalize_phone(key)
elif key_type == "cnpj_cpf":
key = self._normalize_cnpj_cpf(key)
elif key_type == "evp":
key = self._normalize_evp(key)
vals["key"] = key
17 changes: 13 additions & 4 deletions l10n_br_base/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
* Renato Lima <[email protected]>
* Raphaël Valyi <[email protected]>
* Luis Felipe Mileo <[email protected]>
* Michell Stuttgart <[email protected]>
* `Akretion <https://www.akretion.com/pt-BR>`_:

* Renato Lima <[email protected]>
* Raphaël Valyi <[email protected]>

* `KMEE <https://www.kmee.com.br>`_:

* Luis Felipe Mileo <[email protected]>
* Michell Stuttgart <[email protected]>

* `Engenere <https://engenere.one>`_:

* Antônio S. Pereira Neto <[email protected]>
3 changes: 2 additions & 1 deletion l10n_br_base/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
Este é o módulo raiz da localização brasileira. Ele traz adaptações nos modelos do módulo ``base`` do Odoo como Parceiros, Empresas e Endereços:
Este é o módulo 'raiz' da localização brasileira. Ele traz adaptações nos modelos do módulo ``base`` do Odoo como Parceiros, Empresas e Endereços:

* Campo CNPJ e CPF com formatação e validação destes campos;
* Campo de Inscrição Estadual com validação;
* Formatação dos campos de endereço;
* Código do Banco Central e Siscomex para países;
* Código do IBGE para estados e municípios;
* Lista dos Bancos brasileiros;
* Contas bancarias e chaves PIX dos parceiros;
* Lista dos municípios brasileiros.

Se trata de um módulo muito simples e maduro. Existem alguns outros módulos simples que dependem apenas desse módulo ou quase como ``l10n_br_crm`` ou ``l10n_br_portal``.
Expand Down
2 changes: 2 additions & 0 deletions l10n_br_base/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"state_tax_numbers_user","State Tax Numbers for User","model_state_tax_numbers","base.group_user",1,0,0,0
"state_tax_numbers_manager","State Tax Numbers for Manager","model_state_tax_numbers","base.group_system",1,1,1,1
"res_partner_pix_user","Partner PIX for User","model_res_partner_pix","base.group_user",1,0,0,0
"res_partner_pix_manager","Partner PIX for Partner Manager","model_res_partner_pix","base.group_partner_manager",1,1,1,1
2 changes: 2 additions & 0 deletions l10n_br_base/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
from . import test_valid_createid
from . import test_base_onchange
from . import test_other_ie
from . import test_valid_pix
from . import test_partner_bank
Loading

0 comments on commit 6b760a5

Please sign in to comment.