diff --git a/l10n_br_base/models/party_mixin.py b/l10n_br_base/models/party_mixin.py index 04557895aef8..56274037ecab 100644 --- a/l10n_br_base/models/party_mixin.py +++ b/l10n_br_base/models/party_mixin.py @@ -12,20 +12,6 @@ class PartyMixin(models.AbstractModel): _name = "l10n_br_base.party.mixin" _description = "Brazilian partner and company data mixin" - cnpj_cpf_stripped = fields.Char( - string="CNPJ/CPF Stripped", - help="CNPJ/CPF without special characters", - compute="_compute_cnpj_cpf_stripped", - store=True, - index=True, - ) - - cnpj_cpf = fields.Char( - string="CNPJ/CPF", - size=18, - unaccent=False, - ) - inscr_est = fields.Char( string="State Tax Number", size=17, @@ -74,19 +60,9 @@ class PartyMixin(models.AbstractModel): size=32, ) - @api.depends("cnpj_cpf") - def _compute_cnpj_cpf_stripped(self): - for record in self: - if record.cnpj_cpf: - record.cnpj_cpf_stripped = "".join( - char for char in record.cnpj_cpf if char.isalnum() - ) - else: - record.cnpj_cpf_stripped = False - - @api.onchange("cnpj_cpf") - def _onchange_cnpj_cpf(self): - self.cnpj_cpf = cnpj_cpf.formata(str(self.cnpj_cpf)) + @api.onchange("vat") + def _onchange_vat(self): + self.vat = cnpj_cpf.formata(str(self.vat)) @api.onchange("zip") def _onchange_zip(self): diff --git a/l10n_br_base/models/res_company.py b/l10n_br_base/models/res_company.py index 59668ba75cb6..e3d96633ea86 100644 --- a/l10n_br_base/models/res_company.py +++ b/l10n_br_base/models/res_company.py @@ -87,6 +87,17 @@ def _inverse_suframa(self): for company in self: company.partner_id.suframa = company.suframa + # this helps maintaining the compatibility with the existing codebase: + cnpj_cpf = fields.Char(related="vat", readonly=False, string="CNPJ/CPF") + + cnpj_cpf_stripped = fields.Char( + string="CNPJ/CPF Stripped", + help="CNPJ/CPF without special characters", + compute="_compute_cnpj_cpf_stripped", + store=True, + index=True, + ) + legal_name = fields.Char( compute="_compute_address", inverse="_inverse_legal_name", @@ -143,6 +154,16 @@ def _inverse_suframa(self): inverse="_inverse_suframa", ) + @api.depends("cnpj_cpf") + def _compute_cnpj_cpf_stripped(self): + for record in self: + if record.cnpj_cpf: + record.cnpj_cpf_stripped = "".join( + char for char in record.cnpj_cpf if char.isalnum() + ) + else: + record.cnpj_cpf_stripped = False + @api.model def _fields_view_get( self, view_id=None, view_type="form", toolbar=False, submenu=False diff --git a/l10n_br_base/models/res_partner.py b/l10n_br_base/models/res_partner.py index a1c82783f951..82e707356801 100644 --- a/l10n_br_base/models/res_partner.py +++ b/l10n_br_base/models/res_partner.py @@ -30,7 +30,22 @@ def _inverse_street_data(self): partner.street = street return super(Partner, not_br_partner)._inverse_street_data() - vat = fields.Char(related="cnpj_cpf") + # this helps maintaining the compatibility with the existing codebase: + cnpj_cpf = fields.Char( + string="CNPJ/CPF", + inverse="_inverse_cnpj_cpf", + compute="_compute_cnpj_cpf", + ) + + cnpj_cpf_stripped = fields.Char( + string="CNPJ/CPF Stripped", + help="CNPJ/CPF without special characters", + compute="_compute_cnpj_cpf_stripped", + store=True, + index=True, + ) + + l10n_br_cpf_code = fields.Char(string="CPF", help="Natural Persons Register.") is_accountant = fields.Boolean(string="Is accountant?") @@ -61,7 +76,82 @@ def _inverse_street_data(self): help="Indicate if is a Brazilian partner", ) - @api.constrains("cnpj_cpf", "inscr_est") + @api.onchange("cnpj_cpf") + def _inverse_cnpj_cpf(self): + for partner in self: + if len(partner.cnpj_cpf) > 11: + partner.vat = partner.cnpj_cpf + else: + partner.l10n_br_cpf_code = partner.cnpj_cpf + + @api.depends("vat") + def _compute_cnpj_cpf(self): + for partner in self: + partner.cnpj_cpf = partner.vat or partner.l10n_br_cpf_code + + @api.depends("cnpj_cpf") + def _compute_cnpj_cpf_stripped(self): + for record in self: + if record.cnpj_cpf: + record.cnpj_cpf_stripped = "".join( + char for char in record.cnpj_cpf if char.isalnum() + ) + else: + record.cnpj_cpf_stripped = False + + def _commercial_sync_from_company(self): + """ + Overriden to avoid copying the CNPJ (vat field) to children companies + """ + if not self.is_br_partner: + return super()._commercial_sync_from_company() + + commercial_partner = self.commercial_partner_id + if commercial_partner != self: + sync_vals = commercial_partner._update_fields_values( + [field for field in self._commercial_fields() if field != "vat"] + ) + self.write(sync_vals) + self._commercial_sync_to_children() + + def _commercial_sync_to_children(self, fields_to_sync=None): + """ + Overriden to avoid copying the CNPJ (vat field) to parent partners + """ + if not self.is_br_partner: + return super()._commercial_sync_to_children() + + commercial_partner = self.commercial_partner_id + if fields_to_sync is None: + fields_to_sync = self._commercial_fields() + sync_vals = commercial_partner._update_fields_values( + [field for field in self._commercial_fields() if field != "vat"] + ) + sync_children = self.child_ids.filtered(lambda c: not c.is_company) + for child in sync_children: + child._commercial_sync_to_children(fields_to_sync) + res = sync_children.write(sync_vals) + return res + + def _children_sync(self, values): + if not self.is_br_partner: + return super()._children_sync() + + if not self.child_ids: + return + # 2a. Commercial Fields: sync if commercial entity + if self.commercial_partner_id == self: + fields_to_sync = values.keys() & [ + field for field in self._commercial_fields() if field != "vat" + ] + self.sudo()._commercial_sync_to_children(fields_to_sync) + # 2b. Address fields: sync if address changed + address_fields = self._address_fields() + if any(field in values for field in address_fields): + contacts = self.child_ids.filtered(lambda c: c.type == "contact") + contacts.update_address(values) + + @api.constrains("vat", "inscr_est") def _check_cnpj_inscr_est(self): for record in self: domain = [] @@ -88,7 +178,8 @@ def _check_cnpj_inscr_est(self): domain += [("cnpj_cpf", "=", record.cnpj_cpf), ("id", "!=", record.id)] # se encontrar CNPJ iguais - if record.env["res.partner"].search(domain): + matches = record.env["res.partner"].search(domain) + if matches: if cnpj_cpf.validar_cnpj(record.cnpj_cpf): if allow_cnpj_multi_ie == "True": for partner in record.env["res.partner"].search(domain): @@ -104,14 +195,25 @@ def _check_cnpj_inscr_est(self): ) else: raise ValidationError( - _("There is already a partner record with this CNPJ !") + _( + "There is already a partner %(name)s " + "(ID %(partner_id)s) with this CNPJ %(vat)s!", + name=matches[0].name, + partner_id=matches[0].id, + vat=self.vat, + ) ) else: raise ValidationError( - _("There is already a partner record with this CPF/RG!") + _( + "There is already a partner %(name)s (ID %(partner_id)s) " + "with this CPF/RG!", + name=matches[0].name, + partner_id=matches[0].id, + ) ) - @api.constrains("cnpj_cpf", "country_id") + @api.constrains("vat", "country_id") def _check_cnpj_cpf(self): for record in self: check_cnpj_cpf( diff --git a/l10n_br_base/tests/test_base_onchange.py b/l10n_br_base/tests/test_base_onchange.py index 9f12ff8cc301..001820d15a42 100644 --- a/l10n_br_base/tests/test_base_onchange.py +++ b/l10n_br_base/tests/test_base_onchange.py @@ -43,7 +43,7 @@ def test_onchange(self): """ Call all the onchange methods in l10n_br_base """ - self.company_01._onchange_cnpj_cpf() + self.company_01._onchange_vat() self.company_01._onchange_city_id() self.company_01._onchange_zip() self.company_01._onchange_state() @@ -53,7 +53,7 @@ def test_onchange(self): # chamado, por isso existe outro metodo com o final _id self.company_01._onchange_state_id() - self.partner_01._onchange_cnpj_cpf() + self.partner_01._onchange_vat() self.partner_01._onchange_city_id() self.partner_01._onchange_zip() self.partner_01._onchange_state()