From 6a62cd99cb06db36e8eaf82e2d9e78302f85b3d2 Mon Sep 17 00:00:00 2001 From: Celina Devigili Date: Fri, 16 Oct 2026 15:16:39 +0000 Subject: [PATCH] [FIX] partner_state: keep standard fields in the chatter "Changes?" now only adds fields to the chatter. Fields that Odoo tracks by default are always tracked, with or without lines in Partner State Fields. Before, the _message_track override only logged the fields with "Changes?", so a database without lines logged no partner change. The "Changes?" column now shows a computed field: it is checked and read only for fields with native tracking, and it writes the stored value for the other fields. Existing lines show the right value without a migration. The README explains the options of Partner State Fields and lists the fields that Odoo tracks on contacts by default. In 19.0, also drop the unused initial_values and state_fields_to_track from write(). X-original-commit: ff015cb8d250387c489934bbb402aea4e88e2ed4 --- partner_state/README.rst | 19 ++++- partner_state/i18n/es.po | 11 +++ partner_state/models/res_partner.py | 35 ++------ .../models/res_partner_state_field.py | 20 +++++ partner_state/tests/__init__.py | 1 + .../tests/test_partner_state_tracking.py | 80 +++++++++++++++++++ .../views/res_partner_state_field_views.xml | 4 +- 7 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 partner_state/tests/test_partner_state_tracking.py diff --git a/partner_state/README.rst b/partner_state/README.rst index d14e438ee..b686c6ba0 100644 --- a/partner_state/README.rst +++ b/partner_state/README.rst @@ -16,7 +16,7 @@ Partner State Add three stats for partners:: 'Potential', 'Pending Approval' and 'Approved'. Some information is required so that a partner can be approved. That -information is configured on: Sales/Settings/Address Book/Partner State fields. +information is configured on: Contacts > Configuration > Partner State Fields. You can also configure on company if you want to use partner state on that company or not. @@ -34,9 +34,24 @@ Configuration To configure this module, you need to: #. Set "Use partner state " in the company. -#. Set the field that you required to approve partner in Sale/Configuration/Contact: Partner State Fields. +#. Set the field that you required to approve partner in Contacts > Configuration > Partner State Fields. #. Set the permition to approve to the user. +Each line of Partner State Fields has these options: + +* **Approval?**: the field is required to request the approval. +* **Changes?**: the changes of the field are logged in the chatter. It only + adds fields: the fields that Odoo already tracks are always logged, with or + without a line, and for them this option is checked and can not be unchecked. +* **Track?**: if the field changes on an approved or pending partner, the + partner goes back to Potential. Checking it also checks **Changes?**. +* **Block Edition**: the field can not be edited on an approved or pending + partner. + +The fields that Odoo tracks on contacts by default are Name, Email, Phone, +Related Company, Salesperson, Tax ID and Inlined Complete Address. Other +installed modules can add more fields (for example, ``base_ux`` adds Active). + Usage ===== diff --git a/partner_state/i18n/es.po b/partner_state/i18n/es.po index 456f73483..03c2512d9 100644 --- a/partner_state/i18n/es.po +++ b/partner_state/i18n/es.po @@ -49,8 +49,14 @@ msgstr "Aprobado" msgid "Block Edition" msgstr "Bloquear Edición" +#. module: partner_state +#: model:ir.model.fields,field_description:partner_state.field_res_partner_state_field__chatter_tracking +msgid "Chatter Tracking" +msgstr "Registro en el chatter" + #. module: partner_state #: model:ir.model.fields,field_description:partner_state.field_res_partner_state_field__changes +#: model_terms:ir.ui.view,arch_db:partner_state.view_partner_state_tree_2 msgid "Changes?" msgstr "Cambios?" @@ -186,6 +192,11 @@ msgstr "Seguir, y si cambia, volver a Potencial" msgid "Track changes of the partner in the chatter box" msgstr "Seguimiento de los cambios del Contacto en el cuadro de conversación" +#. module: partner_state +#: model:ir.model.fields,help:partner_state.field_res_partner_state_field__chatter_tracking +msgid "Fields with native tracking are always tracked." +msgstr "Los campos con trazabilidad nativa se registran siempre." + #. module: partner_state #: model:ir.model.fields,field_description:partner_state.field_res_partner_state_field__track msgid "Track?" diff --git a/partner_state/models/res_partner.py b/partner_state/models/res_partner.py index f711f0816..e2055d27f 100644 --- a/partner_state/models/res_partner.py +++ b/partner_state/models/res_partner.py @@ -29,18 +29,6 @@ def _compute_partner_state_enable(self): partners.partner_state_enable = True def write(self, vals): - initial_values = {} - state_fields_to_track = [] - - for line in self.env["res.partner.state_field"].search([("track", "=", True)]): - field_name = line.field_id.name - state_fields_to_track.append(field_name) - if field_name in vals: - for partner in self: - if partner.id not in initial_values: - initial_values[partner.id] = {} - initial_values[partner.id][field_name] = getattr(partner, field_name) - ResPartnerStateField = self.env["res.partner.state_field"] for partner in self.filtered(lambda r: r.partner_state in ["approved", "pending"]): partner_block_fields = ResPartnerStateField.search([("block_edition", "=", True)]).mapped("field_id.name") @@ -105,20 +93,9 @@ def check_fields(self, field_type): return ret def _track_get_fields(self): - default_result = super()._track_get_fields() - - fields_to_exclude = set() - fields_to_include = set() - - for line in self.env["res.partner.state_field"].search([]): - field_name = line.field_id.name - if line.changes: - fields_to_include.add(field_name) - else: - fields_to_exclude.add(field_name) - - final_result = set(default_result) - fields_to_exclude - - final_result.update(fields_to_include) - - return final_result + tracked_fields = set(super()._track_get_fields()) + names = self.env["res.partner.state_field"].search([("changes", "=", True)]).mapped("field_id.name") + # fields_get() with an empty list returns every field + if names: + tracked_fields.update(self.fields_get(names, attributes=())) + return tracked_fields diff --git a/partner_state/models/res_partner_state_field.py b/partner_state/models/res_partner_state_field.py index c83b0ebc3..73944a60d 100644 --- a/partner_state/models/res_partner_state_field.py +++ b/partner_state/models/res_partner_state_field.py @@ -19,12 +19,32 @@ class ResPartnerStateField(models.Model): ) approval = fields.Boolean("Approval?", help="Required for Approval", default=True) changes = fields.Boolean("Changes?", help="Track changes of the partner in the chatter box") + native_tracking = fields.Boolean(compute="_compute_native_tracking") + chatter_tracking = fields.Boolean( + compute="_compute_chatter_tracking", + inverse="_inverse_chatter_tracking", + help="Fields with native tracking are always tracked.", + ) track = fields.Boolean("Track?", help="Track and, if change, go back to Potencial", default=True) block_edition = fields.Boolean( help="Do not allow to edit this field if the partner is approved", default=True, ) + @api.depends("field_id.tracking") + def _compute_native_tracking(self): + for line in self: + line.native_tracking = bool(line.field_id.tracking) + + @api.depends("changes", "native_tracking") + def _compute_chatter_tracking(self): + for line in self: + line.chatter_tracking = line.changes or line.native_tracking + + def _inverse_chatter_tracking(self): + for line in self.filtered(lambda x: not x.native_tracking): + line.changes = line.chatter_tracking + @api.onchange("track") def _compute_changes(self): if self.track: diff --git a/partner_state/tests/__init__.py b/partner_state/tests/__init__.py index f02936a48..d229259f0 100644 --- a/partner_state/tests/__init__.py +++ b/partner_state/tests/__init__.py @@ -4,3 +4,4 @@ ############################################################################## from . import test_partner_state +from . import test_partner_state_tracking diff --git a/partner_state/tests/test_partner_state_tracking.py b/partner_state/tests/test_partner_state_tracking.py new file mode 100644 index 000000000..e2077e881 --- /dev/null +++ b/partner_state/tests/test_partner_state_tracking.py @@ -0,0 +1,80 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## + +from odoo.tests.common import TransactionCase + + +class TestPartnerStateTracking(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.StateField = cls.env["res.partner.state_field"] + cls.StateField.search([]).unlink() + cls.env.company.partner_state_enable = True + cls.partner = cls.env["res.partner"].create({"name": "Tracking Partner", "email": "old@example.com"}) + # records created in the same transaction are not tracked until creation is finalized + cls._flush_tracking(cls.env) + + @staticmethod + def _flush_tracking(env): + env.flush_all() + env.cr.precommit.run() + + def _tracked_fields(self, vals): + """Write vals on the partner and return the field names logged in the chatter.""" + self.partner.message_ids.unlink() + self.partner.write(vals) + self._flush_tracking(self.env) + return set(self.partner.message_ids.tracking_value_ids.field_id.mapped("name")) + + def _add_line(self, field_name, changes): + return self.StateField.create( + { + "field_id": self.env["ir.model.fields"]._get("res.partner", field_name).id, + "changes": changes, + "track": False, + "approval": False, + "block_edition": False, + } + ) + + def test_chatter_tracking_by_state_fields(self): + """ "Changes?" only adds fields to the chatter, it never removes standard ones. + + - Without lines, the standard tracked fields are logged. + - A line with "Changes?" logs a field that is not tracked by default, and an + approved partner does not go back to potential. + - A line without "Changes?" does not stop logging a standard tracked field. + """ + with self.subTest("without lines, a standard tracked field is logged"): + self.assertEqual(self._tracked_fields({"email": "new@example.com"}), {"email"}) + + with self.subTest("a line with changes logs a field not tracked by default and keeps the partner approved"): + self._add_line("ref", changes=True) + self.partner.partner_state_approved() + self.assertEqual(self._tracked_fields({"ref": "REF-1", "email": "other@example.com"}), {"ref", "email"}) + self.assertEqual(self.partner.partner_state, "approved") + + with self.subTest("a line without changes keeps logging a standard tracked field"): + self._add_line("email", changes=False) + self.assertEqual( + self._tracked_fields({"ref": "REF-2", "email": "last@example.com"}), + {"ref", "email"}, + ) + + def test_changes_column_for_standard_fields(self): + """The "Changes?" column is checked and fixed for fields already tracked by Odoo.""" + email_line = self._add_line("email", changes=False) + ref_line = self._add_line("ref", changes=False) + + with self.subTest("a standard field shows changes checked even if the line has it unchecked"): + self.assertTrue(email_line.native_tracking) + self.assertTrue(email_line.chatter_tracking) + + with self.subTest("another field shows and writes the stored changes value"): + self.assertFalse(ref_line.native_tracking) + self.assertFalse(ref_line.chatter_tracking) + ref_line.chatter_tracking = True + self.assertTrue(ref_line.changes) diff --git a/partner_state/views/res_partner_state_field_views.xml b/partner_state/views/res_partner_state_field_views.xml index 394274c8b..a2c817a05 100644 --- a/partner_state/views/res_partner_state_field_views.xml +++ b/partner_state/views/res_partner_state_field_views.xml @@ -7,7 +7,9 @@ - + + +