from odoo import api, fields, models class CommunitySchoolEnrollment(models.Model): _name = 'community.school.enrollment' _description = 'School Enrollment' _order = 'id desc' student_id = fields.Many2one('community.school.student', required=True, ondelete='cascade') class_id = fields.Many2one('community.school.class', required=True, ondelete='cascade') term_id = fields.Many2one(related='class_id.term_id', store=True) enrollment_date = fields.Date(default=fields.Date.context_today) state = fields.Selection( [ ('waitlist', 'Waitlisted'), ('enrolled', 'Enrolled'), ('completed', 'Completed'), ('withdrawn', 'Withdrawn'), ], default='enrolled', required=True, ) payment_state = fields.Selection( [('not_invoiced', 'Not Invoiced'), ('invoiced', 'Invoiced'), ('paid', 'Paid')], default='not_invoiced', required=True, ) invoice_id = fields.Many2one('account.move', readonly=True, copy=False) attendance_ids = fields.One2many('community.school.attendance', 'enrollment_id') attendance_rate = fields.Float(compute='_compute_attendance_rate') is_at_risk = fields.Boolean(compute='_compute_is_at_risk') @api.depends('attendance_ids.state') def _compute_attendance_rate(self): for enrollment in self: records = enrollment.attendance_ids total = len(records) if not total: enrollment.attendance_rate = 0.0 continue present = len(records.filtered(lambda a: a.state in ('present', 'late'))) enrollment.attendance_rate = (present / total) * 100.0 @api.depends('attendance_rate', 'attendance_ids') def _compute_is_at_risk(self): threshold = int(self.env['ir.config_parameter'].sudo().get_param( 'community_school.at_risk_attendance_threshold', 70 )) for enrollment in self: enrollment.is_at_risk = bool(enrollment.attendance_ids) and enrollment.attendance_rate < threshold def action_enroll_from_waitlist(self): for enrollment in self: if enrollment.state == 'waitlist' and enrollment.class_id.enrolled_count < enrollment.class_id.max_students: enrollment.state = 'enrolled' return True @api.model_create_multi def create(self, vals_list): enrollments = super().create(vals_list) for enrollment in enrollments: if enrollment.state == 'enrolled': enrollment._lms_enrol() return enrollments def write(self, vals): res = super().write(vals) if 'state' in vals: for enrollment in self: if enrollment.state == 'enrolled': enrollment._lms_enrol() elif enrollment.state == 'withdrawn': enrollment._lms_unenrol() return res def _lms_enrol(self): self.ensure_one() channel = self.class_id.slide_channel_id if channel and self.student_id.partner_id: channel.sudo()._action_add_members(self.student_id.partner_id, member_status='joined') def _lms_unenrol(self): self.ensure_one() channel = self.class_id.slide_channel_id if not channel: return membership = self.env['slide.channel.partner'].sudo().search([ ('channel_id', '=', channel.id), ('partner_id', '=', self.student_id.partner_id.id), ]) membership.write({'active': False}) def _create_fee_invoice(self): self.ensure_one() product = self.class_id.fee_product_id or self.class_id._get_or_create_fee_product() if not self.class_id.fee: return self.env['account.move'] invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.student_id.parent_partner_id.id, 'invoice_origin': 'School Registration', 'invoice_line_ids': [(0, 0, { 'product_id': product.id, 'quantity': 1, 'price_unit': self.class_id.fee, })], }) self.write({'invoice_id': invoice.id, 'payment_state': 'invoiced'}) return invoice @api.model def _cron_promote_waitlist(self): """Hourly: promote the earliest waitlisted enrollment(s) into any newly-freed seats.""" classes = self.env['community.school.class'].search([]) for klass in classes: free_seats = klass.max_students - klass.enrolled_count if free_seats <= 0: continue waitlisted = self.search([ ('class_id', '=', klass.id), ('state', '=', 'waitlist'), ], order='enrollment_date asc, id asc', limit=free_seats) for enrollment in waitlisted: enrollment.write({'state': 'enrolled'}) enrollment._send_seat_available_notice() return True def _send_seat_available_notice(self): self.ensure_one() template = self.env.ref('community_school.mail_template_seat_available', raise_if_not_found=False) if template: template.send_mail(self.id, force_send=False)