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