first commit

This commit is contained in:
Suherdy Yacob 2026-05-15 17:34:29 +07:00
commit a33bda5b62
14 changed files with 191 additions and 0 deletions

2
__init__.py Normal file
View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

20
__manifest__.py Normal file
View File

@ -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',
}

Binary file not shown.

4
models/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import survey_survey
from . import survey_question
from . import survey_user_input

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

22
models/survey_question.py Normal file
View File

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

28
models/survey_survey.py Normal file
View File

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

View File

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

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="survey_question_view_form" model="ir.ui.view">
<field name="name">survey.question.view.form.inherit.scale.multiplier</field>
<field name="model">survey.question</field>
<field name="inherit_id" ref="survey.survey_question_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='scale_max']" position="after">
<field name="scale_multiplier" invisible="question_type != 'scale'"/>
</xpath>
<!-- Ensure is_scored_question is visible for scale -->
<xpath expr="//div[@name='survey_scored_question']/.." position="attributes">
<attribute name="invisible">scoring_type == 'no_scoring' or question_type not in ['numerical_box', 'date', 'datetime', 'scale']</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="survey_survey_view_form" model="ir.ui.view">
<field name="name">survey.survey.view.form.inherit.scoring.formula</field>
<field name="model">survey.survey</field>
<field name="inherit_id" ref="survey.survey_survey_view_form"/>
<field name="arch" type="xml">
<field name="scoring_type" position="after">
<field name="scoring_formula" invisible="scoring_type == 'no_scoring'"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="survey_user_input_view_form_inherit_scoring" model="ir.ui.view">
<field name="name">survey.user_input.view.form.inherit.scoring</field>
<field name="model">survey.user_input</field>
<field name="inherit_id" ref="survey.survey_user_input_view_form"/>
<field name="arch" type="xml">
<!-- Add scoring_formula to check in visibility -->
<xpath expr="//field[@name='survey_id']" position="after">
<field name="scoring_formula" invisible="1"/>
</xpath>
<!-- Modify the Score display to show average if formula is average -->
<xpath expr="//label[@for='scoring_percentage']" position="attributes">
<attribute name="invisible">scoring_type == 'no_scoring' or scoring_formula == 'average'</attribute>
</xpath>
<xpath expr="//field[@name='scoring_percentage']/.." position="attributes">
<attribute name="invisible">scoring_type == 'no_scoring' or scoring_formula == 'average'</attribute>
</xpath>
<!-- Add Average Score display -->
<xpath expr="//label[@for='scoring_percentage']" position="after">
<label for="scoring_average" string="Average Score" invisible="scoring_type == 'no_scoring' or scoring_formula != 'average'"/>
<div invisible="scoring_type == 'no_scoring' or scoring_formula != 'average'" class="d-inline-flex">
<field name="scoring_average" nolabel="1"/>
</div>
</xpath>
<!-- Update the footer in the answers list to show average if needed?
Actually, changing the 'sum' attribute is not dynamic.
But we can add a field below the list. -->
</field>
</record>
</odoo>