From 29d81030ac2fe0e3a0f4503340495033761403da Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Mon, 24 Jan 2022 14:03:39 +0100 Subject: [PATCH] #28 - holidays management --- app/Helpers/PolishHolidaysRetriever.php | 34 ++++ app/Http/Controllers/HolidayController.php | 65 +++++++ app/Http/Requests/HolidayRequest.php | 32 ++++ .../Resources/HolidayFormDataResource.php | 21 +++ app/Http/Resources/HolidayResource.php | 22 +++ app/Models/Holiday.php | 32 ++++ app/Models/YearPeriod.php | 19 +- app/Observers/YearPeriodObserver.php | 20 +++ app/Providers/AppServiceProvider.php | 6 +- app/Rules/YearPeriodExists.php | 24 +++ composer.json | 1 + composer.lock | 75 +++++++- database/factories/HolidayFactory.php | 21 +++ ...022_01_20_140544_create_holidays_table.php | 26 +++ database/seeders/DatabaseSeeder.php | 11 ++ resources/js/Pages/Holidays/Create.vue | 106 +++++++++++ resources/js/Pages/Holidays/Edit.vue | 112 ++++++++++++ resources/js/Pages/Holidays/Index.vue | 167 ++++++++++++++++++ resources/js/Shared/MainMenu.vue | 2 +- routes/web.php | 5 +- 20 files changed, 792 insertions(+), 9 deletions(-) create mode 100644 app/Helpers/PolishHolidaysRetriever.php create mode 100644 app/Http/Controllers/HolidayController.php create mode 100644 app/Http/Requests/HolidayRequest.php create mode 100644 app/Http/Resources/HolidayFormDataResource.php create mode 100644 app/Http/Resources/HolidayResource.php create mode 100644 app/Models/Holiday.php create mode 100644 app/Rules/YearPeriodExists.php create mode 100644 database/factories/HolidayFactory.php create mode 100644 database/migrations/2022_01_20_140544_create_holidays_table.php create mode 100644 resources/js/Pages/Holidays/Create.vue create mode 100644 resources/js/Pages/Holidays/Edit.vue create mode 100644 resources/js/Pages/Holidays/Index.vue diff --git a/app/Helpers/PolishHolidaysRetriever.php b/app/Helpers/PolishHolidaysRetriever.php new file mode 100644 index 0000000..cc80633 --- /dev/null +++ b/app/Helpers/PolishHolidaysRetriever.php @@ -0,0 +1,34 @@ +year); + + $holidays = $polishProvider->getHolidays(); + + return $this->prepareHolidays($holidays); + } + + protected function prepareHolidays(array $holidays): Collection + { + return collect($holidays)->map(fn(Holiday $holiday) => [ + "name" => $holiday->getName([static::LANG_KEY]), + "date" => Carbon::createFromTimestamp($holiday->getTimestamp()), + ])->values(); + } +} diff --git a/app/Http/Controllers/HolidayController.php b/app/Http/Controllers/HolidayController.php new file mode 100644 index 0000000..d82190b --- /dev/null +++ b/app/Http/Controllers/HolidayController.php @@ -0,0 +1,65 @@ +orderBy("date") + ->get(); + + return inertia("Holidays/Index", [ + "holidays" => HolidayResource::collection($holidays), + ]); + } + + public function create(): Response + { + return inertia("Holidays/Create"); + } + + public function store(HolidayRequest $request): RedirectResponse + { + Holiday::query()->create($request->data()); + + return redirect() + ->route("holidays.index") + ->with("success", __("Holiday has been created")); + } + + public function edit(Holiday $holiday): Response + { + return inertia("Holidays/Edit", [ + "holiday" => new HolidayFormDataResource($holiday), + ]); + } + + public function update(HolidayRequest $request, Holiday $holiday): RedirectResponse + { + $holiday->update($request->data()); + + return redirect() + ->route("holidays.index") + ->with("success", __("Holiday has been updated")); + } + + public function destroy(Holiday $holiday): RedirectResponse + { + $holiday->delete(); + + return redirect() + ->route("holidays.index") + ->with("success", __("Holiday has been deleted")); + } +} diff --git a/app/Http/Requests/HolidayRequest.php b/app/Http/Requests/HolidayRequest.php new file mode 100644 index 0000000..4d04dfe --- /dev/null +++ b/app/Http/Requests/HolidayRequest.php @@ -0,0 +1,32 @@ + ["required", "min:3", "max:150"], + "date" => ["required", "date", new YearPeriodExists()], + ]; + } + + public function data(): array + { + $date = $this->get("date"); + + return [ + "name" => $this->get("name"), + "date" => $date, + "year_period_id" => YearPeriod::findByYear(Carbon::create($date)->year)->id, + ]; + } +} diff --git a/app/Http/Resources/HolidayFormDataResource.php b/app/Http/Resources/HolidayFormDataResource.php new file mode 100644 index 0000000..dc99444 --- /dev/null +++ b/app/Http/Resources/HolidayFormDataResource.php @@ -0,0 +1,21 @@ + $this->id, + "name" => $this->name, + "date" => $this->date->toDateString(), + ]; + } +} diff --git a/app/Http/Resources/HolidayResource.php b/app/Http/Resources/HolidayResource.php new file mode 100644 index 0000000..3852f2a --- /dev/null +++ b/app/Http/Resources/HolidayResource.php @@ -0,0 +1,22 @@ + $this->id, + "name" => $this->name, + "displayDate" => $this->date->toDisplayString(), + "dayOfWeek" => $this->date->dayName, + ]; + } +} diff --git a/app/Models/Holiday.php b/app/Models/Holiday.php new file mode 100644 index 0000000..f9df73e --- /dev/null +++ b/app/Models/Holiday.php @@ -0,0 +1,32 @@ + "date", + ]; + + public function yearPeriod(): BelongsTo + { + return $this->belongsTo(YearPeriod::class); + } +} diff --git a/app/Models/YearPeriod.php b/app/Models/YearPeriod.php index 569ad03..f58c408 100644 --- a/app/Models/YearPeriod.php +++ b/app/Models/YearPeriod.php @@ -4,29 +4,33 @@ declare(strict_types=1); namespace Toby\Models; -use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; /** * @property int $id * @property int $year * @property Collection $vacationLimits + * @property Collection $holidays */ class YearPeriod extends Model { use HasFactory; - protected $fillable = [ - "year", - ]; + protected $guarded = []; public static function current(): ?static + { + static::findByYear(Carbon::now()->year); + } + + public static function findByYear(int $year): ?static { /** @var YearPeriod $year */ - $year = static::query()->where("year", Carbon::now()->year)->first(); + $year = static::query()->where("year", $year)->first(); return $year; } @@ -35,4 +39,9 @@ class YearPeriod extends Model { return $this->hasMany(VacationLimit::class); } + + public function holidays(): HasMany + { + return $this->hasMany(Holiday::class); + } } diff --git a/app/Observers/YearPeriodObserver.php b/app/Observers/YearPeriodObserver.php index 71caa82..cd5beec 100644 --- a/app/Observers/YearPeriodObserver.php +++ b/app/Observers/YearPeriodObserver.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Toby\Observers; +use Toby\Helpers\PolishHolidaysRetriever; use Toby\Helpers\UserAvatarGenerator; use Toby\Models\User; use Toby\Models\YearPeriod; @@ -12,10 +13,17 @@ class YearPeriodObserver { public function __construct( protected UserAvatarGenerator $generator, + protected PolishHolidaysRetriever $polishHolidaysRetriever, ) { } public function created(YearPeriod $yearPeriod): void + { + $this->createVacationLimitsFor($yearPeriod); + $this->createHolidaysFor($yearPeriod); + } + + protected function createVacationLimitsFor(YearPeriod $yearPeriod): void { $users = User::all(); @@ -25,4 +33,16 @@ class YearPeriodObserver ]); } } + + protected function createHolidaysFor(YearPeriod $yearPeriod): void + { + $holidays = $this->polishHolidaysRetriever->getForYearPeriod($yearPeriod); + + foreach ($holidays as $holiday) { + $yearPeriod->holidays()->create([ + "name" => $holiday["name"], + "date" => $holiday["date"], + ]); + } + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cf3fe47..b341244 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,6 +6,7 @@ namespace Toby\Providers; use Illuminate\Support\Carbon; use Illuminate\Support\ServiceProvider; +use Toby\Models\Holiday; use Toby\Models\VacationLimit; use Toby\Scopes\SelectedYearPeriodScope; @@ -15,6 +16,9 @@ class AppServiceProvider extends ServiceProvider { Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y")); - VacationLimit::addGlobalScope($this->app->make(SelectedYearPeriodScope::class)); + $selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class); + + VacationLimit::addGlobalScope($selectedYearPeriodScope); + Holiday::addGlobalScope($selectedYearPeriodScope); } } diff --git a/app/Rules/YearPeriodExists.php b/app/Rules/YearPeriodExists.php new file mode 100644 index 0000000..29ee43c --- /dev/null +++ b/app/Rules/YearPeriodExists.php @@ -0,0 +1,24 @@ +year); + + return $yearPeriod !== null; + } + + public function message(): string + { + return "The year period for given year doesn't exist"; + } +} diff --git a/composer.json b/composer.json index 3d13d3f..b464c21 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "require": { "php": "^8.1", "ext-pdo": "*", + "azuyalabs/yasumi": "^2.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", "inertiajs/inertia-laravel": "^0.5.1", diff --git a/composer.lock b/composer.lock index bc06f33..7984d2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3412dd2a403927b829237ae4db36351a", + "content-hash": "e3c6ffae4c01db02d0471c52d2370b79", "packages": [ { "name": "asm89/stack-cors", @@ -62,6 +62,79 @@ }, "time": "2022-01-18T09:12:03+00:00" }, + { + "name": "azuyalabs/yasumi", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/azuyalabs/yasumi.git", + "reference": "083a0d0579fee17e68d688d463bc01098ac2691f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/083a0d0579fee17e68d688d463bc01098ac2691f", + "reference": "083a0d0579fee17e68d688d463bc01098ac2691f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.17 | ^0.22", + "mikey179/vfsstream": "^1.6", + "phan/phan": "^4.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^8.5 | ^9.4", + "vimeo/psalm": "^4" + }, + "suggest": { + "ext-calendar": "For calculating the date of Easter" + }, + "type": "library", + "autoload": { + "psr-4": { + "Yasumi\\": "src/Yasumi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sacha Telgenhof", + "email": "me@sachatelgenhof.com", + "role": "Maintainer" + } + ], + "description": "The easy PHP Library for calculating holidays.", + "homepage": "https://www.yasumi.dev", + "keywords": [ + "Bank", + "calculation", + "calendar", + "celebration", + "date", + "holiday", + "holidays", + "national", + "time" + ], + "support": { + "docs": "https://www.yasumi.dev", + "issues": "https://github.com/azuyalabs/yasumi/issues", + "source": "https://github.com/azuyalabs/yasumi" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/sachatelgenhof", + "type": "other" + } + ], + "time": "2021-05-09T09:03:34+00:00" + }, { "name": "brick/math", "version": "0.9.3", diff --git a/database/factories/HolidayFactory.php b/database/factories/HolidayFactory.php new file mode 100644 index 0000000..0538626 --- /dev/null +++ b/database/factories/HolidayFactory.php @@ -0,0 +1,21 @@ + $this->faker->word, + "date" => $this->faker->dateTimeBetween($now->startOfYear(), $now->endOfYear()), + ]; + } +} diff --git a/database/migrations/2022_01_20_140544_create_holidays_table.php b/database/migrations/2022_01_20_140544_create_holidays_table.php new file mode 100644 index 0000000..941418c --- /dev/null +++ b/database/migrations/2022_01_20_140544_create_holidays_table.php @@ -0,0 +1,26 @@ +id(); + $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); + $table->string("name"); + $table->date("date")->unique(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists("holidays"); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d6a4f27..e3f9f68 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -7,6 +7,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Toby\Helpers\PolishHolidaysRetriever; use Toby\Helpers\UserAvatarGenerator; use Toby\Models\User; use Toby\Models\VacationLimit; @@ -54,6 +55,16 @@ class DatabaseSeeder extends Seeder ->create(); } }) + ->afterCreating(function (YearPeriod $yearPeriod): void { + $polishHolidaysRetriever = new PolishHolidaysRetriever(); + + foreach ($polishHolidaysRetriever->getForYearPeriod($yearPeriod) as $holiday) { + $yearPeriod->holidays()->create([ + "name" => $holiday["name"], + "date" => $holiday["date"], + ]); + } + }) ->create(); } diff --git a/resources/js/Pages/Holidays/Create.vue b/resources/js/Pages/Holidays/Create.vue new file mode 100644 index 0000000..322eae6 --- /dev/null +++ b/resources/js/Pages/Holidays/Create.vue @@ -0,0 +1,106 @@ + + + diff --git a/resources/js/Pages/Holidays/Edit.vue b/resources/js/Pages/Holidays/Edit.vue new file mode 100644 index 0000000..be4b095 --- /dev/null +++ b/resources/js/Pages/Holidays/Edit.vue @@ -0,0 +1,112 @@ + + + diff --git a/resources/js/Pages/Holidays/Index.vue b/resources/js/Pages/Holidays/Index.vue new file mode 100644 index 0000000..3999192 --- /dev/null +++ b/resources/js/Pages/Holidays/Index.vue @@ -0,0 +1,167 @@ + + + diff --git a/resources/js/Shared/MainMenu.vue b/resources/js/Shared/MainMenu.vue index 4966645..4b2c8a0 100644 --- a/resources/js/Shared/MainMenu.vue +++ b/resources/js/Shared/MainMenu.vue @@ -322,7 +322,7 @@ export default { {name: 'Strona główna', href: '/', current: true}, {name: 'Użytkownicy', href: '/users', current: false}, {name: 'Dostępne urlopy', href: '/vacation-limits', current: false}, - {name: 'Company Directory', href: '#', current: false}, + {name: 'Dni wolne', href: '/holidays', current: false}, {name: 'Openings', href: '#', current: false}, ]; const userNavigation = [ diff --git a/routes/web.php b/routes/web.php index acd4d43..b6711bc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ declare(strict_types=1); use Illuminate\Support\Facades\Route; use Toby\Http\Controllers\GoogleController; +use Toby\Http\Controllers\HolidayController; use Toby\Http\Controllers\LogoutController; use Toby\Http\Controllers\SelectYearPeriodController; use Toby\Http\Controllers\UserController; @@ -14,7 +15,9 @@ Route::middleware("auth")->group(function (): void { Route::post("/logout", LogoutController::class); Route::resource("users", UserController::class); - Route::post("users/{user}/restore", [UserController::class, "restore"])->withTrashed(); + Route::post("/users/{user}/restore", [UserController::class, "restore"])->withTrashed(); + + Route::resource("holidays", HolidayController::class); Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits"); Route::put("/vacation-limits", [VacationLimitController::class, "update"]);