feat(services): add database storage for public holidays
This commit is contained in:
		
							parent
							
								
									24f36c16d0
								
							
						
					
					
						commit
						9b907cf25d
					
				@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user