From f709adbcecebdf67abd339cb1bb2fb80bdf8bb84 Mon Sep 17 00:00:00 2001 From: Quentin Mondot Date: Fri, 24 Oct 2025 16:57:49 +0200 Subject: [PATCH] [IMP] survey_record_generation: feature be able to update existing record --- survey_record_generation/i18n/fr.po | 54 ++++- .../models/survey_record_creation.py | 35 +++- .../models/survey_user_input.py | 23 +++ .../tests/test_survey_record_creation.py | 188 +++++++++++++++++- .../views/survey_survey_views.xml | 54 +++-- 5 files changed, 316 insertions(+), 38 deletions(-) diff --git a/survey_record_generation/i18n/fr.po b/survey_record_generation/i18n/fr.po index 72d581e..4a9e9fd 100644 --- a/survey_record_generation/i18n/fr.po +++ b/survey_record_generation/i18n/fr.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 13:02+0000\n" -"PO-Revision-Date: 2025-10-23 13:02+0000\n" +"POT-Creation-Date: 2025-11-03 16:35+0000\n" +"PO-Revision-Date: 2025-11-03 16:35+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -31,6 +31,11 @@ msgstr "" msgid "Active survey input" msgstr "Participation active" +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__allowed_field_ids +msgid "Allowed Fields" +msgstr "Champs acceptés" + #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__allowed_question_ids msgid "Allowed Question" @@ -55,6 +60,16 @@ msgstr "Associer une valeur à la réponse" msgid "Attempts Count" msgstr "" +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation__field_to_retrieve_existing_records +msgid "" +"Choose the field you want to use to retrieve the existing record. WARNING: " +"We update only the first record found." +msgstr "" +"Choisissez le champs à partir duquel nous allons chercher l'enregistrement " +"existant. Attention : nous mettons à jour seulement le premier " +"enregistrement trouvé." + #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__create_uid #: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__create_uid @@ -102,6 +117,11 @@ msgstr "" msgid "Field" msgstr "Champ" +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__field_to_retrieve_existing_records +msgid "Field To Retrieve Existing Records" +msgstr "Champs pour retrouver l'enregistrement existant" + #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__field_type msgid "Field Type" @@ -252,6 +272,15 @@ msgstr "" "Lors de la création d'un enregistrement, si un autre enregistrement existe " "avec la même valeur, l'enregistrement ne sera pas créé." +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_survey_view_form +msgid "" +"Only the first matched record will be updated.\n" +" Also to be noticed, the unicity check feature has priority over updating the existing record." +msgstr "" +"Attention, seul le premier enregistrement trouvé sera mis à jour. " +"Aussi, si vous avez des champs avec une contrainte d'unicité, cette contrainte aura la priorité sur la mise à jour des enregistrements." + #. module: survey_record_generation #. odoo-python #: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 @@ -342,7 +371,7 @@ msgstr "Sondage Création d'enregistrement Valeur des champs" #. module: survey_record_generation #: model:ir.model,name:survey_record_generation.model_survey_user_input msgid "Survey User Input" -msgstr "Entrée utilisateur du sondage" +msgstr "Saisie utilisateur du sondage" #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__survey_record_creation_id @@ -361,11 +390,25 @@ msgstr "" "Création d'un enregistrement, placez la ligne %(record)s au dessus de la " "ligne du modèle %(model)s." +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +msgid "" +"The field %s is mandatory. In Record Creation tab, drag %s at the top of the" +" table" +msgstr "" + #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__unicity_check msgid "Unicity constraint" msgstr "Contrainte d'unicité" +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__update_existing_records +msgid "Update existing records" +msgstr "Mettre à jour les enregistrements existants" + #. module: survey_record_generation #: model:ir.model.fields,field_description:survey_record_generation.field_survey_question_answer__value_char #: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__displayed_value @@ -410,8 +453,9 @@ msgid "" "[Survey record generation] The answer values type '%(type)s' is not " "supported (for question %(question)s). Use 'record' or 'value' instead." msgstr "" -"[Survey record generation] La valeur '%(type)s' pour le champs 'answer_values_type' n'est pas supportée " -"(pour la question %(question)s). Veuillez utiliser 'Enregistrement' ou 'Valeur' à la place." +"[Survey record generation] La valeur '%(type)s' pour le champs " +"'answer_values_type' n'est pas supportée (pour la question %(question)s). " +"Veuillez utiliser 'Enregistrement' ou 'Valeur' à la place." #. module: survey_record_generation #. odoo-python diff --git a/survey_record_generation/models/survey_record_creation.py b/survey_record_generation/models/survey_record_creation.py index 963289f..f3377c4 100644 --- a/survey_record_generation/models/survey_record_creation.py +++ b/survey_record_generation/models/survey_record_creation.py @@ -1,4 +1,3 @@ - import logging from odoo import api, fields, models, _ @@ -13,16 +12,38 @@ class SurveyRecordCreation(models.Model): name = fields.Char('Name') survey_id = fields.Many2one('survey.survey', string="Survey") - model_id = fields.Many2one('ir.model', "Model", help="Model of generated record") + model_id = fields.Many2one('ir.model', "Model", help="Model of generated record") field_values_ids = fields.One2many('survey.record.creation.field.values', 'survey_record_creation_id', string="Field values") warning_message = fields.Html('Warning message', compute="_compute_warning_message") sequence = fields.Integer("sequence") - @api.onchange('model_id') + update_existing_records = fields.Boolean( + string="Update existing records", + ) + field_to_retrieve_existing_records = fields.Many2one( + "ir.model.fields", + domain="[('id', 'in', allowed_field_ids), ('readonly', '=', False)]", + ondelete="cascade", + help="Choose the field you want to use to retrieve the existing record. " + "WARNING: We update only the first record found.", + ) + allowed_field_ids = fields.Many2many( + "ir.model.fields", + compute="_compute_allowed_field_ids", + store=True, + string="Allowed Fields", + ) + + @api.depends("field_values_ids.field_id") + def _compute_allowed_field_ids(self): + for record in self: + record.allowed_field_ids = record.field_values_ids.mapped("field_id") + + @api.onchange("model_id") def clear_field_values_ids(self): self.field_values_ids = None - @api.depends('model_id','field_values_ids') + @api.depends("model_id","field_values_ids") def _compute_warning_message(self): for record_creation in self: # check if all mandatory fields set @@ -30,14 +51,10 @@ class SurveyRecordCreation(models.Model): required_field_ids = self.model_id.field_id.filtered(lambda f:f.required and "property_" not in f.name) set_field_ids = self.field_values_ids.field_id missing_fields = required_field_ids - set_field_ids - + if missing_fields: record_creation.warning_message = _("Some required fields are not set : %s",', '.join([f"{f.field_description} ({f.name})" for f in missing_fields])) else: record_creation.warning_message = None else: record_creation.warning_message = None - - - - \ No newline at end of file diff --git a/survey_record_generation/models/survey_user_input.py b/survey_record_generation/models/survey_user_input.py index 28bc09b..7e80e39 100644 --- a/survey_record_generation/models/survey_user_input.py +++ b/survey_record_generation/models/survey_user_input.py @@ -64,12 +64,21 @@ class SurveyUserInput(models.Model): vals[field_name] = value + existing_record = self.find_existing_record(record_creation, vals) + duplicate = self.find_duplicate_if_there_are_fields_with_unicity_check( model, record_creation, vals ) if duplicate: record = duplicate + elif existing_record: + vals_with_keys_not_in_record = { + k: v + for k, v in vals.items() + if not getattr(existing_record, k, False) + } + record = existing_record.write(vals_with_keys_not_in_record) else: if ( model == "res.partner" @@ -110,6 +119,20 @@ class SurveyUserInput(models.Model): return super()._mark_done() + def find_existing_record( + self, record_creation: "SurveyRecordCreation", vals: dict + ) -> Any: + if record_creation.update_existing_records: + model = record_creation.model_id.model + search_field = record_creation.field_to_retrieve_existing_records + user_answer_value = vals.get(search_field.name) + if user_answer_value: + return self.env[model].search( + [(search_field.name, "=", user_answer_value)], limit=1 + ) + + return None + def get_value_based_on_value_origin( self, field_value: "SurveyRecordCreationFieldValues", diff --git a/survey_record_generation/tests/test_survey_record_creation.py b/survey_record_generation/tests/test_survey_record_creation.py index 59c603b..81e0ac1 100644 --- a/survey_record_generation/tests/test_survey_record_creation.py +++ b/survey_record_generation/tests/test_survey_record_creation.py @@ -495,9 +495,9 @@ class TestSurveyRecordCreation(SurveyCase): ) self.second_answer._mark_done() - partners = self.env["res.partner"].search([("name", "=", "Jean")]) + partner = self.env["res.partner"].search([("name", "=", "Jean")]) self.assertTrue(partner.name == "Jean") - self.assertFalse(len(partners) == 2) + self.assertTrue(len(partner) == 1) def test_some_questions_are_not_answered(self): self.question_email = self._add_question( @@ -654,3 +654,187 @@ class TestSurveyRecordCreation(SurveyCase): partner = self.env["res.partner"].search([("name", "=", "Jean")]) self.assertTrue(partner.employee) + + def test_update_existing_record(self): + # A contact with name 'Jean' already exists. + # We'll update the email of this partner (and not create a new one) + self.env["res.partner"].create({"name": "Jean"}) + + self.question_email = self._add_question( + page=None, + name="Email", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.survey_record_creation.write( + { + "update_existing_records": True, + "field_to_retrieve_existing_records": self.name_field.id, + } + ) + email_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "email")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": email_field.id, + "value_origin": "question", + "question_id": self.question_email.id, + } + ) + + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + self._add_answer_line( + question=self.question_name, answer=self.answer, answer_value="Jean" + ) + self._add_answer_line( + question=self.question_email, + answer=self.answer, + answer_value="jean@test.fr", + ) + self.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(len(partner) == 1) + self.assertTrue(partner.email == "jean@test.fr") + + def test_update_only_empty_fields_when_updating_records(self): + # A contact with name 'Jean' and email 'jean@test.fr' already exists. + # We'll update the field 'function' of this partner and won't update the email + # because it's already filled up + self.env["res.partner"].create( + { + "name": "Jean", + "email": "jean@test.fr", + # when the survey is submitted, email should not be updated + } + ) + self.question_email = self._add_question( + page=None, + name="Email", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + self.question_function = self._add_question( + page=None, + name="Function", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.survey_record_creation.write( + { + "update_existing_records": True, + "field_to_retrieve_existing_records": self.name_field.id, + } + ) + email_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "email")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": email_field.id, + "value_origin": "question", + "question_id": self.question_email.id, + } + ) + function_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "function")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": function_field.id, + "value_origin": "question", + "question_id": self.question_function.id, + } + ) + + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + self._add_answer_line( + question=self.question_name, answer=self.answer, answer_value="Jean" + ) + self._add_answer_line( + question=self.question_email, + answer=self.answer, + answer_value="ThisEmailShouldNotBeUpdated@test.fr", + ) + self._add_answer_line( + question=self.question_function, + answer=self.answer, + answer_value="happiness office manager", + ) + self.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(len(partner) == 1) + self.assertTrue(partner.email == "jean@test.fr") + self.assertTrue(partner.function == "happiness office manager") + + def test_unicity_check_has_priority_over_update(self): + # In this test, we verify that if a field is set up with unicity_check + # it has priority over updating the existing record + self.name_survey_record_creation_field_values.unicity_check = True + + self.env["res.partner"].create({"name": "Jean"}) + + self.question_email = self._add_question( + page=None, + name="Email", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.survey_record_creation.write( + { + "update_existing_records": True, + "field_to_retrieve_existing_records": self.name_field.id, + } + ) + email_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "email")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": email_field.id, + "value_origin": "question", + "question_id": self.question_email.id, + } + ) + + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + self._add_answer_line( + question=self.question_name, answer=self.answer, answer_value="Jean" + ) + self._add_answer_line( + question=self.question_email, + answer=self.answer, + answer_value="jean@test.fr", + ) + self.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(len(partner) == 1) + self.assertTrue(getattr(partner, "Email", None) is None) diff --git a/survey_record_generation/views/survey_survey_views.xml b/survey_record_generation/views/survey_survey_views.xml index 13f5007..fcb93dd 100644 --- a/survey_record_generation/views/survey_survey_views.xml +++ b/survey_record_generation/views/survey_survey_views.xml @@ -18,16 +18,26 @@ + + + +
+
+ Only the first matched record will be updated. + Also to be noticed, the unicity check feature has priority over updating the existing record. +
+
- +
- - + + @@ -40,37 +50,37 @@
- - + - - - - - - - - @@ -104,7 +114,7 @@
-
+