diff --git a/app/Domain/VacationDaysCalculator.php b/app/Domain/VacationDaysCalculator.php new file mode 100644 index 0000000..fcbc9e8 --- /dev/null +++ b/app/Domain/VacationDaysCalculator.php @@ -0,0 +1,42 @@ +holidays()->pluck("date"); + + $validDays = collect(); + + foreach ($period as $day) { + if ($this->passes($day, $holidays)) { + $validDays->add($day); + } + } + + return $validDays; + } + + protected function passes(CarbonInterface $day, Collection $holidays): bool + { + if ($day->isWeekend()) { + return false; + } + + if ($holidays->contains($day)) { + return false; + } + + return true; + } +} diff --git a/app/Domain/Validation/Rules/ApprovedVacationDaysInSameRange.php b/app/Domain/Validation/Rules/ApprovedVacationDaysInSameRange.php deleted file mode 100644 index eb39bf4..0000000 --- a/app/Domain/Validation/Rules/ApprovedVacationDaysInSameRange.php +++ /dev/null @@ -1,16 +0,0 @@ -vacationDaysCalculator + ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) + ->isNotEmpty(); + } + + public function errorMessage(): string + { + return __("Vacation needs minimum one day."); } } diff --git a/app/Domain/Validation/Rules/NoApprovedVacationRequestsInRange.php b/app/Domain/Validation/Rules/NoApprovedVacationRequestsInRange.php new file mode 100644 index 0000000..d06fd95 --- /dev/null +++ b/app/Domain/Validation/Rules/NoApprovedVacationRequestsInRange.php @@ -0,0 +1,26 @@ +user + ->vacationRequests() + ->overlapsWith($vacationRequest) + ->states(VacationRequestState::successStates()) + ->exists(); + } + + public function errorMessage(): string + { + return __("You have approved vacation request in this range."); + } +} diff --git a/app/Domain/Validation/Rules/NoPendingVacationRequestInRange.php b/app/Domain/Validation/Rules/NoPendingVacationRequestInRange.php new file mode 100644 index 0000000..3031b56 --- /dev/null +++ b/app/Domain/Validation/Rules/NoPendingVacationRequestInRange.php @@ -0,0 +1,26 @@ +user + ->vacationRequests() + ->overlapsWith($vacationRequest) + ->states(VacationRequestState::pendingStates()) + ->exists(); + } + + public function errorMessage(): string + { + return __("You have pending vacation request in this range."); + } +} diff --git a/app/Domain/Validation/Rules/PendingVacationRequestInSameRange.php b/app/Domain/Validation/Rules/PendingVacationRequestInSameRange.php deleted file mode 100644 index a0081b6..0000000 --- a/app/Domain/Validation/Rules/PendingVacationRequestInSameRange.php +++ /dev/null @@ -1,16 +0,0 @@ -from->isSameYear($vacationRequest->to); + } + + public function errorMessage(): string + { + return __("The vacation request cannot be created at the turn of the year."); + } +} diff --git a/app/Domain/Validation/Rules/VacationRequestRule.php b/app/Domain/Validation/Rules/VacationRequestRule.php index e50c92b..07af8d2 100644 --- a/app/Domain/Validation/Rules/VacationRequestRule.php +++ b/app/Domain/Validation/Rules/VacationRequestRule.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace Toby\Domain\Validation\Rules; -use Closure; use Toby\Eloquent\Models\VacationRequest; interface VacationRequestRule { - public function check(VacationRequest $vacationRequest, Closure $next); + public function check(VacationRequest $vacationRequest): bool; + public function errorMessage(): string; } diff --git a/app/Domain/Validation/VacationRequestValidator.php b/app/Domain/Validation/VacationRequestValidator.php index f3cfa23..2bc39e6 100644 --- a/app/Domain/Validation/VacationRequestValidator.php +++ b/app/Domain/Validation/VacationRequestValidator.php @@ -4,32 +4,49 @@ declare(strict_types=1); namespace Toby\Domain\Validation; -use Illuminate\Pipeline\Pipeline; -use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange; +use Illuminate\Contracts\Container\Container; +use Illuminate\Validation\ValidationException; use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule; use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule; -use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange; +use Toby\Domain\Validation\Rules\NoApprovedVacationRequestsInRange; +use Toby\Domain\Validation\Rules\NoPendingVacationRequestInRange; +use Toby\Domain\Validation\Rules\VacationRangeIsInTheSameYearRule; +use Toby\Domain\Validation\Rules\VacationRequestRule; use Toby\Eloquent\Models\VacationRequest; class VacationRequestValidator { protected array $rules = [ + VacationRangeIsInTheSameYearRule::class, MinimumOneVacationDayRule::class, DoesNotExceedLimitRule::class, - PendingVacationRequestInSameRange::class, - ApprovedVacationDaysInSameRange::class, + NoPendingVacationRequestInRange::class, + NoApprovedVacationRequestsInRange::class, ]; public function __construct( - protected Pipeline $pipeline, + protected Container $container, ) { } + /** + * @throws ValidationException + */ public function validate(VacationRequest $vacationRequest): void { - $this->pipeline - ->send($vacationRequest) - ->through($this->rules) - ->via("check"); + foreach ($this->rules as $rule) { + $rule = $this->makeRule($rule); + + if (!$rule->check($vacationRequest)) { + throw ValidationException::withMessages([ + "vacationRequest" => $rule->errorMessage(), + ]); + } + } + } + + protected function makeRule(string $class): VacationRequestRule + { + return $this->container->make($class); } } diff --git a/app/Eloquent/Models/VacationRequest.php b/app/Eloquent/Models/VacationRequest.php index 9c7ada0..0a81620 100644 --- a/app/Eloquent/Models/VacationRequest.php +++ b/app/Eloquent/Models/VacationRequest.php @@ -21,6 +21,7 @@ use Toby\Domain\Enums\VacationType; * @property VacationRequestState $state * @property Carbon $from * @property Carbon $to + * @property int $estimated_days * @property string $comment * @property User $user * @property YearPeriod $yearPeriod @@ -68,6 +69,12 @@ class VacationRequest extends Model return $query->whereIn("state", $states); } + public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder + { + return $query->where("from", "<=", $vacationRequest->to) + ->where("to", ">=", $vacationRequest->from); + } + protected static function newFactory(): VacationRequestFactory { return VacationRequestFactory::new(); diff --git a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php new file mode 100644 index 0000000..b8a8c33 --- /dev/null +++ b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php @@ -0,0 +1,20 @@ +calculateDays($request->yearPeriod(), $request->from(), $request->to()); + + return new JsonResponse($days->all()); + } +} diff --git a/app/Infrastructure/Http/Controllers/VacationRequestController.php b/app/Infrastructure/Http/Controllers/VacationRequestController.php index 3c2eaf8..ca8be78 100644 --- a/app/Infrastructure/Http/Controllers/VacationRequestController.php +++ b/app/Infrastructure/Http/Controllers/VacationRequestController.php @@ -11,6 +11,7 @@ use Illuminate\Http\Response as LaravelResponse; use Inertia\Response; use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Enums\VacationType; +use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationRequestStateManager; use Toby\Domain\Validation\VacationRequestValidator; use Toby\Eloquent\Helpers\YearPeriodRetriever; @@ -64,9 +65,15 @@ class VacationRequestController extends Controller VacationRequestRequest $request, VacationRequestValidator $vacationRequestValidator, VacationRequestStateManager $stateManager, + VacationDaysCalculator $vacationDaysCalculator, ): RedirectResponse { /** @var VacationRequest $vacationRequest */ $vacationRequest = $request->user()->vacationRequests()->make($request->data()); + $vacationRequest->estimated_days = $vacationDaysCalculator->calculateDays( + $vacationRequest->yearPeriod, + $vacationRequest->from, + $vacationRequest->to, + )->count(); $vacationRequestValidator->validate($vacationRequest); diff --git a/app/Infrastructure/Http/Requests/Api/CalculateVacationDaysRequest.php b/app/Infrastructure/Http/Requests/Api/CalculateVacationDaysRequest.php new file mode 100644 index 0000000..72e37de --- /dev/null +++ b/app/Infrastructure/Http/Requests/Api/CalculateVacationDaysRequest.php @@ -0,0 +1,36 @@ + ["required", "date_format:Y-m-d", new YearPeriodExists()], + "to" => ["required", "date_format:Y-m-d", new YearPeriodExists()], + ]; + } + + public function from(): Carbon + { + return Carbon::create($this->request->get("from")); + } + + public function to(): Carbon + { + return Carbon::create($this->request->get("to")); + } + + public function yearPeriod(): YearPeriod + { + return YearPeriod::findByYear(Carbon::create($this->request->get("from"))->year); + } +} diff --git a/app/Infrastructure/Http/Resources/VacationRequestResource.php b/app/Infrastructure/Http/Resources/VacationRequestResource.php index 859dfea..009eeb6 100644 --- a/app/Infrastructure/Http/Resources/VacationRequestResource.php +++ b/app/Infrastructure/Http/Resources/VacationRequestResource.php @@ -20,6 +20,7 @@ class VacationRequestResource extends JsonResource "state" => $this->state->label(), "from" => $this->from->toDisplayString(), "to" => $this->to->toDisplayString(), + "estimatedDays" => $this->estimated_days, "comment" => $this->comment, ]; } diff --git a/config/cors.php b/config/cors.php index c025685..34e1c4f 100644 --- a/config/cors.php +++ b/config/cors.php @@ -10,5 +10,5 @@ return [ "allowed_headers" => ["*"], "exposed_headers" => [], "max_age" => 0, - "supports_credentials" => false, + "supports_credentials" => true, ]; diff --git a/database/factories/VacationRequestFactory.php b/database/factories/VacationRequestFactory.php index e8bec48..3939367 100644 --- a/database/factories/VacationRequestFactory.php +++ b/database/factories/VacationRequestFactory.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Database\Factories; use Carbon\CarbonImmutable; +use Carbon\CarbonPeriod; use Illuminate\Database\Eloquent\Factories\Factory; use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Enums\VacationType; @@ -15,6 +16,7 @@ use Toby\Eloquent\Models\YearPeriod; class VacationRequestFactory extends Factory { protected $model = VacationRequest::class; + private static int $number = 1; public function definition(): array { @@ -29,6 +31,7 @@ class VacationRequestFactory extends Factory "state" => $this->faker->randomElement(VacationRequestState::cases()), "from" => $from, "to" => $from->addDays($days), + "estimated_days" => fn(array $attributes) => $this->estimateDays($attributes), "comment" => $this->faker->boolean ? $this->faker->paragraph() : null, ]; } @@ -36,12 +39,15 @@ class VacationRequestFactory extends Factory protected function generateName(array $attributes): string { $year = YearPeriod::find($attributes["year_period_id"])->year; - $user = User::find($attributes["user_id"]); - - $number = $user->vacationRequests() - ->whereYear("from", $year) - ->count() + 1; + $number = static::$number++; return "{$number}/{$year}"; } + + protected function estimateDays(array $attributes): int + { + $period = CarbonPeriod::create($attributes["from"], $attributes["to"]); + + return $period->count(); + } } diff --git a/database/migrations/2022_01_26_100039_create_vacation_requests_table.php b/database/migrations/2022_01_26_100039_create_vacation_requests_table.php index f5270da..b3f4e48 100644 --- a/database/migrations/2022_01_26_100039_create_vacation_requests_table.php +++ b/database/migrations/2022_01_26_100039_create_vacation_requests_table.php @@ -18,6 +18,7 @@ return new class() extends Migration { $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); $table->string("type"); $table->string("state")->nullable(); + $table->integer("estimated_days"); $table->date("from"); $table->date("to"); $table->text("comment")->nullable(); diff --git a/package-lock.json b/package-lock.json index 64ebfd5..7795438 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "application", + "name": "toby", "lockfileVersion": 2, "requires": true, "packages": { @@ -15,6 +15,7 @@ "@tailwindcss/typography": "^0.5.0", "@vue/compiler-sfc": "^3.2.26", "autoprefixer": "^10.4.2", + "axios": "^0.25.0", "echarts": "^5.2.2", "flatpickr": "^4.6.9", "laravel-mix": "^6.0.6", @@ -1741,6 +1742,14 @@ "vue": "^3.0.0" } }, + "node_modules/@inertiajs/inertia/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/@inertiajs/progress": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@inertiajs/progress/-/progress-0.2.7.tgz", @@ -2642,11 +2651,11 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.14.7" } }, "node_modules/babel-loader": { @@ -10664,6 +10673,16 @@ "axios": "^0.21.1", "deepmerge": "^4.0.0", "qs": "^6.9.0" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + } } }, "@inertiajs/inertia-vue3": { @@ -11468,11 +11487,11 @@ } }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.14.7" } }, "babel-loader": { diff --git a/package.json b/package.json index 6a0d9af..c69812f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tailwindcss/typography": "^0.5.0", "@vue/compiler-sfc": "^3.2.26", "autoprefixer": "^10.4.2", + "axios": "^0.25.0", "echarts": "^5.2.2", "flatpickr": "^4.6.9", "laravel-mix": "^6.0.6", diff --git a/resources/js/Pages/VacationRequest/Create.vue b/resources/js/Pages/VacationRequest/Create.vue index 9333545..d2cef55 100644 --- a/resources/js/Pages/VacationRequest/Create.vue +++ b/resources/js/Pages/VacationRequest/Create.vue @@ -10,8 +10,26 @@ class="border-t border-gray-200 px-6" @submit.prevent="createForm" > +
+
+
+ +
+
+

