from psycopg2 import IntegrityError from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase from odoo.tools import mute_logger class TestEnrollment(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() # Names/codes are deliberately distinct from demo data (see # demo/mc_program_demo.xml, demo/mc_academic_year_demo.xml) - # these tests must pass whether or not demo data is loaded. cls.year = cls.env["mc.academic.year"].create({ "name": "TEST-2026-27", "date_start": "2026-06-01", "date_end": "2027-04-30", }) cls.other_year = cls.env["mc.academic.year"].create({ "name": "TEST-2027-28", "date_start": "2027-06-01", "date_end": "2028-04-30", }) cls.program = cls.env["mc.program"].create({ "name": "Test Grade 8 CBSE", "code": "TEST-G8", "sequence_no": 8, "display_label": "Test Grade 8", }) cls.batch_a = cls.env["mc.batch"].create({ "name": "Grade 8-A", "program_id": cls.program.id, "year_id": cls.year.id, }) cls.batch_b = cls.env["mc.batch"].create({ "name": "Grade 8-B", "program_id": cls.program.id, "year_id": cls.year.id, }) partner = cls.env["res.partner"].create({"name": "Test Student"}) cls.student = cls.env["mc.student"].create({ "partner_id": partner.id, "name": "Test Student", }) def _enroll(self, batch, year, state="active", roll_no="1"): return self.env["mc.enrollment"].create({ "student_id": self.student.id, "program_id": self.program.id, "batch_id": batch.id, "year_id": year.id, "state": state, "roll_no": roll_no, }) def test_admission_no_auto_generated(self): self.assertTrue(self.student.admission_no) self.assertIn("ADM", self.student.admission_no) def test_second_active_enrollment_same_year_rejected(self): self._enroll(self.batch_a, self.year) with self.assertRaises(ValidationError): self._enroll(self.batch_b, self.year) @mute_logger("odoo.sql_db") def test_db_index_backs_the_rule_even_if_orm_check_is_bypassed(self): # The @api.constrains gives a friendly message; the partial unique # index is what actually guarantees the rule. Prove the index is # there by writing around the ORM check. self._enroll(self.batch_a, self.year) with self.assertRaises(IntegrityError): with self.cr.savepoint(): self.env.cr.execute( "INSERT INTO mc_enrollment " "(student_id, program_id, batch_id, year_id, state, create_uid, write_uid, create_date, write_date) " "VALUES (%s, %s, %s, %s, 'active', %s, %s, now(), now())", (self.student.id, self.program.id, self.batch_b.id, self.year.id, self.env.uid, self.env.uid), ) def test_active_enrollment_allowed_in_different_year(self): self._enroll(self.batch_a, self.year) # Should not raise: different academic year, no conflict. self._enroll(self.batch_a, self.other_year) def test_withdrawing_then_re_enrolling_active_is_allowed(self): first = self._enroll(self.batch_a, self.year) first.state = "withdrawn" # Should not raise: the only active row for this student+year was # just withdrawn, so a new active enrollment is legitimate. self._enroll(self.batch_b, self.year)