153 lines
6.1 KiB
Python
153 lines
6.1 KiB
Python
from odoo import models, fields, api
|
|
from odoo.exceptions import ValidationError
|
|
|
|
class EducationExamMarksEntryWizard(models.TransientModel):
|
|
_name = 'education.exam.marks.entry.wizard'
|
|
_description = 'Subject-wise Marks Entry Wizard'
|
|
|
|
exam_id = fields.Many2one(
|
|
'education.exam',
|
|
string='Exam',
|
|
required=True,
|
|
domain="[('state', 'in', ['ongoing', 'completed'])]"
|
|
)
|
|
subject_id = fields.Many2one(
|
|
'education.subject',
|
|
string='Subject',
|
|
required=True
|
|
)
|
|
max_marks = fields.Float(string='Maximum Marks', readonly=True)
|
|
pass_marks = fields.Float(string='Pass Marks', readonly=True)
|
|
faculty_id = fields.Many2one('res.users', string='Assigned Faculty', readonly=True)
|
|
line_ids = fields.One2many(
|
|
'education.exam.marks.entry.wizard.line',
|
|
'wizard_id',
|
|
string='Student Marks'
|
|
)
|
|
|
|
@api.onchange('exam_id')
|
|
def _onchange_exam_id(self):
|
|
self.subject_id = False
|
|
self.line_ids = [(5, 0, 0)]
|
|
if self.exam_id:
|
|
# Restrict subject domain to subjects scheduled in this exam
|
|
subject_ids = self.exam_id.line_ids.mapped('subject_id.id')
|
|
return {'domain': {'subject_id': [('id', 'in', subject_ids)]}}
|
|
return {'domain': {'subject_id': []}}
|
|
|
|
@api.onchange('exam_id', 'subject_id')
|
|
def _onchange_exam_and_subject(self):
|
|
if self.exam_id and self.subject_id:
|
|
self._check_teacher_authorization()
|
|
self._load_subject_details()
|
|
self._load_student_lines()
|
|
|
|
def _check_teacher_authorization(self):
|
|
self.ensure_one()
|
|
if not self.env.user.has_group('education_exam.group_education_exam_administrator'):
|
|
schedule_line = self.env['education.exam.line'].search([
|
|
('exam_id', '=', self.exam_id.id),
|
|
('subject_id', '=', self.subject_id.id),
|
|
], limit=1)
|
|
if schedule_line and schedule_line.faculty_id and schedule_line.faculty_id != self.env.user:
|
|
raise ValidationError(
|
|
f"Access Denied: You are not authorized to enter marks for subject '{self.subject_id.name}'. "
|
|
f"This subject is assigned to {schedule_line.faculty_id.name}."
|
|
)
|
|
|
|
def _load_subject_details(self):
|
|
schedule_line = self.env['education.exam.line'].search([
|
|
('exam_id', '=', self.exam_id.id),
|
|
('subject_id', '=', self.subject_id.id),
|
|
], limit=1)
|
|
if schedule_line:
|
|
self.max_marks = schedule_line.max_marks
|
|
self.pass_marks = schedule_line.pass_marks
|
|
self.faculty_id = schedule_line.faculty_id.id
|
|
else:
|
|
self.max_marks = 0.0
|
|
self.pass_marks = 0.0
|
|
self.faculty_id = False
|
|
|
|
def _load_student_lines(self):
|
|
self.line_ids = [(5, 0, 0)]
|
|
result_lines = self.env['education.exam.result.line'].sudo().search([
|
|
('result_id.exam_id', '=', self.exam_id.id),
|
|
('subject_id', '=', self.subject_id.id),
|
|
])
|
|
lines = []
|
|
for rl in result_lines:
|
|
student = rl.result_id.student_id.sudo()
|
|
lines.append((0, 0, {
|
|
'result_line_id': rl.id,
|
|
'student_id': student.id,
|
|
'roll_number': student.roll_number or '',
|
|
'admission_number': student.admission_number or '',
|
|
'obtained_marks': rl.obtained_marks,
|
|
'remarks': rl.remarks or '',
|
|
}))
|
|
self.line_ids = lines
|
|
|
|
def action_load_students(self):
|
|
"""Action button to reload student lines explicitly."""
|
|
self.ensure_one()
|
|
self._check_teacher_authorization()
|
|
self._load_subject_details()
|
|
self._load_student_lines()
|
|
return {'type': 'ir.actions.do_nothing'}
|
|
|
|
def action_save_marks(self):
|
|
self.ensure_one()
|
|
self._check_teacher_authorization()
|
|
if self.exam_id.state == 'results_published':
|
|
raise ValidationError("Cannot save marks for published exams. Results must be unpublished first.")
|
|
|
|
if not self.line_ids:
|
|
raise ValidationError("No student marks to save. Please select a valid exam and subject, or generate results first.")
|
|
|
|
for line in self.line_ids:
|
|
if line.obtained_marks < -1.0:
|
|
raise ValidationError(
|
|
f"Marks for student '{line.student_id.full_name}' cannot be negative (use -1 for pending)."
|
|
)
|
|
if line.obtained_marks > self.max_marks:
|
|
raise ValidationError(
|
|
f"Marks for student '{line.student_id.full_name}' ({line.obtained_marks}) "
|
|
f"cannot exceed maximum marks ({self.max_marks})."
|
|
)
|
|
if line.result_line_id:
|
|
line.result_line_id.write({
|
|
'obtained_marks': line.obtained_marks,
|
|
'remarks': line.remarks,
|
|
})
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'title': 'Marks Saved',
|
|
'message': f"Marks successfully saved for '{self.subject_id.name}'.",
|
|
'type': 'success',
|
|
'sticky': False,
|
|
'next': {'type': 'ir.actions.act_window_close'},
|
|
}
|
|
}
|
|
|
|
|
|
class EducationExamMarksEntryWizardLine(models.TransientModel):
|
|
_name = 'education.exam.marks.entry.wizard.line'
|
|
_description = 'Subject-wise Marks Entry Wizard Line'
|
|
|
|
wizard_id = fields.Many2one(
|
|
'education.exam.marks.entry.wizard',
|
|
string='Wizard',
|
|
required=True,
|
|
ondelete='cascade'
|
|
)
|
|
result_line_id = fields.Many2one('education.exam.result.line', string='Exam Result Line')
|
|
student_id = fields.Many2one('education.student', string='Student', required=True)
|
|
roll_number = fields.Char(string='Roll No.', readonly=True)
|
|
admission_number = fields.Char(string='Admission No.', readonly=True)
|
|
obtained_marks = fields.Float(string='Obtained Marks', default=-1.0, required=True)
|
|
remarks = fields.Char(string='Remarks')
|