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]>
This commit is contained in:
2026-07-24 11:07:06 -03:00
co-authored by Claude Opus 4.8
parent 991ed2f5ad
commit 32619a1b75
12 changed files with 569 additions and 21 deletions
+95
View File
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Tests\Unit\Booking;
use Mockery;
use Unsupervised\Schedular\Booking\LessonDetail;
use Unsupervised\Schedular\Policy\AcceptanceRepository;
use Unsupervised\Schedular\Policy\Policy;
use Unsupervised\Schedular\Policy\PolicyAcceptance;
use Unsupervised\Schedular\Policy\PolicyRepository;
use Unsupervised\Schedular\Policy\PolicyVersion;
use Unsupervised\Schedular\Policy\PolicyVersionRepository;
use Unsupervised\Schedular\Registration\Answer;
use Unsupervised\Schedular\Registration\AnswerRepository;
use Unsupervised\Schedular\Registration\Question;
use Unsupervised\Schedular\Registration\QuestionRepository;
use Unsupervised\Schedular\Tests\Unit\TestCase;
class LessonDetailTest extends TestCase
{
private AnswerRepository&Mockery\MockInterface $answers;
private QuestionRepository&Mockery\MockInterface $questions;
private AcceptanceRepository&Mockery\MockInterface $acceptances;
private PolicyRepository&Mockery\MockInterface $policies;
private PolicyVersionRepository&Mockery\MockInterface $versions;
private LessonDetail $detail;
protected function setUp(): void
{
parent::setUp();
$this->answers = Mockery::mock(AnswerRepository::class);
$this->questions = Mockery::mock(QuestionRepository::class);
$this->acceptances = Mockery::mock(AcceptanceRepository::class);
$this->policies = Mockery::mock(PolicyRepository::class);
$this->versions = Mockery::mock(PolicyVersionRepository::class);
$this->detail = new LessonDetail(
$this->answers,
$this->questions,
$this->acceptances,
$this->policies,
$this->versions
);
}
public function testAnswersPairEachAnswerWithItsQuestionLabel(): void
{
$this->answers->shouldReceive('findByRegistration')->once()->with(Answer::REG_LESSON, 7)->andReturn([
new Answer(questionId: 2, registrationType: Answer::REG_LESSON, registrationId: 7, studentId: 5, answerValue: 'Beginner'),
new Answer(questionId: 9, registrationType: Answer::REG_LESSON, registrationId: 7, studentId: 5, answerValue: null),
]);
$this->questions->shouldReceive('findById')->with(2)->andReturn(new Question(offeringId: 1, label: 'Skill level', id: 2));
$this->questions->shouldReceive('findById')->with(9)->andReturn(null);
self::assertSame(
[
['question' => 'Skill level', 'answer' => 'Beginner'],
['question' => '#9', 'answer' => '—'],
],
$this->detail->answers(7)
);
}
public function testAcceptancesResolvePolicyTitleVersionAndAuditTrail(): void
{
$this->acceptances->shouldReceive('findByRegistration')->once()->with(PolicyAcceptance::REG_LESSON, 7)->andReturn([
new PolicyAcceptance(
policyVersionId: 4,
studentId: 5,
registrationType: PolicyAcceptance::REG_LESSON,
registrationId: 7,
ipAddress: '1.2.3.4',
acceptedAt: '2026-07-01 10:00:00'
),
]);
$this->versions->shouldReceive('findById')->with(4)->andReturn(new PolicyVersion(policyId: 3, versionNumber: 2, id: 4));
$this->policies->shouldReceive('findById')->with(3)->andReturn(new Policy(title: 'Cancellation', slug: 'cancellation', id: 3));
self::assertSame(
[
[
'policy' => 'Cancellation',
'version' => 'v2',
'accepted_at' => '2026-07-01 10:00:00',
'ip' => '1.2.3.4',
],
],
$this->detail->acceptances(7)
);
}
}