feat: store questionnaire responses per disposition
This commit is contained in:
1 parent
8070fda64d
commit
df6734c939
9 files changed
+521
-1
No files matched your search
@@ -8,6 +8,7 @@ use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\Teamer;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,23 @@ class AssignmentTest extends TestCase
|
||||
$this->assertSame($disposition, $assignment->getActiveDispositionByTeamer($teamer));
|
||||
}
|
||||
|
||||
/**
|
||||
* The "Busbegleitung ab" field stores 0 for "Keine Busbegleitung" as well as null.
|
||||
*/
|
||||
#[DataProvider('providePickups')]
|
||||
public function testHasBusEscort(int|string|null $pickup, bool $expected): void
|
||||
{
|
||||
$this->assertSame($expected, (new Assignment())->setPickup($pickup)->hasBusEscort());
|
||||
}
|
||||
|
||||
public static function providePickups(): iterable
|
||||
{
|
||||
yield 'none' => [null, false];
|
||||
yield 'zero' => [0, false];
|
||||
yield 'empty form value' => ['', false];
|
||||
yield 'pickup' => [4711, true];
|
||||
}
|
||||
|
||||
private function createDisposition(Assignment $assignment, Teamer $teamer): Disposition
|
||||
{
|
||||
$disposition = new Disposition(new Application($assignment, $teamer));
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\QuestionnaireResponse;
|
||||
use App\Entity\Teamer;
|
||||
use App\Enum\ConditionOperator;
|
||||
use App\Enum\QuestionnaireResponseStatus;
|
||||
use App\Enum\QuestionType;
|
||||
use App\Model\Questionnaire\Condition;
|
||||
use App\Model\Questionnaire\Definition;
|
||||
use App\Model\Questionnaire\Option;
|
||||
use App\Model\Questionnaire\Question;
|
||||
use App\Model\Questionnaire\Section;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class QuestionnaireResponseTest extends TestCase
|
||||
{
|
||||
public function testNewResponseIsADraftOfTheCurrentVersion(): void
|
||||
{
|
||||
$response = $this->response();
|
||||
|
||||
$this->assertSame('sample', $response->getQuestionnaire());
|
||||
$this->assertSame(1, $response->getVersion());
|
||||
$this->assertSame(QuestionnaireResponseStatus::DRAFT, $response->getStatus());
|
||||
$this->assertNull($response->getSubmittedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* The respondent switched "damage" back to "no" after writing a description; switching it to
|
||||
* "yes" again must bring the description back.
|
||||
*/
|
||||
public function testDraftKeepsAnswersToHiddenQuestions(): void
|
||||
{
|
||||
$response = $this->response()->saveDraft(['damage' => 'no', 'details' => 'Spiegel ab'], ['teamerName' => 'Kim']);
|
||||
|
||||
$this->assertSame(['damage' => 'no', 'details' => 'Spiegel ab'], $response->getAnswers());
|
||||
$this->assertSame(['teamerName' => 'Kim'], $response->getContext());
|
||||
$this->assertFalse($response->isSubmitted());
|
||||
}
|
||||
|
||||
public function testSubmitDropsAnswersToHiddenQuestions(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable('2026-03-01 12:00');
|
||||
$response = $this->response()->submit($this->definition(), ['damage' => 'no', 'details' => 'Spiegel ab'], [], $now);
|
||||
|
||||
$this->assertSame(['damage' => 'no'], $response->getAnswers());
|
||||
$this->assertTrue($response->isSubmitted());
|
||||
$this->assertSame($now, $response->getSubmittedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* A draft begun before the definition was bumped is submitted, and therefore rendered, under
|
||||
* the version it was completed in.
|
||||
*/
|
||||
public function testSubmitRecordsTheVersionAnsweredUnder(): void
|
||||
{
|
||||
$response = $this->response()->submit($this->definition(version: 2), ['damage' => 'no'], [], new \DateTimeImmutable());
|
||||
|
||||
$this->assertSame(2, $response->getVersion());
|
||||
}
|
||||
|
||||
public function testSubmittedResponseCannotBeChanged(): void
|
||||
{
|
||||
$response = $this->response()->submit($this->definition(), ['damage' => 'no'], [], new \DateTimeImmutable());
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
$response->saveDraft(['damage' => 'yes'], []);
|
||||
}
|
||||
|
||||
public function testResponseCannotBeSubmittedWithAnotherQuestionnaire(): void
|
||||
{
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
$this->response()->submit($this->definition(key: 'other'), [], [], new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
private function response(): QuestionnaireResponse
|
||||
{
|
||||
$disposition = new Disposition(new Application(new Assignment(), new Teamer()));
|
||||
|
||||
return QuestionnaireResponse::forDisposition($this->definition(), $disposition);
|
||||
}
|
||||
|
||||
private function definition(string $key = 'sample', int $version = 1): Definition
|
||||
{
|
||||
return new Definition($key, $version, 'Beispiel', null, [
|
||||
new Section('main', null, [
|
||||
new Question('damage', 'Schäden?', QuestionType::CHOICE, options: [new Option('yes', 'Ja'), new Option('no', 'Nein')]),
|
||||
new Question('details', 'Was ist passiert?', QuestionType::TEXTAREA, showIf: new Condition('damage', ConditionOperator::EQUALS, 'yes')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Questionnaire;
|
||||
|
||||
use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\Destination;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\JobProfile;
|
||||
use App\Entity\Teamer;
|
||||
use App\Service\Questionnaire\DispositionContext;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DispositionContextTest extends TestCase
|
||||
{
|
||||
public function testEscortDispositionIsSnapshotted(): void
|
||||
{
|
||||
$context = DispositionContext::build($this->disposition(pickup: 4711));
|
||||
|
||||
$this->assertSame([
|
||||
'teamerName' => 'Kim Muster',
|
||||
'jobProfileName' => 'Reiseleitung',
|
||||
'jobProfileCategory' => JobProfile::CATEGORY_GUIDE,
|
||||
'destinationProduct' => 'Davos Klosters - Schwendi',
|
||||
'hotel' => 'Schwendi',
|
||||
'hotelCode' => 'DKI',
|
||||
'dateFrom' => '2026-01-10',
|
||||
'dateTo' => '2026-01-17',
|
||||
'escortPickup' => ['busProId' => 4711, 'city' => 'Stuttgart', 'street' => 'Busbahnhof', 'time' => '21:30'],
|
||||
// the escort leaves the evening before arrival
|
||||
'escortPickupDate' => '2026-01-09',
|
||||
], $context);
|
||||
}
|
||||
|
||||
public function testDispositionWithoutEscortHasNoPickup(): void
|
||||
{
|
||||
$context = DispositionContext::build($this->disposition(pickup: 0));
|
||||
|
||||
$this->assertNull($context['escortPickup']);
|
||||
$this->assertNull($context['escortPickupDate']);
|
||||
}
|
||||
|
||||
private function disposition(int $pickup): Disposition
|
||||
{
|
||||
$destination = (new Destination())
|
||||
->setProduct('Davos Klosters - Schwendi')
|
||||
->setHotel('Schwendi')
|
||||
->setHotelCode('DKI')
|
||||
->setDateFrom(new \DateTimeImmutable('2026-01-10'))
|
||||
->setDateTo(new \DateTimeImmutable('2026-01-17'))
|
||||
->setPickups([
|
||||
['busProId' => 4711, 'city' => 'Stuttgart', 'street' => 'Busbahnhof', 'time' => '21:30'],
|
||||
['busProId' => 4712, 'city' => 'Ulm', 'street' => 'ZOB', 'time' => '22:45'],
|
||||
])
|
||||
;
|
||||
$assignment = (new Assignment())
|
||||
->setDestination($destination)
|
||||
->setJobProfile((new JobProfile())->setName('Reiseleitung')->setCategory(JobProfile::CATEGORY_GUIDE))
|
||||
->setPickup($pickup)
|
||||
;
|
||||
$teamer = (new Teamer())->setFirstName('Kim')->setLastName('Muster');
|
||||
|
||||
return new Disposition(new Application($assignment, $teamer));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user