diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py
index 4594bad3b99..0d3f13b4afc 100644
--- a/addons/base_vat/models/res_partner.py
+++ b/addons/base_vat/models/res_partner.py
@@ -32,6 +32,7 @@ _ref_vat = {
'au': '83 914 571 673',
'be': 'BE0477472701',
'bg': 'BG1234567892',
+ 'br': '48.906.308/0001-27',
'ch': 'CHE-123.456.788 TVA or CHE-123.456.788 MWST or CHE-123.456.788 IVA', # Swiss by Yannick Vaucher @ Camptocamp
'cl': 'CL76086428-5',
'co': 'CO213123432-1 or CO213.123.432-1',
diff --git a/addons/l10n_br/__manifest__.py b/addons/l10n_br/__manifest__.py
index 72d26642e9d..79250fc61d5 100644
--- a/addons/l10n_br/__manifest__.py
+++ b/addons/l10n_br/__manifest__.py
@@ -46,9 +46,11 @@ come with any additional paid permission for online use of 'private modules'.
'depends': [
'account',
'base_vat',
+ 'l10n_latam_base',
],
'data': [
'data/account_tax_report_data.xml',
+ 'data/l10n_latam.identification.type.csv',
'views/account_view.xml',
'views/account_fiscal_position_views.xml',
'views/res_company_views.xml',
diff --git a/addons/l10n_br/data/l10n_latam.identification.type.csv b/addons/l10n_br/data/l10n_latam.identification.type.csv
new file mode 100644
index 00000000000..cbaccd170e8
--- /dev/null
+++ b/addons/l10n_br/data/l10n_latam.identification.type.csv
@@ -0,0 +1,3 @@
+id,name,country_id:id,is_vat
+cnpj,CNPJ,base.br,True
+cpf,CPF,base.br,False
diff --git a/addons/l10n_br/i18n/l10n_br.pot b/addons/l10n_br/i18n/l10n_br.pot
index 29313d84c96..caed4ee0adc 100644
--- a/addons/l10n_br/i18n/l10n_br.pot
+++ b/addons/l10n_br/i18n/l10n_br.pot
@@ -243,14 +243,7 @@ msgstr ""
#. odoo-python
#: code:addons/l10n_br/models/res_partner.py:0
#, python-format
-msgid "Invalid CNPJ. Make sure that all the typs are entered correctly."
-msgstr ""
-
-#. module: l10n_br
-#. odoo-python
-#: code:addons/l10n_br/models/res_partner.py:0
-#, python-format
-msgid "Invalid CNPJ. Make sure that the CNPJ is a 14 digits number."
+msgid "CPF number %s for %s is not valid."
msgstr ""
#. module: l10n_br
diff --git a/addons/l10n_br/i18n/pt.po b/addons/l10n_br/i18n/pt.po
index 085b2b28b78..e813b1d2dd0 100644
--- a/addons/l10n_br/i18n/pt.po
+++ b/addons/l10n_br/i18n/pt.po
@@ -230,15 +230,8 @@ msgstr "Interstate Fiscal Position Type"
#. odoo-python
#: code:addons/l10n_br/models/res_partner.py:0
#, python-format
-msgid "Invalid CNPJ. Make sure that all the typs are entered correctly."
-msgstr "Invalid CNPJ. Make sure that all the digits are entered correctly."
-
-#. module: l10n_br
-#. odoo-python
-#: code:addons/l10n_br/models/res_partner.py:0
-#, python-format
-msgid "Invalid CNPJ. Make sure that the CNPJ is a 14 digits number."
-msgstr "Invalid CNPJ. Make sure that the CNPJ is a 14 digits number."
+msgid "CPF number %s for %s is not valid."
+msgstr "O número de CPF %s para %s não é válido."
#. module: l10n_br
#: model:ir.model.fields,field_description:l10n_br.field_account_tax__amount_mva
diff --git a/addons/l10n_br/models/res_partner.py b/addons/l10n_br/models/res_partner.py
index 0a1cb5de94b..8b674741b50 100644
--- a/addons/l10n_br/models/res_partner.py
+++ b/addons/l10n_br/models/res_partner.py
@@ -1,45 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import stdnum.br.cpf
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
-import re
class ResPartner(models.Model):
_inherit = 'res.partner'
- l10n_br_cpf_code = fields.Char(string="CPF", help="Natural Persons Register.")
l10n_br_ie_code = fields.Char(string="IE", help="State Tax Identification Number. Should contain 9-14 digits.")
l10n_br_im_code = fields.Char(string="IM", help="Municipal Tax Identification Number")
l10n_br_isuf_code = fields.Char(string="SUFRAMA code", help="SUFRAMA registration number.")
- @api.constrains("vat")
- def check_vat(self):
- '''
- Example of a Brazilian CNPJ number: 76.634.583/0001-74.
- The 13th digit is the check digit of the previous 12 digits.
- The check digit is calculated by multiplying the first 12 digits by weights and calculate modulo 11 of the result.
- The 14th digit is the check digit of the previous 13 digits. Calculated the same way.
- Both remainders are appended to the first 12 digits.
- '''
- def _l10n_br_calculate_mod_11(check, weights):
- result = (sum([i*j for (i, j) in zip(check, weights)])) % 11
- if result <= 1:
- return 0
- return 11 - result
-
- for partner in self:
- if not partner.vat:
- return
- if not partner.country_code == 'BR':
- return super().check_vat()
- weights = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
- vat_clean = list(map(int, re.sub("[^0-9]", "", partner.vat)))
- if len(vat_clean) != 14:
- raise ValidationError(_("Invalid CNPJ. Make sure that the CNPJ is a 14 digits number."))
- vat_check = vat_clean[:12]
- vat_check.append(_l10n_br_calculate_mod_11(vat_check, weights[1:]))
- vat_check.append(_l10n_br_calculate_mod_11(vat_check, weights))
- if vat_check != vat_clean:
- raise ValidationError(_("Invalid CNPJ. Make sure that all the digits are entered correctly."))
+ @api.constrains('vat', 'l10n_latam_identification_type_id')
+ def check_cpf(self):
+ for partner in self.filtered(lambda partner: partner.l10n_latam_identification_type_id == self.env.ref('l10n_br.cpf')):
+ if partner.vat and not stdnum.br.cpf.is_valid(partner.vat):
+ raise ValidationError(_('CPF number %s for %s is not valid.') % (partner.vat, partner.display_name))
diff --git a/addons/l10n_br/views/res_partner_views.xml b/addons/l10n_br/views/res_partner_views.xml
index 40442a837c0..ad803236c02 100644
--- a/addons/l10n_br/views/res_partner_views.xml
+++ b/addons/l10n_br/views/res_partner_views.xml
@@ -7,7 +7,6 @@
-