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>
44 lines
2.0 KiB
XML
44 lines
2.0 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<data noupdate="1">
|
|
<record id="mail_template_absence_notice" model="mail.template">
|
|
<field name="name">School: Absence Notice</field>
|
|
<field name="model_id" ref="model_community_school_attendance"/>
|
|
<field name="subject">Absence notice: {{ object.enrollment_id.student_id.partner_id.name }}</field>
|
|
<field name="partner_to">{{ object.enrollment_id.student_id.parent_partner_id.id }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
<field name="body_html" type="html">
|
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
|
<p>Dear <t t-out="object.enrollment_id.student_id.parent_partner_id.name or ''">Parent</t>,</p>
|
|
<p>
|
|
This is to let you know that
|
|
<t t-out="object.enrollment_id.student_id.partner_id.name or ''"/>
|
|
was marked absent from
|
|
<t t-out="object.class_id.name or ''"/>
|
|
on <t t-out="format_date(object.date)"/>.
|
|
</p>
|
|
<p t-if="object.notes">Notes: <t t-out="object.notes"/></p>
|
|
</div>
|
|
</field>
|
|
</record>
|
|
<record id="mail_template_seat_available" model="mail.template">
|
|
<field name="name">School: Waitlist Seat Available</field>
|
|
<field name="model_id" ref="model_community_school_enrollment"/>
|
|
<field name="subject">A seat opened up in {{ object.class_id.name }}</field>
|
|
<field name="partner_to">{{ object.student_id.parent_partner_id.id }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
<field name="body_html" type="html">
|
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
|
<p>Dear <t t-out="object.student_id.parent_partner_id.name or ''">Parent</t>,</p>
|
|
<p>
|
|
Good news - a seat has opened up in
|
|
<t t-out="object.class_id.name or ''"/>
|
|
and <t t-out="object.student_id.partner_id.name or ''"/> has been moved from the
|
|
waitlist to enrolled.
|
|
</p>
|
|
</div>
|
|
</field>
|
|
</record>
|
|
</data>
|
|
</odoo>
|