173 lines
5.8 KiB
PHP
173 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Service\Assignment;
|
|
|
|
use App\Entity\Destination;
|
|
use App\Entity\StaffingRule;
|
|
use App\Enum\StaffingFee;
|
|
use App\Enum\StaffingRole;
|
|
use App\Model\PlannedAssignment;
|
|
use App\Service\Assignment\AssignmentPlanner;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* One test per row of the planner table in .ddev/plans/teamer-auto-assignments-plan.md.
|
|
*/
|
|
class AssignmentPlannerTest extends TestCase
|
|
{
|
|
public function testGuidesOnlyIsOneAssignmentWithAPlacePerGuide(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 2));
|
|
|
|
$this->assertSame([[StaffingRole::GUIDE, 2, null, []]], self::summarize($planned));
|
|
}
|
|
|
|
public function testWithoutSplitEachLevelIsAKursleitungOfItsOwn(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 2, beginner: true, advanced: true));
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE, 2, null, []],
|
|
[StaffingRole::SKI_BEGINNER, 1, null, [StaffingFee::COURSE]],
|
|
[StaffingRole::SKI_ADVANCED, 1, null, [StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testSplitWithOneLevelKeepsTheRemainingGuides(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 2, advanced: true, split: true));
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE, 1, null, []],
|
|
[StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testSplitWithBothLevelsUsesUpTwoGuides(): void
|
|
{
|
|
// decision 3: two single-level combined profiles, never the A+F one
|
|
$planned = $this->plan(self::rule(guides: 2, beginner: true, advanced: true, split: true));
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::COURSE]],
|
|
[StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testGuidingIsAFeeOnEveryGuideTypeAssignment(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 3, beginner: true, advanced: true, split: true, guiding: true));
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE, 1, null, [StaffingFee::GUIDING]],
|
|
[StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::GUIDING, StaffingFee::COURSE]],
|
|
[StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [StaffingFee::GUIDING, StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testGuidingStaysOffThePlainKursleitung(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 1, beginner: true, guiding: true));
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE, 1, null, [StaffingFee::GUIDING]],
|
|
[StaffingRole::SKI_BEGINNER, 1, null, [StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testTheBusEscortTakesTheFirstBoardingPointInDepartureOrder(): void
|
|
{
|
|
// stored order is not departure order: Göttingen leaves before Kassel
|
|
$destination = self::destination([
|
|
['busProId' => 7, 'city' => 'Kassel', 'street' => null, 'time' => '00:30'],
|
|
['busProId' => 5, 'city' => 'Göttingen', 'street' => null, 'time' => '08.03.2024 22:00:00'],
|
|
]);
|
|
|
|
$planned = $this->plan(self::rule(guides: 2), $destination);
|
|
|
|
$this->assertSame(5, $planned[0]->pickup);
|
|
}
|
|
|
|
public function testTheBusEscortIsSetOnExactlyOneAssignment(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 3, beginner: true, split: true), self::busTravel());
|
|
|
|
$this->assertSame([
|
|
[StaffingRole::GUIDE, 2, 5, []],
|
|
[StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::COURSE]],
|
|
], self::summarize($planned));
|
|
}
|
|
|
|
public function testWithoutAPlainReiseleitungTheFirstCombinedOneEscortsTheBus(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 2, beginner: true, advanced: true, split: true), self::busTravel());
|
|
|
|
$this->assertSame(5, $planned[0]->pickup);
|
|
$this->assertNull($planned[1]->pickup);
|
|
}
|
|
|
|
public function testNoBusEscortWithoutBoardingPoints(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 1));
|
|
|
|
$this->assertNull($planned[0]->pickup);
|
|
}
|
|
|
|
public function testWithoutAGuideNobodyEscortsTheBus(): void
|
|
{
|
|
$planned = $this->plan(self::rule(guides: 0, beginner: true), self::busTravel());
|
|
|
|
$this->assertSame([[StaffingRole::SKI_BEGINNER, 1, null, [StaffingFee::COURSE]]], self::summarize($planned));
|
|
}
|
|
|
|
/**
|
|
* @return list<PlannedAssignment>
|
|
*/
|
|
private function plan(StaffingRule $rule, ?Destination $destination = null): array
|
|
{
|
|
return (new AssignmentPlanner())->plan($rule, $destination ?? self::destination([]));
|
|
}
|
|
|
|
private static function busTravel(): Destination
|
|
{
|
|
return self::destination([
|
|
['busProId' => 5, 'city' => 'Göttingen', 'street' => null, 'time' => '08.03.2024 22:00:00'],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param list<PlannedAssignment> $planned
|
|
*/
|
|
private static function summarize(array $planned): array
|
|
{
|
|
return array_map(
|
|
static fn (PlannedAssignment $p): array => [$p->role, $p->places, $p->pickup, $p->fees],
|
|
$planned,
|
|
);
|
|
}
|
|
|
|
private static function rule(
|
|
int $guides,
|
|
bool $beginner = false,
|
|
bool $advanced = false,
|
|
bool $split = false,
|
|
bool $guiding = false,
|
|
): StaffingRule {
|
|
return (new StaffingRule())
|
|
->setProductCode('DKIMP')
|
|
->setGuides($guides)
|
|
->setSkiBeginner($beginner)
|
|
->setSkiAdvanced($advanced)
|
|
->setSplit($split)
|
|
->setGuiding($guiding)
|
|
;
|
|
}
|
|
|
|
private static function destination(array $pickups): Destination
|
|
{
|
|
return (new Destination())->setPickups($pickups);
|
|
}
|
|
}
|