c611268bdb55a3c1a155aae693393ec32dd77532
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e8e66eef3c
|
Credit students for cancelled paid lessons
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / PHPStan (pull_request) Successful in 3m12s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m52s
Cancelling a lesson that was already paid for now credits the student that money instead of leaving it as a manual refund, and the daily scheduled-billing scan applies any available credit against their due charges before emailing the notice. - New us_credits ledger + us_payments.credit_applied column (Payment::netDue). - PaymentService::creditForCancelledLesson issues a per-lesson share of the covering payment's total; wired into all three cancel paths (student self-cancel, instructor status update, admin student-detail cancel). - PaymentService::applyCredits draws credit down FIFO across a run's charges, marking a fully-covered charge paid-by-credit; the notice shows the credit applied and reduced total, and the admin queue shows net due. - Student detail page shows a student's credit balance and history. Ships as part of the unreleased 1.2.0 (same release as scheduled billing). Tests: composer test (585), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
4328e8fb5f
|
Add weekly and monthly scheduled billing for offerings
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m12s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m39s
CI / Build Plugin Zip (pull_request) Skipped
Offerings can now bill weekly (a pending payment 24h before each lesson) or monthly (one payment on the 1st for that month's lessons), alongside one-time and full-term. Applies to both private lessons and group classes. - Offering: new `weekly`/`monthly` billing modes + `isScheduledBilling()` - Booking/enrolment defer payment for scheduled modes; a single lesson booked after its due date has passed (e.g. an add-on in an already-billed month) is charged at booking instead - ScheduledBillingRunner: daily WP-Cron scan generates due payments across four cases (private/group × weekly/monthly), deduped via lesson.payment_id and payments.period_key - PaymentDueMailer: one consolidated itemised email per student per scan - Notice batch: payments emailed together share a reference; the admin Payments queue groups them with a lump-sum total for e-transfer reconciliation - Cancellation never voids a scheduled payment (Payment::isScheduled()) - Schema: us_payments gains due_date, period_key, notice_batch; USC_VERSION 1.2.0 composer test, composer lint, composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
32619a1b75
|
Show booked lesson info on upcoming lists and add admin booking detail
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 3m12s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
Front end: the student "upcoming lessons" panel now shows each booked offering's name and length next to the time, and renders only the soonest five lessons with a "Show all" reveal. GET /bookings returns offering_title and duration_minutes so the list needs no extra request. Admin: the Scheduler and My Lessons week/list views now show the booked offering, and each lesson links to a detail view showing the policy versions the student accepted (with acceptance time and IP) and their intake answers. On My Lessons an instructor may only open their own lessons; the studio Scheduler may open any. composer test / composer lint / composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
8b90b8d78d
|
Add cancellation cutoff limiting how close to a lesson a student can cancel
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
Students can no longer cancel their own lesson online once it starts within a configured window; instructors and studio admins can always cancel. - Studio default `us_cancellation_cutoff_hours` (stored/computed in hours, entered and displayed in days under Studio Settings → Cancellations). - Optional per-offering override `cancellation_cutoff_hours` (entered in hours); blank inherits the studio default, 0 allows anytime cancellation. - `Booking\CancellationPolicy` resolves the effective window and decides; `BookingEndpoint::cancel()` returns a 403 `cancellation_closed` when too late. The instructor status endpoint and studio-admin student actions bypass it. Closes #93 Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
4c2cc31c34
|
Merge pull request 'Charge weekly reservations for every claimed occurrence and confirm the whole series' (#81) from fix/recurring-payment-amount into main
CI / Tests (PHP 8.1) (push) Successful in 38s
CI / Coding Standards (push) Successful in 2m47s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / PHPStan (push) Successful in 2m51s
CI / Build Plugin Zip (push) Successful in 2m43s
Reviewed-on: #81 |
||
|
|
ff059909a5
|
Charge weekly reservations for every claimed occurrence and confirm the whole series
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
A weekly booking on a per-lesson (one_time) priced offering was creating its single upfront payment for one week's price while reserving up to 12 weeks, and settling that payment confirmed only the anchor lesson, leaving the rest of the series pending forever. - BookingEndpoint now charges price x claimed occurrences for one_time billing; a full_term price is still charged once since it covers the term. - PaymentService::confirmRegistration resolves the anchor lesson's series and confirms every non-cancelled row via the new BookingRepository::updateStatusForSeries(). Closes #79 Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
14f43232c9
|
Default lessons to a week view on the booking page and in wp-admin
CI / Tests (PHP 8.2) (pull_request) Successful in 1m15s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m16s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 3m15s
CI / PHPStan (pull_request) Successful in 3m14s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped
The front-end booking calendar now opens in the Week view (anchored to the week of the earliest open slot) with List still available. The Scheduler and My Lessons admin pages gain a week calendar (usc_view/usc_week, bucketed via a new generic WeekCalendar::bucket()) and open in it by default; the original table remains as the List view since it carries the HST / e-transfer forms. Closes #76 Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
c7d5d72c9c
|
Require an offering on every lesson booking, with a student-facing picker
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m40s
CI / PHPStan (pull_request) Successful in 2m24s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / Build Plugin Zip (pull_request) Has been skipped
Generic slots (no tied offering) were bookable with no offering at all: free, instantly confirmed, and with no intake questions. POST /bookings now rejects offering-less bookings (400 offering_required), and a student-chosen offering must be an active private-lesson type owned by the slot's instructor whose duration matches the slot. The registration form gains a Lesson type field: locked to the slot's tied offering (title, duration, price) so the student sees what they are booking, or a required picker of fitting offerings for generic slots, with intake questions following the selection. Fixes #55 Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
ab90dae638
|
Let students cancel their own lessons from the booking page
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 46s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / PHPStan (push) Successful in 1m11s
CI / Tests (PHP 8.3) (push) Successful in 1m0s
CI / Build Plugin Zip (push) Successful in 1m10s
Adds POST /bookings/{id}/cancel (owner-only, idempotent): marks the lesson
cancelled, releases the availability slot for rebooking, and voids a
still-pending payment so it leaves the admin confirmation queue. Paid
payments are untouched — refunds stay a manual admin decision.
The instructor PATCH /bookings/{id}/status path now does the same slot
release and payment voiding on cancellation (previously cancelled lessons
left their slot permanently booked), and reinstating a cancelled lesson
re-claims the slot, rejecting with 409 if the freed time was rebooked.
The "Your upcoming lessons" panel gets a Cancel button with a confirm
prompt; on success both the lesson list and the slot calendar refresh.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
||
|
|
5888032ed7
|
Skip payment step for unpriced bookings, confirm them immediately, show students their lessons
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 47s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m35s
CI / Build Plugin Zip (pull_request) Has been skipped
Booking a slot with no priced offering created the lesson but no payment,
yet the front end still called POST /payments/intent, which 400ed with
"Could not start payment for this registration" — the student saw an error
while the backend held a claimed slot and a lesson stuck at pending.
- POST /bookings and POST /enrollments now return a `payment` summary
({id, method, status}) or null when nothing is owed; the JS only runs
the payment step when a payment exists.
- Bookings with nothing owed are confirmed at creation — there is no
payment step that would ever confirm them later.
- The booking page now shows the student's upcoming lessons (GET /bookings,
now scoped to upcoming non-cancelled lessons with slot start/end times)
with a pending-payment/confirmed status badge.
Fixes #53
Co-Authored-By: Claude Fable 5 <[email protected]>
|
||
|
|
9d89bc6d0e
|
Add link-target and auto-redirect options to booking/login blocks
CI / Coding Standards (pull_request) Successful in 51s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 1m24s
The booking block gains a loginPageId attribute choosing which page its logged-out "log in to book a lesson" link points to (default remains the WordPress login screen), and the student-login block gains a bookingPageId attribute controlling the logged-in "View available lessons" link and the post-login redirect target (default remains the current page). Both blocks also gain an autoRedirect toggle, off by default, that sends the visitor straight to the target page; block rendering starts after output, so the redirect runs on template_redirect by parsing the queried page's content for the block, with a self-target guard against redirect loops. The link targets are also available to the shortcodes as login_page_id/booking_page_id. Also fixes a pre-existing fatal: WordPress passes an empty string (not an array) to shortcode callbacks when a shortcode is used without attributes, so bare [us_booking] etc. threw a TypeError against the strictly-typed render(array $atts) methods. ShortcodeRegistrar now wraps each callback to normalize non-array attribute values. Closes #51 Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
6ff733a71f
|
Replace Slot ID column in lessons list with the lesson's date/time
CI / Tests (PHP 8.2) (pull_request) Successful in 1m20s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m21s
CI / Coding Standards (pull_request) Successful in 1m45s
CI / PHPStan (pull_request) Successful in 3m22s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Has been skipped
The admin dashboard and instructor My Lessons pages showed the raw availability-slot database ID, which is meaningless to admins and instructors. LessonController now takes AvailabilityRepository, looks up each lesson's slot, and renders its window as e.g. "Jul 6, 2026 9:00 AM-10:00 AM" via mysql2date. The date is repeated on the end time only when a slot crosses midnight, and lessons whose slot row no longer exists show an em dash. Closes #47 Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
1d6ac46ba3
|
Upgrade PHPStan to 2.x and raise analysis level from 6 to 10
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.3) (pull_request) Successful in 52s
CI / Coding Standards (pull_request) Successful in 57s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / PHPStan (pull_request) Successful in 1m11s
CI / Build Plugin Zip (pull_request) Has been skipped
- Bump phpstan/phpstan ^2.0 and szepeviktor/phpstan-wordpress ^2.0 - Move the analysis level into phpstan.neon (single source) and raise it to 10 - Add Val, a runtime coercion helper that narrows untyped WordPress boundary values (wpdb rows, REST params, superglobals, options) with explicit checks instead of blind casts, plus unit tests - Type value-object fromRow() params as stdClass (what wpdb returns) and map columns through Val so unexpected shapes degrade safely - Use %i identifier placeholders for table names in all wpdb::prepare() calls so every query string is a literal and identifiers are escaped by WordPress; raises the minimum WordPress version to 6.2 where %i was introduced - Guard wpdb::prepare() null result before wpdb::query() in updateTax() - Fix nullable get_permalink()/strtotime() handling, list types at REST and capability call sites, dead null-coalescing on checked superglobals, and narrow get_users() results before mapping - Register Val method names with the ValidatedSanitizedInput sniff so it validates the real sanitizer around each superglobal read - Update repository unit tests for the %i placeholder arguments Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
061d09e034
|
Harden booking, offering exposure, payments, and invites
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / Coding Standards (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 1m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m41s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Build Plugin Zip (pull_request) Has been skipped
Security fixes from a pen-test review (issues #31–#37): - #31 Booking no longer trusts a client-supplied offering_id: a slot-tied offering is authoritative and any offering used must belong to the slot's instructor, closing a free/misrouted-payment bypass. - #34 Availability slot creation rejects an offering the instructor does not own (AvailabilityEndpoint now takes OfferingRepository). - #32 Offering/question/policy listing endpoints now require book_lesson instead of being public (no anonymous consumer exists); Offering::toArray also omits etransfer_email from listings as defense-in-depth. - #33 Slots are claimed atomically (UPDATE ... WHERE is_booked = 0) before a lesson is inserted, preventing a double-booking race. - #35 A single weekly booking is capped (MAX_WEEKLY_OCCURRENCES) and only creates lessons for slots it actually claimed. - #36 Stripe secret/webhook keys are write-only in the settings UI and a blank submit keeps the stored value; secrets are never echoed back into HTML. - #37 Pending invites expire after 14 days (Invite::isAcceptable), enforced at registration and surfaced on the admin invites list. Adds BookingEndpointTest plus Invite/Offering/AvailabilityRepository coverage and minimal WP_REST_Request/WP_REST_Response stubs. composer test (200), lint, and cs all green. Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
8fb5ff8270
|
Add student administration view (studio-admin)
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Coding Standards (pull_request) Successful in 56s
CI / PHPStan (pull_request) Successful in 57s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.3) (pull_request) Successful in 48s
CI / Build Plugin Zip (pull_request) Has been skipped
Implements #22: a read-only Students area for studio admins. - StudentController (manage_students): a list of us_student users with upcoming-lesson and active-enrolment counts, each linking to a detail page showing account info, upcoming/past lessons (offering, instructor, status), and group-class enrolments. - StudentSchedule::partition() — pure, unit-tested upcoming/past split. - Repo counts: BookingRepository::countUpcomingForStudent and EnrollmentRepository::countActiveForStudent (single-query, tested). - Templates: templates/admin/students.php, student-detail.php. - Students admin menu wired in AdminMenu (no Plugin change — the repos were already available there). - Docs: README status flipped to implemented; feature spec updated. Payment history slots into the detail when Payments (#7) lands. Tests: StudentScheduleTest + the two repo count tests. composer test (127), cs, and PHPStan level 6 all pass. Refs #22 Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
6d163e5d0e
|
Add lesson booking registration flow (offering, questions, policies)
CI / Coding Standards (pull_request) Successful in 1m51s
CI / PHPStan (pull_request) Successful in 2m17s
CI / Tests (PHP 8.1) (pull_request) Successful in 2m24s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.3) (pull_request) Successful in 47s
CI / Build Plugin Zip (pull_request) Has been skipped
Implements #3: students register for a private lesson by picking a slot, answering the offering's intake questions, and accepting booking-scoped policies. Payment is a clean seam for #7 (lessons land pending; payment_id null; instructor confirms via PATCH /bookings/{id}/status). - Schema: us_lessons += offering_id, recurrence, series_id, payment_id. - Lesson: new fields + recurrence constants. - BookingRepository::insertSeries() builds a weekly series sharing a series_id; AvailabilityRepository::findUnbookedInGroup() reserves a group. - RegistrationGate (src/Registration/): validate + record intake answers and booking-scoped policy acceptances. Reused by group enrolment (#4). - BookingEndpoint::book(): offering_id, recurrence, answers, accepted_policy_version_ids; single or weekly; records answers/acceptances (type lesson). - GET /policies?scope=booking filter. - Front-end booking.js: slot -> questions + policies -> submit. - Wiring: RegistrationGate built in Plugin, passed via RestRegistrar. - Test-only WP_Error stub in tests/bootstrap.php for gate testing. Refs #3 Co-Authored-By: Claude Opus 4.8 <[email protected]> |
||
|
|
2fb2ca392d
|
Restructure src/ and tests/ from package-by-type to package-by-domain
CI / Coding Standards (push) Successful in 43s
CI / PHPStan (push) Successful in 52s
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 49s
CI / Tests (PHP 8.3) (push) Successful in 37s
CI / No Debug Code (push) Successful in 2s
All classes are now organised by domain (Availability, Booking, Auth). Each domain package contains its value object, repository, admin controller, REST endpoint, and any shortcode pages under a matching sub-namespace. Cross-cutting wiring (Plugin, AdminMenu, RestRegistrar, ShortcodeRegistrar, Schema) lives at src/ root. Tests mirror the domain structure. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> |