Adds migrate_students.py (parent+child partner upsert, current-term enrollment by school level code) and migrate_opening_balances.py (one posted, balance-checked journal entry dated the last fiscal year-end). All three Phase 8 scripts (incl. the existing migrate_members.py) verified against a live Odoo 19 instance: fresh tncsc_migration_test DB with tncsc_deployment installed, dry-run + real run + idempotency re-run each, confirmed via RPC no duplicate records were created. See HANDOFF.md for the full verification log and why a fresh DB was used instead of communityos_dev. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
218 lines
12 KiB
Markdown
218 lines
12 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 |
|
||
| 8 | TNCSC data migration scripts | ✅ Written and verified live, **not committed** — see below |
|
||
| 9 | Packaging for resale + production go-live | ⬜ 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).
|
||
|
||
**No git remote is configured.** This repo only exists on this machine
|
||
right now. Before handing off to a team, push it somewhere (GitHub/GitLab/
|
||
etc.) — see "Handing off to a team" below.
|
||
|
||
## 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.
|
||
- **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 — exactly where it was left off
|
||
|
||
All three migration scripts exist and have been verified against a **live**
|
||
Odoo instance, but **the work is not committed yet** (see "To pick this back
|
||
up" below for why).
|
||
|
||
- `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
|
||
|
||
The scripts work and are proven idempotent, but haven't been committed —
|
||
do that first (a `feat: Phase 8 migration scripts` commit, following the
|
||
same "verified against a live container before committing" convention as
|
||
every other phase), then either wait for real TNCSC export samples to
|
||
validate the assumed CSV schemas against, or move on to Phase 9.
|
||
|
||
## Phase 9 — not started
|
||
|
||
Two sub-parts with very different risk profiles:
|
||
- **9-A (productize)**: static description pages, READMEs, CHANGELOGs,
|
||
release tagging for the product modules. Safe to do autonomously.
|
||
- **9-B (go-live)**: SSL, DNS cutover, firewall rules, backups on a **real
|
||
production server**. This needs a human in the loop with actual server
|
||
access/credentials — don't let an agent run this unattended.
|
||
|
||
## 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`.
|
||
|
||
## Handing off to a team
|
||
|
||
1. **Push this repo to a real remote** (GitHub/GitLab/etc.) — right now it
|
||
only exists locally.
|
||
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.
|