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 @@ - + + +