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, splitAdvanced: 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, splitBeginner: true, splitAdvanced: true)); $this->assertSame([ [StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::COURSE]], [StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [StaffingFee::COURSE]], ], self::summarize($planned)); } public function testOneGuideTakesOverOneLevelAndTheOtherStaysAKursleitung(): void { $planned = $this->plan(self::rule(guides: 1, beginner: true, advanced: true, splitAdvanced: true, guiding: true), self::busTravel()); $this->assertSame([ [StaffingRole::GUIDE_SKI_ADVANCED, 1, 5, [StaffingFee::GUIDING, StaffingFee::COURSE]], [StaffingRole::SKI_BEGINNER, 1, null, [StaffingFee::COURSE]], ], self::summarize($planned)); } public function testATakeOverOfALevelThatIsOffIsIgnored(): void { $planned = $this->plan(self::rule(guides: 1, advanced: true, splitBeginner: true)); $this->assertSame([ [StaffingRole::GUIDE, 1, null, []], [StaffingRole::SKI_ADVANCED, 1, null, [StaffingFee::COURSE]], ], self::summarize($planned)); } public function testGuidingIsAFeeOnTheFirstGuideTypeAssignmentOnly(): void { $planned = $this->plan(self::rule(guides: 3, beginner: true, advanced: true, splitBeginner: true, splitAdvanced: true, guiding: true)); $this->assertSame([ [StaffingRole::GUIDE, 1, null, [StaffingFee::GUIDING]], [StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::COURSE]], [StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [StaffingFee::COURSE]], ], self::summarize($planned)); } public function testGuidingSplitsOffOnePlaceWhichAlsoEscortsTheBus(): void { $planned = $this->plan(self::rule(guides: 3, guiding: true), self::busTravel()); $this->assertSame([ [StaffingRole::GUIDE, 1, 5, [StaffingFee::GUIDING]], [StaffingRole::GUIDE, 2, null, []], ], self::summarize($planned)); } public function testWithoutAPlainReiseleitungTheFirstCombinedOneGuides(): void { $planned = $this->plan(self::rule(guides: 2, beginner: true, advanced: true, splitBeginner: true, splitAdvanced: true, guiding: true)); $this->assertSame([ [StaffingRole::GUIDE_SKI_BEGINNER, 1, null, [StaffingFee::GUIDING, StaffingFee::COURSE]], [StaffingRole::GUIDE_SKI_ADVANCED, 1, null, [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, splitBeginner: 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, splitBeginner: true, splitAdvanced: 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 */ 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 $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 $splitBeginner = false, bool $splitAdvanced = false, bool $guiding = false, ): StaffingRule { return (new StaffingRule()) ->setProductCode('DKIMP') ->setGuides($guides) ->setSkiBeginner($beginner) ->setSkiAdvanced($advanced) ->setSplitBeginner($splitBeginner) ->setSplitAdvanced($splitAdvanced) ->setGuiding($guiding) ; } private static function destination(array $pickups): Destination { return (new Destination())->setPickups($pickups); } }