Collect a birth year instead of a full date of birth
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / PHPStan (pull_request) Successful in 2m55s
CI / Coding Standards (pull_request) Successful in 3m0s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped

Signup and the profile page now ask for a four-digit year between 1900 and
the current year. Anything else — a short year, a full date, a year in the
future — is discarded rather than stored, so a typo cannot leave a nonsense
age on the record.

The year lives in a new us_birth_year user meta rather than reusing
us_date_of_birth, which would have left one key holding two formats. The old
key is not migrated in bulk. Instead GuardianService handles it in two
halves: birthYear() falls back to the year of the old date when the new key
is absent, so a student added before this change still shows one, and
setBirthYear() deletes the old date on every save.

That deletion is what makes the fallback safe rather than merely tidy.
Without it, clearing the birth year on a student who predates the change
would leave the old date behind for the fallback to read straight back, and
the year could never be cleared at all.

Stored in user meta, so no Schema.php change and no USC_VERSION bump.

Closes #147

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-07-29 20:47:56 -03:00
co-authored by Claude Opus 5
parent 3a83decc82
commit 7e2bba79fe
15 changed files with 219 additions and 77 deletions
+23 -4
View File
@@ -67,11 +67,28 @@ requires migrating every existing row.
never be linked twice.
The table is a link table, not a child record: the child's **name** is their
`display_name` on `wp_users`, and their date of birth is the `us_date_of_birth`
`display_name` on `wp_users`, and their birth year is the `us_birth_year`
user meta. Keeping them on the user row means the admin student screens,
`get_users()` ordering, and every existing `student_id` lookup keep working with
no special-casing.
### The legacy `us_date_of_birth` meta
This feature originally collected a full date of birth in `us_date_of_birth`.
Nothing writes that key any more. It is handled entirely inside
`GuardianService`:
- **Read** — `birthYear()` falls back to the year of the old date when
`us_birth_year` is absent, so a child added before the change still shows one
without a migration step.
- **Write** — `setBirthYear()` deletes `us_date_of_birth` on *every* save,
including a save that clears the year. Without that the fallback would
resurrect the old date on the next read and the year could never be cleared.
The upshot is a lazy migration: a child's full date survives until their record
is next edited, then goes for good. There is no bulk purge — a site that wants
the remaining old dates gone should delete the `us_date_of_birth` meta directly.
v1 is deliberately **one guardian per child**: `GuardianRepository::insert()`
refuses to link a child that already has a guardian. The unique key and the
guardian-side lookups already support many-to-many, so adding a second guardian
@@ -130,7 +147,9 @@ least one child name.
Per child the form collects:
- **Name** (required)
- **Date of birth** (optional, `us_date_of_birth` meta)
- **Birth year** (optional, `us_birth_year` meta) — a four-digit year between
1900 and the current year. Anything else is discarded rather than stored, so
a typo cannot leave a nonsense age on the record.
- **Every account-scope registration question** (`Registration\Question`,
`SCOPE_ACCOUNT`) — asked once per child, not once per guardian, because in
practice they describe the student (instrument, level, school). The guardian
@@ -173,12 +192,12 @@ child.
## Managing children
`[us_family]` (block: **Profile**) renders the guardian's manage-children screen:
list the children, add one, edit a name/date of birth, remove one.
list the children, add one, edit a name/birth year, remove one.
- **Add** creates another accountless child user and links it. Account-scope
questions are asked here too, so a child added later carries the same
information as one added at signup.
- **Edit** updates `display_name` and `us_date_of_birth`.
- **Edit** updates `display_name` and `us_birth_year`.
- **Remove** unlinks the child and **deletes the child user**, but only when the
child has no lessons and no enrolments — a child with history is refused, so
removing one can never orphan a lesson, payment or credit