+ Wniosek nie mógł zostać utworzony +

+
+ {{ form.errors.vacationRequest }} +
+
+
+
@@ -21,9 +39,9 @@
- {{ form.vacationType.label }} + {{ form.type.label }} @@ -38,15 +56,15 @@ class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" >
  • - {{ vacationType.label }} + {{ type.label }}

    - {{ form.errors.vacationType }} + {{ form.errors.type }}

  • @@ -77,18 +95,18 @@

    - {{ form.errors.dateFrom }} + {{ form.errors.from }}

    @@ -102,25 +120,25 @@

    - {{ form.errors.dateTo }} + {{ form.errors.to }}

    Liczba dni urlopu
    - 1 + {{ estimatedDays.length }}
    @@ -135,7 +153,7 @@ id="comment" v-model="form.comment" rows="4" - class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md" + class="shadow-sm focus:ring-blumilk-500 focus:border-blumilk-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md" />
    @@ -164,8 +182,9 @@ import {useForm} from '@inertiajs/inertia-vue3' import FlatPickr from 'vue-flatpickr-component' import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue' -import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid' -import {reactive} from 'vue' +import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid' +import {reactive, ref} from 'vue' +import axios from 'axios' export default { name: 'VacationRequestCreate', @@ -178,6 +197,7 @@ export default { ListboxOptions, CheckIcon, SelectorIcon, + XCircleIcon, }, props: { vacationTypes: { @@ -191,12 +211,14 @@ export default { }, setup(props) { const form = useForm({ - dateFrom: null, - dateTo: null, - vacationType: props.vacationTypes[0], + from: null, + to: null, + type: props.vacationTypes[0], comment: null, }) + const estimatedDays = ref([]) + const disableDates = [ date => (date.getDay() === 0 || date.getDay() === 6), ] @@ -213,6 +235,7 @@ export default { return { form, + estimatedDays, fromInputConfig, toInputConfig, } @@ -221,18 +244,26 @@ export default { createForm() { this.form .transform(data => ({ - from: data.dateFrom, - to: data.dateTo, - type: data.vacationType.value, - comment: data.comment, + ...data, + type: data.type.value, })) .post('/vacation-requests') }, onFromChange(selectedDates, dateStr) { this.toInputConfig.minDate = dateStr + + this.refreshEstimatedDays(this.form.from, this.form.to) }, onToChange(selectedDates, dateStr) { this.fromInputConfig.maxDate = dateStr + + this.refreshEstimatedDays(this.form.from, this.form.to) + }, + refreshEstimatedDays(from, to) { + if (from && to) { + axios.post('/api/calculate-vacation-days', {from, to}) + .then(res => this.estimatedDays = res.data) + } }, }, diff --git a/resources/js/Pages/VacationRequest/Index.vue b/resources/js/Pages/VacationRequest/Index.vue index 745f0d3..4530d0e 100644 --- a/resources/js/Pages/VacationRequest/Index.vue +++ b/resources/js/Pages/VacationRequest/Index.vue @@ -33,40 +33,40 @@ Numer Rodzaj urlopu - Status - - Od Do Dni urlopu + + Status + @@ -87,17 +87,17 @@ {{ request.type }} - - {{ request.state }} - {{ request.from }} {{ request.to }} + + {{ request.estimatedDays }} + - X + {{ request.state }} diff --git a/resources/js/Pages/VacationRequest/Show.vue b/resources/js/Pages/VacationRequest/Show.vue index 6bee934..55a3f09 100644 --- a/resources/js/Pages/VacationRequest/Show.vue +++ b/resources/js/Pages/VacationRequest/Show.vue @@ -44,10 +44,10 @@
    - Dni + Dni urlopu
    - x + {{ request.estimatedDays }}
    diff --git a/resources/lang/pl.json b/resources/lang/pl.json index 29fae87..01cf187 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -16,8 +16,8 @@ "sick_vacation": "Zwolnienie lekarskie", "employee": "Pracownik", "administrator": "Administrator", - "technical_approver": "Techniczny klepacz", - "administrative_approver": "Administracyjny klepacz", + "technical_approver": "Techniczny akceptujący", + "administrative_approver": "Administracyjny akceptujący", "created": "Utworzony", "canceled": "Anulowany", "rejected": "Odrzucony", @@ -25,5 +25,10 @@ "waiting_for_technical": "Czeka na akceptację od technicznego", "waiting_for_administrative": "Czeka na akceptację od administracyjnego", "accepted_by_technical": "Zaakceptowany przez technicznego", - "accepted_by_administrative": "Zaakceptowany przez administracyjnego" + "accepted_by_administrative": "Zaakceptowany przez administracyjnego", + "You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.", + "You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.", + "You have exceeded your vacation limit.": "Przekroczyłeś/aś limit urlopu.", + "Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.", + "The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku." } diff --git a/resources/lang/pl/validation.php b/resources/lang/pl/validation.php index 09fdc6d..733418d 100644 --- a/resources/lang/pl/validation.php +++ b/resources/lang/pl/validation.php @@ -114,4 +114,15 @@ return [ "uploaded" => "Nie udało się wgrać pliku :attribute.", "url" => "Format pola :attribute jest nieprawidłowy.", "uuid" => "Pole :attribute musi być poprawnym identyfikatorem UUID.", + "attributes" => [ + "to" => "do", + "from" => "od", + "firstName" => "imię", + "lastName" => "nazwisko", + "email" => "e-mail", + "position" => "stanowisko", + "employmentDate" => "data zatrudnienia", + "date" => "data", + "name" => "nazwa", + ], ]; diff --git a/resources/views/pdf/vacation-request.blade.php b/resources/views/pdf/vacation-request.blade.php index 9f22109..9300bf2 100644 --- a/resources/views/pdf/vacation-request.blade.php +++ b/resources/views/pdf/vacation-request.blade.php @@ -74,7 +74,7 @@

    Wniosek o urlop

    Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }} - do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. x dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}. + do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. {{ $vacationRequest->estimated_days }} dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}.

    diff --git a/routes/api.php b/routes/api.php index 174d7fd..f418a99 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,3 +1,10 @@ group(function (): void { + Route::post("calculate-vacation-days", CalculateVacationDaysController::class); +});