TNCSC_Odoo/HANDOFF.md
metatroncubeswdev 5596f41377 fix(tncsc_deployment): grant admin the Board Admin role so CommunityOS apps show
Every product module's backend app (Membership, School, Benefits,
Classifieds, Interac Payments) has its root menu restricted to its own
manager/coordinator group. A fresh tncsc_deployment install left the
admin user in none of them, so admin's app switcher only showed stock
apps (Events, eLearning, ...) - which looks exactly like the modules
weren't installed, even though ir.module.module.state is 'installed' for
all of them (menu visibility is group-based, not install-state-based).

Fixed by seeding admin into tncsc_deployment.group_tncsc_board_admin,
whose implied_ids already cover every product manager group - this
automates what Phase 8 previously described doing by hand for just the
School Coordinator group. Verified on a fresh install: admin's visible
root menus now include Membership/School/Benefits/Classifieds/Interac
Payments alongside the stock apps, tests still 9/9.

Also documents in HANDOFF.md that `user in group.user_ids` doesn't see
implied-group membership - use has_group() instead, which resolves the
transitive closure correctly.
2026-08-21 18:59:34 -04:00

322 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handoff / Resume Notes
Read this first if you're picking this project back up — whether that's
Claude Code in a future session or a human developer. The phase-by-phase
build plan is in `CommunityOS_Implementation_Plan_for_Claude_Code.md`; this
file is the "where things actually stand" companion to it.
## Status at a glance
| Phase | What it is | Status |
|---|---|---|
| 0 | Repo scaffold, Docker dev stack, CI + brand-leak guardrails | ✅ Done, committed |
| 1 | `community_membership` | ✅ Done, committed |
| 2 | `event_qr_ticketing` | ✅ Done, committed |
| 3 | `community_school` | ✅ Done, committed |
| 4 | `community_classifieds` + `community_benefits` | ✅ Done, committed |
| 5 | `community_interac` (Interac e-Transfer payment provider) | ✅ Done, committed |
| 6 | `community_theme_base` + `community_portal` | ✅ Done, committed |
| 7 | `tncsc_deployment` (client config layer) | ✅ Done, committed. Website pages content migrated from live tncsc.com in a later session — see "TNCSC website migration" below |
| 8 | TNCSC data migration scripts | ✅ Done, committed (`9a24691`) |
| 9 | Packaging for resale + production go-live | 🟡 9-A in progress (see below); 9-B not started |
Every phase 07 commit was verified against a **live** Odoo 19 + Postgres 16
container before being committed — not just written and assumed to work.
Run `git log --oneline` for the full commit list; each commit message
documents what was built and how it was verified, including several real
Odoo 19 API changes that don't match older docs/tutorials (see "Gotchas"
below for the index).
**Git remote is configured**: `origin` -> `https://git.metatroncube.in/admin/TNCSC_Odoo.git`.
## Resuming the dev environment
```bash
cd deploy
docker compose up -d
```
- Odoo: http://localhost:8069, database `communityos_dev`, login `admin` /
password `admin`.
- This dev database has been used for **all** testing across every phase —
it has real posted accounting entries in it (from Interac transaction
confirmations, membership invoices, etc.). That's *why* Phase 7's
`tncsc_deployment` module can't be installed on it directly (Odoo
correctly refuses to change a company's currency once journal entries
exist) — it was instead verified on throwaway fresh databases
(`tncsc_freshN`, all dropped after verification). A real TNCSC production
database should be a genuinely fresh install, not this dev one.
- `tncsc_site` is another fresh DB (with `tncsc_deployment` installed), used
for the website content migration below — kept around this time (not
dropped) since it's the one with the real seeded event/tiers to look at.
Since multiple DBs coexist, a plain `http://localhost:8069/` won't know
which to serve — select one explicitly first, e.g.
`http://localhost:8069/web?db=tncsc_site`, then it's remembered by cookie.
- **After any module code change**, the *running* `deploy-odoo-1` container
has a stale in-memory registry until you either upgrade the module (`-u`)
and restart the container, or just restart it:
```bash
docker compose restart odoo
```
This bit repeatedly during development — don't skip it when testing.
## Phase 8 — done, committed (`9a24691`)
All three migration scripts exist, are committed, and have been verified
against a **live** Odoo instance (see "How each was tested" below).
- `scripts/_migration_common.py` — shared JSON-RPC helpers (auth, execute_kw,
CSV normalization, a `MigrationReport` class) for the migration scripts.
- `scripts/migrate_members.py` — idempotent upsert by email, dry-run flag,
tier lookup by code, report CSV.
- `scripts/migrate_students.py` — idempotent upsert of parent partner (by
email, reusing a partner created by `migrate_members.py` if one exists)
+ child partner + `community.school.student`, matched by (parent, student
name) since students don't have their own email in the source data.
Enrolls into the matching class in the current open term (matched by
`community.school.level` code) when one exists; otherwise still
creates/updates the student record and reports why no enrollment
happened.
- `scripts/migrate_opening_balances.py` — posts one `account.move` in the
Miscellaneous Operations journal, dated the last fiscal year-end
(computed from `res.company.fiscalyear_last_day/month`, or `--date`
override). Refuses to post anything if the CSV doesn't balance
(`sum(debit) != sum(credit)`) or references an unknown account code —
checked before any Odoo write happens. Idempotent via a fixed `ref`
("Opening Balances Import"): re-running finds the existing posted entry
and does nothing.
### How each was tested
Followed the same pattern as `scripts/migrate_classroom.py` (Phase 3):
dry-run first, then a real run, then a second real run to prove
idempotency — against a **fresh** database, not `communityos_dev`. See
"Why a fresh DB, not communityos_dev" below for why.
1. `docker compose up -d`, then created a throwaway DB with both product
modules and the client layer: `odoo -d tncsc_migration_test -i
community_membership,tncsc_deployment --stop-after-init` (installing
`tncsc_deployment` alone pulls in `community_school` and everything else
as dependencies). Restarted the container afterward.
2. `migrate_members.py`: synthetic CSV at
`data/raw/members_export_sample.csv` (gitignored, left in place as a
fixture) with 4 valid rows + 2 deliberately-bad rows (missing email,
unknown tier code). Dry-run matched real-run output; second real run
reported `updated` not `created` for all 4; confirmed via RPC that
exactly one `res.partner` per email existed after both runs and
`membership_state` was `active`.
3. `migrate_students.py`: needed a `community.school.term` (state=open),
`community.school.level` (code=`BEG`), and `community.school.class`
seeded first (the fresh DB has none) — also had to grant the `admin`
user the School Coordinator group, since a fresh install doesn't put
admin in it. Synthetic CSV at `data/raw/students_export_sample.csv`
with 3 valid rows (2 matching the open term's class, 1 with no
`level_code`) + 1 deliberately-bad row (missing parent). Confirmed via
RPC after 2 runs: exactly 3 `community.school.student` records, exactly
2 `community.school.enrollment` records (no duplicates), parent
partners correctly reused from step 2 instead of duplicated.
4. `migrate_opening_balances.py`: synthetic CSV at
`data/raw/opening_balances_sample.csv` using real account codes from
the fresh DB's TNCSC chart of accounts (111100 Cash, 112110 Trade AR,
322000 Retained Earnings as the balancing line). Verified the
out-of-balance guard refuses to post (tested with a deliberately
mismatched debit/credit CSV). Real run posted one balanced, posted
`account.move`; second run detected the existing entry and did nothing.
Confirmed via RPC: exactly one move, three correctly-valued lines, AR
line correctly linked to the migrated Ravi Kumar partner.
Real TNCSC member/student/trial-balance data doesn't exist yet — all three
scripts were intentionally designed against a *documented, assumed* CSV
schema (see each script's docstring) since the real export formats aren't
known yet. Confirm the assumed column names match the real exports before
relying on them, or adjust the scripts to match.
### Why a fresh DB, not `communityos_dev`
`communityos_dev` doesn't have `tncsc_deployment` installed (see the top of
this file — it can't be, since it already has posted journal entries and
Odoo refuses to change a company's currency once those exist). Testing the
Phase 8 scripts there would exercise generic `community_membership`/
`community_school` behavior, not the actual TNCSC-configured target
(TNCSC's real chart of accounts, company currency, groups). Since Phase 8's
entire purpose is rehearsing the real TNCSC data load, a fresh DB with
`tncsc_deployment` installed is the more representative target — and it
avoids adding more synthetic test data to the one long-lived dev DB used
across every other phase. `tncsc_migration_test` was **not** dropped after
this session (unlike the Phase 7 `tncsc_freshN` throwaways) — it's left in
place in case the next session wants to keep testing against it; drop it
before it's mistaken for anything resembling real data.
### To pick this back up
Either wait for real TNCSC export samples to validate the assumed CSV
schemas against, or move on to Phase 9.
## Phase 9 — 9-A in progress, 9-B not started
Two sub-parts with very different risk profiles:
- **9-A (productize)**: safe to do autonomously. Done so far — every
`community_*` module has `static/description/index.html`, a real
`README.rst` (Usage sections filled in past the Phase-0 placeholder),
and a `CHANGELOG.rst`; manifests pass `scripts/check_manifests.py`.
Still open:
- Every product manifest's `images` key points at
`static/description/banner.png`, but no module actually has that
file — the Apps-list/App-Store banner image doesn't exist yet. Needs
a real designed asset per module, not a placeholder.
- No `v1.0.0` git tag yet, and no release report — holding off on both
until the banner-image gap above is resolved. (A later session did run
`--test-enable` for `tncsc_deployment` specifically against live Odoo
while doing the website migration below — 9/9 passing — but that
wasn't a full suite-wide run across every `community_*` module.)
- No role guides (admin/treasurer/teacher/parent) written yet.
- **9-B (go-live)**: not started. `deploy/odoo.conf` is still dev config
(`list_db = True`, `proxy_mode = False`, no `workers`) and
`deploy/nginx-communityos.conf` is an explicit "development skeleton" —
SSL, DNS cutover, firewall rules, and backups on a **real production
server** all remain. This needs a human in the loop with actual server
access/credentials — don't let an agent run this unattended.
## TNCSC website migration (from live tncsc.com, WordPress -> Odoo)
`addons/tncsc_deployment/views/website_pages.xml` was rewritten from the
Phase 7 placeholder pages ("content to be finalized") into the real site
content, adapted from tncsc.com (captured 2026-08-20). Tested against a
dedicated fresh database, `tncsc_site` (not `communityos_dev` or
`tncsc_migration_test`) — `docker compose exec odoo odoo -d tncsc_site -i
tncsc_deployment --stop-after-init` recreates it from scratch.
- Real pages built: Home, About, About/Tamil Nadu (bilingual EN/Tamil),
About/Board of Directors (12 real named members + photos), Membership
hub, Membership Benefits (real 8-point benefit copy), Tamil Class,
Contact, plus the pre-existing Sponsors placeholder. Nav restructured to
match tncsc.com's real structure (Home / About dropdown / Events /
Membership dropdown / Tamil Class / Contact) instead of the old flat
Classifieds/Benefits/Sponsors list.
- Real assets pulled from tncsc.com and committed under
`addons/tncsc_deployment/static/src/img/` (~1.1MB: logo, board photos,
section imagery) rather than hotlinking the WordPress site.
- Events, Membership, and Tamil Class are wired to the real product
modules (`event.event` for the homepage's "Upcoming Events", live
`community.membership.tier` records for pricing, `/school/register` for
Tamil Class signup) instead of being static copies — see each page's
QWeb for the exact query.
- One real event was seeded (`data/events_data.xml`): TNCSC's Summer
Picnic 2026, matching what's live on tncsc.com/events.
- **Deliberately not reproduced**, because it isn't real: tncsc.com's own
Sponsors page (generic `client-01..09` stock logos, dead links) and its
Contact page phone number (`+7 (800) 123 45 69`, a non-Canadian WordPress
theme demo placeholder) are themselves unmigrated theme demo content on
the live site, not real TNCSC data — confirmed by inspecting alt
text/hrefs, not assumed. Contact page ships with the real, verified
email (`support@tncsc.ca`) and social links (Facebook/YouTube/Instagram)
instead.
- **Real gap, not paved over**: `community_membership` has no public
self-service "join online" flow (portal only handles renewal for
partners a staff member already created) — every "Become a Member" CTA
points at `/contact` rather than a fake/broken signup link. Building
that flow is follow-on work, not done here.
- **Known cosmetic leftover**: Odoo's own stock header/footer snippets
ship a placeholder phone number (`+1 555-555-5556`) baked into core
`website` module views (`website.header_text_element`,
`website.footer_custom`, `website.s_contact_info`), independent of
anything in this module. It's normal, editable-in-the-website-builder
content on any fresh Odoo site, not something this module introduced —
left for whoever does the go-live content pass to delete via the
Website editor (Edit -> click the text -> delete), rather than patched
with a defensive XML override here.
- Text throughout was reconstructed via automated extraction from the live
pages, not copy-pasted from raw HTML — treat it as a faithful draft.
**Have TNCSC proofread the migrated copy against the original site
before go-live.**
## Gotchas discovered this build (Odoo 19 API drift + environment quirks)
This Odoo 19 build (`19.0-20260817`, essentially a nightly) diverges from
older tutorials/docs in several places that cost real debugging time. Each
is documented in detail in the commit message where it was found — this is
just the index so you know to search `git log` for it instead of
re-discovering it:
- `res.groups.category_id` → replaced by `privilege_id` →
`res.groups.privilege.category_id` (Session 1-A commit).
- Settings pages use a new `<app>/<block>/<setting>` structure inherited
from `base.res_config_settings_view_form`, not the old raw-div layout
(Session 1-A commit).
- `ir.cron` dropped `numbercall` entirely (Session 1-B commit).
- `event.event` has no `state` field anymore (replaced by a
`stage_id`/`event.stage` kanban system) (Phase 2 commit).
- Route `type='json'` should be `type='jsonrpc'` (`'json'` still works but
is deprecated) (Phase 2 commit).
- Search-view `<group>` elements for "Group By" sections no longer accept a
`string` attribute, only `name` (Phase 3 / Phase 4 commits).
- `res.groups.users` → renamed to `user_ids` (Phase 4 commit).
- `res.config.settings` fields backed by `config_parameter=` only support
boolean/integer/float/char/selection/many2one/datetime — **not**
`Binary`. Using it on a Binary field breaks *every* Settings tab, not
just the one you're adding, since they share one model (`fix:` commit
after Phase 6).
- Anonymous visitors hitting an `auth='user'` + `website=True` route in
this specific build crash with a raw 500 instead of a clean redirect to
`/web/login` (`Request._serve_db`'s `finally: self.env = None` runs
before the website error handler tries to build the redirect). Worked
around by switching those routes to `auth='public'` with a manual
`if request.env.user._is_public(): return request.redirect(...)` check
(`fix:` commit after Phase 6 — this is the fix to reach for if you add
*new* member-only pages later, don't reintroduce `auth='user'` on a
`website=True` route without it).
- Setting `res.company.country_id` on a brand-new company auto-schedules
this build's own chart-template installer via a precommit hook, which
races an explicit `try_loading()` call made in the same install
transaction and silently reverts it afterwards. `ir.cron` code execution
is also sandboxed and forbids direct attribute assignment (`STORE_ATTR`)
— use `.write(...)`. Both fully explained in the Phase 7 commit message,
fix pattern is in `addons/tncsc_deployment/models/res_company.py`.
- Not version drift, but a real trap worth indexing here since it cost
significant debugging time during the website migration: the `website`
module auto-forks its own website-bound ("Copy-On-Write") copy of both
the homepage (`website.homepage`) and the top nav tree (`website.menu_id`)
the first time a website exists. A generic (`website_id`-unset) page or
menu record renders fine at ordinary URLs, but is silently ignored for
the homepage route and the rendered nav specifically — those two routes
only resolve records explicitly bound to the current `website_id`, and on
a same-URL tie the auto-forked stock copy wins over a same-priority
custom one by insertion order. Confirmed empirically against a live
container (not from docs). Fix pattern — explicitly bind `website_id`,
and where a stock auto-forked duplicate exists, remove it — is in
`addons/tncsc_deployment/__init__.py` (`_remove_stock_homepage_page`,
`_fix_website_menus`), both run from `post_init_hook`. Relevant if you
add more top-level pages/menu items later.
- Each product module's backend app (Membership, School, Benefits,
Classifieds, Interac Payments) has its root `ir.ui.menu` restricted to
its own manager/coordinator group, and a fresh `tncsc_deployment`
install doesn't put the `admin` user in any of them by default. Result:
admin's backend app switcher only shows stock apps (Events, eLearning,
Settings, ...) and none of the CommunityOS ones, which looks exactly
like "the modules aren't installed" even though
`ir.module.module.state` is `installed` for all of them — confirmed
by checking `env['ir.ui.menu'].with_user(admin).search([('parent_id',
'=', False)])`, which is the right way to check what a user actually
sees (menu visibility is group-based, not just install state). Fixed
by adding `admin` to `tncsc_deployment.group_tncsc_board_admin` in
`security/tncsc_roles.xml` (that group's `implied_ids` already covers
every product manager group) — this used to be the manual per-group
step described above for Phase 8's School Coordinator grant; it's now
automatic on any fresh install. Note `res.groups.implied_ids` doesn't
populate the implied group's own `user_ids` — checking membership via
`user in group.user_ids` will wrongly say "no" for an implied group;
use `user.has_group('module.xml_id')` instead, which does resolve the
transitive closure correctly.
## Handing off to a team
1. Repo is already pushed to `origin` (see above) — confirm the team has
access to `git.metatroncube.in`.
2. Point them at `CommunityOS_Implementation_Plan_for_Claude_Code.md` (the
plan) and this file (current status).
3. They'll need Docker Desktop (or equivalent) to run `deploy/docker-compose.yml`
locally — no other local dependencies.
4. If they're using Claude Code to continue: just point it at this repo and
this file. It doesn't need conversation history — everything load-bearing
is either in a commit message or in this document.