first commit

This commit is contained in:
Suherdy Yacob 2026-04-20 16:44:36 +07:00
commit f7b6572ded
13 changed files with 1420 additions and 0 deletions

2
__init__.py Normal file
View File

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

23
__manifest__.py Normal file
View 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',
}

Binary file not shown.

1092
data/survey_data.xml Normal file

File diff suppressed because it is too large Load Diff

3
models/__init__.py Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import survey_question_answer
from . import survey_user_input

Binary file not shown.

Binary file not shown.

View 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
View 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')

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

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

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