Files
unsupervised-scheduler/src/Schema.php
T
thatguygriffandClaude Opus 5 8c21a3fa9d
CI / Tests (PHP 8.1) (pull_request) Successful in 6m39s
CI / Tests (PHP 8.2) (pull_request) Successful in 57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m59s
CI / Tests (PHP 8.5) (pull_request) Successful in 3m31s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards & Static Analysis (pull_request) Successful in 3m28s
CI / Build Plugin Zip (pull_request) Skipped
Let the studio book lessons and record intake collected elsewhere
Two related gaps, closed together because the second is created by the first.

A private lesson could only be booked by the student or their guardian, so a
booking taken over the phone had no way in — where group classes have had "Add
students directly" all along. "Book a lesson for a student" is now a panel on
Scheduler and My Lessons: student, open time, lesson type, with weekly term
reservations and a no-charge option for make-up lessons. The booking core is
extracted to Booking\LessonBooker and shared with POST /bookings, so the two
paths cannot drift on offering rules, slot claiming, or billing.

That leaves a registration with no intake answers and no policy acceptances,
because nobody was at a keyboard to give them — already true of every directly
added group-class student. Ticking the boxes on a student's behalf would be an
audit trail that says something untrue, so instead the answers are collected
another way and recorded afterwards, from a lesson's or an enrolment's detail
page. Every recording must say how it was collected, which is stamped on each
row along with who typed it and shown in a new "How it was given" column: a
policy ticked online and one transcribed from paper must never look alike.

Only staff-made registrations qualify (us_lessons.booked_by,
us_group_enrollments.enrolled_by) — one the student made already holds their
own answers. Only what is still missing can be recorded, re-checked at write
time, so a stale or double-posted form cannot duplicate or overwrite. No IP is
stored for a transcription, and accepted_by stays the student while recorded_by
names the staff member.

Intake is now generic over Registration\IntakeSubject, which Lesson and
Enrollment both implement; LessonDetail became Registration\IntakeAudit and is
shared by both detail views rather than duplicated.

Closes #182

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QfHt6CyJHz6KkA4RuaS7WK
2026-08-24 14:06:16 -03:00

