Show a series' policy acceptances on every occurrence
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Coding Standards (pull_request) Successful in 2m59s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Coding Standards (pull_request) Successful in 2m59s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m43s
CI / Build Plugin Zip (pull_request) Skipped
A weekly booking reserves a series of lessons, but the student answers the intake and ticks the policy boxes once — so BookingEndpoint records both against the anchor lesson alone. The admin detail view looked them up by whichever lesson id was being viewed, so every occurrence after the first showed no answers and no acceptances at all. LessonDetail now takes the Lesson rather than a bare id and resolves the registration to `series_id ?? id`, so each occurrence reads the anchor's records. This is the same seam PaymentService already uses to find a series lesson's payment on the anchor. Nothing was ever missing from the database, so existing bookings read correctly with no migration and no schema change. Closes #167 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -233,10 +233,12 @@ class LessonControllerTest extends TestCase
|
||||
$this->bookings->shouldReceive('findById')->once()->with(1)->andReturn($lesson);
|
||||
$this->availability->shouldReceive('findById')->once()->with(10)->andReturn($slot);
|
||||
$this->offerings->shouldReceive('findById')->once()->with(8)->andReturn($offering);
|
||||
$this->detail->shouldReceive('answers')->once()->with(1)->andReturn([
|
||||
// The lesson itself is handed over, so the presenter can follow a series
|
||||
// occurrence back to the anchor its answers and acceptances hang off.
|
||||
$this->detail->shouldReceive('answers')->once()->with($lesson)->andReturn([
|
||||
['question' => 'Skill level', 'answer' => 'Beginner'],
|
||||
]);
|
||||
$this->detail->shouldReceive('acceptances')->once()->with(1)->andReturn([
|
||||
$this->detail->shouldReceive('acceptances')->once()->with($lesson)->andReturn([
|
||||
['policy' => 'Cancellation', 'version' => 'v2', 'accepted_at' => '2026-07-01 10:00:00', 'ip' => '1.2.3.4'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
namespace Unsupervised\Schedular\Tests\Unit\Booking;
|
||||
|
||||
use Mockery;
|
||||
use Unsupervised\Schedular\Booking\Lesson;
|
||||
use Unsupervised\Schedular\Booking\LessonDetail;
|
||||
use Unsupervised\Schedular\Policy\AcceptanceRepository;
|
||||
use Unsupervised\Schedular\Policy\Policy;
|
||||
@@ -60,7 +61,7 @@ class LessonDetailTest extends TestCase
|
||||
['question' => 'Skill level', 'answer' => 'Beginner'],
|
||||
['question' => '#9', 'answer' => '—'],
|
||||
],
|
||||
$this->detail->answers(7)
|
||||
$this->detail->answers($this->lesson(7))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +90,59 @@ class LessonDetailTest extends TestCase
|
||||
'ip' => '1.2.3.4',
|
||||
],
|
||||
],
|
||||
$this->detail->acceptances(7)
|
||||
$this->detail->acceptances($this->lesson(7))
|
||||
);
|
||||
}
|
||||
|
||||
public function testSeriesOccurrenceReadsTheAnchorsAnswersAndAcceptances(): void
|
||||
{
|
||||
// Occurrence #12 of a weekly reservation anchored on lesson 7: the intake
|
||||
// and the agreement were recorded once, against the anchor.
|
||||
$occurrence = $this->lesson(12, seriesId: 7);
|
||||
|
||||
$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'),
|
||||
]);
|
||||
$this->questions->shouldReceive('findById')->with(2)->andReturn(new Question(offeringId: 1, label: 'Skill level', id: 2));
|
||||
|
||||
$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(
|
||||
[['question' => 'Skill level', 'answer' => 'Beginner']],
|
||||
$this->detail->answers($occurrence)
|
||||
);
|
||||
self::assertSame(
|
||||
[[
|
||||
'policy' => 'Cancellation',
|
||||
'version' => 'v2',
|
||||
'accepted_at' => '2026-07-01 10:00:00',
|
||||
'ip' => '1.2.3.4',
|
||||
]],
|
||||
$this->detail->acceptances($occurrence)
|
||||
);
|
||||
}
|
||||
|
||||
private function lesson(int $id, ?int $seriesId = null): Lesson
|
||||
{
|
||||
return new Lesson(
|
||||
slotId: 1,
|
||||
studentId: 5,
|
||||
instructorId: 9,
|
||||
offeringId: 1,
|
||||
recurrence: null === $seriesId ? Lesson::RECURRENCE_SINGLE : Lesson::RECURRENCE_WEEKLY,
|
||||
seriesId: $seriesId,
|
||||
id: $id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user