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>
102 lines
5.2 KiB
XML
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>
|