diff --git a/survey_record_generation/README.rst b/survey_record_generation/README.rst new file mode 100644 index 0000000..e03d218 --- /dev/null +++ b/survey_record_generation/README.rst @@ -0,0 +1,146 @@ +======================== +Survey record generation +======================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:03eeb5c11b7d8330051c416331c84103b2641351fdc1d0a1bad43acd09501a33 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsurvey-lightgray.png?logo=github + :target: https://github.com/OCA/survey/tree/16.0/survey_record_generation + :alt: OCA/survey +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/survey-16-0/survey-16-0-survey_record_generation + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/survey&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to generate any record from surveys answers. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In several cases we want to use survey application to create records. +Website form application give the same possibility but the design is integrated on the web site. +Futhermore this module add other functionnalities like "duplicate" check, or creation of several interconnected records + +Typical use case : Information request form + +#. Submitting the form will create a contact (partner) only if it does not already exist in the database. +#. Submitting the form will also create a crm opportunity linked to previously created partner + + + +Configuration +============= + +Record generation configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. |Image of record creation list| image:: https://raw.githubusercontent.com/OCA/survey/16.0/survey_record_generation/static/description/record-creations.png + +#. Go to the the survey +#. In *Record creation* tab add a new line +#. Set a name for created record, select the Model of the record (eg: Prospect) +#. Add a field configuration. So for each field : + + .. |Image of record creation fields| image:: https://raw.githubusercontent.com/OCA/survey/16.0/survey_record_generation/static/description/record-creation-customer.png + + #. You can check "unicity constraint" to prevent duplicates. + In case of duplicates and if other record use this record to fill a m2o field, the founded record will be used + #. You can configure explicitly where Odoo should retrieve the value of field : + * **fixed**: To set explicit value + * **question**: If value come from user's answer + For m2o or m2m links, question should be configured before. See Question answers configuration section below. + * **other created record**: If value come from other created record (m2o case only) + + + +Question answers configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In a survey question : to configure value of choices + +.. |Question answers| image:: https://raw.githubusercontent.com/OCA/survey/16.0/survey_record_generation/static/description/question-answers.png + +#. Configure a multiple choice question +#. Select value type associated to answer: + * **Value** > eg: to fill a selection field + * **Record** > to fill m2o or m2m field +#. In case of *record* type: + #. Select referenced model + #. You can directly fill answers (eventualy with help of the domain field) + #. Or create new answers and set associated record +#. In case of *value* type: + #. Add new selectable answers and set associated value + + +Usage +===== + +When a survey is properly configured, once it is submited by a user, records should be created. + +To find records generated from a participation go to: + +#. Survey > participation +#. Click on **# Generated records** button (top-right) + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Elabore + +Contributors +~~~~~~~~~~~~ + +* `Elabore `_ + + * Clément Thomas + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/survey `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/survey_record_generation/__init__.py b/survey_record_generation/__init__.py new file mode 100644 index 0000000..9a7e03e --- /dev/null +++ b/survey_record_generation/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/survey_record_generation/__manifest__.py b/survey_record_generation/__manifest__.py new file mode 100644 index 0000000..f439c17 --- /dev/null +++ b/survey_record_generation/__manifest__.py @@ -0,0 +1,28 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Survey record generation", + 'summary': 'Allow to create record of any model when sending the form', + 'description': """ +Allow to create record of any model when sending the form : +---------------------------------------------------- +* Choose list of models created on survey submission +* Set default values for record created +* Associate question with fields +* For x2m fields : Associate values to questions +""", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "Elabore", + "website": "https://www.elabore.coop", + "category": "", + "depends": ["survey"], + "data": [ + "security/ir.model.access.csv", + "views/survey_survey_views.xml", + "views/survey_question_views.xml", + "views/survey_user_input_views.xml", + "views/survey_generated_record_views.xml", + ], + "installable": True, +} diff --git a/survey_record_generation/i18n/fr.po b/survey_record_generation/i18n/fr.po new file mode 100644 index 0000000..574b4e5 --- /dev/null +++ b/survey_record_generation/i18n/fr.po @@ -0,0 +1,540 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_record_generation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-13 16:41+0000\n" +"PO-Revision-Date: 2025-11-13 16:41+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation_field_values__value_origin +msgid "" +"* Fixed: you can set the value in value field\n" +" * Question: Response of the question will set the value. If you do not see your question, maybe the type of question do not match the type of field\n" +" * From other created record: You can set other record creation to link several created records. Can only be used with many2one fields." +msgstr "" +"* Fixé: Vous pouvez attribuer une valeur au champ\n" +" * Question: La réponse à la question attribuera la valeur. Si vous ne voyez pas votre question, peut-être que le type de question ne correspond pas au type de champ\n" +" * Depuis un autre enregistrement créé: Vous pouvez attribuer un autre enregistrement créé pour lier plusieurs enregistrements. Peut être utilisé uniquement avec les champs one2many." + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_generated_record_view_search +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" +msgstr "Question autorisée" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#, python-format +msgid "Answer to question: %s" +msgstr "Réponse à la question : %s" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question__answer_values_type +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question_answer__answer_values_type +msgid "Associate value to answer" +msgstr "Associer une valeur à la réponse" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_user_input__generated_records_count +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 +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__create_uid +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__create_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__create_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__create_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__display_name +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__display_name +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__display_name +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__display_name +msgid "Display Name" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question__fill_domain +msgid "Domain" +msgstr "Domaine" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_question_form +msgid "Empty and fill" +msgstr "Vider et remplir" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_question_form +msgid "" +"Empty the list and fill it with all items of selected model matching domain" +msgstr "" +"Vide la liste et la remplie avec tous les enregistrements du modèle " +"sélectionné qui correspondent au domaine" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__field_id +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" +msgstr "Type de champ" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#, python-format +msgid "Field type is : %s" +msgstr "Le type de champ est : %s" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__field_values_ids +msgid "Field values" +msgstr "Valeurs des champs" + +#. module: survey_record_generation +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_record_creation_field_values__value_origin__fixed +msgid "Fixed" +msgstr "Fixé" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_many2many +msgid "Fixed Value Many2Many" +msgstr "Valeur fixée Many2Many" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation_field_values__field_relation +msgid "For relationship fields, the technical name of the target model" +msgstr "Pour les champs relationnels, le nom technique du modèle ciblé" + +#. module: survey_record_generation +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_record_creation_field_values__value_origin__other_record +msgid "From other created record" +msgstr "Depuis un autre enregistrement créé" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_generated_record_view_tree +msgid "Generated from survey input" +msgstr "Généré depuis la participation" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_generated_record_view_tree +msgid "Generated record type" +msgstr "Type d'enregistrement généré" + +#. module: survey_record_generation +#: model:ir.actions.act_window,name:survey_record_generation.survey_generated_record_action +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_user_input__generated_record_ids +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_user_input_view_form_survey_record_generation +msgid "Generated records" +msgstr "Enregistrements générés" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__field_help +msgid "Help" +msgstr "Aide" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__id +msgid "ID" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation__ignore_if_mandatory_field_is_missing +msgid "" +"If a mandatory field is missing when trying to create the record, an error " +"is raised when the survey is submitted. If this option is activated, the " +"error is ignored." +msgstr "" +"Si un champs requis est manquant lors de la création de l'enregistrement, " +"une erreur est levée lors de la soumission du formulaire. " +"En activant cette option, l'erreur sera ignorée." + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__ignore_if_mandatory_field_is_missing +msgid "Ignore creation if a mandatory field is missing" +msgstr "Ignorer la création si un champs requis est manquant" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record____last_update +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation____last_update +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values____last_update +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__write_uid +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__write_uid +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__write_uid +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__write_uid +msgid "Last Updated by" +msgstr "Dernière modification par" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__write_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__write_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__write_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__write_date +msgid "Last Updated on" +msgstr "Dernière modification le" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_generated_record_view_tree +msgid "Link to generated record" +msgstr "Lien vers l'enregistrement généré" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_survey__survey_record_creation_ids +msgid "List of records created when survey submitted" +msgstr "Liste des enregistrements créés lorsque le sondage est envoyé" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question__model_id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question__model_name +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question_answer__model_id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__model_id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__model_id +msgid "Model" +msgstr "Modèle" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation__model_id +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation_field_values__model_id +msgid "Model of generated record" +msgstr "Modèle de l'enregistrement généré" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__survey_record_creation_name +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__name +msgid "Name" +msgstr "Nom" + +#. module: survey_record_generation +#: model_terms:ir.actions.act_window,help:survey_record_generation.survey_generated_record_action +msgid "No generated records found" +msgstr "Pas d'enregistrements générés trouvés" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_question.py:0 +#: code:addons/survey_record_generation/models/survey_question.py:0 +#, python-format +msgid "No record found in %s" +msgstr "Pas d'enregistrements trouvés parmis %s" + +#. module: survey_record_generation +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_question__answer_values_type__no +msgid "No values" +msgstr "Pas de valeur" + +#. module: survey_record_generation +#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation_field_values__unicity_check +msgid "" +"On record creation, if another record exists with same value, record will " +"not be created." +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 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#, python-format +msgid "Other created record: " +msgstr "Autre enregistrement créé : " + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__other_created_record_id +msgid "Other record" +msgstr "Autre enregistrement" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__user_input_id +msgid "Participation" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__question_id +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_record_creation_field_values__value_origin__question +msgid "Question" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_many2one +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__value_reference +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_question__answer_values_type__record +msgid "Record" +msgstr "Enregistrement" + +#. module: survey_record_generation +#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_survey_view_form +msgid "Record creation" +msgstr "Création d'un enregistrement" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_survey__survey_record_creation_ids +msgid "Records creation" +msgstr "Création d'enregistrements" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__created_record_id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question_answer__record_id +msgid "Referenced record" +msgstr "Enregistrement référencé" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__field_relation +msgid "Related Model" +msgstr "Modèle relatif" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_record_creation.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation.py:0 +#, python-format +msgid "Some required fields are not set : %s" +msgstr "Certains champs requis ne sont pas remplis : %s" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_survey +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__survey_id +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__survey_id +msgid "Survey" +msgstr "Sondage" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_question_answer +msgid "Survey Label" +msgstr "Étiquette du sondage" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_question +msgid "Survey Question" +msgstr "Question du sondage" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__survey_record_creation_id +msgid "Survey Record Creation" +msgstr "Sondage Création d'enregistrements" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values_x2m__survey_record_creation_field_values_id +msgid "Survey Record Creation Field Values" +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 "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 +msgid "Survey record creation" +msgstr "Génération d'enregistrement depuis la participation" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +msgid "" +"The field %(field)s is mandatory for model %(model)s. In Record Creation " +"tab, drag %(record)s on top of the model %(model)s." +msgstr "" +"Le champs %(field)s est obligatoire pour le modèle %(model)s. Dans l'onglet " +"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 +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_boolean +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_char +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_date +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_datetime +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_float +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_html +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_integer +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_selection +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_text +#: model:ir.model.fields.selection,name:survey_record_generation.selection__survey_question__answer_values_type__value +msgid "Value" +msgstr "Valeur" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__value_origin +msgid "Value origin" +msgstr "Origine de la valeur" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__warning_message +msgid "Warning message" +msgstr "Message d'erreur" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#, python-format +msgid "You should append at least one record in %s" +msgstr "Vous devez au moins ajouter un enregistrement dans %s" + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +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." + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +msgid "" +"[Survey record generation] The boolean value %s(value)s is not supported " +"(for question %(question)s)." +msgstr "" +"[Survey record generation] La valeur booléenne %s(value)s n'est pas " +"supportée (pour la question %(question)s)." + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +msgid "" +"[Survey record generation] The question type %(type)s is not recognized (for" +" question %(question)s)." +msgstr "" +"[Survey record generation] Le type de question %(type)s n'est pas reconnu " +"(pour la question %(question)s)." + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_user_input.py:0 +#, python-format +msgid "" +"[Survey record generation] The question type %(type)s is not supported yet." +msgstr "" +"[Survey record generation] Le type de question %(type)s n'est pas encore " +"supporté." + +#. module: survey_record_generation +#. odoo-python +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0 +#, python-format +msgid "possible values are %s" +msgstr "les valeurs possibles sont %s" + +#. module: survey_record_generation +#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__sequence +msgid "sequence" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_generated_record +msgid "survey.generated.record" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_record_creation +msgid "survey.record.creation" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_record_creation_field_values +msgid "survey.record.creation.field.values" +msgstr "" + +#. module: survey_record_generation +#: model:ir.model,name:survey_record_generation.model_survey_record_creation_field_values_x2m +msgid "survey.record.creation.field.values.x2m" +msgstr "" diff --git a/survey_record_generation/models/__init__.py b/survey_record_generation/models/__init__.py new file mode 100644 index 0000000..e8d744a --- /dev/null +++ b/survey_record_generation/models/__init__.py @@ -0,0 +1,7 @@ +from . import survey_question_answer +from . import survey_question +from . import survey_record_creation_field_values +from . import survey_record_creation +from . import survey_survey +from . import survey_user_input +from . import survey_generated_record \ No newline at end of file diff --git a/survey_record_generation/models/survey_generated_record.py b/survey_record_generation/models/survey_generated_record.py new file mode 100644 index 0000000..576a982 --- /dev/null +++ b/survey_record_generation/models/survey_generated_record.py @@ -0,0 +1,16 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models, fields, api + + +class SurveyGeneratedRecord(models.Model): + _name = "survey.generated.record" + _rec_name = "survey_record_creation_name" + + survey_record_creation_name = fields.Char('Name', readonly=True) + survey_record_creation_id = fields.Many2one('survey.record.creation', 'Survey record creation', readonly=True) + user_input_id = fields.Many2one('survey.user_input', 'Participation', readonly=True) + created_record_id = fields.Reference(string="Referenced record", selection='_selection_target_model', readonly=True) + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] \ No newline at end of file diff --git a/survey_record_generation/models/survey_question.py b/survey_record_generation/models/survey_question.py new file mode 100644 index 0000000..4bb0478 --- /dev/null +++ b/survey_record_generation/models/survey_question.py @@ -0,0 +1,49 @@ + +import logging +import ast +from typing import Literal + +from odoo import api, fields, models, _, Command +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +AnswerValuesType: Literal["no", "value", "record"] + +class SurveyQuestion(models.Model): + _inherit = 'survey.question' + + model_id = fields.Many2one('ir.model', string="Model") + model_name = fields.Char(related="model_id.model") + fill_domain = fields.Char("Domain", default="[]") + answer_values_type = fields.Selection([('no', 'No values'),('value','Value'),('record','Record')], string="Associate value to answer", default="no", required=True) + + @api.onchange('model_id') + def onchange_model_id(self): + self.fill_domain = "[]" + if self.model_id: + rec = self.env[self.model_id.model].search([], limit=1) + if not rec: + raise UserError(_('No record found in %s',self.model_id.name)) + else: + for answer in self.suggested_answer_ids: + answer.record_id = f"{self.model_id.model},{rec.id}" + + + def fill(self): + for question in self: + if question.model_id: + new_suggested_answer_ids = [Command.clear()] + record_model = question.model_id.model + + if question.fill_domain: + domain = ast.literal_eval(question.fill_domain) + else: + domain = [] + + records = self.env[record_model].search(domain) + + new_suggested_answer_ids += [Command.create({'value':record.display_name, 'record_id':f"{record_model},{record.id}" +}) for record in records] + question.suggested_answer_ids = new_suggested_answer_ids + diff --git a/survey_record_generation/models/survey_question_answer.py b/survey_record_generation/models/survey_question_answer.py new file mode 100644 index 0000000..fc2a59a --- /dev/null +++ b/survey_record_generation/models/survey_question_answer.py @@ -0,0 +1,42 @@ + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveyQuestionAnswer(models.Model): + _inherit = 'survey.question.answer' + + record_id = fields.Reference(string="Referenced record", selection='_selection_target_model') + model_id = fields.Many2one('ir.model', related="question_id.model_id") + answer_values_type = fields.Selection(related="question_id.answer_values_type") + value_char = fields.Char('Value') + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + @api.onchange('record_id') + def onchange_record_id(self): + if self.record_id: + self.value = self.record_id.display_name + + @api.model + def default_get(self, fields): + result = super().default_get(fields) + if ( + not result.get("model_id") + or "record_id" not in fields + ): + return result + + model = self.env['ir.model'].browse(result.get("model_id")).model + res = self.env[model].search([], limit=1) + if res: + result["record_id"] = "%s,%s" % ( + model, + res.id, + ) + return result diff --git a/survey_record_generation/models/survey_record_creation.py b/survey_record_generation/models/survey_record_creation.py new file mode 100644 index 0000000..fb2eeb1 --- /dev/null +++ b/survey_record_generation/models/survey_record_creation.py @@ -0,0 +1,66 @@ +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveyRecordCreation(models.Model): + """Configure list of models for wich record will be created on survey submission + """ + _name = 'survey.record.creation' + + name = fields.Char('Name') + survey_id = fields.Many2one('survey.survey', string="Survey") + 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") + + 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)]", + 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", + ) + ignore_if_mandatory_field_is_missing = fields.Boolean( + string="Ignore creation if a mandatory field is missing", + help="If a mandatory field is missing when trying to create the record, " + "an error is raised when the survey is submitted. " + "If this option is activated, the error is ignored." + ) + + @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") + def _compute_warning_message(self): + for record_creation in self: + # check if all mandatory fields set + if record_creation.model_id: + required_field_ids = record_creation.model_id.field_id.filtered(lambda f:f.required and "property_" not in f.name) + set_field_ids = record_creation.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 diff --git a/survey_record_generation/models/survey_record_creation_field_values.py b/survey_record_generation/models/survey_record_creation_field_values.py new file mode 100644 index 0000000..b9de430 --- /dev/null +++ b/survey_record_generation/models/survey_record_creation_field_values.py @@ -0,0 +1,199 @@ + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError +from odoo.tools.misc import format_date + +_logger = logging.getLogger(__name__) + + +type_mapping = { #field types on the left, question types on the right. TODO : what about booleans ? + "char": ["char_box", "numerical_box", "date", "datetime", "simple_choice", "multiple_choice"], + "text": ["char_box", "date", "simple_choice"], + "html": ["text_box", "numerical_box", "datetime", "simple_choice"], + "integer": ["numerical_box"], + "float": ["numerical_box"], + "date": ["date"], + "datetime": ["datetime"], + "many2one": ["simple_choice"], + "many2many": ["multiple_choice"], + "selection": ["char_box", "simple_choice"] +} + + +class SurveyRecordCreationFieldValues(models.Model): + """Configure default values of records created on survey submission + """ + _name = 'survey.record.creation.field.values' + _rec_name = 'displayed_value' + + survey_record_creation_id = fields.Many2one('survey.record.creation') + survey_id = fields.Many2one('survey.survey', related="survey_record_creation_id.survey_id") + model_id = fields.Many2one('ir.model', related="survey_record_creation_id.model_id") + + field_id = fields.Many2one( + 'ir.model.fields', + domain="[('model_id','=',model_id),('ttype','in',['char','selection','text','html','integer','float','date','datetime','many2one','many2many', 'boolean'])]", + ondelete="cascade") + field_relation = fields.Char(related='field_id.relation') + field_type = fields.Selection(related="field_id.ttype") + field_help = fields.Html('Help', compute="_compute_field_help") + + value_origin = fields.Selection( + [('fixed','Fixed'),('question','Question'),('other_record','From other created record')], + string="Value origin", + required=True, + default='fixed', + help="""* Fixed: you can set the value in value field + * Question: Response of the question will set the value. If you do not see your question, maybe the type of question do not match the type of field + * From other created record: You can set other record creation to link several created records. Can only be used with many2one fields.""") + + fixed_value_many2one = fields.Reference(string='Record', selection='_selection_target_model') + fixed_value_many2many = fields.One2many('survey.record.creation.field.values.x2m', "survey_record_creation_field_values_id") + fixed_value_char = fields.Char("Value") + fixed_value_selection = fields.Char("Value") + fixed_value_text = fields.Text("Value") + fixed_value_html = fields.Html("Value") + fixed_value_integer = fields.Integer("Value") + fixed_value_float = fields.Float("Value") + fixed_value_date = fields.Date("Value") + fixed_value_datetime = fields.Datetime("Value") + fixed_value_boolean = fields.Boolean("Value") + + displayed_value = fields.Char("Value", compute="_compute_displayed_value") + other_created_record_id = fields.Many2one("survey.record.creation", string="Other record", domain="[('survey_id','=',survey_id),('model_id.model','=',field_relation)]") + + allowed_question_ids = fields.Many2many('survey.question', compute='_compute_allowed_question_ids') + question_id = fields.Many2one('survey.question', string="Question", domain="[('id','in',allowed_question_ids)]") + + unicity_check = fields.Boolean('Unicity constraint', help="On record creation, if another record exists with same value, record will not be created.") + + @api.depends("field_id") + def _compute_field_help(self): + for record in self: + field_help = _("Field type is : %s",record.field_type) + if record.field_type == "selection": + field_help += "
"+_("possible values are %s",', '.join([f"{s.value} ({s.name})" for s in record.field_id.selection_ids])) + record.field_help = field_help + + + @api.depends('field_id') + def _compute_allowed_question_ids(self): + for record_creation_field_values in self: + if not record_creation_field_values.survey_id or not record_creation_field_values.field_id: + record_creation_field_values.allowed_question_ids = None + continue + question_domain = [('survey_id','=',record_creation_field_values.survey_id.id)] + + if record_creation_field_values.field_id.ttype in ['many2one','many2many']: + question_domain.extend(['|','&',('answer_values_type','=','record'),('model_id','=',record_creation_field_values.field_id.relation),('answer_values_type','=','value')]) + if record_creation_field_values.field_id.ttype in type_mapping: + question_domain.append(('question_type','in',type_mapping[record_creation_field_values.field_id.ttype])) + + record_creation_field_values.allowed_question_ids = self.env['survey.question'].search(question_domain) + + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + def clean_values(self): + # clean values + self.fixed_value_many2many = None + self.fixed_value_many2one = None + self.fixed_value_char = None + self.fixed_value_selection = None + self.fixed_value_text = None + self.fixed_value_html = None + self.fixed_value_integer = None + self.fixed_value_float = None + self.fixed_value_date = None + self.fixed_value_datetime = None + self.other_created_record_id = None + self.question_id = None + + @api.onchange('field_id') + def _onchange_field_id(self): + # clean values + self.clean_values() + # Set reference field model and select first record + if self.field_id and self.field_id.ttype == 'many2one' and self.field_id.relation: + rec = self.env[self.field_id.relation].search([], limit=1) + if rec: + self.fixed_value_many2one = f"{self.field_id.relation},{rec.id}" + else: + model_name = self.env['ir.model'].search([('model','=',self.field_id.relation)]).name + self.fixed_value_many2one = None + raise UserError(_('You should append at least one record in %s',model_name)) + else: + self.fixed_value_many2one = None + + + def get_fixed_value_for_record_creation(self): + """return val used in create() method + + """ + if self.value_origin == 'fixed': + if self.field_type == 'many2one': + if self.fixed_value_many2one: + return self.fixed_value_many2one.id + elif self.field_type == 'many2many': + return [m2m.value_reference.id for m2m in self.fixed_value_many2many if m2m.value_reference] + else: + return self["fixed_value_"+self.field_type] + + + @api.onchange("fixed_value_char","fixed_value_selection","fixed_value_text","fixed_value_html","fixed_value_integer","fixed_value_float","fixed_value_date","fixed_value_datetime",'fixed_value_many2one', "fixed_value_many2many","other_created_record_id","question_id") + def _compute_displayed_value(self): + for record in self: + if record.field_id: + if record.value_origin == 'other_record' and record.other_created_record_id: + record.displayed_value = _("Other created record: ")+record.other_created_record_id.name + elif record.value_origin == 'fixed': + if record.field_id.ttype == "many2one": + if record.fixed_value_many2one: + record.displayed_value = record.fixed_value_many2one.display_name + else: + record.displayed_value = None + elif record.field_id.ttype == "many2many": + if record.fixed_value_many2many: + record.displayed_value = ", ".join([r.value_reference.display_name for r in record.fixed_value_many2many if r.value_reference]) + else: + record.displayed_value = None + elif record.field_id.ttype == "date": + record.displayed_value = format_date(self.env, record.fixed_value_date) + elif record.field_id.ttype == "datetime": + record.displayed_value = format_date(self.env, record.fixed_value_datetime) + else: + record.displayed_value = str(record['fixed_value_'+record.field_id.ttype]) + else: #value_origin = question + record.displayed_value = _('Answer to question: %s',record.question_id.title) + else: + record.displayed_value = "" + +class SurveyRecordCreationFieldValuesX2m(models.Model): + """O2m an M2m default values + """ + _name = 'survey.record.creation.field.values.x2m' + _rec_name = 'value_reference' + + survey_record_creation_field_values_id = fields.Many2one('survey.record.creation.field.values') + value_reference = fields.Reference(string='Record', selection='_selection_target_model') + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + + @api.onchange('survey_record_creation_field_values_id') + def _onchange_model_name(self): + # Set reference field model and select first record + field = self.survey_record_creation_field_values_id.field_id + if field and "2many" in field.ttype and field.relation: + rec = self.env[field.relation].search([], limit=1) + if rec: + self.value_reference = f"{field.relation},{rec.id}" + else: + model_name = self.env['ir.model'].search([('model','=',field.relation)]).name + raise ValueError(_('You should append at least one record in %s',(model_name,))) diff --git a/survey_record_generation/models/survey_survey.py b/survey_record_generation/models/survey_survey.py new file mode 100644 index 0000000..e60a8c4 --- /dev/null +++ b/survey_record_generation/models/survey_survey.py @@ -0,0 +1,13 @@ + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveySurvey(models.Model): + _inherit = 'survey.survey' + + survey_record_creation_ids = fields.One2many('survey.record.creation', 'survey_id', 'Records creation', help="List of records created when survey submitted") + \ 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 new file mode 100644 index 0000000..8e3bf7d --- /dev/null +++ b/survey_record_generation/models/survey_user_input.py @@ -0,0 +1,339 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from typing import TYPE_CHECKING, Any, Literal + +from odoo import _, fields, models +from odoo.exceptions import UserError +from odoo.fields import Command + +if TYPE_CHECKING: + from .survey_question import AnswerValuesType + from .survey_record_creation import SurveyRecordCreation + from .survey_record_creation_field_values import SurveyRecordCreationFieldValues + + +class SurveyUserInput(models.Model): + _inherit = "survey.user_input" + + generated_record_ids = fields.One2many( + "survey.generated.record", "user_input_id", "Generated records" + ) + generated_records_count = fields.Integer( + "Attempts Count", compute="_compute_generated_records_count" + ) + + def _compute_generated_records_count(self): + for user_input in self: + user_input.generated_records_count = len(user_input.generated_record_ids) + + def action_redirect_to_generated_records(self): + self.ensure_one() + + action = self.env["ir.actions.act_window"]._for_xml_id( + "survey_record_generation.survey_generated_record_action" + ) + action['domain'] = [('user_input_id.survey_id', '=', self.survey_id.id)] + return action + + def _mark_done(self): + # generate records + for user_input in self: + created_records = {} + other_record_fields_to_update: list[SurveyRecordCreationFieldValues] = [] + + record_creation: SurveyRecordCreation + for ( + record_creation + ) in user_input.survey_id.survey_record_creation_ids.sorted("sequence"): + model: str = record_creation.model_id.model + vals: dict = {} + + field_value: SurveyRecordCreationFieldValues + for field_value in record_creation.field_values_ids: + value, other_record_fields_to_update = ( + self.get_value_based_on_value_origin( + field_value=field_value, + user_input=user_input, + created_records=created_records, + model=model, + other_record_fields_to_update=other_record_fields_to_update, + ) + ) + field_name: str = field_value.field_id.name + + 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) + } + existing_record.write(vals_with_keys_not_in_record) + record = existing_record + else: + try: + with self.env.cr.savepoint(): + record = self.env[model].create(vals) + if model == "res.partner" and not self.partner_id: + self.partner_id = record.id + except Exception: + # This a broad exception because it could be IntegrityError, + # EmptyNamesError in case partner_firstname is installed etc... + if record_creation.ignore_if_mandatory_field_is_missing: + continue + raise + # Link generated records to user input + self.env["survey.generated.record"].create( + { + "survey_record_creation_name": record_creation.name, + "survey_record_creation_id": record_creation.id, + "user_input_id": user_input.id, + "created_record_id": f"{model},{record.id}", + } + ) + + created_records[record_creation.id] = record + + # update linked record + for field_to_update in other_record_fields_to_update: + record_to_update = created_records.get( + field_to_update.survey_record_creation_id.id + ) + if record_to_update: + linked_record = created_records[ + field_to_update.other_created_record_id.id + ] + value = self.get_value_for_relational_field( + field_to_update, linked_record + ) + record_to_update.write({field_to_update.field_id.name: value}) + + 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", + user_input: "SurveyUserInput", + created_records: dict[Any, Any], + model: str, + other_record_fields_to_update: list["SurveyRecordCreationFieldValues"], + ) -> tuple[Any, list["SurveyRecordCreationFieldValues"]]: + value: Any = None + + if field_value.value_origin == "fixed": + value = field_value.get_fixed_value_for_record_creation() + elif field_value.value_origin == "question": + value = self.get_value_from_user_answer(field_value, user_input) + elif field_value.value_origin == "other_record": + # if the other_record value is a required field, get it or raise + value = self.get_required_value_from_other_record( + model, created_records, field_value + ) + # otherwise, we update the record later (out of this for loop) + if not value: + other_record_fields_to_update.append(field_value) + return value, other_record_fields_to_update + + @staticmethod + def get_value_for_relational_field( + field_to_update: "SurveyRecordCreationFieldValues", linked_record + ) -> Any: + field_type = field_to_update.field_id.ttype + if field_type == "many2one": + return linked_record.id + else: + # many2many or one2many + return [Command.set(linked_record.ids)] + + def find_duplicate_if_there_are_fields_with_unicity_check( + self, model: str, record_creation: "SurveyRecordCreation", vals: dict[Any, Any] + ) -> Any: + # check duplicates + unique_fields = [ + field_value.field_id.name + for field_value in record_creation.field_values_ids.filtered( + lambda r: r.unicity_check + ) + ] + duplicate = None + if unique_fields: + uniq_domain = [] + for uniq_field in unique_fields: + uniq_domain.append((uniq_field, "=", vals[uniq_field])) + duplicate = self.env[model].search(uniq_domain, limit=1) + return duplicate + + def get_required_value_from_other_record( + self, + model: str, + created_records: dict[Any, Any], + field_value: "SurveyRecordCreationFieldValues", + ) -> Any: + model_class = self.env[model] + if model_class._fields[field_value.field_id.name].required: + # check if the other record is already created, + # if yes add it to vals, else raise + if ( + len(created_records) > 0 + and created_records[field_value.other_created_record_id.id] + ): + linked_record = created_records[field_value.other_created_record_id.id] + return self.get_value_for_relational_field(field_value, linked_record) + else: + raise UserError( + _( + "The field %(field)s is mandatory for model %(model)s. " + "In Record Creation tab, drag %(record)s " + "on top of the model %(model)s." + ) + % { + "field": field_value.field_id.display_name, + "model": model, + "record": field_value.other_created_record_id.name, + } + ) + + def get_value_from_user_answer( + self, + field_value: "SurveyRecordCreationFieldValues", + user_input: "SurveyUserInput", + ) -> Any: + # find user_input_lines (which are user's answers) for the question + user_input_lines = [ + user_input_line + for user_input_line in user_input.user_input_line_ids + if user_input_line.question_id == field_value.question_id + ] + + if not user_input_lines: + # If the question has not been displayed to the user, + # there are no user_input_lines + return None + if user_input_lines[0].skipped: + # The question has been ignored by the user + return None + + question_type = field_value.question_id.question_type + + if question_type in [ + "char_box", + "text_box", + "numerical_box", + "date", + "datetime", + ]: + return user_input_lines[0][f"value_{user_input_lines[0].answer_type}"] + elif question_type in ["simple_choice", "multiple_choice", "matrix"]: + answer_values_type = field_value.question_id.answer_values_type + return self.get_value_based_on_answer_values_type( + answer_values_type, field_value, question_type, user_input_lines + ) + else: + raise UserError( + _( + "[Survey record generation] The question type %(type)s is not " + "recognized (for question %(question)s)." + ) + % {"type": question_type, "question": field_value.question_id.title} + ) + + def get_value_based_on_answer_values_type( + self, + answer_values_type: "AnswerValuesType", + field_value: "SurveyRecordCreationFieldValues", + question_type: Literal["simple_choice", "multiple_choice", "matrix"], + user_input_lines: list[Any], + ) -> Any: + if answer_values_type == "record": + answered_record_ids = [] + for user_input_line in user_input_lines: + if ( + user_input_line.suggested_answer_id + and user_input_line.suggested_answer_id.record_id + ): + answered_record_ids.append( + user_input_line.suggested_answer_id.record_id.id + ) + if not answered_record_ids: + return None + if question_type == "simple_choice": + return answered_record_ids[0] + elif question_type == "multiple_choice": + return answered_record_ids + else: + raise UserError( + _( + "[Survey record generation] The question type" + " %(type)s is not supported yet." + ) + % {"type": question_type} + ) + elif answer_values_type == "value": + answer_value_char = user_input_lines[0].suggested_answer_id.value_char + if field_value.field_id.ttype != "boolean": + return answer_value_char + else: + return self.get_boolean_value( + answer_value_char=answer_value_char, + question_title=field_value.question_id.title, + ) + else: + raise UserError( + _( + "[Survey record generation] The answer values type '%(type)s' " + "is not supported (for question %(question)s). Use 'record' or " + "'value' instead." + ) + % { + "type": answer_values_type, + "question": field_value.question_id.title, + } + ) + + @staticmethod + def get_boolean_value(answer_value_char: str, question_title: str) -> bool: + # Below code is a trick to be able to use "simple_choice" question + # with values 'yes' and 'no' and transform it to boolean. + if boolean_value := answer_value_char in [ + "1", + "True", + "true", + "Oui", + "oui", + "Yes", + "yes", + ]: + return boolean_value + else: + raise UserError( + _( + "[Survey record generation] The boolean value %s(value)s " + "is not supported (for question %(question)s)." + ) + % { + "value": answer_value_char, + "question": question_title, + } + ) diff --git a/survey_record_generation/readme/CONFIGURE.rst b/survey_record_generation/readme/CONFIGURE.rst new file mode 100644 index 0000000..99d1c5c --- /dev/null +++ b/survey_record_generation/readme/CONFIGURE.rst @@ -0,0 +1,40 @@ +Record generation configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. |Image of record creation list| image:: ../static/description/record-creations.png + +#. Go to the the survey +#. In *Record creation* tab add a new line +#. Set a name for created record, select the Model of the record (eg: Prospect) +#. Add a field configuration. So for each field : + + .. |Image of record creation fields| image:: ../static/description/record-creation-customer.png + + #. You can check "unicity constraint" to prevent duplicates. + In case of duplicates and if other record use this record to fill a m2o field, the founded record will be used + #. You can configure explicitly where Odoo should retrieve the value of field : + * **fixed**: To set explicit value + * **question**: If value come from user's answer + For m2o or m2m links, question should be configured before. See Question answers configuration section below. + * **other created record**: If value come from other created record (m2o case only) + + + +Question answers configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In a survey question : to configure value of choices + +.. |Question answers| image:: ../static/description/question-answers.png + +#. Configure a multiple choice question +#. Select value type associated to answer: + * **Value** > eg: to fill a selection field + * **Record** > to fill m2o or m2m field +#. In case of *record* type: + #. Select referenced model + #. You can directly fill answers (eventualy with help of the domain field) + #. Or create new answers and set associated record +#. In case of *value* type: + #. Add new selectable answers and set associated value + diff --git a/survey_record_generation/readme/CONTEXT.rst b/survey_record_generation/readme/CONTEXT.rst new file mode 100644 index 0000000..6887590 --- /dev/null +++ b/survey_record_generation/readme/CONTEXT.rst @@ -0,0 +1,10 @@ +In several cases we want to use survey application to create records. +Website form application give the same possibility but the design is integrated on the web site. +Futhermore this module add other functionnalities like "duplicate" check, or creation of several interconnected records + +Typical use case : Information request form + +#. Submitting the form will create a contact (partner) only if it does not already exist in the database. +#. Submitting the form will also create a crm opportunity linked to previously created partner + + diff --git a/survey_record_generation/readme/CONTRIBUTORS.rst b/survey_record_generation/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..6aaf080 --- /dev/null +++ b/survey_record_generation/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Elabore `_ + + * Clément Thomas + * Quentin Mondot diff --git a/survey_record_generation/readme/DESCRIPTION.rst b/survey_record_generation/readme/DESCRIPTION.rst new file mode 100644 index 0000000..2f732ef --- /dev/null +++ b/survey_record_generation/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to generate any record from surveys answers. \ No newline at end of file diff --git a/survey_record_generation/readme/USAGE.rst b/survey_record_generation/readme/USAGE.rst new file mode 100644 index 0000000..6c9047c --- /dev/null +++ b/survey_record_generation/readme/USAGE.rst @@ -0,0 +1,6 @@ +When a survey is properly configured, once it is submited by a user, records should be created. + +To find records generated from a participation go to: + +#. Survey > participation +#. Click on **# Generated records** button (top-right) diff --git a/survey_record_generation/security/ir.model.access.csv b/survey_record_generation/security/ir.model.access.csv new file mode 100644 index 0000000..8bd10dc --- /dev/null +++ b/survey_record_generation/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_survey_record_creation,survey.record.creation,survey_record_generation.model_survey_record_creation,survey.group_survey_user,1,1,1,1 +access_survey_record_creation_field_values,survey.record.creation.field.values,survey_record_generation.model_survey_record_creation_field_values,survey.group_survey_user,1,1,1,1 +access_survey_record_creation_field_values_x2m,survey.record.creation.field.values.x2m,survey_record_generation.model_survey_record_creation_field_values_x2m,survey.group_survey_user,1,1,1,1 +access_survey_generated_record,survey.generated.record,survey_record_generation.model_survey_generated_record,survey.group_survey_user,1,1,1,1 \ No newline at end of file diff --git a/survey_record_generation/static/description/index.html b/survey_record_generation/static/description/index.html new file mode 100644 index 0000000..8c697d4 --- /dev/null +++ b/survey_record_generation/static/description/index.html @@ -0,0 +1,526 @@ + + + + + +Survey record generation + + + +
+

Survey record generation

+ + +

Beta License: AGPL-3 OCA/survey Translate me on Weblate Try me on Runboat

+

This module allows to generate any record from surveys answers.

+

Table of contents

+ +
+

Use Cases / Context

+

In several cases we want to use survey application to create records. +Website form application give the same possibility but the design is integrated on the web site. +Futhermore this module add other functionnalities like “duplicate” check, or creation of several interconnected records

+

Typical use case : Information request form

+
    +
  1. Submitting the form will create a contact (partner) only if it does not already exist in the database.
  2. +
  3. Submitting the form will also create a crm opportunity linked to previously created partner
  4. +
+
+
+

Configuration

+
+

Record generation configuration

+
    +
  1. Go to the the survey

    +
  2. +
  3. In Record creation tab add a new line

    +
  4. +
  5. Set a name for created record, select the Model of the record (eg: Prospect)

    +
  6. +
  7. Add a field configuration. So for each field :

    +
    +
      +
    1. +
      You can check “unicity constraint” to prevent duplicates.
      +
      In case of duplicates and if other record use this record to fill a m2o field, the founded record will be used
      +
      +
    2. +
    3. +
      You can configure explicitly where Odoo should retrieve the value of field :
      +
        +
      • fixed: To set explicit value
      • +
      • +
        question: If value come from user’s answer
        +
        For m2o or m2m links, question should be configured before. See Question answers configuration section below.
        +
        +
      • +
      • other created record: If value come from other created record (m2o case only)
      • +
      +
      +
      +
    4. +
    +
    +
  8. +
+
+
+

Question answers configuration

+

In a survey question : to configure value of choices

+
    +
  1. Configure a multiple choice question
  2. +
  3. +
    Select value type associated to answer:
    +
      +
    • Value > eg: to fill a selection field
    • +
    • Record > to fill m2o or m2m field
    • +
    +
    +
    +
  4. +
  5. +
    In case of record type:
    +
      +
    1. Select referenced model
    2. +
    3. You can directly fill answers (eventualy with help of the domain field)
    4. +
    5. Or create new answers and set associated record
    6. +
    +
    +
    +
  6. +
  7. +
    In case of value type:
    +
      +
    1. Add new selectable answers and set associated value
    2. +
    +
    +
    +
  8. +
+
+
+
+

Usage

+

When a survey is properly configured, once it is submited by a user, records should be created.

+

To find records generated from a participation go to:

+
    +
  1. Survey > participation
  2. +
  3. Click on # Generated records button (top-right)
  4. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Elabore
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/survey project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/survey_record_generation/static/description/question-answers.png b/survey_record_generation/static/description/question-answers.png new file mode 100644 index 0000000..50d6e02 Binary files /dev/null and b/survey_record_generation/static/description/question-answers.png differ diff --git a/survey_record_generation/static/description/record-creation-customer.png b/survey_record_generation/static/description/record-creation-customer.png new file mode 100644 index 0000000..8fc8feb Binary files /dev/null and b/survey_record_generation/static/description/record-creation-customer.png differ diff --git a/survey_record_generation/static/description/record-creations.png b/survey_record_generation/static/description/record-creations.png new file mode 100644 index 0000000..03a7c27 Binary files /dev/null and b/survey_record_generation/static/description/record-creations.png differ diff --git a/survey_record_generation/tests/__init__.py b/survey_record_generation/tests/__init__.py new file mode 100644 index 0000000..5b794a3 --- /dev/null +++ b/survey_record_generation/tests/__init__.py @@ -0,0 +1 @@ +from . import test_survey_record_creation diff --git a/survey_record_generation/tests/test_survey_record_creation.py b/survey_record_generation/tests/test_survey_record_creation.py new file mode 100644 index 0000000..243ebf1 --- /dev/null +++ b/survey_record_generation/tests/test_survey_record_creation.py @@ -0,0 +1,872 @@ +from psycopg2 import IntegrityError + +from odoo.addons.survey.tests.common import SurveyCase +from odoo.tests.common import mute_logger + + +class TestSurveyRecordCreation(SurveyCase): + def setUp(self): + super().setUp() + # We create a survey + self.survey = self.env["survey.survey"].create( + { + "title": "Test Survey", + } + ) + # With a single question "name" + self.question_name = self._add_question( + page=None, + name="Name", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.res_partner_model = self.env["ir.model"]._get("res.partner") + # The administrator has set up a record creation on res.partner model + self.survey_record_creation = self.env["survey.record.creation"].create( + { + "name": "Contact", + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + } + ) + self.name_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "name")] + ) + # And linked the res_partner field "name" to the answer of question_name + self.name_survey_record_creation_field_values = 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": self.name_field.id, + "value_origin": "question", + "question_id": self.question_name.id, + } + ) + + def test_record_is_created(self): + # Easy test to become familiar with the subject (check comments of setUp) + + # We simulate that jean@test.fr has answered the survey + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + # He answered "Jean" to the question_name + self._add_answer_line( + question=self.question_name, answer=self.answer, answer_value="Jean" + ) + # And validate the survey + self.answer._mark_done() + + # Thus, the res.partner with the name "Jean" has been created + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + + def test_all_types_of_question(self): + # Still todo : "datetime" and "text_box" + # Also todo : "simple_choice" with answer_values_type "no" + # Also todo : "multiple_choice" with answer_values_type "no" and "value" + # Note : matrix question type is never proposed in the allowed_question_ids + + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + + ### "char_box" type of question, tested with field CHAR "name" ### + self._add_answer_line( + question=self.question_name, answer=self.answer, answer_value="Jean" + ) + + ### "numerical_box" type of question, + # tested with FLOAT field "partner_latitude" ### + self.question_partner_latitude = self._add_question( + page=None, + name="Partner latitude", + qtype="numerical_box", + survey_id=self.survey.id, + sequence=1, + ) + + partner_latitude_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "partner_latitude")] + ) + 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": partner_latitude_field.id, + "value_origin": "question", + "question_id": self.question_partner_latitude.id, + } + ) + + self._add_answer_line( + question=self.question_partner_latitude, + answer=self.answer, + answer_value=44.73333, + ) + + # NOTE: "date" question type not tested here — res.partner has no writable Date field in v18. + + ### "simple_choice" type of question, tested with SELECTION field "type" ### + ### Here we also test answer_values_type "value" + self.question_type = self._add_question( + page=None, + name="Type", + qtype="simple_choice", + labels=[ + {"value": "contact"}, + {"value": "other"}, + ], + survey_id=self.survey.id, + sequence=1, + answer_values_type="value", + ) + + self.question_type.suggested_answer_ids[0].value_char = ( + self.question_type.suggested_answer_ids[0].value + ) + self.question_type.suggested_answer_ids[1].value_char = ( + self.question_type.suggested_answer_ids[1].value + ) + + type_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "type")] + ) + 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": type_field.id, + "value_origin": "question", + "question_id": self.question_type.id, + } + ) + + self._add_answer_line( + question=self.question_type, + answer=self.answer, + answer_value=self.question_type.suggested_answer_ids[0].id, + ) + + ### "simple_choice" type of question, tested with MANY2ONE field "title" ### + ### Here we also test answer_values_type "record" + + mister_title = self.env["res.partner.title"].create({"name": "Mister"}) + madam_title = self.env["res.partner.title"].create({"name": "Madam"}) + self.question_title = self._add_question( + page=None, + name="Title", + qtype="simple_choice", + labels=[ + {"value": mister_title.display_name}, + {"value": madam_title.display_name}, + ], + survey_id=self.survey.id, + sequence=1, + answer_values_type="record", + ) + + self.question_title.suggested_answer_ids[0].record_id = ( + f"res.partner.title,{mister_title.id}" + ) + self.question_title.suggested_answer_ids[1].record_id = ( + f"res.partner.title,{madam_title.id}" + ) + + title_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "title")] + ) + 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": title_field.id, + "value_origin": "question", + "question_id": self.question_title.id, + } + ) + + self._add_answer_line( + question=self.question_title, + answer=self.answer, + answer_value=self.question_title.suggested_answer_ids[0].id, + ) + + ### "multiple_choice" type of question, + # tested with MANY2MANY field "category" ### + adult_category = self.env["res.partner.category"].create({"name": "Adult"}) + teenager_category = self.env["res.partner.category"].create( + {"name": "Teenager"} + ) + child_category = self.env["res.partner.category"].create({"name": "Child"}) + self.question_category = self._add_question( + page=None, + name="Category", + qtype="multiple_choice", + labels=[ + {"value": adult_category.display_name}, + {"value": teenager_category.display_name}, + {"value": child_category.display_name}, + ], + survey_id=self.survey.id, + sequence=1, + answer_values_type="record", + ) + + self.question_category.suggested_answer_ids[0].record_id = ( + f"res.partner.category,{adult_category.id}" + ) + self.question_category.suggested_answer_ids[1].record_id = ( + f"res.partner.category,{teenager_category.id}" + ) + self.question_category.suggested_answer_ids[2].record_id = ( + f"res.partner.category,{child_category.id}" + ) + + category_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "category_id")] + ) + 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": category_field.id, + "value_origin": "question", + "question_id": self.question_category.id, + } + ) + + self._add_answer_line( + question=self.question_category, + answer=self.answer, + answer_value=self.question_category.suggested_answer_ids[0].id, + ) + self._add_answer_line( + question=self.question_category, + answer=self.answer, + answer_value=self.question_category.suggested_answer_ids[1].id, + ) + + self.answer._mark_done() + partner = self.env["res.partner"].search( + [ + ("name", "=", "Jean"), + ("partner_latitude", "=", 44.73333), + ("type", "=", "contact"), + ("title", "=", mister_title.id), + ("category_id", "=", adult_category.id), + ("category_id", "=", teenager_category.id), + ("category_id", "!=", child_category.id), + ] + ) + self.assertTrue(partner.name == "Jean") + + def test_records_are_created(self): + # we test that several records can be created at the end of the same survey + # concurrently, we test the value_origin "other_record" and "fixed" + res_partner_bank_model = self.env["ir.model"]._get("res.partner.bank") + self.bank_survey_record_creation = self.env["survey.record.creation"].create( + { + "name": "Bank", + "survey_id": self.survey.id, + "model_id": res_partner_bank_model.id, + } + ) + # Below we test "value_origin": "other_record" with a required field + partner_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner.bank"), ("name", "=", "partner_id")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.bank_survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": res_partner_bank_model.id, + "field_id": partner_field.id, + "value_origin": "other_record", + "other_created_record_id": self.survey_record_creation.id, + } + ) + # Below we test "value_origin": "fixed" + acc_number_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner.bank"), ("name", "=", "acc_number")] + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.bank_survey_record_creation.id, + "survey_id": self.survey.id, + "model_id": res_partner_bank_model.id, + "field_id": acc_number_field.id, + "value_origin": "fixed", + "fixed_value_char": "FR76 1444 5004 0004 0000 0000 000", + } + ) + # Below we test "value_origin": "other_record" with a NOT required field + bank_ids_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "bank_ids")] + ) + 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": bank_ids_field.id, + "value_origin": "other_record", + "other_created_record_id": self.bank_survey_record_creation.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.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + bank_account = self.env["res.partner.bank"].search( + [("partner_id", "=", partner.id)] + ) + self.assertTrue(bank_account.acc_number == "FR76 1444 5004 0004 0000 0000 000") + + def test_records_of_same_model_are_created(self): + # When we have 2 survey.record.creation on res.partner, + # we check that 2 contacts are created + self.second_question_name = self._add_question( + page=None, + name="Name of second person", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.second_contact_creation = self.env["survey.record.creation"].create( + { + "name": "Contact 2", + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + } + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.second_contact_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": self.name_field.id, + "value_origin": "question", + "question_id": self.second_question_name.id, + } + ) + + self.first_answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + self._add_answer_line( + question=self.question_name, + answer=self.first_answer, + answer_value="Jean", + ) + self._add_answer_line( + question=self.second_question_name, + answer=self.first_answer, + answer_value="Jeanne", + ) + self.first_answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + partner = self.env["res.partner"].search([("name", "=", "Jeanne")]) + self.assertTrue(partner.name == "Jeanne") + + def test_survey_submitted_twice_by_same_user(self): + 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.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + + 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.answer._mark_done() + + partners = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(len(partners) == 2) + + def test_unicity_check(self): + # In this test, we check the behavior of unicity_check + self.name_survey_record_creation_field_values.unicity_check = True + + 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.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + + self.second_answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + self._add_answer_line( + question=self.question_name, answer=self.second_answer, answer_value="Jean" + ) + self.second_answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + self.assertTrue(len(partner) == 1) + + def test_some_questions_are_not_answered(self): + self.question_email = self._add_question( + page=None, + name="Email", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + mister_title = self.env["res.partner.title"].create({"name": "Mister"}) + madam_title = self.env["res.partner.title"].create({"name": "Madam"}) + self.question_title = self._add_question( + page=None, + name="Title", + qtype="simple_choice", + labels=[ + {"value": mister_title.display_name}, + {"value": madam_title.display_name}, + ], + survey_id=self.survey.id, + sequence=1, + answer_values_type="record", + ) + self.question_title.suggested_answer_ids[0].record_id = ( + f"res.partner.title,{mister_title.id}" + ) + self.question_title.suggested_answer_ids[1].record_id = ( + f"res.partner.title,{madam_title.id}" + ) + + self.question_street = self._add_question( + page=None, + name="Street", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + 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, + } + ) + title_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "title")] + ) + 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": title_field.id, + "value_origin": "question", + "question_id": self.question_title.id, + } + ) + street_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "street")] + ) + 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": street_field.id, + "value_origin": "question", + "question_id": self.question_street.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=False, + skipped=True, + answer_type=False, + ) + self._add_answer_line( + question=self.question_title, + answer=self.answer, + answer_value=False, + skipped=True, + answer_type=False, + ) + self.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertTrue(partner.name == "Jean") + self.assertFalse(partner.email) + self.assertFalse(partner.title) + self.assertFalse(partner.street) + + def test_boolean_field(self): + self.question_employee = self._add_question( + page=None, + name="Employee", + qtype="simple_choice", + labels=[ + {"value": "yes"}, + {"value": "no"}, + ], + survey_id=self.survey.id, + sequence=1, + answer_values_type="value", + ) + + self.question_employee.suggested_answer_ids[0].value_char = ( + self.question_employee.suggested_answer_ids[0].value + ) + self.question_employee.suggested_answer_ids[1].value_char = ( + self.question_employee.suggested_answer_ids[1].value + ) + + employee_field = self.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "employee")] + ) + 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": employee_field.id, + "value_origin": "question", + "question_id": self.question_employee.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_employee, + answer=self.answer, + answer_value=self.question_employee.suggested_answer_ids[0].id, + ) + self.answer._mark_done() + + 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) + + def test_required_fields_are_not_filled_up(self): + # In this test, we check the behavior when a required field (name) is missing + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + + with self.assertRaises(IntegrityError): + # TODO : propose a better user experience than IntegrityError when + # a mandatory field is missing + with mute_logger("odoo.sql_db"): + self.answer._mark_done() + + def test_ignore_if_mandatory_field_is_missing(self): + # In this test, we check the behavior of ignore_if_mandatory_field_is_missing + + self.survey_record_creation.write( + {"ignore_if_mandatory_field_is_missing": True} + ) + + self.answer = self._add_answer( + survey=self.survey, partner=False, email="jean@test.fr" + ) + + with mute_logger("odoo.sql_db"): + self.answer._mark_done() + + # No partner has been created, and no IntegrityError has been raised + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertEqual(len(partner), 0) + + def test_fill_up_partner_id_in_survey_input(self): + # In this test, we check that the field partner_id is filled up + # when we create a res.partner + + 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.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertEqual(self.answer.partner_id, partner) + + def test_partner_id_in_survey_input_is_filled_up_by_first_contact_record_creation(self): + # In this test, we verify that when creating several contacts with the same survey, + # the 1st created contact is used to fill up survey_input.partner_id + self.second_question_name = self._add_question( + page=None, + name="Name of second person", + qtype="char_box", + survey_id=self.survey.id, + sequence=1, + ) + + self.second_contact_creation = self.env["survey.record.creation"].create( + { + "name": "Contact 2", + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + } + ) + self.env["survey.record.creation.field.values"].create( + { + "survey_record_creation_id": self.second_contact_creation.id, + "survey_id": self.survey.id, + "model_id": self.res_partner_model.id, + "field_id": self.name_field.id, + "value_origin": "question", + "question_id": self.second_question_name.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.second_question_name, + answer=self.answer, + answer_value="Jeanne", + ) + self.answer._mark_done() + + partner = self.env["res.partner"].search([("name", "=", "Jean")]) + self.assertEqual(self.answer.partner_id, partner) diff --git a/survey_record_generation/views/survey_generated_record_views.xml b/survey_record_generation/views/survey_generated_record_views.xml new file mode 100644 index 0000000..90c9de4 --- /dev/null +++ b/survey_record_generation/views/survey_generated_record_views.xml @@ -0,0 +1,44 @@ + + + + + survey.generated.record.view.tree + survey.generated.record + + + + + + + + + + + survey.generated.record.view.search + survey.generated.record + + + + + + + + + + + + Generated records + survey.generated.record + list + + {'search_default_active_input': True} + +

+ No generated records found +

+
+
+
+
diff --git a/survey_record_generation/views/survey_question_views.xml b/survey_record_generation/views/survey_question_views.xml new file mode 100644 index 0000000..4795066 --- /dev/null +++ b/survey_record_generation/views/survey_question_views.xml @@ -0,0 +1,35 @@ + + + + survey.question.view.form.inherit.record.generation + survey.question + + + + + + + + + + + + +