281 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace Unsupervised\Schedular;
class Schema {
/**
* Returns CREATE TABLE statements for dbDelta.
*
* @return list<string>
*/
public static function tables( string $prefix, string $charset ): array {
return [
"CREATE TABLE {$prefix}us_availability (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
instructor_id BIGINT UNSIGNED NOT NULL,
offering_id BIGINT UNSIGNED DEFAULT NULL,
start_dt DATETIME NOT NULL,
end_dt DATETIME NOT NULL,
duration_minutes SMALLINT UNSIGNED NOT NULL DEFAULT 60,
is_booked TINYINT(1) NOT NULL DEFAULT 0,
recurrence_group BIGINT UNSIGNED DEFAULT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY instructor_id (instructor_id),
KEY offering_id (offering_id),
KEY start_dt (start_dt),
KEY recurrence_group (recurrence_group)
) {$charset};",
"CREATE TABLE {$prefix}us_lessons (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
slot_id BIGINT UNSIGNED NOT NULL,
offering_id BIGINT UNSIGNED DEFAULT NULL,
student_id BIGINT UNSIGNED NOT NULL,
instructor_id BIGINT UNSIGNED NOT NULL,
recurrence VARCHAR(10) NOT NULL DEFAULT 'single',
series_id BIGINT UNSIGNED DEFAULT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
payment_id BIGINT UNSIGNED DEFAULT NULL,
notes TEXT,
booked_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY slot_id (slot_id),
KEY offering_id (offering_id),
KEY student_id (student_id),
KEY instructor_id (instructor_id),
KEY series_id (series_id)
) {$charset};",
"CREATE TABLE {$prefix}us_offerings (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
instructor_id BIGINT UNSIGNED NOT NULL,
kind VARCHAR(20) NOT NULL,
title VARCHAR(191) NOT NULL,
description TEXT,
duration_minutes SMALLINT UNSIGNED DEFAULT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0,
currency VARCHAR(3) NOT NULL DEFAULT 'CAD',
billing_mode VARCHAR(20) NOT NULL DEFAULT 'one_time',
allow_weekly TINYINT(1) NOT NULL DEFAULT 0,
capacity SMALLINT UNSIGNED DEFAULT NULL,
term_start DATE DEFAULT NULL,
term_end DATE DEFAULT NULL,
class_time TIME DEFAULT NULL,
enrollment_deadline DATE DEFAULT NULL,
withdrawal_deadline DATE DEFAULT NULL,
schedule_note VARCHAR(191) DEFAULT NULL,
etransfer_email VARCHAR(191) DEFAULT NULL,
cancellation_cutoff_hours SMALLINT UNSIGNED DEFAULT NULL,
access_mode VARCHAR(20) NOT NULL DEFAULT 'public',
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY instructor_id (instructor_id),
KEY kind (kind),
KEY is_active (is_active)
) {$charset};",
"CREATE TABLE {$prefix}us_questions (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
offering_id BIGINT UNSIGNED DEFAULT NULL,
scope VARCHAR(20) NOT NULL DEFAULT 'offering',
label VARCHAR(255) NOT NULL,
field_type VARCHAR(20) NOT NULL DEFAULT 'text',
options TEXT,
audience VARCHAR(20) NOT NULL DEFAULT 'all',
is_required TINYINT(1) NOT NULL DEFAULT 0,
is_required_child TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY offering_id (offering_id),
KEY scope (scope),
KEY is_active (is_active)
) {$charset};",
"CREATE TABLE {$prefix}us_question_answers (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
question_id BIGINT UNSIGNED NOT NULL,
registration_type VARCHAR(20) NOT NULL,
registration_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED NOT NULL,
answer_value TEXT,
collected_via VARCHAR(20) DEFAULT NULL,
collected_note VARCHAR(191) DEFAULT NULL,
recorded_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY question_id (question_id),
KEY registration (registration_type, registration_id),
KEY student_id (student_id)
) {$charset};",
"CREATE TABLE {$prefix}us_policies (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(191) NOT NULL,
slug VARCHAR(191) NOT NULL,
current_version_id BIGINT UNSIGNED DEFAULT NULL,
acceptance_scope VARCHAR(20) NOT NULL DEFAULT 'booking',
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY slug (slug),
KEY acceptance_scope (acceptance_scope)
) {$charset};",
"CREATE TABLE {$prefix}us_policy_versions (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
policy_id BIGINT UNSIGNED NOT NULL,
version_number INT NOT NULL DEFAULT 1,
body LONGTEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
published_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY policy_id (policy_id),
KEY status (status)
) {$charset};",
"CREATE TABLE {$prefix}us_policy_acceptances (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
policy_version_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED NOT NULL,
accepted_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
registration_type VARCHAR(20) NOT NULL,
registration_id BIGINT UNSIGNED NOT NULL,
accepted_at DATETIME NOT NULL,
ip_address VARCHAR(45) DEFAULT NULL,
collected_via VARCHAR(20) DEFAULT NULL,
collected_note VARCHAR(191) DEFAULT NULL,
recorded_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
KEY policy_version_id (policy_version_id),
KEY student_id (student_id),
KEY accepted_by (accepted_by),
KEY registration (registration_type, registration_id)
) {$charset};",
"CREATE TABLE {$prefix}us_payments (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
student_id BIGINT UNSIGNED NOT NULL,
payer_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
instructor_id BIGINT UNSIGNED NOT NULL,
registration_type VARCHAR(20) NOT NULL,
registration_id BIGINT UNSIGNED NOT NULL,
amount DECIMAL(10,2) NOT NULL DEFAULT 0,
currency VARCHAR(3) NOT NULL DEFAULT 'CAD',
method VARCHAR(20) NOT NULL DEFAULT 'etransfer',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
credit_applied DECIMAL(10,2) NOT NULL DEFAULT 0,
due_date DATE DEFAULT NULL,
period_key VARCHAR(20) DEFAULT NULL,
notice_batch VARCHAR(32) DEFAULT NULL,
etransfer_email VARCHAR(191) DEFAULT NULL,
stripe_payment_intent_id VARCHAR(255) DEFAULT NULL,
receipt_number VARCHAR(50) DEFAULT NULL,
receipt_sent_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL,
paid_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
KEY student_id (student_id),
KEY payer_id (payer_id),
KEY instructor_id (instructor_id),
KEY registration (registration_type, registration_id),
KEY status (status)
) {$charset};",
"CREATE TABLE {$prefix}us_credits (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
student_id BIGINT UNSIGNED NOT NULL,
payer_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
amount DECIMAL(10,2) NOT NULL DEFAULT 0,
remaining DECIMAL(10,2) NOT NULL DEFAULT 0,
currency VARCHAR(3) NOT NULL DEFAULT 'CAD',
source_payment_id BIGINT UNSIGNED DEFAULT NULL,
source_lesson_id BIGINT UNSIGNED DEFAULT NULL,
reason VARCHAR(191) DEFAULT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'available',
created_at DATETIME NOT NULL,
updated_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
KEY student_id (student_id),
KEY payer_id (payer_id),
KEY status (status),
KEY source_lesson_id (source_lesson_id)
) {$charset};",
"CREATE TABLE {$prefix}us_group_enrollments (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
offering_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED NOT NULL,
instructor_id BIGINT UNSIGNED NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
payment_id BIGINT UNSIGNED DEFAULT NULL,
enrolled_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
enrolled_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY offering_id (offering_id),
KEY student_id (student_id),
KEY instructor_id (instructor_id),
KEY status (status)
) {$charset};",
"CREATE TABLE {$prefix}us_invites (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(191) NOT NULL,
token VARCHAR(64) NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'us_student',
kind VARCHAR(10) NOT NULL DEFAULT 'personal',
offering_id BIGINT UNSIGNED DEFAULT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
invited_by BIGINT UNSIGNED DEFAULT NULL,
accepted_user_id BIGINT UNSIGNED DEFAULT NULL,
created_at DATETIME NOT NULL,
accepted_at DATETIME DEFAULT NULL,
expires_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY email (email),
KEY status (status)
) {$charset};",
// Links a parent/guardian account to a child who books through it. The
// child is a real (login-less) wp_users row, so student_id keeps meaning
// "a WordPress user" on every other table.
"CREATE TABLE {$prefix}us_guardians (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
guardian_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED NOT NULL,
relationship VARCHAR(50) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY guardian_student (guardian_id, student_id),
KEY guardian_id (guardian_id),
KEY student_id (student_id)
) {$charset};",
"CREATE TABLE {$prefix}us_group_access (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
offering_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED DEFAULT NULL,
email VARCHAR(191) NOT NULL DEFAULT '',
invite_id BIGINT UNSIGNED DEFAULT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'invited',
invited_by BIGINT UNSIGNED DEFAULT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY offering_id (offering_id),
KEY student_id (student_id),
KEY email (email),
KEY status (status)
) {$charset};",
];
}
}