From 17cdee38e0f464bddf75c42e026a2bec922c749c Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Tue, 22 Feb 2022 11:02:36 +0100 Subject: [PATCH] #38 - vacation request by administrative approver (#57) * #38 - wip * #38 - wip * #38 - fix * #38 - fix * #38 - fix * #38 - fix * Update resources/lang/pl.json Co-authored-by: Krzysztof Rewak * #38 - cr fix Co-authored-by: EwelinaLasowy Co-authored-by: Krzysztof Rewak --- .../HandleCreatedVacationRequest.php | 6 + ...SendCreatedVacationRequestNotification.php | 9 +- ...VacationRequestCreatedOnEmployeeBehalf.php | 74 ++++++++++++ app/Eloquent/Models/User.php | 5 + app/Eloquent/Models/VacationRequest.php | 12 ++ .../Controllers/VacationRequestController.php | 10 +- .../Http/Requests/VacationRequestRequest.php | 4 + database/factories/VacationRequestFactory.php | 3 +- ..._100039_create_vacation_requests_table.php | 2 + database/seeders/DatabaseSeeder.php | 1 + resources/js/Composables/yearPeriodInfo.js | 12 ++ resources/js/Pages/Holidays/Create.vue | 12 +- resources/js/Pages/VacationRequest/Create.vue | 111 +++++++++++++++++- resources/lang/pl.json | 10 +- tests/Feature/VacationRequestTest.php | 86 ++++++++++++++ tests/Unit/VacationRequestStatesTest.php | 3 + 16 files changed, 346 insertions(+), 14 deletions(-) create mode 100644 app/Domain/Notifications/VacationRequestCreatedOnEmployeeBehalf.php create mode 100644 resources/js/Composables/yearPeriodInfo.js diff --git a/app/Domain/Listeners/HandleCreatedVacationRequest.php b/app/Domain/Listeners/HandleCreatedVacationRequest.php index 998f82a..bdc0824 100644 --- a/app/Domain/Listeners/HandleCreatedVacationRequest.php +++ b/app/Domain/Listeners/HandleCreatedVacationRequest.php @@ -20,6 +20,12 @@ class HandleCreatedVacationRequest { $vacationRequest = $event->vacationRequest; + if ($vacationRequest->hasFlowSkipped()) { + $this->stateManager->approve($vacationRequest); + + return; + } + if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) { $this->stateManager->waitForTechnical($vacationRequest); diff --git a/app/Domain/Listeners/SendCreatedVacationRequestNotification.php b/app/Domain/Listeners/SendCreatedVacationRequestNotification.php index 5aece51..56bd726 100644 --- a/app/Domain/Listeners/SendCreatedVacationRequestNotification.php +++ b/app/Domain/Listeners/SendCreatedVacationRequestNotification.php @@ -6,6 +6,7 @@ namespace Toby\Domain\Listeners; use Toby\Domain\Events\VacationRequestCreated; use Toby\Domain\Notifications\VacationRequestCreatedNotification; +use Toby\Domain\Notifications\VacationRequestCreatedOnEmployeeBehalf; class SendCreatedVacationRequestNotification { @@ -15,6 +16,12 @@ class SendCreatedVacationRequestNotification public function handle(VacationRequestCreated $event): void { - $event->vacationRequest->user->notify(new VacationRequestCreatedNotification($event->vacationRequest)); + $vacationRequest = $event->vacationRequest; + + if ($vacationRequest->creator->is($vacationRequest->user)) { + $vacationRequest->user->notify(new VacationRequestCreatedNotification($vacationRequest)); + } else { + $vacationRequest->user->notify(new VacationRequestCreatedOnEmployeeBehalf($vacationRequest)); + } } } diff --git a/app/Domain/Notifications/VacationRequestCreatedOnEmployeeBehalf.php b/app/Domain/Notifications/VacationRequestCreatedOnEmployeeBehalf.php new file mode 100644 index 0000000..cf54b6b --- /dev/null +++ b/app/Domain/Notifications/VacationRequestCreatedOnEmployeeBehalf.php @@ -0,0 +1,74 @@ + $this->vacationRequest, + ], + ); + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $creator = $this->vacationRequest->creator->fullName; + $user = $this->vacationRequest->user->first_name; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + $appName = config("app.name"); + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title has been created on your behalf", [ + "title" => $title, + ])) + ->line(__("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [ + "title" => $title, + "appName" => $appName, + "creator" => $creator, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Eloquent/Models/User.php b/app/Eloquent/Models/User.php index 1f8c6e3..eafa9aa 100644 --- a/app/Eloquent/Models/User.php +++ b/app/Eloquent/Models/User.php @@ -58,6 +58,11 @@ class User extends Authenticatable return $this->hasMany(VacationRequest::class); } + public function createdVacationRequests(): HasMany + { + return $this->hasMany(VacationRequest::class, "creator_id"); + } + public function vacations(): HasMany { return $this->hasMany(Vacation::class); diff --git a/app/Eloquent/Models/VacationRequest.php b/app/Eloquent/Models/VacationRequest.php index f8d29b9..da28c1a 100644 --- a/app/Eloquent/Models/VacationRequest.php +++ b/app/Eloquent/Models/VacationRequest.php @@ -22,7 +22,9 @@ use Toby\Domain\Enums\VacationType; * @property Carbon $from * @property Carbon $to * @property string $comment + * @property bool $flow_skipped * @property User $user + * @property User $creator * @property YearPeriod $yearPeriod * @property Collection $activities * @property Collection $vacations @@ -47,6 +49,11 @@ class VacationRequest extends Model return $this->belongsTo(User::class); } + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, "creator_id"); + } + public function yearPeriod(): BelongsTo { return $this->belongsTo(YearPeriod::class); @@ -69,6 +76,11 @@ class VacationRequest extends Model $this->save(); } + public function hasFlowSkipped(): bool + { + return $this->flow_skipped; + } + public function scopeStates(Builder $query, array $states): Builder { return $query->whereIn("state", $states); diff --git a/app/Infrastructure/Http/Controllers/VacationRequestController.php b/app/Infrastructure/Http/Controllers/VacationRequestController.php index c24cbe2..6f1c8c6 100644 --- a/app/Infrastructure/Http/Controllers/VacationRequestController.php +++ b/app/Infrastructure/Http/Controllers/VacationRequestController.php @@ -15,8 +15,10 @@ use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationRequestStateManager; use Toby\Domain\Validation\VacationRequestValidator; use Toby\Eloquent\Helpers\YearPeriodRetriever; +use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; use Toby\Infrastructure\Http\Requests\VacationRequestRequest; +use Toby\Infrastructure\Http\Resources\UserResource; use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource; use Toby\Infrastructure\Http\Resources\VacationRequestResource; @@ -61,8 +63,14 @@ class VacationRequestController extends Controller public function create(): Response { + $users = User::query() + ->orderBy("last_name") + ->orderBy("first_name") + ->get(); + return inertia("VacationRequest/Create", [ "vacationTypes" => VacationType::casesToSelect(), + "users" => UserResource::collection($users), ]); } @@ -73,7 +81,7 @@ class VacationRequestController extends Controller VacationDaysCalculator $vacationDaysCalculator, ): RedirectResponse { /** @var VacationRequest $vacationRequest */ - $vacationRequest = $request->user()->vacationRequests()->make($request->data()); + $vacationRequest = $request->user()->createdVacationRequests()->make($request->data()); $vacationRequestValidator->validate($vacationRequest); $vacationRequest->save(); diff --git a/app/Infrastructure/Http/Requests/VacationRequestRequest.php b/app/Infrastructure/Http/Requests/VacationRequestRequest.php index 3fd9e9f..f69ef10 100644 --- a/app/Infrastructure/Http/Requests/VacationRequestRequest.php +++ b/app/Infrastructure/Http/Requests/VacationRequestRequest.php @@ -16,9 +16,11 @@ class VacationRequestRequest extends FormRequest public function rules(): array { return [ + "user" => ["required", "exists:users,id"], "type" => ["required", new Enum(VacationType::class)], "from" => ["required", "date_format:Y-m-d", new YearPeriodExists()], "to" => ["required", "date_format:Y-m-d", new YearPeriodExists()], + "flowSkipped" => ["nullable", "boolean"], "comment" => ["nullable"], ]; } @@ -28,11 +30,13 @@ class VacationRequestRequest extends FormRequest $from = $this->get("from"); return [ + "user_id" => $this->get("user"), "type" => $this->get("type"), "from" => $from, "to" => $this->get("to"), "year_period_id" => YearPeriod::findByYear(Carbon::create($from)->year)->id, "comment" => $this->get("comment"), + "flow_skipped" => $this->boolean("flowSkipped"), ]; } } diff --git a/database/factories/VacationRequestFactory.php b/database/factories/VacationRequestFactory.php index 4816781..a0fb00c 100644 --- a/database/factories/VacationRequestFactory.php +++ b/database/factories/VacationRequestFactory.php @@ -24,8 +24,9 @@ class VacationRequestFactory extends Factory return [ "user_id" => User::factory(), + "creator_id" => fn(array $attributes): int => $attributes["user_id"], "year_period_id" => YearPeriod::factory(), - "name" => fn(array $attributes) => $this->generateName($attributes), + "name" => fn(array $attributes): string => $this->generateName($attributes), "type" => $this->faker->randomElement(VacationType::cases()), "state" => $this->faker->randomElement(VacationRequestState::cases()), "from" => $from, 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..d9efcc7 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 @@ -14,6 +14,7 @@ return new class() extends Migration { Schema::create("vacation_requests", function (Blueprint $table): void { $table->id(); $table->string("name"); + $table->foreignIdFor(User::class, "creator_id")->constrained("users")->cascadeOnDelete(); $table->foreignIdFor(User::class)->constrained()->cascadeOnDelete(); $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); $table->string("type"); @@ -21,6 +22,7 @@ return new class() extends Migration { $table->date("from"); $table->date("to"); $table->text("comment")->nullable(); + $table->boolean("flow_skipped")->default(false); $table->timestamps(); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 614d712..12390fa 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -77,6 +77,7 @@ class DatabaseSeeder extends Seeder VacationRequest::factory() ->count(10) ->for($user) + ->for($user, "creator") ->sequence(fn() => [ "year_period_id" => $yearPeriods->random()->id, ]) diff --git a/resources/js/Composables/yearPeriodInfo.js b/resources/js/Composables/yearPeriodInfo.js new file mode 100644 index 0000000..8425e0a --- /dev/null +++ b/resources/js/Composables/yearPeriodInfo.js @@ -0,0 +1,12 @@ +import {computed} from 'vue' +import {usePage} from '@inertiajs/inertia-vue3' + +export default function useCurrentYearPeriodInfo() { + const minDate = computed(() => new Date(usePage().props.value.years.current, 0, 1)) + const maxDate = computed(() => new Date(usePage().props.value.years.current, 11, 31)) + + return { + minDate, + maxDate, + } +} diff --git a/resources/js/Pages/Holidays/Create.vue b/resources/js/Pages/Holidays/Create.vue index b906242..48a7850 100644 --- a/resources/js/Pages/Holidays/Create.vue +++ b/resources/js/Pages/Holidays/Create.vue @@ -48,6 +48,7 @@ id="date" v-model="form.date" placeholder="Wybierz datę" + :config="{minDate, maxDate}" class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }" /> @@ -81,8 +82,9 @@