diff --git a/survey_answer_generation/README.rst b/survey_answer_generation/README.rst new file mode 100644 index 00000000..42a6c1d6 --- /dev/null +++ b/survey_answer_generation/README.rst @@ -0,0 +1,113 @@ +======================== +Survey answer generation +======================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e7f355f8840e090177aa255082a1bd6e2767e370a936bf151878950af0fb9a7a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/18.0/survey_answer_generation + :alt: OCA/survey +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/survey-18-0/survey-18-0-survey_answer_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=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to prefill answers from one survey to another. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +You can configure either a new or an existing survey. + +1. Go to *Surveys* and choose an existing one or create it. +2. In the *Options* tab, *Questions* group, choose the *Next Survey*. + +Now you'll have to configure the destination questions linked to this +survey's questions. + +1. Go to the *Answers* tab choose the linked question. +2. For simple/multiple choice qustions you'll have to link the answers + as well. + +Usage +===== + +Once the survey is configured, the users can fill them up as usual. The +answers in the next survey will be prefilled according to the linked +questions. + +If the user changes any response in the next question, we can tell the +difference in the backend: go to the participation and in the answers +tree let the column *Diff with origin* show. + +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 +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__ + + - David Vidal + - Pilar Vargas + +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. + +.. |maintainer-pilarvargas-tecnativa| image:: https://github.com/pilarvargas-tecnativa.png?size=40px + :target: https://github.com/pilarvargas-tecnativa + :alt: pilarvargas-tecnativa + +Current `maintainer `__: + +|maintainer-pilarvargas-tecnativa| + +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_answer_generation/__init__.py b/survey_answer_generation/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/survey_answer_generation/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/survey_answer_generation/__manifest__.py b/survey_answer_generation/__manifest__.py new file mode 100644 index 00000000..ec77e4be --- /dev/null +++ b/survey_answer_generation/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2022 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Survey answer generation", + "summary": "Link and compare answers from another survey", + "version": "18.0.1.0.0", + "development_status": "Beta", + "category": "Marketing/Survey", + "website": "https://github.com/OCA/survey", + "author": "Tecnativa, Odoo Community Association (OCA)", + "maintainers": ["pilarvargas-tecnativa"], + "license": "AGPL-3", + "depends": ["survey"], + "data": [ + "views/survey_question_views.xml", + "views/survey_survey_views.xml", + "views/survey_user_input_views.xml", + ], +} diff --git a/survey_answer_generation/i18n/es.po b/survey_answer_generation/i18n/es.po new file mode 100644 index 00000000..2046f7d5 --- /dev/null +++ b/survey_answer_generation/i18n/es.po @@ -0,0 +1,104 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_answer_generation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-11-16 15:06+0000\n" +"PO-Revision-Date: 2023-11-16 16:11+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.4\n" + +#. module: survey_answer_generation +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Current" +msgstr "Actual" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__diff_user_input_line_count +msgid "Diff User Input Line Count" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__diff_with_origin +msgid "Diff With Origin" +msgstr "Diferencias con el original" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_survey__next_survey_id +msgid "Next Survey" +msgstr "Próxima encuesta" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__next_survey_input_id +msgid "Next Survey Input" +msgstr "Respuestas de la próxima encuesta" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Next Survey Question" +msgstr "Pregunta de la próxima encuesta" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +msgid "Next Survey Question Answer" +msgstr "Respuesta a la pregunta de la próxima encuesta" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__origin_input_id +msgid "Origin Input" +msgstr "Respuestas de origen" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__origin_input_line +msgid "Origin Input Line" +msgstr "Línea de repuestas de origen" + +#. module: survey_answer_generation +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Prefill this answer of the next survey with the answer of this one" +msgstr "Rellenar previamente la respuesta de la próxima encuesta con la respuesta de esta" + +#. module: survey_answer_generation +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Previous" +msgstr "Anterior" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_survey +msgid "Survey" +msgstr "Encuesta" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question_answer +msgid "Survey Label" +msgstr "Etiqueta de la encuesta" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question +msgid "Survey Question" +msgstr "Pregunta de la encuesta" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input +msgid "Survey User Input" +msgstr "Entrada de usuario de la encuesta" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input_line +msgid "Survey User Input Line" +msgstr "Línea de entrada de usuario de la encuesta" diff --git a/survey_answer_generation/i18n/it.po b/survey_answer_generation/i18n/it.po new file mode 100644 index 00000000..4f678d62 --- /dev/null +++ b/survey_answer_generation/i18n/it.po @@ -0,0 +1,105 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_answer_generation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-23 12:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: survey_answer_generation +#. odoo-python +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Current" +msgstr "Attuale" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__diff_user_input_line_count +msgid "Diff User Input Line Count" +msgstr "Conteggio righe inserimento utente diverse" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__diff_with_origin +msgid "Diff With Origin" +msgstr "Differenza con l'origine" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_survey__next_survey_id +msgid "Next Survey" +msgstr "Sondaggio successivo" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__next_survey_input_id +msgid "Next Survey Input" +msgstr "Immissione sondaggio successivo" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Next Survey Question" +msgstr "Domanda sondaggio successivo" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +msgid "Next Survey Question Answer" +msgstr "Risposta domanda sondaggio successivo" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__origin_input_id +msgid "Origin Input" +msgstr "Immissione origine" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__origin_input_line +msgid "Origin Input Line" +msgstr "Riga immissione origine" + +#. module: survey_answer_generation +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Prefill this answer of the next survey with the answer of this one" +msgstr "Precompila questa risposta del sondaggio successivo con questa risposta" + +#. module: survey_answer_generation +#. odoo-python +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Previous" +msgstr "Precedente" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_survey +msgid "Survey" +msgstr "Sondaggio" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question_answer +msgid "Survey Label" +msgstr "Etichetta sondaggio" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question +msgid "Survey Question" +msgstr "Domanda sondaggio" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input +msgid "Survey User Input" +msgstr "Risposta utente al sondaggio" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input_line +msgid "Survey User Input Line" +msgstr "Riga risposta utente al sondaggio" diff --git a/survey_answer_generation/i18n/survey_answer_generation.pot b/survey_answer_generation/i18n/survey_answer_generation.pot new file mode 100644 index 00000000..cd263d28 --- /dev/null +++ b/survey_answer_generation/i18n/survey_answer_generation.pot @@ -0,0 +1,102 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * survey_answer_generation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \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_answer_generation +#. odoo-python +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Current" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__diff_user_input_line_count +msgid "Diff User Input Line Count" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__diff_with_origin +msgid "Diff With Origin" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_survey__next_survey_id +msgid "Next Survey" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__next_survey_input_id +msgid "Next Survey Input" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Next Survey Question" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +msgid "Next Survey Question Answer" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input__origin_input_id +msgid "Origin Input" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,field_description:survey_answer_generation.field_survey_user_input_line__origin_input_line +msgid "Origin Input Line" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question__next_survey_question_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_answer_id +#: model:ir.model.fields,help:survey_answer_generation.field_survey_question_answer__next_survey_question_id +msgid "Prefill this answer of the next survey with the answer of this one" +msgstr "" + +#. module: survey_answer_generation +#. odoo-python +#: code:addons/survey_answer_generation/models/survey_user_input.py:0 +#, python-format +msgid "Previous" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_survey +msgid "Survey" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question_answer +msgid "Survey Label" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_question +msgid "Survey Question" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input +msgid "Survey User Input" +msgstr "" + +#. module: survey_answer_generation +#: model:ir.model,name:survey_answer_generation.model_survey_user_input_line +msgid "Survey User Input Line" +msgstr "" diff --git a/survey_answer_generation/models/__init__.py b/survey_answer_generation/models/__init__.py new file mode 100644 index 00000000..7b981765 --- /dev/null +++ b/survey_answer_generation/models/__init__.py @@ -0,0 +1,3 @@ +from . import survey_question +from . import survey_user_input +from . import survey_survey diff --git a/survey_answer_generation/models/survey_question.py b/survey_answer_generation/models/survey_question.py new file mode 100644 index 00000000..4b7ee278 --- /dev/null +++ b/survey_answer_generation/models/survey_question.py @@ -0,0 +1,30 @@ +# Copyright 2022 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class SurveyQuestion(models.Model): + _inherit = "survey.question" + + next_survey_id = fields.Many2one(related="survey_id.next_survey_id") + next_survey_question_id = fields.Many2one( + comodel_name="survey.question", + domain=( + "[('survey_id', '=', next_survey_id), " + "('question_type', '=', question_type)]" + ), + help="Prefill this answer of the next survey with the answer of this one", + ) + + +class SurveyLabel(models.Model): + _inherit = "survey.question.answer" + + next_survey_question_id = fields.Many2one( + related="question_id.next_survey_question_id" + ) + next_survey_question_answer_id = fields.Many2one( + comodel_name="survey.question.answer", + domain="[('question_id', '=', next_survey_question_id)]", + help="Prefill this answer of the next survey with the answer of this one", + ) diff --git a/survey_answer_generation/models/survey_survey.py b/survey_answer_generation/models/survey_survey.py new file mode 100644 index 00000000..03636165 --- /dev/null +++ b/survey_answer_generation/models/survey_survey.py @@ -0,0 +1,42 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class SurveySurvey(models.Model): + _inherit = "survey.survey" + + next_survey_id = fields.Many2one( + comodel_name="survey.survey", domain="[('id', '!=', id)]" + ) + + def _create_answer( + self, + user=False, + partner=False, + email=False, + test_entry=False, + check_attempts=True, + **additional_vals, + ): + """We link the next user input right away so we can sync the responses when + they're submitted""" + user_inputs = super()._create_answer( + user=user, + partner=partner, + email=email, + test_entry=test_entry, + check_attempts=check_attempts, + **additional_vals, + ) + for user_input in user_inputs.filtered("survey_id.next_survey_id"): + next_survey_input = user_input.survey_id.next_survey_id._create_answer( + user=user, + email=email, + test_entry=test_entry, + check_attempts=check_attempts, + **additional_vals, + ) + user_input.next_survey_input_id = next_survey_input + next_survey_input.origin_input_id = user_input + return user_inputs diff --git a/survey_answer_generation/models/survey_user_input.py b/survey_answer_generation/models/survey_user_input.py new file mode 100644 index 00000000..0b3db405 --- /dev/null +++ b/survey_answer_generation/models/survey_user_input.py @@ -0,0 +1,120 @@ +# Copyright 2022 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.tools.misc import get_diff + + +class SurveyUserInput(models.Model): + _inherit = "survey.user_input" + + next_survey_input_id = fields.Many2one( + comodel_name="survey.user_input", readonly=True + ) + origin_input_id = fields.Many2one(comodel_name="survey.user_input", readonly=True) + diff_user_input_line_count = fields.Integer( + compute="_compute_diff_user_input_line_count" + ) + + def _save_lines(self, question, answer, comment=None, overwrite_existing=True): + """Sync the answers for the next question when they're saved so we don't have + to repeat the logic again""" + res = super()._save_lines( + question, answer, comment=comment, overwrite_existing=overwrite_existing + ) + if question.next_survey_question_id and self.next_survey_input_id: + self.next_survey_input_id.with_context( + save_next_question_answer=True + )._save_lines(question.next_survey_question_id, answer, comment) + return res + + def _get_line_answer_values(self, question, answer, answer_type): + """Link the answers""" + vals = super()._get_line_answer_values(question, answer, answer_type) + if not self.env.context.get("save_next_question_answer"): + return vals + # Find out the next question answer id to save it instead of the current one + if answer_type == "suggestion": + suggested_answer_id = vals.get("suggested_answer_id") + if suggested_answer_id: + suggested_answer = self.env["survey.question.answer"].browse( + suggested_answer_id + ) + vals["suggested_answer_id"] = ( + suggested_answer.next_survey_question_answer_id.id + ) + origin_line = self.origin_input_id.user_input_line_ids.filtered( + lambda x: x.question_id.next_survey_question_id == question + and x.answer_type == "suggestion" + and x.suggested_answer_id.id == suggested_answer_id + ) + else: + origin_line = self.origin_input_id.user_input_line_ids.filtered( + lambda x: x.question_id.next_survey_question_id == question + ) + if origin_line: + vals["origin_input_line"] = origin_line.id + return vals + + def _get_line_comment_values(self, question, comment): + """Comments go their own way""" + vals = super()._get_line_comment_values(question, comment) + if self.origin_input_id and ( + origin_line := self.origin_input_id.user_input_line_ids.filtered( + lambda x: x.question_id.next_survey_question_id == question + and x.answer_type == "char_box" + ) + ): + vals["origin_input_line"] = origin_line.id + return vals + + def _compute_diff_user_input_line_count(self): + self.diff_user_input_line_count = False + for user_input in self.filtered("origin_input_id"): + user_input.diff_user_input_line_count = len( + user_input.user_input_line_ids.filtered("diff_with_origin") + ) + + def _mark_done(self): + """Ensure that the contact info matches for both surveys""" + res = super()._mark_done() + if ( + self.next_survey_input_id + and self.next_survey_input_id.partner_id != self.partner_id + ): + self.next_survey_input_id.partner_id = self.partner_id + self.next_survey_input_id.email = self.email + return res + + +class SurveyUserInputLine(models.Model): + _inherit = "survey.user_input.line" + + origin_input_line = fields.Many2one(comodel_name="survey.user_input.line") + diff_with_origin = fields.Html( + compute="_compute_diff_with_origin", + store=True, + ) + + @api.depends( + *[ + f"value_{x}" + for x in {"char_box", "text_box", "numerical_box", "date", "datetime"} + ] + ) + def _compute_diff_with_origin(self): + """When two answers are linked the next survey question is prefilled. If the + user changes the answer in the next survey we want to spot the difference""" + self.diff_with_origin = False + for line in self.filtered("origin_input_line"): + if line.answer_type and line.answer_type != "suggestion": + value = f"value_{line.answer_type}" + previous_value = str(line.origin_input_line[value]) + current_value = str(line[value]) + else: + current_value = line.suggested_answer_id.value + previous_value = line.origin_input_line.suggested_answer_id.value + if previous_value == current_value: + continue + line.diff_with_origin = get_diff( + (previous_value, _("Previous")), (current_value, _("Current")) + ) diff --git a/survey_answer_generation/pyproject.toml b/survey_answer_generation/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/survey_answer_generation/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/survey_answer_generation/readme/CONFIGURE.md b/survey_answer_generation/readme/CONFIGURE.md new file mode 100644 index 00000000..3861743a --- /dev/null +++ b/survey_answer_generation/readme/CONFIGURE.md @@ -0,0 +1,11 @@ +You can configure either a new or an existing survey. + +1. Go to *Surveys* and choose an existing one or create it. +2. In the *Options* tab, *Questions* group, choose the *Next Survey*. + +Now you'll have to configure the destination questions linked to this +survey's questions. + +1. Go to the *Answers* tab choose the linked question. +2. For simple/multiple choice qustions you'll have to link the answers + as well. diff --git a/survey_answer_generation/readme/CONTRIBUTORS.md b/survey_answer_generation/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..68e9227d --- /dev/null +++ b/survey_answer_generation/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Tecnativa](https://www.tecnativa.com) + - David Vidal + - Pilar Vargas diff --git a/survey_answer_generation/readme/DESCRIPTION.md b/survey_answer_generation/readme/DESCRIPTION.md new file mode 100644 index 00000000..411964cf --- /dev/null +++ b/survey_answer_generation/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module allows to prefill answers from one survey to another. diff --git a/survey_answer_generation/readme/USAGE.md b/survey_answer_generation/readme/USAGE.md new file mode 100644 index 00000000..d96ba6d6 --- /dev/null +++ b/survey_answer_generation/readme/USAGE.md @@ -0,0 +1,7 @@ +Once the survey is configured, the users can fill them up as usual. The +answers in the next survey will be prefilled according to the linked +questions. + +If the user changes any response in the next question, we can tell the +difference in the backend: go to the participation and in the answers +tree let the column *Diff with origin* show. diff --git a/survey_answer_generation/static/description/icon.png b/survey_answer_generation/static/description/icon.png new file mode 100644 index 00000000..ae0af814 Binary files /dev/null and b/survey_answer_generation/static/description/icon.png differ diff --git a/survey_answer_generation/static/description/index.html b/survey_answer_generation/static/description/index.html new file mode 100644 index 00000000..f514ab61 --- /dev/null +++ b/survey_answer_generation/static/description/index.html @@ -0,0 +1,455 @@ + + + + + +Survey answer generation + + + +
+

