first commit
This commit is contained in:
commit
f7b6572ded
2
__init__.py
Normal file
2
__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import models
|
||||||
23
__manifest__.py
Normal file
23
__manifest__.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
{
|
||||||
|
'name': 'DISC Personality Test Survey',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'summary': 'DISC Personality Assessment Test in Survey module',
|
||||||
|
'description': """
|
||||||
|
This module adds a DISC Personality Test to the Odoo Survey module.
|
||||||
|
It includes 24 sets of questions where participants choose the 'Most' (Paling)
|
||||||
|
and 'Least' (Kurang) descriptive traits.
|
||||||
|
""",
|
||||||
|
'depends': ['survey'],
|
||||||
|
'data': [
|
||||||
|
'data/survey_data.xml',
|
||||||
|
'views/survey_user_input_views.xml',
|
||||||
|
'views/survey_templates.xml',
|
||||||
|
'report/survey_disc_report.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
1092
data/survey_data.xml
Normal file
1092
data/survey_data.xml
Normal file
File diff suppressed because it is too large
Load Diff
3
models/__init__.py
Normal file
3
models/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import survey_question_answer
|
||||||
|
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_answer.cpython-312.pyc
Normal file
BIN
models/__pycache__/survey_question_answer.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.
21
models/survey_question_answer.py
Normal file
21
models/survey_question_answer.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
class SurveyQuestionAnswer(models.Model):
|
||||||
|
_inherit = 'survey.question.answer'
|
||||||
|
|
||||||
|
disc_category_p = fields.Selection([
|
||||||
|
('D', 'Dominance'),
|
||||||
|
('I', 'Influence'),
|
||||||
|
('S', 'Steadiness'),
|
||||||
|
('C', 'Conscientiousness'),
|
||||||
|
('*', 'Star (No Score)'),
|
||||||
|
], string='DISC Category (Most/P)')
|
||||||
|
|
||||||
|
disc_category_k = fields.Selection([
|
||||||
|
('D', 'Dominance'),
|
||||||
|
('I', 'Influence'),
|
||||||
|
('S', 'Steadiness'),
|
||||||
|
('C', 'Conscientiousness'),
|
||||||
|
('*', 'Star (No Score)'),
|
||||||
|
], string='DISC Category (Least/K)')
|
||||||
122
models/survey_user_input.py
Normal file
122
models/survey_user_input.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
class SurveyUserInput(models.Model):
|
||||||
|
_inherit = 'survey.user_input'
|
||||||
|
|
||||||
|
# MOST (Graph 1)
|
||||||
|
p_d = fields.Integer('P: D', compute='_compute_disc_scores', store=True)
|
||||||
|
p_i = fields.Integer('P: I', compute='_compute_disc_scores', store=True)
|
||||||
|
p_s = fields.Integer('P: S', compute='_compute_disc_scores', store=True)
|
||||||
|
p_c = fields.Integer('P: C', compute='_compute_disc_scores', store=True)
|
||||||
|
p_star = fields.Integer('P: *', compute='_compute_disc_scores', store=True)
|
||||||
|
|
||||||
|
# LEAST (Graph 2)
|
||||||
|
k_d = fields.Integer('K: D', compute='_compute_disc_scores', store=True)
|
||||||
|
k_i = fields.Integer('K: I', compute='_compute_disc_scores', store=True)
|
||||||
|
k_s = fields.Integer('K: S', compute='_compute_disc_scores', store=True)
|
||||||
|
k_c = fields.Integer('K: C', compute='_compute_disc_scores', store=True)
|
||||||
|
k_star = fields.Integer('K: *', compute='_compute_disc_scores', store=True)
|
||||||
|
|
||||||
|
# CHANGE (Graph 3)
|
||||||
|
c_d = fields.Integer('C: D', compute='_compute_disc_scores', store=True)
|
||||||
|
c_i = fields.Integer('C: I', compute='_compute_disc_scores', store=True)
|
||||||
|
c_s = fields.Integer('C: S', compute='_compute_disc_scores', store=True)
|
||||||
|
c_c = fields.Integer('C: C', compute='_compute_disc_scores', store=True)
|
||||||
|
|
||||||
|
disc_profile = fields.Char('DISC Profile', compute='_compute_disc_scores', store=True)
|
||||||
|
|
||||||
|
def _mark_done(self):
|
||||||
|
""" Strictly enforce DISC validation: Exactly 1 P and 1 K per set. """
|
||||||
|
for rec in self:
|
||||||
|
# Group lines by question
|
||||||
|
lines_by_question = {}
|
||||||
|
for line in rec.user_input_line_ids:
|
||||||
|
if not line.matrix_row_id or not line.suggested_answer_id:
|
||||||
|
continue
|
||||||
|
q_id = line.question_id
|
||||||
|
if q_id not in lines_by_question:
|
||||||
|
lines_by_question[q_id] = []
|
||||||
|
lines_by_question[q_id].append(line)
|
||||||
|
|
||||||
|
# We expect 24 sets
|
||||||
|
if len(lines_by_question) < 24:
|
||||||
|
raise ValidationError(_("You must answer all 24 sets of the assessment."))
|
||||||
|
|
||||||
|
for q_id, lines in lines_by_question.items():
|
||||||
|
p_count = len([l for l in lines if l.suggested_answer_id.value == 'P'])
|
||||||
|
k_count = len([l for l in lines if l.suggested_answer_id.value == 'K'])
|
||||||
|
|
||||||
|
if p_count != 1 or k_count != 1:
|
||||||
|
raise ValidationError(
|
||||||
|
_("Invalid selection in '%s'. You must select exactly one 'P' (Most) and one 'K' (Least).") % q_id.title
|
||||||
|
)
|
||||||
|
return super(SurveyUserInput, self)._mark_done()
|
||||||
|
|
||||||
|
@api.depends('user_input_line_ids', 'user_input_line_ids.suggested_answer_id')
|
||||||
|
def _compute_disc_scores(self):
|
||||||
|
for rec in self:
|
||||||
|
# Initialize counts
|
||||||
|
counts = {
|
||||||
|
'p': {'D': 0, 'I': 0, 'S': 0, 'C': 0, '*': 0},
|
||||||
|
'k': {'D': 0, 'I': 0, 'S': 0, 'C': 0, '*': 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Group lines by question to check for multiple selections per set
|
||||||
|
lines_by_question = {}
|
||||||
|
for line in rec.user_input_line_ids:
|
||||||
|
if not line.matrix_row_id or not line.suggested_answer_id:
|
||||||
|
continue
|
||||||
|
q_id = line.question_id.id
|
||||||
|
if q_id not in lines_by_question:
|
||||||
|
lines_by_question[q_id] = []
|
||||||
|
lines_by_question[q_id].append(line)
|
||||||
|
|
||||||
|
# Process each question set
|
||||||
|
for q_id, lines in lines_by_question.items():
|
||||||
|
p_selections = [l for l in lines if l.suggested_answer_id.value == 'P']
|
||||||
|
k_selections = [l for l in lines if l.suggested_answer_id.value == 'K']
|
||||||
|
|
||||||
|
# Validation: We only count if exactly one P and one K are selected per set
|
||||||
|
# This enforces the "1 P and 1 K per set" rule
|
||||||
|
if len(p_selections) == 1:
|
||||||
|
line = p_selections[0]
|
||||||
|
cat = line.matrix_row_id.disc_category_p
|
||||||
|
if cat in counts['p']:
|
||||||
|
counts['p'][cat] += 1
|
||||||
|
|
||||||
|
if len(k_selections) == 1:
|
||||||
|
line = k_selections[0]
|
||||||
|
cat = line.matrix_row_id.disc_category_k
|
||||||
|
if cat in counts['k']:
|
||||||
|
counts['k'][cat] += 1
|
||||||
|
|
||||||
|
# Update fields
|
||||||
|
rec.p_d = counts['p']['D']
|
||||||
|
rec.p_i = counts['p']['I']
|
||||||
|
rec.p_s = counts['p']['S']
|
||||||
|
rec.p_c = counts['p']['C']
|
||||||
|
rec.p_star = counts['p']['*']
|
||||||
|
|
||||||
|
rec.k_d = counts['k']['D']
|
||||||
|
rec.k_i = counts['k']['I']
|
||||||
|
rec.k_s = counts['k']['S']
|
||||||
|
rec.k_c = counts['k']['C']
|
||||||
|
rec.k_star = counts['k']['*']
|
||||||
|
|
||||||
|
# Graph 3: Change (P - K)
|
||||||
|
rec.c_d = rec.p_d - rec.k_d
|
||||||
|
rec.c_i = rec.p_i - rec.k_i
|
||||||
|
rec.c_s = rec.p_s - rec.k_s
|
||||||
|
rec.c_c = rec.p_c - rec.k_c
|
||||||
|
|
||||||
|
# Determine Profile (based on highest score in Graph 3)
|
||||||
|
scores = {'D': rec.c_d, 'I': rec.c_i, 'S': rec.c_s, 'C': rec.c_c}
|
||||||
|
top_trait = max(scores, key=scores.get)
|
||||||
|
profile_map = {
|
||||||
|
'D': 'Director (Dominance)',
|
||||||
|
'I': 'Influencer (Influence)',
|
||||||
|
'S': 'Steady (Steadiness)',
|
||||||
|
'C': 'Compliant (Compliance)'
|
||||||
|
}
|
||||||
|
rec.disc_profile = profile_map.get(top_trait, 'Mixed')
|
||||||
89
report/survey_disc_report.xml
Normal file
89
report/survey_disc_report.xml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="action_report_disc_result" model="ir.actions.report">
|
||||||
|
<field name="name">DISC Assessment Result</field>
|
||||||
|
<field name="model">survey.user_input</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">survey_disc_test.report_disc_result</field>
|
||||||
|
<field name="report_file">survey_disc_test.report_disc_result</field>
|
||||||
|
<field name="print_report_name">'DISC_Result_%s' % (object.partner_id.name or 'User')</field>
|
||||||
|
<field name="binding_model_id" ref="model_survey_user_input"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<template id="report_disc_result">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2>DISC Personality Assessment Report</h2>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt32">
|
||||||
|
<div class="col-6">
|
||||||
|
<strong>Participant:</strong> <span t-field="o.partner_id.name"/><br/>
|
||||||
|
<strong>Email:</strong> <span t-field="o.email"/><br/>
|
||||||
|
<strong>Date:</strong> <span t-field="o.create_date" t-options='{"widget": "date"}'/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 text-right">
|
||||||
|
<h3>Profile: <span t-field="o.disc_profile" class="text-primary"/></h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt32 text-center">
|
||||||
|
<div class="col-4">
|
||||||
|
<table class="table table-sm border">
|
||||||
|
<thead class="bg-primary text-white">
|
||||||
|
<tr><th colspan="2">GRAPH 1: MOST (MASK)</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>D</td><td t-field="o.p_d"/></tr>
|
||||||
|
<tr><td>I</td><td t-field="o.p_i"/></tr>
|
||||||
|
<tr><td>S</td><td t-field="o.p_s"/></tr>
|
||||||
|
<tr><td>C</td><td t-field="o.p_c"/></tr>
|
||||||
|
<tr><td>*</td><td t-field="o.p_star"/></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<table class="table table-sm border">
|
||||||
|
<thead class="bg-primary text-white">
|
||||||
|
<tr><th colspan="2">GRAPH 2: LEAST (CORE)</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>D</td><td t-field="o.k_d"/></tr>
|
||||||
|
<tr><td>I</td><td t-field="o.k_i"/></tr>
|
||||||
|
<tr><td>S</td><td t-field="o.k_s"/></tr>
|
||||||
|
<tr><td>C</td><td t-field="o.k_c"/></tr>
|
||||||
|
<tr><td>*</td><td t-field="o.k_star"/></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<table class="table table-sm border">
|
||||||
|
<thead class="bg-primary text-white">
|
||||||
|
<tr><th colspan="2">GRAPH 3: CHANGE (MIRROR)</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>D</td><td t-field="o.c_d"/></tr>
|
||||||
|
<tr><td>I</td><td t-field="o.c_i"/></tr>
|
||||||
|
<tr><td>S</td><td t-field="o.c_s"/></tr>
|
||||||
|
<tr><td>C</td><td t-field="o.c_c"/></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt32 p-3 bg-light border">
|
||||||
|
<h4>Behavioral Interpretation</h4>
|
||||||
|
<p>Based on the scores above, the participant exhibits a <strong><span t-field="o.disc_profile"/></strong> personality profile.</p>
|
||||||
|
<p>This report provides an automated scoring based on the 24-question DISC assessment. For a deeper analysis, please consult with a behavioral specialist.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
23
views/survey_templates.xml
Normal file
23
views/survey_templates.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="survey_fill_form_done_inherit_disc" inherit_id="survey.survey_fill_form_done">
|
||||||
|
<xpath expr="//div[hasclass('o_survey_finished')]" position="inside">
|
||||||
|
<t t-if="survey.title == 'DISC Personality Test'">
|
||||||
|
<div class="mt-4 p-4 bg-light border rounded text-center">
|
||||||
|
<h3 class="text-primary">Your DISC Profile</h3>
|
||||||
|
<h2 class="display-4 font-weight-bold" t-esc="answer.disc_profile"/>
|
||||||
|
<p class="lead mt-3">Thank you for completing the assessment!</p>
|
||||||
|
</div>
|
||||||
|
<!-- Hide the default scoring -->
|
||||||
|
<style>
|
||||||
|
.o_survey_finished div:has(> h1:contains("You scored")),
|
||||||
|
.o_survey_finished div:has(> h1:contains("Congratulations")),
|
||||||
|
.o_survey_finished .text-danger,
|
||||||
|
.o_survey_finished .text-success {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</t>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
45
views/survey_user_input_views.xml
Normal file
45
views/survey_user_input_views.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="survey_user_input_view_form_inherit_disc" model="ir.ui.view">
|
||||||
|
<field name="name">survey.user.input.view.form.inherit.disc</field>
|
||||||
|
<field name="model">survey.user_input</field>
|
||||||
|
<field name="inherit_id" ref="survey.survey_user_input_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook" position="inside">
|
||||||
|
<page string="DISC Result" invisible="not p_d and not p_i and not p_s and not p_c">
|
||||||
|
<group string="DISC Profile Summary">
|
||||||
|
<field name="disc_profile" readonly="1" class="oe_inline oe_bold" style="font-size: 20px;"/>
|
||||||
|
</group>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<group string="Graph 1: MOST (Mask)">
|
||||||
|
<field name="p_d" string="D"/>
|
||||||
|
<field name="p_i" string="I"/>
|
||||||
|
<field name="p_s" string="S"/>
|
||||||
|
<field name="p_c" string="C"/>
|
||||||
|
<field name="p_star" string="*"/>
|
||||||
|
</group>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<group string="Graph 2: LEAST (Core)">
|
||||||
|
<field name="k_d" string="D"/>
|
||||||
|
<field name="k_i" string="I"/>
|
||||||
|
<field name="k_s" string="S"/>
|
||||||
|
<field name="k_c" string="C"/>
|
||||||
|
<field name="k_star" string="*"/>
|
||||||
|
</group>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<group string="Graph 3: CHANGE (Mirror)">
|
||||||
|
<field name="c_d" string="D"/>
|
||||||
|
<field name="c_i" string="I"/>
|
||||||
|
<field name="c_s" string="S"/>
|
||||||
|
<field name="c_c" string="C"/>
|
||||||
|
</group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user