Adds scripts/migrate_wp_members.py to import TNCSC's real WordPress
member export (data/raw/users.csv, gitignored - real PII, never
committed) into Community OS Membership. Separate from
migrate_members.py, which expects a clean documented schema - this one
reads the real WooCommerce export's messy shape directly: duplicate
"Country"/"State" columns (read by position, not name, to get the real
profile ones instead of empty leftover checkout-form columns), free-text
membership levels mapped to tier codes, and legacy WordPress
"Membership ID"s preserved as a chatter note rather than reused as the
new member ID (229 of 493 rows didn't have one at all).
Test/spam rows (@abbuzz.com) are skipped; date of birth and the
citizenship/immigration-eligibility columns in the source export are
never read anywhere - no field for them and they're sensitive with no
operational purpose here. Ambiguous multi-value Levels are imported
without a tier and flagged 'needs_review' in the report rather than
guessed.
Run against tncsc_site and verified: 474 real members created (472 with
a resolved tier or legacy-ID note), 10 junk rows skipped, 8 flagged for
manual tier review, 1 non-member org inbox correctly excluded. Re-run
confirmed idempotent (474 updated, zero duplicates).
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>
HANDOFF.md is the "where things actually stand" companion to the plan
document: a phase-by-phase status table, how to spin the dev environment
back up (including the "restart the container after any module change or
you'll hit a stale registry" gotcha that bit repeatedly this session), an
index of every real Odoo 19 API-drift gotcha discovered during the build
(pointing at the commit that documents each in full rather than
duplicating it), and instructions for pushing this repo to a remote before
handing it to a team (none is configured yet - this repo only exists
locally).
Also commits the two Phase 8 files that existed only as uncommitted local
changes (scripts/_migration_common.py, a first pass at
scripts/migrate_members.py) so they survive a git clone rather than being
fragile local-only WIP. Neither is wired into anything or tested yet -
migrate_members.py has no live run against Odoo, and migrate_students.py /
migrate_opening_balances.py don't exist yet. Paused here at the user's
request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the multi-step website registration at /school/register (parent ->
student -> class), carrying state across steps in the request session
(auth='public' - a family with no account yet can register). Finalizing
creates/finds the parent partner by email, creates the child partner +
student, and creates the enrollment as 'enrolled' or 'waitlist' depending
on whether the chosen class.fee_product_id/max_students still has room -
generating a draft fee invoice when the class has a fee (a new
class.fee/fee_product_id, following the same auto-created-product pattern
as community_membership's tiers).
LMS glue: enrollment create/write now auto-enrols the student's partner
into the class's slide.channel via _action_add_members() when state
becomes 'enrolled', and deactivates the slide.channel.partner membership
on withdrawal. An hourly cron (_cron_promote_waitlist) fills freed seats
from the waitlist in enrollment-date order and emails the parent.
Adds scripts/migrate_classroom.py: a standalone, dependency-free (stdlib
only) JSON-RPC script that reads a title,url CSV and creates slide.slide
records in a target channel - YouTube links become published video slides
(source_type='external' lets Odoo's own compute fields resolve youtube_id
automatically), everything else becomes an unpublished document slide
flagged for manual re-upload/review, since the script can't read Google
Drive content itself. Idempotent by (channel_id, title); --dry-run and
--force supported.
Verified against a live Odoo 19 + Postgres 16 container: 14/14 automated
tests pass, plus full manual live runs - walked all three registration
steps over real HTTP (with CSRF tokens) and confirmed the resulting
enrollment and draft invoice; ran the migration script twice against a
real channel and confirmed the second run skipped both already-created
slides, with the YouTube slide's youtube_id correctly auto-derived.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scaffolds the CommunityOS monorepo per the implementation plan: 8
brand-neutral product modules (community_theme_base, community_membership,
event_qr_ticketing, community_school, community_classifieds,
community_benefits, community_interac, community_portal) plus the
tncsc_deployment client layer, each with an App-Store-ready manifest,
LGPL-3 license, and empty security/data/demo/tests/views scaffolding.
Adds deploy/docker-compose.yml (Odoo 19 CE + Postgres 16), CI workflow
that installs all modules with --test-enable, and scripts/check_brand_leak.py
+ check_manifests.py enforcing the no-client-identity-in-product-code and
manifest-completeness rules. Verified locally: all 9 modules install clean
on a fresh Odoo 19 database, and the brand-leak check correctly fails when
a client term is added to a product module and passes once removed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>