['minutes' => 285], '2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25'], ]; $this->assertSame($stored, WorkMonthDays::fromArray($stored)->toArray()); } public function testItSortsAndDropsEntriesItCannotRead(): void { $days = WorkMonthDays::fromArray([ '2026-03-05' => ['minutes' => 60], 'not-a-date' => ['minutes' => 120], '2026-03-01' => ['minutes' => 30, 'absence' => ''], '2026-03-09' => 'not an entry', ]); $this->assertSame(['2026-03-01', '2026-03-05'], array_keys($days->toArray())); $this->assertArrayNotHasKey('absence', $days->toArray()['2026-03-01'], 'An empty label is no label.'); } public function testAMissingMinutesKeyCountsAsZero(): void { $days = WorkMonthDays::fromArray(['2026-03-01' => ['absence' => 'Ferien']]); $this->assertSame(0, $days->getTotalMinutes()); $this->assertSame(1, $days->countAbsenceDays()); $this->assertSame(0, $days->countWorkedDays()); } public function testItSummarisesTheMonth(): void { $days = WorkMonthDays::fromArray([ '2026-03-01' => ['minutes' => 285], '2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25'], '2026-03-06' => ['minutes' => 600], ]); $this->assertSame(885, $days->getTotalMinutes()); $this->assertSame(2, $days->countWorkedDays()); $this->assertSame(1, $days->countAbsenceDays()); } public function testItBuildsFromATimeAccount(): void { $account = new TimeAccount('e-1', 2026, 3, 345, 9900, 'Operations', [ '2026-03-01' => new TimeAccountDay(new \DateTimeImmutable('2026-03-01'), 285, null, '09:00', '14:45', 60), '2026-03-05' => new TimeAccountDay(new \DateTimeImmutable('2026-03-05'), 60, 'Ferien'), ], []); $this->assertSame([ '2026-03-01' => ['minutes' => 285, 'break' => 60, 'start' => '09:00', 'end' => '14:45'], '2026-03-05' => ['minutes' => 60, 'absence' => 'Ferien'], ], WorkMonthDays::fromTimeAccount($account)->toArray()); } public function testASpanNeedsBothEnds(): void { $days = WorkMonthDays::fromArray([ '2026-03-01' => ['minutes' => 60, 'start' => '09:00'], '2026-03-02' => ['minutes' => 60, 'start' => '09:00', 'end' => 'noon'], '2026-03-03' => ['minutes' => 60, 'start' => '09:00', 'end' => '10:00'], ])->forCalendarMonth(2026, 3); $this->assertFalse($days[0]->hasSpan()); $this->assertFalse($days[1]->hasSpan()); $this->assertTrue($days[2]->hasSpan()); } public function testItTotalsTheBreaks(): void { $days = WorkMonthDays::fromArray([ '2026-03-01' => ['minutes' => 285, 'break' => 60], '2026-03-02' => ['minutes' => 405, 'break' => 150], '2026-03-03' => ['minutes' => 180], ]); $this->assertSame(210, $days->getTotalBreakMinutes()); $this->assertSame(0, $days->forCalendarMonth(2026, 3)[2]->getBreakMinutes()); } /** * The calendar, not the payload, decides how many rows a month has: days the SaaS never * reported still need a line in the table and in the PDF. */ public function testItFillsEveryCalendarDayOfALongMonth(): void { $days = WorkMonthDays::fromArray(['2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25']]); $calendar = $days->forCalendarMonth(2026, 3); $this->assertCount(31, $calendar); $this->assertSame('2026-03-01', $calendar[0]->getDate()->format('Y-m-d')); $this->assertSame('2026-03-31', $calendar[30]->getDate()->format('Y-m-d')); } public function testItFillsEveryCalendarDayOfAShortMonth(): void { $this->assertCount(28, WorkMonthDays::fromArray([])->forCalendarMonth(2026, 2)); $this->assertCount(29, WorkMonthDays::fromArray([])->forCalendarMonth(2024, 2)); } /** * "Not reported" and "reported as zero" are different things: a rest day is a real * booking of nothing, an unreported day is a gap. */ public function testItSeparatesUnreportedDaysFromZeroDays(): void { $calendar = WorkMonthDays::fromArray([ '2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25'], ])->forCalendarMonth(2026, 3); $restDay = $calendar[4]; $this->assertSame('2026-03-05', $restDay->getDate()->format('Y-m-d')); $this->assertTrue($restDay->isReported()); $this->assertSame(0, $restDay->getMinutes()); $this->assertSame('Ruhetag 24/25', $restDay->getAbsence()); $gap = $calendar[0]; $this->assertFalse($gap->isReported()); $this->assertNull($gap->getMinutes()); $this->assertFalse($gap->isAbsence()); } public function testItKnowsWeekends(): void { $calendar = WorkMonthDays::fromArray([])->forCalendarMonth(2026, 3); $this->assertTrue($calendar[0]->isWeekend(), '1 March 2026 is a Sunday.'); $this->assertFalse($calendar[1]->isWeekend()); } }