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

232 lines
9.6 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Term -->
<record id="view_school_term_list" model="ir.ui.view">
<field name="name">community.school.term.list</field>
<field name="model">community.school.term</field>
<field name="arch" type="xml">
<list string="School Terms">
<field name="name"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="registration_open"/>
<field name="state"/>
</list>
</field>
</record>
<record id="view_school_term_form" model="ir.ui.view">
<field name="name">community.school.term.form</field>
<field name="model">community.school.term</field>
<field name="arch" type="xml">
<form string="School Term">
<sheet>
<div class="oe_title"><h1><field name="name"/></h1></div>
<group>
<group>
<field name="start_date"/>
<field name="end_date"/>
<field name="state"/>
</group>
<group>
<field name="registration_open"/>
<field name="registration_deadline"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_school_term" model="ir.actions.act_window">
<field name="name">Terms</field>
<field name="res_model">community.school.term</field>
<field name="view_mode">list,form</field>
</record>
<!-- Level -->
<record id="view_school_level_list" model="ir.ui.view">
<field name="name">community.school.level.list</field>
<field name="model">community.school.level</field>
<field name="arch" type="xml">
<list string="School Levels" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="min_age"/>
<field name="max_age"/>
</list>
</field>
</record>
<record id="action_school_level" model="ir.actions.act_window">
<field name="name">Levels</field>
<field name="res_model">community.school.level</field>
<field name="view_mode">list</field>
</record>
<!-- Class -->
<record id="view_school_class_list" model="ir.ui.view">
<field name="name">community.school.class.list</field>
<field name="model">community.school.class</field>
<field name="arch" type="xml">
<list string="School Classes">
<field name="name"/>
<field name="teacher_id"/>
<field name="weekday"/>
<field name="enrolled_count"/>
<field name="max_students"/>
<field name="state"/>
</list>
</field>
</record>
<record id="view_school_class_form" model="ir.ui.view">
<field name="name">community.school.class.form</field>
<field name="model">community.school.class</field>
<field name="arch" type="xml">
<form string="School Class">
<sheet>
<div class="oe_title"><h1><field name="name" readonly="1"/></h1></div>
<group>
<group>
<field name="level_id"/>
<field name="term_id"/>
<field name="teacher_id"/>
<field name="state"/>
</group>
<group>
<field name="weekday"/>
<field name="start_time" widget="float_time"/>
<field name="end_time" widget="float_time"/>
<field name="location"/>
<field name="max_students"/>
<field name="enrolled_count"/>
<field name="slide_channel_id" readonly="1"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_school_class" model="ir.actions.act_window">
<field name="name">Classes</field>
<field name="res_model">community.school.class</field>
<field name="view_mode">list,form</field>
</record>
<!-- Student -->
<record id="view_school_student_list" model="ir.ui.view">
<field name="name">community.school.student.list</field>
<field name="model">community.school.student</field>
<field name="arch" type="xml">
<list string="Students">
<field name="partner_id"/>
<field name="parent_partner_id"/>
<field name="age"/>
<field name="proficiency"/>
</list>
</field>
</record>
<record id="view_school_student_form" model="ir.ui.view">
<field name="name">community.school.student.form</field>
<field name="model">community.school.student</field>
<field name="arch" type="xml">
<form string="Student">
<sheet>
<div class="oe_title"><h1><field name="partner_id"/></h1></div>
<group>
<group>
<field name="parent_partner_id"/>
<field name="date_of_birth"/>
<field name="age" readonly="1"/>
<field name="proficiency"/>
<field name="grade_ref"/>
</group>
<group>
<field name="emergency_contact_name"/>
<field name="emergency_contact_phone"/>
</group>
</group>
<group string="Health Notes">
<field name="health_notes" nolabel="1"/>
</group>
<notebook>
<page string="Enrollments" name="enrollments">
<field name="enrollment_ids">
<list>
<field name="class_id"/>
<field name="term_id"/>
<field name="state"/>
<field name="payment_state"/>
<field name="attendance_rate" widget="percentage"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_school_student" model="ir.actions.act_window">
<field name="name">Students</field>
<field name="res_model">community.school.student</field>
<field name="view_mode">list,form</field>
</record>
<!-- Enrollment -->
<record id="view_school_enrollment_list" model="ir.ui.view">
<field name="name">community.school.enrollment.list</field>
<field name="model">community.school.enrollment</field>
<field name="arch" type="xml">
<list string="Enrollments">
<field name="student_id"/>
<field name="class_id"/>
<field name="term_id"/>
<field name="state"/>
<field name="payment_state"/>
<field name="attendance_rate" widget="percentage"/>
</list>
</field>
</record>
<record id="action_school_enrollment" model="ir.actions.act_window">
<field name="name">Enrollments</field>
<field name="res_model">community.school.enrollment</field>
<field name="view_mode">list,form</field>
</record>
<record id="action_school_enrollment_at_risk" model="ir.actions.act_window">
<field name="name">At-Risk Students</field>
<field name="res_model">community.school.enrollment</field>
<field name="view_mode">list,form</field>
<field name="domain">[('is_at_risk', '=', True)]</field>
</record>
<!-- Attendance pivot report -->
<record id="view_school_attendance_pivot" model="ir.ui.view">
<field name="name">community.school.attendance.pivot</field>
<field name="model">community.school.attendance</field>
<field name="arch" type="xml">
<pivot string="Attendance">
<field name="class_id" type="row"/>
<field name="date" type="col" interval="week"/>
<field name="state" type="row"/>
</pivot>
</field>
</record>
<record id="view_school_attendance_list" model="ir.ui.view">
<field name="name">community.school.attendance.list</field>
<field name="model">community.school.attendance</field>
<field name="arch" type="xml">
<list string="Attendance">
<field name="date"/>
<field name="class_id"/>
<field name="enrollment_id"/>
<field name="state"/>
<field name="notes"/>
</list>
</field>
</record>
<record id="action_school_attendance_report" model="ir.actions.act_window">
<field name="name">Attendance Report</field>
<field name="res_model">community.school.attendance</field>
<field name="view_mode">pivot,list</field>
</record>
</odoo>