TNCSC_Odoo/addons/community_school/views/registration_templates.xml
metatroncubeswdev e05448b939 feat(community_school): multi-step registration, waitlist, LMS glue (Session 3-C)
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>
2026-08-17 21:42:51 -04:00

102 lines
5.2 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="registration_step_parent" name="School Registration: Parent">
<t t-call="website.layout">
<div class="container" style="max-width: 480px; margin-top: 24px; margin-bottom: 60px;">
<h2>Register - Step 1 of 3: Parent/Guardian</h2>
<form method="POST" t-attf-action="/school/register">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label class="form-label">Full Name</label>
<input type="text" class="form-control" name="name" required="required"/>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email" required="required"/>
</div>
<div class="mb-3">
<label class="form-label">Phone</label>
<input type="text" class="form-control" name="phone"/>
</div>
<button type="submit" class="btn btn-primary">Continue</button>
</form>
</div>
</t>
</template>
<template id="registration_step_student" name="School Registration: Student">
<t t-call="website.layout">
<div class="container" style="max-width: 480px; margin-top: 24px; margin-bottom: 60px;">
<h2>Register - Step 2 of 3: Student</h2>
<form method="POST" t-attf-action="/school/register/student">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label class="form-label">Student Full Name</label>
<input type="text" class="form-control" name="name" required="required"/>
</div>
<div class="mb-3">
<label class="form-label">Date of Birth</label>
<input type="date" class="form-control" name="date_of_birth"/>
</div>
<div class="mb-3">
<label class="form-label">Health Notes</label>
<textarea class="form-control" name="health_notes"/>
</div>
<div class="mb-3">
<label class="form-label">Emergency Contact Name</label>
<input type="text" class="form-control" name="emergency_contact_name"/>
</div>
<div class="mb-3">
<label class="form-label">Emergency Contact Phone</label>
<input type="text" class="form-control" name="emergency_contact_phone"/>
</div>
<button type="submit" class="btn btn-primary">Continue</button>
</form>
</div>
</t>
</template>
<template id="registration_step_class" name="School Registration: Class">
<t t-call="website.layout">
<div class="container" style="max-width: 480px; margin-top: 24px; margin-bottom: 60px;">
<h2>Register - Step 3 of 3: Class</h2>
<form method="POST" t-attf-action="/school/register/class">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label class="form-label">Class</label>
<select class="form-select" name="class_id" required="required">
<t t-foreach="classes" t-as="klass">
<option t-att-value="klass.id">
<t t-out="klass.name"/> (<t t-out="klass.enrolled_count"/>/<t t-out="klass.max_students"/> seats)
<t t-if="klass.fee"> - <t t-out="klass.fee"/></t>
</option>
</t>
</select>
</div>
<button type="submit" class="btn btn-primary">Complete Registration</button>
</form>
</div>
</t>
</template>
<template id="registration_done" name="School Registration: Done">
<t t-call="website.layout">
<div class="container" style="max-width: 480px; margin-top: 60px; margin-bottom: 60px;">
<t t-if="waitlisted">
<div class="alert alert-warning">
<h2>You're on the Waitlist</h2>
<p>The class is currently full. We'll notify you as soon as a seat opens up.</p>
</div>
</t>
<t t-else="">
<div class="alert alert-success">
<h2>Registration Complete</h2>
<p t-out="enrollment.student_id.partner_id.name"/> is enrolled in
<span t-out="enrollment.class_id.name"/>.
</div>
</t>
</div>
</t>
</template>
</odoo>