first commit
This commit is contained in:
commit
a33bda5b62
2
__init__.py
Normal file
2
__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import models
|
||||||
20
__manifest__.py
Normal file
20
__manifest__.py
Normal 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',
|
||||||
|
}
|
||||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
4
models/__init__.py
Normal file
4
models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import survey_survey
|
||||||
|
from . import survey_question
|
||||||
|
from . import survey_user_input
|
||||||
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/survey_question.cpython-312.pyc
Normal file
BIN
models/__pycache__/survey_question.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/survey_survey.cpython-312.pyc
Normal file
BIN
models/__pycache__/survey_survey.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/survey_user_input.cpython-312.pyc
Normal file
BIN
models/__pycache__/survey_user_input.cpython-312.pyc
Normal file
Binary file not shown.
22
models/survey_question.py
Normal file
22
models/survey_question.py
Normal 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
28
models/survey_survey.py
Normal 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
|
||||||
51
models/survey_user_input.py
Normal file
51
models/survey_user_input.py
Normal 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
|
||||||
17
views/survey_question_views.xml
Normal file
17
views/survey_question_views.xml
Normal 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>
|
||||||
13
views/survey_survey_views.xml
Normal file
13
views/survey_survey_views.xml
Normal 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>
|
||||||
34
views/survey_user_input_views.xml
Normal file
34
views/survey_user_input_views.xml
Normal 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>
|
||||||
Loading…
Reference in New Issue
Block a user