Let parents register once and book for their children
A parent registers once and manages lessons for one or more children, who need no login of their own. A child is a real wp_users row with the student role but no usable login — so student_id keeps meaning "a WordPress user" on every table, and booking, credits, policies and enrolments work unchanged. A us_guardians link table maps guardian to child. The signup form gains a parent/guardian tick that reveals a block per child, with the account-signup questions asked per child rather than per guardian — they describe the student, not the account holder. Signup policies are recorded once per child with the guardian as the acceptor, which is the record that actually means something. A family that half-creates is rolled back entirely rather than leaving a guardian who cannot re-register. The booking and enrolment forms gain a "Who is this for?" picker listing children first, so the default selection is never the parent — booking for the wrong child is correctable, quietly billing a parent for their kid's lesson is not. POST /bookings and POST /enrollments take an optional student_id honoured only for that child's guardian; anything else is a 403. That check is the authorisation boundary of the feature. Payments and credits gain a payer: the charge names the child it was for and the guardian who owes it, so per-child reporting is unchanged while notices, receipts and the payment step reach the parent. Credit is held by the payer, so one child's cancellation can settle a sibling's charge, and the daily billing scan sends a guardian one notice covering every child. Closes #132 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* "Who is this for?" picker, shared by the lesson-booking and group-class
|
||||
* registration forms.
|
||||
*
|
||||
* The list arrives from the server already ordered children-first, with the
|
||||
* account holder last, and this module preserves that order: a guardian's
|
||||
* default selection is their first child, never themselves. Booking for the
|
||||
* wrong child is a correctable mistake; quietly enrolling the parent in a class
|
||||
* meant for their kid is not.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the server-rendered student list off a `data-students` attribute.
|
||||
* Anything unparseable degrades to an empty list, which renders no picker
|
||||
* and books for the signed-in user — the pre-guardian behaviour.
|
||||
*/
|
||||
function parseStudents(raw) {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
const list = JSON.parse(raw);
|
||||
return Array.isArray(list) ? list : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The picker's markup, or an empty string when there is nothing to choose:
|
||||
* an account with only itself on the list never sees the question.
|
||||
*/
|
||||
function selectorHtml(students, id) {
|
||||
if (!students || students.length < 2) return '';
|
||||
|
||||
const options = students.map((s) => {
|
||||
// The account holder reads as "Myself" — their own name next to their
|
||||
// children's is ambiguous about which row is the parent.
|
||||
const label = s.is_self ? `Myself (${s.name})` : s.name;
|
||||
return `<option value="${Number(s.id)}">${escHtml(label)}</option>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<p class="us-student-picker">
|
||||
<label for="${id}">Who is this for?<br>
|
||||
<select id="${id}" required>${options}</select></label>
|
||||
</p>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The chosen student id, or 0 when no picker was rendered — the server
|
||||
* reads 0 as "the caller books for themselves".
|
||||
*/
|
||||
function selectedId(id) {
|
||||
const el = document.getElementById(id);
|
||||
return el ? Number(el.value) || 0 : 0;
|
||||
}
|
||||
|
||||
window.usGuardian = { parseStudents, selectorHtml, selectedId };
|
||||
}());
|
||||
Reference in New Issue
Block a user