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.
322 lines
18 KiB
Markdown
322 lines
18 KiB
Markdown
# 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 0–7 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.
|