feat: generate draft assignments from a staffing matrix
This commit is contained in:
1 parent
3116d23199
commit
2fc9806c69
41 files changed
+2618
No files matched your search
@@ -0,0 +1,169 @@
|
||||
<?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, busEscort: true), $destination);
|
||||
|
||||
$this->assertSame(5, $planned[0]->pickup);
|
||||
}
|
||||
|
||||
public function testTheBusEscortIsSetOnExactlyOneAssignment(): void
|
||||
{
|
||||
$planned = $this->plan(self::rule(guides: 3, beginner: true, split: true, busEscort: true));
|
||||
|
||||
$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, busEscort: true));
|
||||
|
||||
$this->assertSame(5, $planned[0]->pickup);
|
||||
$this->assertNull($planned[1]->pickup);
|
||||
}
|
||||
|
||||
public function testNoBusEscortWithoutBoardingPoints(): void
|
||||
{
|
||||
$planned = $this->plan(self::rule(guides: 1, busEscort: true), self::destination([]));
|
||||
|
||||
$this->assertNull($planned[0]->pickup);
|
||||
}
|
||||
|
||||
public function testNoBusEscortWhenTheMatrixSaysNo(): void
|
||||
{
|
||||
$planned = $this->plan(self::rule(guides: 1));
|
||||
|
||||
$this->assertNull($planned[0]->pickup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<PlannedAssignment>
|
||||
*/
|
||||
private function plan(StaffingRule $rule, ?Destination $destination = null): array
|
||||
{
|
||||
return (new AssignmentPlanner())->plan($rule, $destination ?? 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,
|
||||
bool $busEscort = false,
|
||||
): StaffingRule {
|
||||
return (new StaffingRule())
|
||||
->setProductCode('DKIMP')
|
||||
->setGuides($guides)
|
||||
->setSkiBeginner($beginner)
|
||||
->setSkiAdvanced($advanced)
|
||||
->setSplit($split)
|
||||
->setGuiding($guiding)
|
||||
->setBusEscort($busEscort)
|
||||
;
|
||||
}
|
||||
|
||||
private static function destination(array $pickups): Destination
|
||||
{
|
||||
return (new Destination())->setPickups($pickups);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user