feat(services): add database storage for public holidays

This commit is contained in:
Ondrej Vlach 2024-08-07 12:55:57 +02:00
parent 24f36c16d0
commit 9b907cf25d
No known key found for this signature in database
GPG Key ID: 7F141CDACEDEE2DE
4 changed files with 216 additions and 0 deletions

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Services\WorkingDays\PublicHolidays;
use App\Models\NonWorkingDays;
/**
* Storage for storing public holidays in persistent storage.
*/
interface PublicHolidaysStateStorageInterface
{
/**
* Store a public holiday in the storage.
* @param \DateTimeImmutable $publicHolidayDate
* @return NonWorkingDays|null
*/
public function storePublicHoliday(\DateTimeImmutable $publicHolidayDate): ?NonWorkingDays;
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Services\WorkingDays\PublicHolidays\Storage;
use App\Models\Country;
use App\Models\NonWorkingDays;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Facades\Log;
class DatabaseStorage implements PublicHolidaysStorageInterface
{
public function isPublicHoliday(string $countryCode, \DateTimeImmutable $holidayDate): bool
{
$country = $this->getCountryByCountryCode($countryCode);
return NonWorkingDays::where('country_id', $country->id)
->where('non_working_date', $holidayDate->format('Y-m-d'))
->exists();
}
public function storePublicHoliday(string $countryCode, \DateTimeImmutable $holidayDate): ?NonWorkingDays
{
$country = $this->getCountryByCountryCode($countryCode);
try {
return NonWorkingDays::create([
'country_id' => $country->id,
'non_working_date' => $holidayDate->format('Y-m-d'),
]);
} catch (UniqueConstraintViolationException $e) {
// safe to ignore ... combination exists
Log::warning("Non-working day already exists for country: " . $country . ", date: {$holidayDate->format('Y-m-d')}");
}
return null;
}
public function getPublicHolidaysInInterval(string $countryCode, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): array
{
$country = $this->getCountryByCountryCode($countryCode);
return NonWorkingDays::where('country_id', $country->id)
->whereBetween('non_working_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
->get()->all();
}
public function countPublicHolidaysInInterval(string $countryCode, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int
{
$country = $this->getCountryByCountryCode($countryCode);
return NonWorkingDays::where('country_id', $country->id)
->whereBetween('non_working_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
->count();
}
protected function getCountryByCountryCode(string $countryCode): Country
{
return Country::where('country_code', $countryCode)->first() ?? throw new \InvalidArgumentException("Country code '$countryCode' does not exist.");
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Services\WorkingDays\PublicHolidays\Storage;
use App\Models\NonWorkingDays;
/**
* Interface for storing and retrieving public holidays independent on country.
*/
interface PublicHolidaysStorageInterface
{
/**
* Determine if a given public holiday is in a given country.
* @param string $countryCode
* @param \DateTimeImmutable $holidayDate
* @return bool
*/
public function isPublicHoliday(string $countryCode, \DateTimeImmutable $holidayDate): bool;
/**
* Returns all public holidays in a given country within a given interval.
* @param string $countryCode
* @param \DateTimeImmutable $startDate
* @param \DateTimeImmutable $endDate
* @return NonWorkingDays[]
*/
public function getPublicHolidaysInInterval(string $countryCode, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): array;
/**
* Counts all public holidays in a given country within a given interval.
* @param string $countryCode
* @param \DateTimeImmutable $startDate
* @param \DateTimeImmutable $endDate
* @return int
*/
public function countPublicHolidaysInInterval(string $countryCode, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int;
/**
* Stores a public holiday in the storage.
* @param string $countryCode
* @param \DateTimeImmutable $holidayDate
* @return NonWorkingDays|null
*/
public function storePublicHoliday(string $countryCode, \DateTimeImmutable $holidayDate): ?NonWorkingDays;
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\WorkingDays\PublicHolidays\Storage;
use App\Models\Country;
use App\Models\NonWorkingDays;
use App\Services\PublicHolidays\Storage\DatabaseStoreTrait;
use App\Services\PublicHolidays\Storage\NWDStore;
use App\Services\WorkingDays\PublicHolidays\Storage\DatabaseStorage;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class DatabaseStorageTest extends TestCase
{
use DatabaseTransactions;
public function testStoreWillStoreDate(): void
{
$country = Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$databaseStorage = new DatabaseStorage();
$item1 = $databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$item2 = $databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-02'));
$this->assertEquals(2, NonWorkingDays::where('country_id', $country->id)->count());
$this->assertEquals('2022-01-01', NonWorkingDays::where('id', $item1->id)->first()->non_working_date); // @phpstan-ignore property.nonObject
$this->assertEquals('2022-01-02', NonWorkingDays::where('id', $item2->id)->first()->non_working_date); // @phpstan-ignore property.nonObject
}
public function testStoreWillIgnoreDuplicateDates(): void
{
$country = Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$databaseStorage = new DatabaseStorage();
$databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$this->assertEquals(1, NonWorkingDays::where('country_id', $country->id)->count());
}
public function testStoreWillStoreDateIfUsedAnotherCountry(): void
{
$country = Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$country2 = Country::create(['country_code' => 'YY', 'name' => 'second Czech republic']);
$databaseStorage = new DatabaseStorage();
$item1 = $databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$item2 = $databaseStorage->storePublicHoliday('YY', new \DateTimeImmutable('2022-01-01'));
$this->assertEquals(1, NonWorkingDays::where('country_id', $country->id)->count());
$this->assertEquals(1, NonWorkingDays::where('country_id', $country2->id)->count());
$this->assertEquals('2022-01-01', NonWorkingDays::where('id', $item1->id)->first()->non_working_date); // @phpstan-ignore property.nonObject
$this->assertEquals('2022-01-01', NonWorkingDays::where('id', $item2->id)->first()->non_working_date); // @phpstan-ignore property.nonObject
}
public function testCountPublicHolidaysInIntervalWillReturnCorrectCount(): void
{
Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$databaseStorage = new DatabaseStorage();
$databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-02'));
$this->assertEquals(2, $databaseStorage->countPublicHolidaysInInterval('XX', new \DateTimeImmutable('2022-01-01'), new \DateTimeImmutable('2022-01-07')));
}
public function testCountPublicHolidaysInIntervalWillReturnCorrectPublicHolidays(): void
{
Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$databaseStorage = new DatabaseStorage();
$item1 = $databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$item2 = $databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-02'));
$holidays = $databaseStorage->getPublicHolidaysInInterval('XX', new \DateTimeImmutable('2022-01-01'), new \DateTimeImmutable('2022-01-07'));
$this->assertCount(2, $holidays);
$this->assertEquals($item1->id, $holidays[0]->id); // @phpstan-ignore property.nonObject
$this->assertEquals($item2->id, $holidays[1]->id); // @phpstan-ignore property.nonObject
}
public function testIsPublicHolidayWillReturnTrueIfDateIsPublicHoliday(): void
{
Country::create(['country_code' => 'XX', 'name' => 'Czech Republic']);
$databaseStorage = new DatabaseStorage();
$databaseStorage->storePublicHoliday('XX', new \DateTimeImmutable('2022-01-01'));
$this->assertTrue($databaseStorage->isPublicHoliday('XX', new \DateTimeImmutable('2022-01-01')));
$this->assertFalse($databaseStorage->isPublicHoliday('XX', new \DateTimeImmutable('2022-01-02')));
}
public function testStoreWillRaiseExceptionIfCountryCodeDoesNotExist(): void
{
$databaseStorage = new DatabaseStorage();
$this->expectException(\InvalidArgumentException::class);
$databaseStorage->storePublicHoliday('ZZ', new \DateTimeImmutable('2022-01-01'));
}
}