TNCSC_Odoo/addons/community_school/tests/test_school_attendance.py
metatroncubeswdev 7ac5880f20 feat(community_school): core models + teacher attendance portal (Sessions 3-A, 3-B)
Session 3-A - core models: community.school.term, .level (admin-defined,
so the same module fits a Grade 1-12 school or a Beginner-Advanced
language school with no code change), .class (auto-creates a linked
slide.channel for LMS glue), .student, .enrollment, and .attendance, plus
is_teacher on res.partner. Class enrolled_count and enrollment
attendance_rate are stored/non-stored computes driven by the
enrollment/attendance one2many chains.

Session 3-B - teacher attendance + at-risk reporting: a portal page at
/school/attendance (auth='user', scoped to classes where teacher_id
matches the logged-in user's partner - works whether the teacher is an
internal or portal user) with a roster and batch save, built as a v19
Interaction (same pattern as event_qr_ticketing's check-in page). Marking
a student absent queues a configurable notice to the parent partner.
Enrollment.is_at_risk flags students below a configurable attendance
threshold (School settings page, same <app>/<block>/<setting> pattern as
community_membership), plus an admin pivot attendance report and an
At-Risk Students list.

Verified against a live Odoo 19 + Postgres 16 container: 14/14 automated
tests pass (class/slide-channel creation, enrolled_count tracking incl.
withdrawal, attendance_rate and at-risk computation, unique
enrollment+date constraint, absence email queuing), plus a full manual
live run - created a term/level/class/student/enrollment via JSON-RPC,
loaded the actual /school/attendance page as the teacher, POSTed a batch
save marking the student absent, and confirmed both the attendance record
and the queued "Absence notice" mail.mail record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:37:56 -04:00

56 lines
2.7 KiB
Python

from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestSchoolAttendance(TransactionCase):
def setUp(self):
super().setUp()
self.term = self.env['community.school.term'].create({
'name': 'Fall 2030', 'start_date': '2030-09-01', 'end_date': '2030-12-15',
})
self.level = self.env['community.school.level'].create({'name': 'Beginner', 'code': 'ATT-BEG'})
self.klass = self.env['community.school.class'].create({
'level_id': self.level.id, 'term_id': self.term.id,
})
self.parent = self.env['res.partner'].create({'name': 'Attendance Parent', 'email': 'parent@example.com'})
self.child = self.env['res.partner'].create({'name': 'Attendance Child'})
self.student = self.env['community.school.student'].create({
'partner_id': self.child.id, 'parent_partner_id': self.parent.id,
})
self.enrollment = self.env['community.school.enrollment'].create({
'student_id': self.student.id, 'class_id': self.klass.id,
})
def test_absence_queues_mail_to_parent(self):
mail_count_before = self.env['mail.mail'].search_count([])
attendance = self.env['community.school.attendance'].create({
'enrollment_id': self.enrollment.id, 'date': '2030-09-08', 'state': 'absent',
})
attendance._send_absence_notice()
mail_count_after = self.env['mail.mail'].search_count([])
self.assertGreater(mail_count_after, mail_count_before)
def test_present_attendance_does_not_require_notice(self):
attendance = self.env['community.school.attendance'].create({
'enrollment_id': self.enrollment.id, 'date': '2030-09-08', 'state': 'present',
})
self.assertEqual(attendance.state, 'present')
def test_at_risk_flag(self):
self.env['ir.config_parameter'].sudo().set_param('community_school.at_risk_attendance_threshold', '70')
for i, state in enumerate(['absent', 'absent', 'present']):
self.env['community.school.attendance'].create({
'enrollment_id': self.enrollment.id, 'date': f'2030-09-0{i + 1}', 'state': state,
})
self.assertTrue(self.enrollment.is_at_risk, "1/3 present should be below a 70% threshold")
def test_unique_attendance_per_enrollment_and_date(self):
self.env['community.school.attendance'].create({
'enrollment_id': self.enrollment.id, 'date': '2030-09-08', 'state': 'present',
})
with self.assertRaises(Exception):
self.env['community.school.attendance'].create({
'enrollment_id': self.enrollment.id, 'date': '2030-09-08', 'state': 'absent',
})