Rounds out mc_education_base with the remaining O1 models: mc.program,
mc.subject, mc.room, mc.batch, mc.teacher, mc.student, mc.guardian,
mc.student.guardian, and mc.enrollment - the spine everything else in
the O1 table (attendance, timetable, exam, portal) is built against.
Two rules get the same "DB constraint is the real guarantee, the ORM
does a friendly pre-check" treatment as academic.year.is_current:
- At most one primary guardian per student (mc.student.guardian):
partial unique index on student_id WHERE is_primary, plus a
create/write toggle.
- At most one Active enrollment per student per year
(mc.enrollment): partial unique index on (student_id, year_id)
WHERE state='active'. This one is CLAUDE.md's flagship rule
("Enforce as a database constraint, not application logic").
Two more real bugs surfaced by testing against a live odoo:19.0
container rather than trusting the code by inspection:
- The partial unique index fires at INSERT/UPDATE time, before
@api.constrains ever runs - so a naive "index + constrains for a
friendly message" design never reaches the friendly message, the
raw IntegrityError wins the race. Fixed by pre-checking for a
conflict in create()/write() before calling super(), with
@api.constrains kept only as a backstop for batch creates.
- create() issues a direct SQL INSERT that does not wait for
unrelated pending writes in the ORM cache (e.g. withdrawing one
enrollment right before creating its replacement, in the same
method) - needs an explicit self.env.flush_all() first, same
lesson as the is_current toggle.
Also caught before it became a permanent test flake: the enrollment
and academic-calendar tests originally hardcoded the same year names
("2025-26", "2026-27") and program code ("G8") as the demo data.
Passed in isolation, failed as soon as demo data was loaded first -
so verification here included a combined
`--without-demo=False --test-enable` run, matching what CI actually
does, not just an isolated test-tagged run. Renamed to TEST-prefixed
fixtures.
Security access rows added for all new models across the four
internal groups (Administrator: full CRUD everywhere; Staff: full
CRUD on the people/enrollment models that are front office's daily
job, read-only on academic structure; Teacher/Accountant: read-only
across the board, narrower record rules land with the modules that
need them - attendance, exam, portal). No portal-group access yet;
that is O7's job once explicit ownership-scoped record rules exist -
granting it now without those rules would be exactly the "identifier
supplied by the client" hole CLAUDE.md's standing security rule
warns about.
Demo data populates the shared/DEMO_SCRIPT.md cast: Meera Krishnan as
primary guardian of both Aditya (Grade 8-A) and Ananya (Grade 5-B) -
the multi-child guardian view the script calls "the single most
convincing portal feature" - plus Arun Prakash as Grade 8-A's class
teacher. Verified by querying the resulting database directly, not
just by the install succeeding.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.9 KiB
3.9 KiB
| 1 | id | name | model_id:id | group_id:id | perm_read | perm_write | perm_create | perm_unlink |
|---|---|---|---|---|---|---|---|---|
| 2 | access_mc_academic_year_administrator | mc.academic.year.administrator | model_mc_academic_year | group_school_administrator | 1 | 1 | 1 | 1 |
| 3 | access_mc_academic_year_staff | mc.academic.year.staff | model_mc_academic_year | group_school_staff | 1 | 0 | 0 | 0 |
| 4 | access_mc_academic_term_administrator | mc.academic.term.administrator | model_mc_academic_term | group_school_administrator | 1 | 1 | 1 | 1 |
| 5 | access_mc_academic_term_staff | mc.academic.term.staff | model_mc_academic_term | group_school_staff | 1 | 0 | 0 | 0 |
| 6 | access_mc_room_administrator | mc.room.administrator | model_mc_room | group_school_administrator | 1 | 1 | 1 | 1 |
| 7 | access_mc_room_staff | mc.room.staff | model_mc_room | group_school_staff | 1 | 0 | 0 | 0 |
| 8 | access_mc_room_teacher | mc.room.teacher | model_mc_room | group_teacher | 1 | 0 | 0 | 0 |
| 9 | access_mc_room_accountant | mc.room.accountant | model_mc_room | group_accountant | 1 | 0 | 0 | 0 |
| 10 | access_mc_program_administrator | mc.program.administrator | model_mc_program | group_school_administrator | 1 | 1 | 1 | 1 |
| 11 | access_mc_program_staff | mc.program.staff | model_mc_program | group_school_staff | 1 | 0 | 0 | 0 |
| 12 | access_mc_program_teacher | mc.program.teacher | model_mc_program | group_teacher | 1 | 0 | 0 | 0 |
| 13 | access_mc_program_accountant | mc.program.accountant | model_mc_program | group_accountant | 1 | 0 | 0 | 0 |
| 14 | access_mc_subject_administrator | mc.subject.administrator | model_mc_subject | group_school_administrator | 1 | 1 | 1 | 1 |
| 15 | access_mc_subject_staff | mc.subject.staff | model_mc_subject | group_school_staff | 1 | 0 | 0 | 0 |
| 16 | access_mc_subject_teacher | mc.subject.teacher | model_mc_subject | group_teacher | 1 | 0 | 0 | 0 |
| 17 | access_mc_subject_accountant | mc.subject.accountant | model_mc_subject | group_accountant | 1 | 0 | 0 | 0 |
| 18 | access_mc_teacher_administrator | mc.teacher.administrator | model_mc_teacher | group_school_administrator | 1 | 1 | 1 | 1 |
| 19 | access_mc_teacher_staff | mc.teacher.staff | model_mc_teacher | group_school_staff | 1 | 0 | 0 | 0 |
| 20 | access_mc_teacher_teacher | mc.teacher.teacher | model_mc_teacher | group_teacher | 1 | 0 | 0 | 0 |
| 21 | access_mc_teacher_accountant | mc.teacher.accountant | model_mc_teacher | group_accountant | 1 | 0 | 0 | 0 |
| 22 | access_mc_batch_administrator | mc.batch.administrator | model_mc_batch | group_school_administrator | 1 | 1 | 1 | 1 |
| 23 | access_mc_batch_staff | mc.batch.staff | model_mc_batch | group_school_staff | 1 | 1 | 1 | 0 |
| 24 | access_mc_batch_teacher | mc.batch.teacher | model_mc_batch | group_teacher | 1 | 0 | 0 | 0 |
| 25 | access_mc_batch_accountant | mc.batch.accountant | model_mc_batch | group_accountant | 1 | 0 | 0 | 0 |
| 26 | access_mc_student_administrator | mc.student.administrator | model_mc_student | group_school_administrator | 1 | 1 | 1 | 1 |
| 27 | access_mc_student_staff | mc.student.staff | model_mc_student | group_school_staff | 1 | 1 | 1 | 0 |
| 28 | access_mc_student_teacher | mc.student.teacher | model_mc_student | group_teacher | 1 | 0 | 0 | 0 |
| 29 | access_mc_student_accountant | mc.student.accountant | model_mc_student | group_accountant | 1 | 0 | 0 | 0 |
| 30 | access_mc_guardian_administrator | mc.guardian.administrator | model_mc_guardian | group_school_administrator | 1 | 1 | 1 | 1 |
| 31 | access_mc_guardian_staff | mc.guardian.staff | model_mc_guardian | group_school_staff | 1 | 1 | 1 | 0 |
| 32 | access_mc_guardian_teacher | mc.guardian.teacher | model_mc_guardian | group_teacher | 1 | 0 | 0 | 0 |
| 33 | access_mc_guardian_accountant | mc.guardian.accountant | model_mc_guardian | group_accountant | 1 | 0 | 0 | 0 |
| 34 | access_mc_student_guardian_administrator | mc.student.guardian.administrator | model_mc_student_guardian | group_school_administrator | 1 | 1 | 1 | 1 |
| 35 | access_mc_student_guardian_staff | mc.student.guardian.staff | model_mc_student_guardian | group_school_staff | 1 | 1 | 1 | 0 |
| 36 | access_mc_student_guardian_teacher | mc.student.guardian.teacher | model_mc_student_guardian | group_teacher | 1 | 0 | 0 | 0 |
| 37 | access_mc_student_guardian_accountant | mc.student.guardian.accountant | model_mc_student_guardian | group_accountant | 1 | 0 | 0 | 0 |
| 38 | access_mc_enrollment_administrator | mc.enrollment.administrator | model_mc_enrollment | group_school_administrator | 1 | 1 | 1 | 1 |
| 39 | access_mc_enrollment_staff | mc.enrollment.staff | model_mc_enrollment | group_school_staff | 1 | 1 | 1 | 0 |
| 40 | access_mc_enrollment_teacher | mc.enrollment.teacher | model_mc_enrollment | group_teacher | 1 | 0 | 0 | 0 |
| 41 | access_mc_enrollment_accountant | mc.enrollment.accountant | model_mc_enrollment | group_accountant | 1 | 0 | 0 | 0 |