From 6b2556c1da6fbc61be1a14036dc5a409baef836f Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 15:26:55 +0200 Subject: [PATCH] #126 - vacation request reminders (#130) * #126 - vacation request reminders * #126 - fix workdays * #126 - changes * #126 - cs fix * #5 - bump codestyle * #126 - fix * #126 - fix * #126 - fix * #126 - fix * #126 - tests * #126 - fix * #126 - fix * #126 - fix seeders * #126 - fix * #126 - tests Co-authored-by: EwelinaLasowy --- .env.example | 5 + .../Actions/VacationRequest/CreateAction.php | 9 +- .../KeyHasBeenGivenNotification.php | 14 +- .../KeyHasBeenTakenNotification.php | 14 +- .../VacationRequestCreatedNotification.php | 37 ++--- ...cationRequestStatusChangedNotification.php | 9 +- ...ionRequestWaitsForApprovalNotification.php | 9 +- .../VacationRequestsSummaryNotification.php | 70 ++++++++++ .../Rules/DoesNotExceedLimitRule.php | 8 +- .../Rules/MinimumOneVacationDayRule.php | 6 +- ...sCalculator.php => WorkDaysCalculator.php} | 2 +- app/Eloquent/Models/VacationRequest.php | 8 ++ ...endVacationRequestSummariesToApprovers.php | 36 +++++ .../Api/CalculateVacationDaysController.php | 4 +- .../Slack/Channels/SlackApiChannel.php | 7 +- .../Slack/Elements/SlackMessage.php | 53 +++++++ .../Elements/VacationRequestsAttachment.php | 33 +++++ database/seeders/DatabaseSeeder.php | 5 +- database/seeders/DemoSeeder.php | 11 +- resources/js/Pages/VacationRequest/Show.vue | 2 +- routes/api.php | 4 +- .../Unit/SendVacationRequestSummariesTest.php | 130 ++++++++++++++++++ 22 files changed, 407 insertions(+), 69 deletions(-) create mode 100644 app/Domain/Notifications/VacationRequestsSummaryNotification.php rename app/Domain/{VacationDaysCalculator.php => WorkDaysCalculator.php} (97%) create mode 100644 app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php create mode 100644 app/Infrastructure/Slack/Elements/SlackMessage.php create mode 100644 app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php create mode 100644 tests/Unit/SendVacationRequestSummariesTest.php diff --git a/.env.example b/.env.example index fe3a597..0689964 100644 --- a/.env.example +++ b/.env.example @@ -60,3 +60,8 @@ GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT=http://localhost/login/google/end GOOGLE_CALENDAR_ID= LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= + +SLACK_URL=https://slack.com/api +SLACK_CLIENT_TOKEN= +SLACK_SIGNING_SECRET= +SLACK_DEFAULT_CHANNEL="#general" diff --git a/app/Domain/Actions/VacationRequest/CreateAction.php b/app/Domain/Actions/VacationRequest/CreateAction.php index 1bb8a7a..b569954 100644 --- a/app/Domain/Actions/VacationRequest/CreateAction.php +++ b/app/Domain/Actions/VacationRequest/CreateAction.php @@ -6,10 +6,10 @@ namespace Toby\Domain\Actions\VacationRequest; use Illuminate\Validation\ValidationException; use Toby\Domain\Notifications\VacationRequestCreatedNotification; -use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationRequestStateManager; use Toby\Domain\VacationTypeConfigRetriever; use Toby\Domain\Validation\VacationRequestValidator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; @@ -19,7 +19,7 @@ class CreateAction protected VacationRequestStateManager $stateManager, protected VacationRequestValidator $vacationRequestValidator, protected VacationTypeConfigRetriever $configRetriever, - protected VacationDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, protected WaitForTechApprovalAction $waitForTechApprovalAction, protected WaitForAdminApprovalAction $waitForAdminApprovalAction, protected ApproveAction $approveAction, @@ -52,10 +52,7 @@ class CreateAction $vacationRequest->save(); - $days = $this->vacationDaysCalculator->calculateDays( - $vacationRequest->from, - $vacationRequest->to, - ); + $days = $this->workDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); foreach ($days as $day) { $vacationRequest->vacations()->create([ diff --git a/app/Domain/Notifications/KeyHasBeenGivenNotification.php b/app/Domain/Notifications/KeyHasBeenGivenNotification.php index 977f5bf..2759396 100644 --- a/app/Domain/Notifications/KeyHasBeenGivenNotification.php +++ b/app/Domain/Notifications/KeyHasBeenGivenNotification.php @@ -7,6 +7,7 @@ namespace Toby\Domain\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Toby\Eloquent\Models\User; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class KeyHasBeenGivenNotification extends Notification { @@ -22,13 +23,14 @@ class KeyHasBeenGivenNotification extends Notification return [Channels::SLACK]; } - public function toSlack(Notifiable $notifiable): string + public function toSlack(Notifiable $notifiable): SlackMessage { - return __(":sender gives key no :key to :recipient", [ - "sender" => $this->getName($this->sender), - "recipient" => $this->getName($this->recipient), - "key" => $notifiable->id, - ]); + return (new SlackMessage()) + ->text(__(":sender gives key no :key to :recipient", [ + "sender" => $this->getName($this->sender), + "recipient" => $this->getName($this->recipient), + "key" => $notifiable->id, + ])); } protected function getName(User $user): string diff --git a/app/Domain/Notifications/KeyHasBeenTakenNotification.php b/app/Domain/Notifications/KeyHasBeenTakenNotification.php index 71e8b7b..435fc02 100644 --- a/app/Domain/Notifications/KeyHasBeenTakenNotification.php +++ b/app/Domain/Notifications/KeyHasBeenTakenNotification.php @@ -7,6 +7,7 @@ namespace Toby\Domain\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Toby\Eloquent\Models\User; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class KeyHasBeenTakenNotification extends Notification { @@ -22,13 +23,14 @@ class KeyHasBeenTakenNotification extends Notification return [Channels::SLACK]; } - public function toSlack(Notifiable $notifiable): string + public function toSlack(Notifiable $notifiable): SlackMessage { - return __(":recipient takes key no :key from :sender", [ - "recipient" => $this->getName($this->recipient), - "sender" => $this->getName($this->sender), - "key" => $notifiable->id, - ]); + return (new SlackMessage()) + ->text(__(":recipient takes key no :key from :sender", [ + "recipient" => $this->getName($this->recipient), + "sender" => $this->getName($this->sender), + "key" => $notifiable->id, + ])); } protected function getName(User $user): string diff --git a/app/Domain/Notifications/VacationRequestCreatedNotification.php b/app/Domain/Notifications/VacationRequestCreatedNotification.php index 9a352c6..187ee45 100644 --- a/app/Domain/Notifications/VacationRequestCreatedNotification.php +++ b/app/Domain/Notifications/VacationRequestCreatedNotification.php @@ -9,6 +9,7 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; use InvalidArgumentException; use Toby\Eloquent\Models\VacationRequest; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class VacationRequestCreatedNotification extends Notification { @@ -23,14 +24,12 @@ class VacationRequestCreatedNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** @@ -56,19 +55,25 @@ class VacationRequestCreatedNotification extends Notification $days = $this->vacationRequest->vacations()->count(); return (new MailMessage()) - ->greeting(__("Hi :user!", [ - "user" => $user, - ])) + ->greeting( + __("Hi :user!", [ + "user" => $user, + ]), + ) ->subject($this->buildSubject()) ->line($this->buildDescription()) - ->line(__("Vacation type: :type", [ - "type" => $type, - ])) - ->line(__("From :from to :to (number of days: :days)", [ - "from" => $from, - "to" => $to, - "days" => $days, - ])) + ->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/Domain/Notifications/VacationRequestStatusChangedNotification.php b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php index 746e3a0..cacd1e4 100644 --- a/app/Domain/Notifications/VacationRequestStatusChangedNotification.php +++ b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php @@ -10,6 +10,7 @@ use Illuminate\Notifications\Notification; use InvalidArgumentException; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class VacationRequestStatusChangedNotification extends Notification { @@ -25,14 +26,12 @@ class VacationRequestStatusChangedNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** diff --git a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php index 63ea770..b4033ad 100644 --- a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php +++ b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php @@ -11,6 +11,7 @@ use InvalidArgumentException; use Toby\Domain\States\VacationRequest\WaitingForTechnical; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class VacationRequestWaitsForApprovalNotification extends Notification { @@ -26,14 +27,12 @@ class VacationRequestWaitsForApprovalNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php new file mode 100644 index 0000000..1865b1b --- /dev/null +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -0,0 +1,70 @@ +text("Wnioski oczekujące na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") + ->withAttachment(new VacationRequestsAttachment($this->vacationRequests)); + } + + public function toMail(Notifiable $notifiable): MailMessage + { + $url = route( + "vacation.requests.indexForApprovers", + [ + "status" => "waiting_for_action", + ], + ); + + return $this->buildMailMessage($notifiable, $url); + } + + protected function buildMailMessage(Notifiable $notifiable, string $url): MailMessage + { + $user = $notifiable->profile->first_name; + + $message = (new MailMessage()) + ->greeting( + __("Hi :user!", [ + "user" => $user, + ]), + ) + ->line("Lista wniosków oczekujących na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") + ->subject("Wnioski oczekujące na akcje - stan na dzień {$this->day->toDisplayString()}"); + + foreach ($this->vacationRequests as $request) { + $message->line( + "Wniosek nr {$request->name} użytkownika {$request->user->profile->full_name} ({$request->from->toDisplayString()} - {$request->to->toDisplayString()})", + ); + } + + return $message + ->action("Przejdź do wniosków", $url); + } +} diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index 3b53892..c0ec8df 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -7,9 +7,9 @@ namespace Toby\Domain\Validation\Rules; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Toby\Domain\Enums\VacationType; -use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationRequestStatesRetriever; use Toby\Domain\VacationTypeConfigRetriever; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\YearPeriod; @@ -18,7 +18,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule { public function __construct( protected VacationTypeConfigRetriever $configRetriever, - protected VacationDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, ) {} public function check(VacationRequest $vacationRequest): bool @@ -29,7 +29,9 @@ class DoesNotExceedLimitRule implements VacationRequestRule $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); - $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to)->count(); + $estimatedDays = $this->workDaysCalculator + ->calculateDays($vacationRequest->from, $vacationRequest->to) + ->count(); return $limit >= ($vacationDays + $estimatedDays); } diff --git a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php index 4968d4d..5e78a75 100644 --- a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php +++ b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php @@ -4,18 +4,18 @@ declare(strict_types=1); namespace Toby\Domain\Validation\Rules; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\VacationRequest; class MinimumOneVacationDayRule implements VacationRequestRule { public function __construct( - protected VacationDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, ) {} public function check(VacationRequest $vacationRequest): bool { - return $this->vacationDaysCalculator + return $this->workDaysCalculator ->calculateDays($vacationRequest->from, $vacationRequest->to) ->isNotEmpty(); } diff --git a/app/Domain/VacationDaysCalculator.php b/app/Domain/WorkDaysCalculator.php similarity index 97% rename from app/Domain/VacationDaysCalculator.php rename to app/Domain/WorkDaysCalculator.php index 2d3227e..3dd3c5f 100644 --- a/app/Domain/VacationDaysCalculator.php +++ b/app/Domain/WorkDaysCalculator.php @@ -9,7 +9,7 @@ use Carbon\CarbonPeriod; use Illuminate\Support\Collection; use Toby\Eloquent\Models\YearPeriod; -class VacationDaysCalculator +class WorkDaysCalculator { public function calculateDays(CarbonInterface $from, CarbonInterface $to): Collection { diff --git a/app/Eloquent/Models/VacationRequest.php b/app/Eloquent/Models/VacationRequest.php index 97f94b7..cb77f1c 100644 --- a/app/Eloquent/Models/VacationRequest.php +++ b/app/Eloquent/Models/VacationRequest.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Spatie\ModelStates\HasStates; @@ -84,6 +85,13 @@ class VacationRequest extends Model return $query->whereNotState("state", $states); } + public function scopeType(Builder $query, VacationType|array $types): Builder + { + $types = Arr::wrap($types); + + return $query->whereIn("type", $types); + } + public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder { return $query->where("from", "<=", $vacationRequest->to) diff --git a/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php b/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php new file mode 100644 index 0000000..9437021 --- /dev/null +++ b/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php @@ -0,0 +1,36 @@ +whereIn("role", [Role::AdministrativeApprover, Role::TechnicalApprover, Role::Administrator]) + ->get(); + + foreach ($users as $user) { + $vacationRequests = VacationRequest::query() + ->states(VacationRequestStatesRetriever::waitingForUserActionStates($user)) + ->get(); + + if ($vacationRequests->isNotEmpty()) { + $user->notify(new VacationRequestsSummaryNotification(Carbon::today(), $vacationRequests)); + } + } + } +} diff --git a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php index 3f3caa5..be7348c 100644 --- a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php +++ b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php @@ -6,13 +6,13 @@ namespace Toby\Infrastructure\Http\Controllers\Api; use Illuminate\Http\JsonResponse; use Illuminate\Support\Carbon; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Infrastructure\Http\Controllers\Controller; use Toby\Infrastructure\Http\Requests\Api\CalculateVacationDaysRequest; class CalculateVacationDaysController extends Controller { - public function __invoke(CalculateVacationDaysRequest $request, VacationDaysCalculator $calculator): JsonResponse + public function __invoke(CalculateVacationDaysRequest $request, WorkDaysCalculator $calculator): JsonResponse { $days = $calculator->calculateDays($request->from(), $request->to()); diff --git a/app/Infrastructure/Slack/Channels/SlackApiChannel.php b/app/Infrastructure/Slack/Channels/SlackApiChannel.php index d790555..b8a9e67 100644 --- a/app/Infrastructure/Slack/Channels/SlackApiChannel.php +++ b/app/Infrastructure/Slack/Channels/SlackApiChannel.php @@ -17,11 +17,12 @@ class SlackApiChannel $url = "{$baseUrl}/chat.postMessage"; $channel = $notifiable->routeNotificationFor("slack", $notification); + $message = $notification->toSlack($notifiable); + return Http::withToken($this->getClientToken()) - ->post($url, [ + ->post($url, array_merge($message->getPayload(), [ "channel" => $channel, - "text" => $notification->toSlack($notifiable), - ]); + ])); } protected function getClientToken(): string diff --git a/app/Infrastructure/Slack/Elements/SlackMessage.php b/app/Infrastructure/Slack/Elements/SlackMessage.php new file mode 100644 index 0000000..05d8e1b --- /dev/null +++ b/app/Infrastructure/Slack/Elements/SlackMessage.php @@ -0,0 +1,53 @@ +attachments = new Collection(); + } + + public function text(string $text): static + { + $this->text = $text; + + return $this; + } + + public function withAttachment(Attachment $attachment): static + { + $this->attachments->push($attachment); + + return $this; + } + + public function withAttachments(Collection $attachments): static + { + foreach ($attachments as $attachment) { + $this->withAttachment($attachment); + } + + return $this; + } + + public function getPayload(): array + { + return [ + "text" => $this->text, + "link_names" => true, + "unfurl_links" => true, + "unfurl_media" => true, + "mrkdwn" => true, + "attachments" => $this->attachments->toArray(), + ]; + } +} diff --git a/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php b/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php new file mode 100644 index 0000000..933b8fb --- /dev/null +++ b/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php @@ -0,0 +1,33 @@ +setColor("#527aba") + ->setItems($this->mapVacationRequests($vacationRequests)); + } + + protected function mapVacationRequests(Collection $vacationRequests): Collection + { + return $vacationRequests->map(function (VacationRequest $request): string { + $url = route("vacation.requests.show", ["vacationRequest" => $request->id]); + + $date = $request->from->equalTo($request->to) + ? "{$request->from->toDisplayString()}" + : "{$request->from->toDisplayString()} - {$request->to->toDisplayString()}"; + + return "<{$url}|Wniosek nr {$request->name}> użytkownika {$request->user->profile->full_name} ({$date})"; + }); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index f5e0e51..ea21c71 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -7,7 +7,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Carbon; use Toby\Domain\PolishHolidaysRetriever; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\Key; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; @@ -70,8 +70,7 @@ class DatabaseSeeder extends Seeder "year_period_id" => $yearPeriods->random()->id, ]) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->from, $vacationRequest->to, ); diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index 41470ba..5d321ab 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -18,7 +18,7 @@ use Toby\Domain\States\VacationRequest\Created; use Toby\Domain\States\VacationRequest\Rejected; use Toby\Domain\States\VacationRequest\WaitingForAdministrative; use Toby\Domain\States\VacationRequest\WaitingForTechnical; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\Key; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; @@ -164,8 +164,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->from, $vacationRequest->to, ); @@ -234,8 +233,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->from, $vacationRequest->to, ); @@ -291,8 +289,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->from, $vacationRequest->to, ); diff --git a/resources/js/Pages/VacationRequest/Show.vue b/resources/js/Pages/VacationRequest/Show.vue index ad58a1e..a606539 100644 --- a/resources/js/Pages/VacationRequest/Show.vue +++ b/resources/js/Pages/VacationRequest/Show.vue @@ -77,7 +77,7 @@
{{ request.comment }}
diff --git a/routes/api.php b/routes/api.php index 3220d19..7fa9ef1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,9 +7,9 @@ use Toby\Infrastructure\Http\Controllers\Api\CalculateUserUnavailableDaysControl use Toby\Infrastructure\Http\Controllers\Api\CalculateUserVacationStatsController; use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController; use Toby\Infrastructure\Http\Controllers\Api\GetAvailableVacationTypesController; -use Toby\Infrastructure\Slack\Controller as SlackController; +use Toby\Infrastructure\Slack\Controller as SlackCommandController; -Route::post("slack", [SlackController::class, "getResponse"]); +Route::post("slack", [SlackCommandController::class, "getResponse"]); Route::middleware("auth:sanctum")->group(function (): void { Route::post("vacation/calculate-days", CalculateVacationDaysController::class); diff --git a/tests/Unit/SendVacationRequestSummariesTest.php b/tests/Unit/SendVacationRequestSummariesTest.php new file mode 100644 index 0000000..97370bd --- /dev/null +++ b/tests/Unit/SendVacationRequestSummariesTest.php @@ -0,0 +1,130 @@ +createCurrentYearPeriod(); + } + + public function testSummariesAreSentOnlyToProperApprovers(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $administrativeApprover = User::factory([ + "role" => Role::AdministrativeApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => WaitingForTechnical::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestsSummaryNotification::class); + } + + public function testSummariesAreSentOnlyIfVacationRequestWaitingForActionExists(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => WaitingForTechnical::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + Notification::assertNotSentTo([$user], VacationRequestsSummaryNotification::class); + } + + public function testSummariesAreNotSentIfThereAreNoWaitingForActionVacationRequests(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Approved::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Cancelled::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Rejected::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Created::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertNotSentTo([$user, $technicalApprover, $admin], VacationRequestsSummaryNotification::class); + } +}