# -*- coding: utf-8 -*- # pyrefly: ignore [missing-import] from odoo import api, fields, models # pyrefly: ignore [missing-import] from odoo.exceptions import ValidationError import base64 MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024 # 5 MB class EducationStudentDocument(models.Model): _name = 'education.student.document' _description = 'Student Document' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'upload_date desc, id desc' name = fields.Char(string='Document Title', required=True, tracking=True) student_id = fields.Many2one( 'education.student', string='Student', required=True, ondelete='cascade', index=True, tracking=True ) category = fields.Selection([ ('identity', 'Identity Proof'), ('academic', 'Academic Certificate / Marksheet'), ('transfer', 'Transfer / Migration Certificate'), ('address', 'Address Proof'), ('other', 'Other Document'), ], string='Category', required=True, default='academic', tracking=True) document_file = fields.Binary(string='Document File', required=True, attachment=True) file_name = fields.Char(string='Filename') file_size = fields.Integer(string='File Size (Bytes)', compute='_compute_file_metadata', store=True) mime_type = fields.Char(string='MIME Type') verification_status = fields.Selection([ ('submitted', 'Submitted'), ('verified', 'Verified'), ('rejected', 'Rejected'), ], string='Verification Status', default='submitted', required=True, tracking=True, index=True) verified_by = fields.Many2one('res.users', string='Verified By', readonly=True, tracking=True) verification_date = fields.Datetime(string='Verification Date', readonly=True, tracking=True) rejection_reason = fields.Text(string='Rejection Reason', tracking=True) upload_date = fields.Datetime(string='Upload Date', default=fields.Datetime.now, readonly=True) company_id = fields.Many2one( 'res.company', string='Company', related='student_id.company_id', store=True, index=True ) @api.depends('document_file') def _compute_file_metadata(self): for rec in self: if rec.document_file: try: decoded = base64.b64decode(rec.document_file) rec.file_size = len(decoded) except Exception: rec.file_size = 0 else: rec.file_size = 0 @api.constrains('document_file', 'file_name') def _check_file_validity(self): for rec in self: if rec.document_file: decoded = base64.b64decode(rec.document_file) if len(decoded) > MAX_FILE_SIZE_BYTES: raise ValidationError(f"File size exceeds the maximum allowed limit of 5 MB ({len(decoded)} bytes).") if rec.file_name: ext = rec.file_name.split('.')[-1].lower() if '.' in rec.file_name else '' if ext not in ['pdf', 'jpg', 'jpeg', 'png']: raise ValidationError("Invalid file type. Allowed formats: PDF, JPG, JPEG, PNG.") def action_verify(self): for rec in self: if not (self.env.is_admin() or self.env.user.has_group('education_student.group_education_student_user')): raise ValidationError("Only authorized staff can verify student documents.") rec.write({ 'verification_status': 'verified', 'verified_by': self.env.user.id, 'verification_date': fields.Datetime.now(), 'rejection_reason': False, }) def action_reject(self, reason="Document details could not be verified."): for rec in self: if not (self.env.is_admin() or self.env.user.has_group('education_student.group_education_student_user')): raise ValidationError("Only authorized staff can reject student documents.") rec.write({ 'verification_status': 'rejected', 'verified_by': self.env.user.id, 'verification_date': fields.Datetime.now(), 'rejection_reason': reason, })