Survey answer generation

+ + +

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

+

This module allows to prefill answers from one survey to another.

+

Table of contents

+ +
+

Configuration

+

You can configure either a new or an existing survey.

+
    +
  1. Go to Surveys and choose an existing one or create it.
  2. +
  3. In the Options tab, Questions group, choose the Next Survey.
  4. +
+

Now you’ll have to configure the destination questions linked to this +survey’s questions.

+
    +
  1. Go to the Answers tab choose the linked question.
  2. +
  3. For simple/multiple choice qustions you’ll have to link the answers +as well.
  4. +
+
+
+

Usage

+

Once the survey is configured, the users can fill them up as usual. The +answers in the next survey will be prefilled according to the linked +questions.

+

If the user changes any response in the next question, we can tell the +difference in the backend: go to the participation and in the answers +tree let the column Diff with origin show.

+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

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.

+

Current maintainer:

+

pilarvargas-tecnativa

+

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_answer_generation/tests/__init__.py b/survey_answer_generation/tests/__init__.py new file mode 100644 index 00000000..72db1c0f --- /dev/null +++ b/survey_answer_generation/tests/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_survey_answer_generation diff --git a/survey_answer_generation/tests/test_survey_answer_generation.py b/survey_answer_generation/tests/test_survey_answer_generation.py new file mode 100644 index 00000000..0502a6fa --- /dev/null +++ b/survey_answer_generation/tests/test_survey_answer_generation.py @@ -0,0 +1,237 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import TransactionCase + + +class TestSurveyAnswerGeneration(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.survey1 = cls.env["survey.survey"].create( + {"title": "Survey 1", "access_mode": "public"} + ) + cls.survey2 = cls.env["survey.survey"].create( + {"title": "Survey 2", "access_mode": "public"} + ) + cls.survey1.next_survey_id = cls.survey2 + + cls.text_q1 = cls.env["survey.question"].create( + { + "title": "Describe yourself", + "survey_id": cls.survey1.id, + "question_type": "char_box", + "sequence": 1, + } + ) + cls.text_q2 = cls.env["survey.question"].create( + { + "title": "Describe yourself", + "survey_id": cls.survey2.id, + "question_type": "char_box", + "sequence": 1, + } + ) + cls.text_q1.next_survey_question_id = cls.text_q2 + + cls.choice_q1 = cls.env["survey.question"].create( + { + "title": "Choose an option", + "survey_id": cls.survey1.id, + "question_type": "simple_choice", + "sequence": 2, + } + ) + cls.choice_q2 = cls.env["survey.question"].create( + { + "title": "Choose an option", + "survey_id": cls.survey2.id, + "question_type": "simple_choice", + "sequence": 2, + } + ) + cls.choice_q1.next_survey_question_id = cls.choice_q2 + + cls.option_a1 = cls.env["survey.question.answer"].create( + {"question_id": cls.choice_q1.id, "value": "Option A"} + ) + cls.option_a2 = cls.env["survey.question.answer"].create( + {"question_id": cls.choice_q2.id, "value": "Option A"} + ) + cls.option_a1.next_survey_question_answer_id = cls.option_a2 + + cls.partner = cls.env["res.partner"].create({"name": "Test User"}) + + def _create_input(self, survey, **kwargs): + return survey._create_answer(test_entry=True, **kwargs) + + def test_create_answer_creates_linked_input(self): + """Creating an answer for survey1 also creates a linked input for survey2.""" + input1 = self._create_input(self.survey1) + self.assertTrue(input1.next_survey_input_id) + self.assertEqual(input1.next_survey_input_id.survey_id, self.survey2) + self.assertEqual(input1.next_survey_input_id.origin_input_id, input1) + + def test_create_answer_no_next_survey(self): + """Creating an answer for a survey with no next_survey_id has no linked + input.""" + input2 = self._create_input(self.survey2) + self.assertFalse(input2.next_survey_input_id) + + def test_save_text_answer_syncs_to_next_survey(self): + """Saving a char_box answer in survey1 pre-fills the linked question in + survey2.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.text_q1, "Hello OCA") + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + ) + self.assertTrue(synced_line) + self.assertEqual(synced_line.value_char_box, "Hello OCA") + + def test_save_suggestion_answer_syncs_to_next_survey(self): + """Saving a choice answer in survey1 pre-fills the mapped option in survey2.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.choice_q1, self.option_a1.id) + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.choice_q2 + ) + self.assertTrue(synced_line) + self.assertEqual(synced_line.suggested_answer_id, self.option_a2) + + def test_save_lines_without_linked_question_does_not_sync(self): + """Answers to questions without next_survey_question_id are not synced.""" + unlinked_q = self.env["survey.question"].create( + { + "title": "Unlinked question", + "survey_id": self.survey1.id, + "question_type": "char_box", + "sequence": 3, + } + ) + input1 = self._create_input(self.survey1) + input1._save_lines(unlinked_q, "should not sync") + next_input = input1.next_survey_input_id + self.assertFalse(next_input.user_input_line_ids) + + def test_diff_with_origin_computed_on_change(self): + """When a pre-filled answer is changed in survey2, the diff is computed.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.text_q1, "Original answer") + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + ) + # Simulate user changing the pre-filled answer in survey2 + synced_line.value_char_box = "Changed answer" + self.assertTrue(synced_line.diff_with_origin) + + def test_diff_with_origin_empty_when_unchanged(self): + """When the pre-filled answer is not changed, no diff is shown.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.text_q1, "Same answer") + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + ) + self.assertFalse(synced_line.diff_with_origin) + + def test_mark_done_syncs_partner_to_next_survey(self): + """Completing survey1 updates the partner on the linked survey2 input.""" + input1 = self._create_input(self.survey1, partner=self.partner) + next_input = input1.next_survey_input_id + self.assertFalse(next_input.partner_id) + input1._mark_done() + self.assertEqual(next_input.partner_id, self.partner) + + def test_comment_syncs_to_next_survey(self): + """A comment on a linked question is synced to the next survey input.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.text_q1, "Answer", comment="my comment") + next_input = input1.next_survey_input_id + comment_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + and line.answer_type == "char_box" + ) + self.assertTrue(comment_line) + + def test_diff_with_origin_suggestion_type(self): + """diff_with_origin is computed when a pre-filled suggestion answer changes.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.choice_q1, self.option_a1.id) + next_input = input1.next_survey_input_id + option_b2 = self.env["survey.question.answer"].create( + {"question_id": self.choice_q2.id, "value": "Option B"} + ) + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.choice_q2 + ) + synced_line.suggested_answer_id = option_b2 + self.assertTrue(synced_line.diff_with_origin) + + def test_diff_line_count_computed(self): + """diff_user_input_line_count reflects the number of lines with diffs.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.text_q1, "Original") + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + ) + self.assertEqual(next_input.diff_user_input_line_count, 0) + synced_line.value_char_box = "Modified" + next_input._compute_diff_user_input_line_count() + self.assertEqual(next_input.diff_user_input_line_count, 1) + + def test_mark_done_no_next_survey_input(self): + """_mark_done on an input with no linked input completes without error.""" + input2 = self._create_input(self.survey2) + self.assertFalse(input2.next_survey_input_id) + input2._mark_done() + + def test_mark_done_partner_already_matches(self): + """_mark_done skips the partner update when the next input already has the + same partner.""" + input1 = self._create_input(self.survey1, partner=self.partner) + input1.next_survey_input_id.partner_id = self.partner + input1._mark_done() + self.assertEqual(input1.next_survey_input_id.partner_id, self.partner) + + def test_save_skipped_choice_syncs_to_next_survey(self): + """Saving a skipped (falsy) choice answer is synced without error and does + not set origin_input_line on the synced line.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.choice_q1, False) + next_input = input1.next_survey_input_id + synced_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.choice_q2 + ) + self.assertTrue(synced_line.skipped) + self.assertFalse(synced_line.origin_input_line) + + def test_comment_on_choice_answer_syncs_origin_line(self): + """A comment on a linked choice question is synced to the next survey input + with origin_input_line set, exercising _get_line_comment_values.""" + input1 = self._create_input(self.survey1) + input1._save_lines(self.choice_q1, self.option_a1.id, comment="my comment") + next_input = input1.next_survey_input_id + comment_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.choice_q2 + and line.answer_type == "char_box" + ) + self.assertTrue(comment_line) + self.assertTrue(comment_line.origin_input_line) + + def test_save_lines_to_next_input_without_origin_line(self): + """Saving directly to next_input with context but without a corresponding + origin line does not set origin_input_line on the saved line.""" + input1 = self._create_input(self.survey1) + next_input = input1.next_survey_input_id + next_input.with_context(save_next_question_answer=True)._save_lines( + self.text_q2, "Direct value" + ) + saved_line = next_input.user_input_line_ids.filtered( + lambda line: line.question_id == self.text_q2 + ) + self.assertTrue(saved_line) + self.assertFalse(saved_line.origin_input_line) diff --git a/survey_answer_generation/views/survey_question_views.xml b/survey_answer_generation/views/survey_question_views.xml new file mode 100644 index 00000000..d7f1c966 --- /dev/null +++ b/survey_answer_generation/views/survey_question_views.xml @@ -0,0 +1,25 @@ + + + + survey.question + + + + + + + + + + + + + + + diff --git a/survey_answer_generation/views/survey_survey_views.xml b/survey_answer_generation/views/survey_survey_views.xml new file mode 100644 index 00000000..8e1fc49f --- /dev/null +++ b/survey_answer_generation/views/survey_survey_views.xml @@ -0,0 +1,12 @@ + + + + survey.survey + + + + + + + + diff --git a/survey_answer_generation/views/survey_user_input_views.xml b/survey_answer_generation/views/survey_user_input_views.xml new file mode 100644 index 00000000..24229c88 --- /dev/null +++ b/survey_answer_generation/views/survey_user_input_views.xml @@ -0,0 +1,40 @@ + + + + survey.user_input + + + + + + + + + diff_with_origin != False + + + + + + + +