first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
from . import controllers
from . import models
+35
View File
@@ -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
@@ -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
+30
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
from . import survey_question
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 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>