Serves Demo Scene 3: a fee structure (category x amount lines) with a
term-wise installment schedule generates a correct account.move for a
given enrollment + term, a concession recalculates it correctly, and
the invoice is billed to the primary guardian's partner so stock
portal invoice visibility (partner_id-based) works with no new record
rule. Invoices are plain account.move (_inherit adds mc_student_id/
mc_enrollment_id only) - no invoice model was built, per CLAUDE.md
sec 1.3.
Fixed a real modeling mistake before it shipped: the schedule's
"percentages must total 100%" rule was originally a blocking
@api.constrains on every line write, which breaks the normal workflow
of adding one term at a time (every intermediate state before the
last line is, correctly, under 100%) - and would have broken this
module's own demo data loading, since each schedule line is a
separate XML record. Moved the check to where it actually matters:
the invoice-generation wizard now raises a clear UserError if the
resolved schedule doesn't total 100% at the point of use, while a
live constraint still blocks the one thing that's unambiguously wrong
at any point - allocating more than 100%.
Also found, by testing money arithmetic against a live odoo:19.0
container rather than trusting the arithmetic by inspection: every
generated invoice total came back at exactly 1.15x the expected
amount, because the standing "School Fee" product picked up the demo
company's default sales tax. Fixed by explicitly clearing taxes_id on
the product - school fees are correctly untaxed (education services
are GST-exempt in India), not just conveniently untaxed for the test.
Testing this module's access rules surfaced two real bugs in the
security model, not just test bugs, fixed here:
- group_school_staff (from O1) never implied base.group_user, so
any real user holding only this app's custom groups lacked
ordinary internal-user access to core models like res.company -
caught directly via a test user unable to even create an
mc.fee.structure (whose company_id defaults through
self.env.company).
- mc.fee.concession reveals sensitive per-student financial data
(e.g. a need-based hardship discount and its reason). Staff had
read access, and since group_teacher implies group_school_staff,
teachers inherited it too - exposing family financial
circumstances to a role with no legitimate need for it. Removed
the staff access row; only Administrator/Accountant keep it now.
Fee *structure* (per-program pricing, not sensitive) correctly
stays staff/teacher-readable.
CLAUDE.md gets one more Odoo 19 API correction:
res.users.groups_id -> group_ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- group_school_administrator now includes base.user_admin, matching
core Odoo's own convention (see hr.group_hr_manager) - otherwise
every fresh install requires a manual trip to Settings > Users just
to see this module's own menus.
- odoo.conf: drop db_name. Found by hand while testing: when db_name
is set and dbfilter is not, Odoo's list_dbs() returns db_name's
value verbatim instead of querying postgres, so the database
selector shows only that one database no matter how many actually
exist. Silently breaks O9's "swap to the second demo school in
under five minutes" requirement, which depends on switching between
multiple real databases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mc.academic.year and mc.academic.term: the two models every other O1
model (program, batch, enrollment...) will hang off. Exactly one
current year is enforced two ways - the ORM toggles is_current off
the previous year on create/write, and a partial unique index on
(company_id) WHERE is_current backs it at the database level so the
rule holds even if something writes around the ORM.
Security groups for all six roles from the spec (Administrator,
Staff, Teacher, Accountant, Guardian, Student) are scaffolded now
since every later O1 model needs them, though only Administrator/
Staff have access rows on these two models so far.
Verified against a real odoo:19.0 container, not just read: module
installs clean with views, menus and demo data, and all 7 test
methods pass. That surfaced two things CLAUDE.md's Coding Standards
section didn't anticipate, since Odoo 19 moved past 17/18-era APIs
in ways not caught by an 18-era mental model:
- `_sql_constraints` is gone; constraints are now per-attribute
`models.Constraint(sql, message)`.
- `res.groups.category_id` is gone; groups now hang off a new
`res.groups.privilege` record, which carries the category.
Both addons/mc_education_base files already use the new APIs.
CLAUDE.md itself needs a note added in a follow-up so this isn't
rediscovered per-module - flagging here per its own closing
instruction ("say so and propose the correction... update this file
in the same PR") rather than leaving it implicit in this commit body.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>