first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
'name': "survey_answer_limit",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['survey'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'views/survey_question_views.xml',
|
||||
# 'views/survey_templates.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import controllers
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
# from odoo import http
|
||||
# from odoo.http import request
|
||||
#
|
||||
#
|
||||
# class SurveyController(http.Controller):
|
||||
#
|
||||
# @http.route()
|
||||
# def fill(self, survey, *args, **kwargs):
|
||||
# response = super().fill(survey, *args, **kwargs)
|
||||
#
|
||||
# # Inject JavaScript
|
||||
# js_code = """
|
||||
# <script>
|
||||
# document.addEventListener('DOMContentLoaded', function() {
|
||||
# console.log('Survey validation loaded');
|
||||
# // ... your validation code here ...
|
||||
# });
|
||||
# </script>
|
||||
# """
|
||||
#
|
||||
# # Inject before </body>
|
||||
# response.set_data(response.get_data(as_text=True).replace('</body>', js_code + '</body>'))
|
||||
# return response
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="survey_answer_limit.survey_answer_limit">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="survey_answer_limit.survey_answer_limit">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="survey_answer_limit.survey_answer_limit">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="survey_answer_limit.survey_answer_limit">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="survey_answer_limit.survey_answer_limit">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
from . import survey_question
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class SurveyQuestion(models.Model):
|
||||
_inherit = "survey.question"
|
||||
|
||||
validation_multiple_answers_min = fields.Integer(
|
||||
string="Minimum Number of Answers",
|
||||
default=0,
|
||||
help="Minimum number of answers required for this multiple choice question"
|
||||
)
|
||||
validation_multiple_answers_max = fields.Integer(
|
||||
string="Maximum Number of Answers",
|
||||
default=0,
|
||||
help="Maximum number of answers allowed for this multiple choice question (0 = no limit)"
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"non_neg_multiple_ans_min",
|
||||
"CHECK (validation_multiple_answers_min >= 0)",
|
||||
"The minimum number of answers must be non-negative!",
|
||||
),
|
||||
(
|
||||
"non_neg_multiple_ans_max",
|
||||
"CHECK (validation_multiple_answers_max >= 0)",
|
||||
"The maximum number of answers must be non-negative!",
|
||||
),
|
||||
(
|
||||
"validation_multiple_ans_range",
|
||||
"CHECK (validation_multiple_answers_min <= validation_multiple_answers_max OR validation_multiple_answers_max = 0)",
|
||||
"Max number of multiple answers cannot be smaller than min number!",
|
||||
),
|
||||
]
|
||||
|
||||
@api.constrains('validation_multiple_answers_min', 'validation_multiple_answers_max', 'question_type')
|
||||
def _check_answer_limit_config(self):
|
||||
"""Ensure answer limits are only configured for multiple_choice questions"""
|
||||
for question in self:
|
||||
if question.question_type != 'multiple_choice':
|
||||
if question.validation_multiple_answers_min > 0 or question.validation_multiple_answers_max > 0:
|
||||
raise ValidationError(_(
|
||||
"Answer limits can only be configured for Multiple Choice questions."
|
||||
))
|
||||
|
||||
def _validate_choice(self, answer, comment):
|
||||
"""Override to add min/max answer validation for multiple_choice questions."""
|
||||
# Call parent validation first
|
||||
res = super()._validate_choice(answer, comment)
|
||||
|
||||
# If parent already found an error, return it
|
||||
if res:
|
||||
return res
|
||||
|
||||
# Only apply custom logic for multiple_choice questions
|
||||
if self.question_type != "multiple_choice":
|
||||
return res
|
||||
|
||||
# Normalize answer to list format
|
||||
if isinstance(answer, list):
|
||||
answer_count = len([a for a in answer if a]) # Count non-empty answers
|
||||
elif answer:
|
||||
answer_count = 1
|
||||
else:
|
||||
answer_count = 0
|
||||
|
||||
# Add comment to count if it counts as answer
|
||||
if comment and self.comment_count_as_answer:
|
||||
answer_count += 1
|
||||
|
||||
# Apply min/max validation ONLY if max is set (> 0)
|
||||
if self.validation_multiple_answers_max > 0:
|
||||
min_answers = self.validation_multiple_answers_min
|
||||
max_answers = self.validation_multiple_answers_max
|
||||
|
||||
if not (min_answers <= answer_count <= max_answers):
|
||||
error_msg = self.validation_error_msg or _(
|
||||
"Please select between %(min)d and %(max)d answers.",
|
||||
min=min_answers,
|
||||
max=max_answers
|
||||
)
|
||||
return {self.id: error_msg}
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_survey_answer_limit_survey_answer_limit,survey_answer_limit.survey_answer_limit,model_survey_answer_limit_survey_answer_limit,base.group_user,1,1,1,1
|
||||
|
@@ -0,0 +1,111 @@
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("survey").add("answer_limit_validation", {
|
||||
start(env, { question }) {
|
||||
// ✅ Only apply to multiple choice questions with max_answers set
|
||||
if (question.question_type !== 'multiple_choice' || !question.max_answers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxAnswers = Number(question.max_answers);
|
||||
const questionSelector = `[data-question-id="${question.id}"]`;
|
||||
const checkboxes = document.querySelectorAll(`${questionSelector} input[type="checkbox"]`);
|
||||
|
||||
// 🎨 Add visual hint below options (Khmer language)
|
||||
const hint = document.createElement('small');
|
||||
hint.className = 'text-muted d-block mt-2 limit-hint';
|
||||
hint.textContent = `(សូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ)`;
|
||||
|
||||
const answersContainer = document.querySelector(`${questionSelector} .o_survey_question_answers`);
|
||||
if (answersContainer && !answersContainer.querySelector('.limit-hint')) {
|
||||
hint.classList.add('limit-hint');
|
||||
answersContainer.appendChild(hint);
|
||||
}
|
||||
|
||||
// 📌 Track checkbox changes
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const checked = document.querySelectorAll(`${questionSelector} input[type="checkbox"]:checked`);
|
||||
|
||||
// ❌ If user exceeds limit during selection
|
||||
if (checked.length > maxAnswers) {
|
||||
this.checked = false;
|
||||
alert(`⚠️ ចំនួនជម្រើសលើសពីកំណត់!\nសូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ។`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔒 Disable unchecked boxes when limit is reached
|
||||
checkboxes.forEach(cb => {
|
||||
if (!cb.checked) {
|
||||
cb.disabled = (checked.length >= maxAnswers);
|
||||
} else {
|
||||
cb.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 🚀 VALIDATION ON SUBMIT/NEXT BUTTON CLICK
|
||||
const submitButton = document.querySelector('button[type="submit"][value="next"]');
|
||||
const nextPageButton = document.querySelector('#next_page');
|
||||
|
||||
// Function to validate before submit
|
||||
function validateBeforeSubmit(e) {
|
||||
const checked = document.querySelectorAll(`${questionSelector} input[type="checkbox"]:checked`);
|
||||
const checkedCount = checked.length;
|
||||
|
||||
// ❌ Check if user selected MORE than allowed
|
||||
if (checkedCount > maxAnswers) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
alert(`❌ ការជ្រើសរើសរបស់អ្នកលើសពីកំណត់!\nសូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ。\n\nចំនួនដែលបានជ្រើសរើស: ${checkedCount}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ⚠️ Optional: Check if user selected FEWER than required (if mandatory)
|
||||
// Uncomment if you want to enforce minimum selection
|
||||
/*
|
||||
if (checkedCount < maxAnswers) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
alert(`⚠️ សូមជ្រើសរើសឱយបាន ${maxAnswers} ជម្រើស。\n\nចំនួនដែលបានជ្រើសរើស: ${checkedCount}`);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
// ✅ Validation passed
|
||||
console.log('✅ Validation passed. Proceeding...');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attach validation to Submit/Next buttons
|
||||
if (submitButton) {
|
||||
submitButton.addEventListener('click', validateBeforeSubmit);
|
||||
console.log('✅ Validation attached to Submit button');
|
||||
}
|
||||
|
||||
if (nextPageButton) {
|
||||
nextPageButton.addEventListener('click', validateBeforeSubmit);
|
||||
console.log('✅ Validation attached to Next Page button');
|
||||
}
|
||||
|
||||
// Also validate on form submit (backup)
|
||||
const surveyForm = document.querySelector('form.o_survey_form, form[name="survey"]');
|
||||
if (surveyForm) {
|
||||
surveyForm.addEventListener('submit', function(e) {
|
||||
// Create a mock event for validation
|
||||
const mockEvent = {
|
||||
preventDefault: () => e.preventDefault(),
|
||||
stopPropagation: () => e.stopPropagation()
|
||||
};
|
||||
|
||||
if (!validateBeforeSubmit(mockEvent)) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
console.log('✅ Validation attached to Form submit');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="survey_question_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">survey.question.form.inherit.answer_limit</field>
|
||||
<field name="model">survey.question</field>
|
||||
<field name="inherit_id" ref="survey.survey_question_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Insert after suggested answers section for multiple_choice -->
|
||||
<xpath expr="//field[@name='suggested_answer_ids']/../.." position="after">
|
||||
<group
|
||||
string="Answer Limits" invisible="question_type != 'multiple_choice'"
|
||||
colspan="2"
|
||||
>
|
||||
<field name="validation_multiple_answers_min" required="validation_multiple_answers_max != False"/>
|
||||
<field name="validation_multiple_answers_max"/>
|
||||
<field name="validation_error_msg" colspan="2"
|
||||
required="validation_multiple_answers_max != False"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="survey_answer_limit_assets" inherit_id="survey.survey_page">
|
||||
<xpath expr="//head" position="inside">
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🔍 Survey validation script loaded');
|
||||
|
||||
// Find all multiple choice questions with max_answers
|
||||
const questionContainers = document.querySelectorAll('.o_survey_question');
|
||||
|
||||
questionContainers.forEach(container => {
|
||||
// Look for max_answers in data attribute or hidden field
|
||||
const maxAnswersEl = container.querySelector('[data-max-answers]');
|
||||
const maxAnswers = maxAnswersEl ? parseInt(maxAnswersEl.dataset.maxAnswers) : 0;
|
||||
|
||||
if (!maxAnswers) return;
|
||||
|
||||
console.log('✅ Found question with max_answers:', maxAnswers);
|
||||
|
||||
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
||||
const questionId = container.dataset.questionId || 'unknown';
|
||||
|
||||
// Add visual hint
|
||||
const hint = document.createElement('small');
|
||||
hint.className = 'text-muted d-block mt-2';
|
||||
hint.textContent = `(សូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ)`;
|
||||
const answersContainer = container.querySelector('.o_survey_question_answers');
|
||||
if (answersContainer) {
|
||||
answersContainer.appendChild(hint);
|
||||
}
|
||||
|
||||
// Checkbox change handler
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const checked = container.querySelectorAll('input[type="checkbox"]:checked');
|
||||
|
||||
if (checked.length > maxAnswers) {
|
||||
this.checked = false;
|
||||
alert(`⚠️ ចំនួនជម្រើសលើសពីកំណត់!\nសូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ។`);
|
||||
}
|
||||
|
||||
// Disable unchecked when limit reached
|
||||
checkboxes.forEach(cb => {
|
||||
if (!cb.checked) {
|
||||
cb.disabled = (checked.length >= maxAnswers);
|
||||
} else {
|
||||
cb.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Submit/Next button validation
|
||||
const submitBtn = container.closest('form').querySelector('button[type="submit"][value="next"], #next_page, .btn-primary');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function(e) {
|
||||
const checked = container.querySelectorAll('input[type="checkbox"]:checked');
|
||||
|
||||
if (checked.length > maxAnswers) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
alert(`❌ ការជ្រើសរើសរបស់អ្នកលើសពីកំណត់!\nសូមជ្រើសរើសត្រឹមតែ ${maxAnswers} ជម្រើសប៉ុណ្ណោះ。\n\nចំនួនដែលបានជ្រើសរើស: ${checked.length}`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user