Leads & Intake
Tables: leads, lead_students, lead_notes, incomplete_inquiries,
incomplete_inquiry_notes
The two public forms — the Registration Form
and the Inquiry Form — write only to these
tables. They are deliberately not live data: no users, no profiles, no
reservations exist until an admin converts the lead in Admin > Leads.
leads
One submission from one of the two public forms, discriminated by
source ENUM(registration,inquiry):
- registration — the wizard that quotes a price and may take payment.
semester_idrecords which semester registration was open for at submit time. - inquiry — the “Request Information” form, which asks about interest and
never quotes.
semester_idis alwaysNULL; insteadsemester_labelholds a free-text option from theinquiry_semester_optionssetting (not asemestersrow).
status ENUM(new,contacted,scheduled,converted,declined) is the
admin’s working state for the lead.
The rest of the columns fall into groups:
- Parent contact:
parent_first_name/parent_last_name,email,phone,sms_consent,newsletter_opt_in, and a nullable address block (nullable because an inquiry can be abandoned mid-flow or come from outside the US; the registration wizard enforces its own address rules). - Scheduling preferences (registration):
location_preference,preferred_days(JSON array of day names),availability_blocks(JSON array of9-11,11-1,1-3,3-5,5-7),scheduling_notes. - Inquiry-only:
owned_instruments(JSON) +owned_instruments_other,music_background,theory_program_interestENUM(yes,no,need_info),theory_knowledge(same vocabulary asstudent_profiles.experience_level),inquiry_comments. - Both:
referral_source(validated in PHP so options can change without DDL),policies_agreed_at,installment_plan. - Money:
quote_json(the itemized quote as computed at submit),amount_quoted_cents,amount_due_now_cents,amount_paid_cents,stripe_checkout_session_id(unique),stripe_payment_intent_id,paid_at. A payment made during registration is held on the lead until conversion moves it to a student’s ledger. - Conversion tracking:
converted_parent_user_id,converted_at.
lead_students
The students named on a lead. The two sources fill different columns:
| Registration lead | Inquiry lead | |
|---|---|---|
| Filled | instrument (as entered, incl. “Cello/Bass”), lesson_length_minutes (30 or 60), class_of, guitar_ensemble, shirt_size |
age, enrollment_status ENUM(new,continuing), instruments_of_interest (JSON) + instruments_other |
| Left NULL | inquiry columns | instrument, lesson_length_minutes — nothing has been decided yet |
The mapping to a real instruments row is picked by the admin at convert
time. converted_student_user_id makes conversion idempotent:
already-converted rows are skipped on re-entry, so a conversion that failed
half-way can simply be run again.
lead_notes
An admin’s internal notes on a lead — append-only, so who said what and
when is never lost and two admins working the same lead cannot clobber each
other. body may be empty when the entry only records a status change;
status_after records a status change made in the same save, keeping the
history and the lead in agreement. created_by_user_id is NULL for notes
migrated from the old single leads.admin_notes column.
incomplete_inquiries
Drop-off capture for the public /inquiry/ flow. Page 1 (contact info) writes
a row here so staff can reach out to a visitor who never finishes; page 2
(address) updates it (last_step_completed: 1 = contact only, 2 = address
too). Page 3 (student info) promotes the row into a real lead and deletes
it — so a row in this table always means “this family never told us about a
student.” They appear in Admin > Leads > Uncompleted Forms as a peer to
leads, but are never converted directly.
incomplete_inquiry_notes
Internal notes on an uncompleted form, on the same append-only terms as
lead_notes. When the form is finished and becomes a lead, these are carried
across as a single note on that lead, so the record of the chase survives the
conversion.
