commit a33bda5b6229ac0a9dd7a1ac88d01cd9b7d77222 Author: Suherdy Yacob Date: Fri May 15 17:34:29 2026 +0700 first commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..cdec054 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Survey Scoring Extended', + 'version': '1.0', + 'category': 'Marketing/Surveys', + 'summary': 'Extended scoring for scale questions and average scoring formula', + 'description': """ +Extended scoring for scale questions and average scoring formula in surveys. + """, + 'author': 'Suherdy Yacob', + 'depends': ['survey'], + 'data': [ + 'views/survey_survey_views.xml', + 'views/survey_question_views.xml', + 'views/survey_user_input_views.xml', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..799f1d9 Binary files /dev/null and b/__pycache__/__init__.cpython-312.pyc differ diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..b309f42 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import survey_survey +from . import survey_question +from . import survey_user_input diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..ace8ae2 Binary files /dev/null and b/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/models/__pycache__/survey_question.cpython-312.pyc b/models/__pycache__/survey_question.cpython-312.pyc new file mode 100644 index 0000000..4485afb Binary files /dev/null and b/models/__pycache__/survey_question.cpython-312.pyc differ diff --git a/models/__pycache__/survey_survey.cpython-312.pyc b/models/__pycache__/survey_survey.cpython-312.pyc new file mode 100644 index 0000000..afe463f Binary files /dev/null and b/models/__pycache__/survey_survey.cpython-312.pyc differ diff --git a/models/__pycache__/survey_user_input.cpython-312.pyc b/models/__pycache__/survey_user_input.cpython-312.pyc new file mode 100644 index 0000000..2aa2ce9 Binary files /dev/null and b/models/__pycache__/survey_user_input.cpython-312.pyc differ diff --git a/models/survey_question.py b/models/survey_question.py new file mode 100644 index 0000000..2fa88df --- /dev/null +++ b/models/survey_question.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models + +class SurveyQuestion(models.Model): + _inherit = 'survey.question' + + scale_multiplier = fields.Float("Scale Multiplier", default=1.0, help="Multiply the scale value by this number for scoring.") + + @api.depends('question_type', 'scoring_type', 'answer_date', 'answer_datetime', 'answer_numerical_box', 'suggested_answer_ids.is_correct') + def _compute_is_scored_question(self): + super()._compute_is_scored_question() + for question in self: + if question.scoring_type == 'no_scoring': + question.is_scored_question = False + elif question.question_type == 'scale': + # Allow is_scored_question to be True for scale if set by user + # We don't want super() to force it to False. + # Since super() set it to False, we might want to default it to True if it's an assessment? + if question.survey_id.survey_type == 'assessment': + question.is_scored_question = True + elif question.question_type not in ['simple_choice', 'multiple_choice', 'date', 'datetime', 'numerical_box']: + question.is_scored_question = False diff --git a/models/survey_survey.py b/models/survey_survey.py new file mode 100644 index 0000000..5220f1c --- /dev/null +++ b/models/survey_survey.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models + +class SurveySurvey(models.Model): + _inherit = 'survey.survey' + + scoring_formula = fields.Selection([ + ('total', 'Total Score'), + ('average', 'Average Score'), + ], string='Scoring Formula', default='total', required=True) + + @api.depends( + 'question_and_page_ids', + 'question_and_page_ids.suggested_answer_ids', + 'question_and_page_ids.suggested_answer_ids.answer_score', + 'question_and_page_ids.question_type', + 'question_and_page_ids.scale_max', + 'question_and_page_ids.scale_multiplier', + ) + def _compute_scoring_max_obtainable(self): + super()._compute_scoring_max_obtainable() + for survey in self: + scale_max_obtainable = sum( + (question.scale_max * question.scale_multiplier) + for question in survey.question_ids + if question.question_type == 'scale' and question.is_scored_question + ) + survey.scoring_max_obtainable += scale_max_obtainable diff --git a/models/survey_user_input.py b/models/survey_user_input.py new file mode 100644 index 0000000..cdb44eb --- /dev/null +++ b/models/survey_user_input.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models + +class SurveyUserInput(models.Model): + _inherit = 'survey.user_input' + + scoring_formula = fields.Selection(related='survey_id.scoring_formula') + scoring_average = fields.Float("Average Score", compute='_compute_scoring_values', store=True) + + @api.depends('user_input_line_ids.answer_score', 'user_input_line_ids.question_id', 'predefined_question_ids.answer_score', 'survey_id.scoring_formula') + def _compute_scoring_values(self): + super()._compute_scoring_values() + for user_input in self: + # Re-calculate total_possible_score to include scale questions + scored_questions = user_input.predefined_question_ids.filtered(lambda q: q.is_scored_question) + + # Recalculate max possible score (sum of max points for each scored question) + new_max = 0 + for question in scored_questions: + if question.question_type == 'scale': + new_max += question.scale_max * question.scale_multiplier + elif question.question_type == 'simple_choice': + new_max += max([score for score in question.mapped('suggested_answer_ids.answer_score') if score > 0], default=0) + elif question.question_type == 'multiple_choice': + new_max += sum(score for score in question.mapped('suggested_answer_ids.answer_score') if score > 0) + else: + new_max += question.answer_score + + # Calculate total score obtained + total_score = sum(user_input.user_input_line_ids.mapped('answer_score')) + + # Update fields + user_input.scoring_total = total_score + user_input.scoring_average = total_score / (len(scored_questions) or 1) + + if new_max > 0: + user_input.scoring_percentage = round((total_score / new_max) * 100, 2) + else: + user_input.scoring_percentage = 0 + +class SurveyUserInputLine(models.Model): + _inherit = 'survey.user_input.line' + + @api.depends('answer_type', 'value_text_box', 'value_numerical_box', 'value_date', 'value_datetime', 'value_scale', + 'suggested_answer_id', 'user_input_id') + def _compute_answer_score(self): + super()._compute_answer_score() + for line in self: + if line.answer_type == 'scale' and line.question_id.is_scored_question: + line.answer_score = line.value_scale * line.question_id.scale_multiplier + line.answer_is_correct = True # Scale answers are considered "correct" if they provide points diff --git a/views/survey_question_views.xml b/views/survey_question_views.xml new file mode 100644 index 0000000..dde4fee --- /dev/null +++ b/views/survey_question_views.xml @@ -0,0 +1,17 @@ + + + + survey.question.view.form.inherit.scale.multiplier + survey.question + + + + + + + + scoring_type == 'no_scoring' or question_type not in ['numerical_box', 'date', 'datetime', 'scale'] + + + + diff --git a/views/survey_survey_views.xml b/views/survey_survey_views.xml new file mode 100644 index 0000000..e63d560 --- /dev/null +++ b/views/survey_survey_views.xml @@ -0,0 +1,13 @@ + + + + survey.survey.view.form.inherit.scoring.formula + survey.survey + + + + + + + + diff --git a/views/survey_user_input_views.xml b/views/survey_user_input_views.xml new file mode 100644 index 0000000..86b42bc --- /dev/null +++ b/views/survey_user_input_views.xml @@ -0,0 +1,34 @@ + + + + survey.user_input.view.form.inherit.scoring + survey.user_input + + + + + + + + + + scoring_type == 'no_scoring' or scoring_formula == 'average' + + + scoring_type == 'no_scoring' or scoring_formula == 'average' + + + + + + + + + +