from odoo import models, fields, api from odoo.exceptions import ValidationError class EducationGradingSystem(models.Model): _name = 'education.grading.system' _description = 'Grading System' name = fields.Char(string='Name', required=True) code = fields.Char(string='Code', required=True) scale_type = fields.Selection([ ('percentage', 'Percentage'), ('gpa_4', 'GPA 4.0'), ('gpa_10', 'GPA 10.0') ], string='Scale Type', required=True, default='percentage') active = fields.Boolean(default=True) grade_ids = fields.One2many('education.grade', 'grading_system_id', string='Grades') def get_grade(self, percentage): self.ensure_one() grade = self.grade_ids.filtered(lambda g: g.min_percentage <= percentage <= g.max_percentage) if grade: # If multiple match due to floating point edge cases, take the highest grade point return grade.sorted('grade_point', reverse=True)[0] return self.env['education.grade'] class EducationGrade(models.Model): _name = 'education.grade' _description = 'Education Grade' _order = 'sequence, id' grading_system_id = fields.Many2one('education.grading.system', string='Grading System', required=True, ondelete='cascade') name = fields.Char(string='Name', required=True) code = fields.Char(string='Code') min_percentage = fields.Float(string='Minimum Percentage', required=True) max_percentage = fields.Float(string='Maximum Percentage', required=True) grade_point = fields.Float(string='Grade Point', required=True) result_status = fields.Selection([ ('pass', 'Pass'), ('fail', 'Fail') ], string='Result Status', required=True, default='pass') sequence = fields.Integer(string='Sequence', default=10) active = fields.Boolean(default=True) @api.constrains('min_percentage', 'max_percentage') def _check_percentages(self): for record in self: if record.min_percentage < 0 or record.max_percentage > 100: raise ValidationError("Percentages must be between 0 and 100.") if record.min_percentage > record.max_percentage: raise ValidationError("Minimum percentage cannot be greater than maximum percentage.") # Check overlaps domain = [ ('grading_system_id', '=', record.grading_system_id.id), ('id', '!=', record.id), '|', '|', '&', ('min_percentage', '<=', record.min_percentage), ('max_percentage', '>=', record.min_percentage), '&', ('min_percentage', '<=', record.max_percentage), ('max_percentage', '>=', record.max_percentage), '&', ('min_percentage', '>=', record.min_percentage), ('max_percentage', '<=', record.max_percentage), ] if self.search_count(domain) > 0: raise ValidationError("Grade percentage ranges cannot overlap within the same grading system.") @api.constrains('grade_point') def _check_grade_point(self): for record in self: if record.grade_point < 0: raise ValidationError("Grade point cannot be negative.")