From fad4290cc39ef952c8346ea1b1a3d54c888ec142 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Fri, 22 Apr 2022 12:41:00 +0200 Subject: [PATCH] wip --- .../Providers/AppServiceProvider.php | 11 +++ .../Actions/VacationRequest/CreateAction.php | 1 - .../VacationRequestCreatedNotification.php | 20 ++-- ...cationRequestStatusChangedNotification.php | 43 +++++--- ...ionRequestWaitsForApprovalNotification.php | 12 ++- app/Domain/Slack/Channels/SlackApiChannel.php | 25 +++++ app/Domain/Slack/Handlers/CatchAll.php | 51 ++++++++++ app/Domain/Slack/Handlers/DailySummary.php | 74 ++++++++++++++ .../Slack/{ => Handlers}/GiveKeysTo.php | 8 +- app/Domain/Slack/Handlers/Help.php | 45 +++++++++ .../Slack/{ => Handlers}/HomeOffice.php | 11 ++- app/Domain/Slack/Handlers/KeyList.php | 33 +++++++ app/Domain/Slack/Handlers/SaySomething.php | 24 +++++ .../Slack/{ => Handlers}/TakeKeysFrom.php | 8 +- app/Domain/Slack/KeyList.php | 30 ------ app/Domain/VacationDaysCalculator.php | 3 +- .../Rules/DoesNotExceedLimitRule.php | 2 +- .../Rules/MinimumOneVacationDayRule.php | 2 +- app/Eloquent/Models/Profile.php | 2 + app/Eloquent/Models/User.php | 5 + .../Commands/SendDailySummaryToSlack.php | 97 +++++++++++++++++++ .../Api/CalculateVacationDaysController.php | 2 +- .../Http/Requests/UserRequest.php | 2 + .../Http/Resources/UserFormDataResource.php | 1 + config/app.php | 1 - config/laravel-slack-slash-command.php | 20 ++-- config/services.php | 5 + database/factories/ProfileFactory.php | 1 + ...nd_birthday_columns_in_profiles_table.php} | 4 +- resources/js/Pages/Users/Create.vue | 24 +++++ resources/js/Pages/Users/Edit.vue | 24 +++++ resources/lang/pl.json | 4 +- tests/Unit/SendDailySummaryToSlackTest.php | 82 ++++++++++++++++ 33 files changed, 599 insertions(+), 78 deletions(-) create mode 100644 app/Domain/Slack/Channels/SlackApiChannel.php create mode 100644 app/Domain/Slack/Handlers/CatchAll.php create mode 100644 app/Domain/Slack/Handlers/DailySummary.php rename app/Domain/Slack/{ => Handlers}/GiveKeysTo.php (83%) create mode 100644 app/Domain/Slack/Handlers/Help.php rename app/Domain/Slack/{ => Handlers}/HomeOffice.php (79%) create mode 100644 app/Domain/Slack/Handlers/KeyList.php create mode 100644 app/Domain/Slack/Handlers/SaySomething.php rename app/Domain/Slack/{ => Handlers}/TakeKeysFrom.php (83%) delete mode 100644 app/Domain/Slack/KeyList.php create mode 100644 app/Infrastructure/Console/Commands/SendDailySummaryToSlack.php rename database/migrations/{2022_04_21_101027_add_slack_id_column_in_profiles_table.php => 2022_04_21_101027_add_slack_id_and_birthday_columns_in_profiles_table.php} (78%) create mode 100644 tests/Unit/SendDailySummaryToSlackTest.php diff --git a/app/Architecture/Providers/AppServiceProvider.php b/app/Architecture/Providers/AppServiceProvider.php index ce611b9..37f7884 100644 --- a/app/Architecture/Providers/AppServiceProvider.php +++ b/app/Architecture/Providers/AppServiceProvider.php @@ -4,11 +4,22 @@ declare(strict_types=1); namespace Toby\Architecture\Providers; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Notifications\ChannelManager; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Notification; use Illuminate\Support\ServiceProvider; +use Toby\Domain\Slack\Channels\SlackApiChannel; class AppServiceProvider extends ServiceProvider { + public function register() + { + Notification::resolved(function (ChannelManager $service) { + $service->extend("slack", fn(Application $app) => $app->make(SlackApiChannel::class)); + }); + } + public function boot(): void { Carbon::macro("toDisplayString", fn() => $this->translatedFormat("d.m.Y")); diff --git a/app/Domain/Actions/VacationRequest/CreateAction.php b/app/Domain/Actions/VacationRequest/CreateAction.php index 8a823e3..1bb8a7a 100644 --- a/app/Domain/Actions/VacationRequest/CreateAction.php +++ b/app/Domain/Actions/VacationRequest/CreateAction.php @@ -53,7 +53,6 @@ class CreateAction $vacationRequest->save(); $days = $this->vacationDaysCalculator->calculateDays( - $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, ); diff --git a/app/Domain/Notifications/VacationRequestCreatedNotification.php b/app/Domain/Notifications/VacationRequestCreatedNotification.php index d84d108..69198e7 100644 --- a/app/Domain/Notifications/VacationRequestCreatedNotification.php +++ b/app/Domain/Notifications/VacationRequestCreatedNotification.php @@ -20,7 +20,17 @@ class VacationRequestCreatedNotification extends Notification public function via(): array { - return ["mail"]; + return ["mail", "slack"]; + } + + public function toSlack(): string + { + $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); + + return implode("\n", [ + $this->buildDescription(), + "<${url}|Zobacz szczegóły>", + ]); } /** @@ -80,18 +90,16 @@ class VacationRequestCreatedNotification extends Notification protected function buildDescription(): string { $name = $this->vacationRequest->name; - $appName = config("app.name"); if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) { - return __("The vacation request :title has been created correctly in the :appName.", [ + return __("The vacation request :title for user :user has been created successfully.", [ + "user" => $this->vacationRequest->user->profile->full_name, "title" => $name, - "appName" => $appName, ]); } - return __("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [ + return __("The vacation request :title has been created successfully by user :creator on your behalf.", [ "title" => $this->vacationRequest->name, - "appName" => $appName, "creator" => $this->vacationRequest->creator->profile->full_name, ]); } diff --git a/app/Domain/Notifications/VacationRequestStatusChangedNotification.php b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php index 11594a1..1cc8d7c 100644 --- a/app/Domain/Notifications/VacationRequestStatusChangedNotification.php +++ b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php @@ -22,7 +22,17 @@ class VacationRequestStatusChangedNotification extends Notification public function via(): array { - return ["mail"]; + return ["mail", "slack"]; + } + + public function toSlack(): string + { + $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); + + return implode("\n", [ + $this->buildDescription(), + "<${url}|Zobacz szczegóły>", + ]); } /** @@ -43,27 +53,17 @@ class VacationRequestStatusChangedNotification extends Notification protected function buildMailMessage(string $url): MailMessage { $user = $this->user->profile->first_name; - $title = $this->vacationRequest->name; $type = $this->vacationRequest->type->label(); - $status = $this->vacationRequest->state->label(); $from = $this->vacationRequest->from->toDisplayString(); $to = $this->vacationRequest->to->toDisplayString(); $days = $this->vacationRequest->vacations()->count(); - $requester = $this->vacationRequest->user->profile->full_name; return (new MailMessage()) ->greeting(__("Hi :user!", [ "user" => $user, ])) - ->subject(__("Vacation request :title has been :status", [ - "title" => $title, - "status" => $status, - ])) - ->line(__("The vacation request :title from user :requester has been :status.", [ - "title" => $title, - "requester" => $requester, - "status" => $status, - ])) + ->subject($this->buildSubject()) + ->line($this->buildDescription()) ->line(__("Vacation type: :type", [ "type" => $type, ])) @@ -74,4 +74,21 @@ class VacationRequestStatusChangedNotification extends Notification ])) ->action(__("Click here for details"), $url); } + + protected function buildSubject(): string + { + return __("Vacation request :title has been :status", [ + "title" => $this->vacationRequest->name, + "status" => $this->vacationRequest->state->label(), + ]); + } + + protected function buildDescription(): string + { + return __("The vacation request :title from user :requester has been :status.", [ + "title" => $this->vacationRequest->name, + "requester" => $this->vacationRequest->user->profile->full_name, + "status" => $this->vacationRequest->state->label(), + ]); + } } diff --git a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php index 109eef9..3f07a1d 100644 --- a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php +++ b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php @@ -23,7 +23,17 @@ class VacationRequestWaitsForApprovalNotification extends Notification public function via(): array { - return ["mail"]; + return ["mail", "slack"]; + } + + public function toSlack(): string + { + $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); + + return implode("\n", [ + $this->buildDescription(), + "<${url}|Zobacz szczegóły>", + ]); } /** diff --git a/app/Domain/Slack/Channels/SlackApiChannel.php b/app/Domain/Slack/Channels/SlackApiChannel.php new file mode 100644 index 0000000..6b40cb0 --- /dev/null +++ b/app/Domain/Slack/Channels/SlackApiChannel.php @@ -0,0 +1,25 @@ +routeNotificationFor('slack', $notification); + + return Http::withToken(config("services.slack.client_token")) + ->post($url, [ + "channel" => $channel, + "text" => $notification->toSlack(), + ]); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/Handlers/CatchAll.php b/app/Domain/Slack/Handlers/CatchAll.php new file mode 100644 index 0000000..c38930f --- /dev/null +++ b/app/Domain/Slack/Handlers/CatchAll.php @@ -0,0 +1,51 @@ +respondToSlack("Nie rozpoznaję tej komendy: `/{$request->command} {$request->text}`"); + + [$command] = explode(' ', $this->request->text ?? ""); + + $alternativeHandlers = $this->findAlternativeHandlers($command); + + if ($alternativeHandlers->count()) { + $response->withAttachment($this->getCommandListAttachment($alternativeHandlers)); + } + + if ($this->containsHelpHandler($alternativeHandlers)) { + $response->withAttachment(Attachment::create() + ->setText("Aby wyświetlić wszystkie komendy, napisz: `/toby pomoc`") + ); + } + + return $response; + } + + protected function getCommandListAttachment(Collection $handlers): Attachment + { + $attachmentFields = $handlers + ->map(function (SignatureHandler $handler) { + return AttachmentField::create($handler->getFullCommand(), $handler->getDescription()); + }) + ->all(); + + return Attachment::create() + ->setColor('warning') + ->setTitle('Czy miałeś na myśli:') + ->setFields($attachmentFields); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/Handlers/DailySummary.php b/app/Domain/Slack/Handlers/DailySummary.php new file mode 100644 index 0000000..b3ae2be --- /dev/null +++ b/app/Domain/Slack/Handlers/DailySummary.php @@ -0,0 +1,74 @@ +with(["user", "vacationRequest"]) + ->whereDate("date", $now) + ->approved() + ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))) + ->get() + ->map(fn(Vacation $vacation) => $vacation->user->profile->full_name); + + /** @var Collection $remoteDays */ + $remoteDays = Vacation::query() + ->with(["user", "vacationRequest"]) + ->whereDate("date", $now) + ->approved() + ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => !$configRetriever->isVacation($type))) + ->get() + ->map(fn(Vacation $vacation) => $vacation->user->profile->full_name); + + $birthdays = User::query() + ->whereRelation("profile", "birthday", $now) + ->get() + ->map(fn(User $user) => $user->profile->full_name); + + $absencesAttachment = Attachment::create() + ->setTitle("Nieobecności :palm_tree:") + ->setColor('#eab308') + ->setText($absences->isNotEmpty() ? $absences->implode("\n") : "Wszyscy dzisiaj pracują :muscle:"); + + $remoteAttachment = Attachment::create() + ->setTitle("Praca zdalna :house_with_garden:") + ->setColor('#d946ef') + ->setText($remoteDays->isNotEmpty() ? $remoteDays->implode("\n") : "Wszyscy dzisiaj są w biurze :boom:"); + + $birthdayAttachment = Attachment::create() + ->setTitle("Urodziny :birthday:") + ->setColor('#3C5F97') + ->setText($birthdays->isNotEmpty() ? $birthdays->implode("\n") : "Dzisiaj nikt nie ma urodzin :cry:"); + + return $this->respondToSlack("Podsumowanie dla dnia {$now->toDisplayString()}") + ->withAttachment($absencesAttachment) + ->withAttachment($remoteAttachment) + ->withAttachment($birthdayAttachment) + ->displayResponseToEveryoneOnChannel(); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/GiveKeysTo.php b/app/Domain/Slack/Handlers/GiveKeysTo.php similarity index 83% rename from app/Domain/Slack/GiveKeysTo.php rename to app/Domain/Slack/Handlers/GiveKeysTo.php index c45597f..bfaf900 100644 --- a/app/Domain/Slack/GiveKeysTo.php +++ b/app/Domain/Slack/Handlers/GiveKeysTo.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Toby\Domain\Slack; +namespace Toby\Domain\Slack\Handlers; use Illuminate\Support\Str; use Spatie\SlashCommand\Request; @@ -13,13 +13,13 @@ use Toby\Eloquent\Models\User; class GiveKeysTo extends SignatureHandler { - protected $signature = "toby klucze:dla {to}"; + protected $signature = "toby klucze:dla {użytkownik}"; - protected $description = "Daj klucze użytkownikowi {to}"; + protected $description = "Daj klucze wskazanemu użytkownikowi"; public function handle(Request $request): Response { - $to = $this->getArgument('to'); + $to = $this->getArgument('użytkownik'); $id = Str::between($to, "@", "|"); diff --git a/app/Domain/Slack/Handlers/Help.php b/app/Domain/Slack/Handlers/Help.php new file mode 100644 index 0000000..45fc7bf --- /dev/null +++ b/app/Domain/Slack/Handlers/Help.php @@ -0,0 +1,45 @@ +findAvailableHandlers(); + + return $this->displayListOfAllCommands($handlers); + } + + protected function displayListOfAllCommands(Collection $handlers): Response + { + $attachmentFields = $handlers + ->sort(function (SignatureHandler $handlerA, SignatureHandler $handlerB) { + return strcmp($handlerA->getFullCommand(), $handlerB->getFullCommand()); + }) + ->map(function (SignatureHandler $handler) { + return AttachmentField::create("/{$handler->getSignature()}", $handler->getDescription()); + }) + ->all(); + + return $this->respondToSlack('Dostępne komendy') + ->withAttachment( + Attachment::create() + ->setColor('good') + ->setFields($attachmentFields) + ); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/HomeOffice.php b/app/Domain/Slack/Handlers/HomeOffice.php similarity index 79% rename from app/Domain/Slack/HomeOffice.php rename to app/Domain/Slack/Handlers/HomeOffice.php index bb0fe1e..87270d8 100644 --- a/app/Domain/Slack/HomeOffice.php +++ b/app/Domain/Slack/Handlers/HomeOffice.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Toby\Domain\Slack; +namespace Toby\Domain\Slack\Handlers; use Illuminate\Support\Carbon; use Spatie\SlashCommand\Request; @@ -15,12 +15,12 @@ use Toby\Eloquent\Models\YearPeriod; class HomeOffice extends SignatureHandler { - protected $signature = "toby zdalnie {date=dzisiaj}"; - protected $description = "Praca zdalna {kiedy}"; + protected $signature = "toby zdalnie {kiedy?}"; + protected $description = "Pracuj zdalnie wybranego dnia (domyślnie dzisiaj)"; public function handle(Request $request): Response { - $date = $this->getDateFromArgument($this->getArgument('date')); + $date = $this->getDateFromArgument($this->getArgument('kiedy') ?? "dzisiaj"); $user = $this->findUserBySlackId($request->userId); $yearPeriod = YearPeriod::findByYear($date->year); @@ -34,7 +34,8 @@ class HomeOffice extends SignatureHandler "flow_skipped" => false, ], $user); - return $this->respondToSlack("Praca zdalna dnia {$date->toDisplayString()} została utworzona pomyślnie"); + return $this->respondToSlack("Praca zdalna dnia {$date->toDisplayString()} została utworzona pomyślnie.") + ->displayResponseToEveryoneOnChannel(); } protected function getDateFromArgument(string $argument): Carbon diff --git a/app/Domain/Slack/Handlers/KeyList.php b/app/Domain/Slack/Handlers/KeyList.php new file mode 100644 index 0000000..ac60a93 --- /dev/null +++ b/app/Domain/Slack/Handlers/KeyList.php @@ -0,0 +1,33 @@ +orderBy("id") + ->get() + ->map(fn(Key $key) => "Klucz nr {$key->id} - <@{$key->user->profile->slack_id}>"); + + return $this->respondToSlack("Lista kluczy") + ->withAttachment( + Attachment::create() + ->setColor('#3C5F97') + ->setText($keys->implode("\n")) + ); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/Handlers/SaySomething.php b/app/Domain/Slack/Handlers/SaySomething.php new file mode 100644 index 0000000..4099172 --- /dev/null +++ b/app/Domain/Slack/Handlers/SaySomething.php @@ -0,0 +1,24 @@ +getArgument("zdanie"); + + return $this->respondToSlack($sentence) + ->displayResponseToEveryoneOnChannel(); + } +} \ No newline at end of file diff --git a/app/Domain/Slack/TakeKeysFrom.php b/app/Domain/Slack/Handlers/TakeKeysFrom.php similarity index 83% rename from app/Domain/Slack/TakeKeysFrom.php rename to app/Domain/Slack/Handlers/TakeKeysFrom.php index 31860ff..eed3a89 100644 --- a/app/Domain/Slack/TakeKeysFrom.php +++ b/app/Domain/Slack/Handlers/TakeKeysFrom.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Toby\Domain\Slack; +namespace Toby\Domain\Slack\Handlers; use Illuminate\Support\Str; use Spatie\SlashCommand\Request; @@ -13,13 +13,13 @@ use Toby\Eloquent\Models\User; class TakeKeysFrom extends SignatureHandler { - protected $signature = "toby klucze:od {from}"; + protected $signature = "toby klucze:od {użytkownik}"; - protected $description = "Zabierz klucze użytkownikowi {from}"; + protected $description = "Zabierz klucze wskazanemu użytkownikowi"; public function handle(Request $request): Response { - $from = $this->getArgument('from'); + $from = $this->getArgument("użytkownik"); $id = Str::between($from, "@", "|"); diff --git a/app/Domain/Slack/KeyList.php b/app/Domain/Slack/KeyList.php deleted file mode 100644 index af1d2c5..0000000 --- a/app/Domain/Slack/KeyList.php +++ /dev/null @@ -1,30 +0,0 @@ -get() as $key) { - $temp[] = "Klucz nr {$key->id} - <@{$key->user->profile->slack_id}>"; - } - - - return $this->respondToSlack(implode("\n", $temp)) - ->displayResponseToEveryoneOnChannel(); - } -} \ No newline at end of file diff --git a/app/Domain/VacationDaysCalculator.php b/app/Domain/VacationDaysCalculator.php index 0e41a4a..2d3227e 100644 --- a/app/Domain/VacationDaysCalculator.php +++ b/app/Domain/VacationDaysCalculator.php @@ -11,9 +11,10 @@ use Toby\Eloquent\Models\YearPeriod; class VacationDaysCalculator { - public function calculateDays(YearPeriod $yearPeriod, CarbonInterface $from, CarbonInterface $to): Collection + public function calculateDays(CarbonInterface $from, CarbonInterface $to): Collection { $period = CarbonPeriod::create($from, $to); + $yearPeriod = YearPeriod::findByYear($from->year); $holidays = $yearPeriod->holidays()->pluck("date"); $validDays = new Collection(); diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index 3fd5429..299e0a5 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -29,7 +29,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); - $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count(); + $estimatedDays = $this->vacationDaysCalculator->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 ae9f58b..4968d4d 100644 --- a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php +++ b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php @@ -16,7 +16,7 @@ class MinimumOneVacationDayRule implements VacationRequestRule public function check(VacationRequest $vacationRequest): bool { return $this->vacationDaysCalculator - ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) + ->calculateDays($vacationRequest->from, $vacationRequest->to) ->isNotEmpty(); } diff --git a/app/Eloquent/Models/Profile.php b/app/Eloquent/Models/Profile.php index df237e9..84bbd7c 100644 --- a/app/Eloquent/Models/Profile.php +++ b/app/Eloquent/Models/Profile.php @@ -19,6 +19,7 @@ use Toby\Eloquent\Helpers\ColorGenerator; * @property string $position * @property EmploymentForm $employment_form * @property Carbon $employment_date + * @property Carbon $birthday */ class Profile extends Model { @@ -32,6 +33,7 @@ class Profile extends Model protected $casts = [ "employment_form" => EmploymentForm::class, "employment_date" => "date", + "birthday" => "date", ]; public function user(): BelongsTo diff --git a/app/Eloquent/Models/User.php b/app/Eloquent/Models/User.php index 4bf2891..2518c95 100644 --- a/app/Eloquent/Models/User.php +++ b/app/Eloquent/Models/User.php @@ -125,6 +125,11 @@ class User extends Authenticatable ); } + public function routeNotificationForSlack() + { + return $this->profile->slack_id; + } + protected static function newFactory(): UserFactory { return UserFactory::new(); diff --git a/app/Infrastructure/Console/Commands/SendDailySummaryToSlack.php b/app/Infrastructure/Console/Commands/SendDailySummaryToSlack.php new file mode 100644 index 0000000..3959be0 --- /dev/null +++ b/app/Infrastructure/Console/Commands/SendDailySummaryToSlack.php @@ -0,0 +1,97 @@ +option("force") && !$this->shouldHandle($now)) { + return; + } + + /** @var Collection $absences */ + $absences = Vacation::query() + ->with(["user", "vacationRequest"]) + ->whereDate("date", $now) + ->approved() + ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))) + ->get() + ->map(fn(Vacation $vacation) => $vacation->user->profile->full_name); + + /** @var Collection $remoteDays */ + $remoteDays = Vacation::query() + ->with(["user", "vacationRequest"]) + ->whereDate("date", $now) + ->approved() + ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => !$configRetriever->isVacation($type))) + ->get() + ->map(fn(Vacation $vacation) => $vacation->user->profile->full_name); + + $birthdays = User::query() + ->whereRelation("profile", "birthday", $now) + ->get() + ->map(fn(User $user) => $user->profile->full_name); + + $absencesAttachment = Attachment::create() + ->setTitle("Nieobecności :palm_tree:") + ->setColor('#eab308') + ->setText($absences->isNotEmpty() ? $absences->implode("\n") : "Wszyscy dzisiaj pracują :muscle:"); + + $remoteAttachment = Attachment::create() + ->setTitle("Praca zdalna :house_with_garden:") + ->setColor('#d946ef') + ->setText($remoteDays->isNotEmpty() ? $remoteDays->implode("\n") : "Wszyscy dzisiaj są w biurze :boom:"); + + $birthdayAttachment = Attachment::create() + ->setTitle("Urodziny :birthday:") + ->setColor('#3C5F97') + ->setText($birthdays->isNotEmpty() ? $birthdays->implode("\n") : "Dzisiaj nikt nie ma urodzin :cry:"); + + $baseUrl = config("services.slack.url"); + $url = "{$baseUrl}/chat.postMessage"; + + Http::withToken(config("services.slack.client_token")) + ->post($url, [ + "channel" => config("services.slack.default_channel"), + "text" => "Podsumowanie dla dnia {$now->toDisplayString()}", + 'attachments' => collect([$absencesAttachment, $remoteAttachment, $birthdayAttachment])->map( + fn(Attachment $attachment) => $attachment->toArray() + )->toArray(), + ]); + } + + protected function shouldHandle(CarbonInterface $day): bool + { + $holidays = Holiday::query()->whereDate("date", $day)->pluck("date"); + + if ($day->isWeekend()) { + return false; + } + + if ($holidays->contains($day)) { + return false; + } + + return true; + } +} diff --git a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php index c204be1..79b0bf9 100644 --- a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php +++ b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php @@ -14,7 +14,7 @@ class CalculateVacationDaysController extends Controller { public function __invoke(CalculateVacationDaysRequest $request, VacationDaysCalculator $calculator): JsonResponse { - $days = $calculator->calculateDays($request->yearPeriod(), $request->from(), $request->to()); + $days = $calculator->calculateDays($request->from(), $request->to()); return new JsonResponse($days->map(fn(Carbon $day) => $day->toDateString())->all()); } diff --git a/app/Infrastructure/Http/Requests/UserRequest.php b/app/Infrastructure/Http/Requests/UserRequest.php index d3f094f..9319121 100644 --- a/app/Infrastructure/Http/Requests/UserRequest.php +++ b/app/Infrastructure/Http/Requests/UserRequest.php @@ -22,6 +22,7 @@ class UserRequest extends FormRequest "position" => ["required"], "employmentForm" => ["required", new Enum(EmploymentForm::class)], "employmentDate" => ["required", "date_format:Y-m-d"], + "birthday" => ["nullable", "date_format:Y-m-d"], ]; } @@ -41,6 +42,7 @@ class UserRequest extends FormRequest "position" => $this->get("position"), "employment_form" => $this->get("employmentForm"), "employment_date" => $this->get("employmentDate"), + "birthday" => $this->get("birthday"), ]; } } diff --git a/app/Infrastructure/Http/Resources/UserFormDataResource.php b/app/Infrastructure/Http/Resources/UserFormDataResource.php index 219a2c9..bade223 100644 --- a/app/Infrastructure/Http/Resources/UserFormDataResource.php +++ b/app/Infrastructure/Http/Resources/UserFormDataResource.php @@ -21,6 +21,7 @@ class UserFormDataResource extends JsonResource "position" => $this->profile->position, "employmentForm" => $this->profile->employment_form, "employmentDate" => $this->profile->employment_date->toDateString(), + "birthday" => $this->profile->birthday->toDateString(), ]; } } diff --git a/config/app.php b/config/app.php index 2c2ab89..6ef5b69 100644 --- a/config/app.php +++ b/config/app.php @@ -38,7 +38,6 @@ return [ Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Barryvdh\DomPDF\ServiceProvider::class, - Spatie\SlashCommand\SlashCommandServiceProvider::class, Toby\Architecture\Providers\AppServiceProvider::class, Toby\Architecture\Providers\AuthServiceProvider::class, Toby\Architecture\Providers\EventServiceProvider::class, diff --git a/config/laravel-slack-slash-command.php b/config/laravel-slack-slash-command.php index f19d56c..51ea7d4 100644 --- a/config/laravel-slack-slash-command.php +++ b/config/laravel-slack-slash-command.php @@ -1,9 +1,15 @@ 'api/slack', @@ -14,7 +20,9 @@ return [ GiveKeysTo::class, KeyList::class, HomeOffice::class, - Spatie\SlashCommand\Handlers\Help::class, - Spatie\SlashCommand\Handlers\CatchAll::class, + DailySummary::class, + SaySomething::class, + Help::class, + CatchAll::class ], ]; diff --git a/config/services.php b/config/services.php index e535c9e..8940ddd 100644 --- a/config/services.php +++ b/config/services.php @@ -8,4 +8,9 @@ return [ "client_secret" => env("GOOGLE_CLIENT_SECRET"), "redirect" => env("GOOGLE_REDIRECT"), ], + "slack" => [ + "url" => "https://slack.com/api", + "client_token" => env("SLACK_CLIENT_TOKEN"), + "default_channel" => env("SLACK_DEFAULT_CHANNEL"), + ], ]; diff --git a/database/factories/ProfileFactory.php b/database/factories/ProfileFactory.php index 706df10..ba96c86 100644 --- a/database/factories/ProfileFactory.php +++ b/database/factories/ProfileFactory.php @@ -23,6 +23,7 @@ class ProfileFactory extends Factory "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), "position" => $this->faker->jobTitle(), "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), + "birthday" => Carbon::createFromInterface($this->faker->dateTimeBetween("1970-01-01", "1998-01-01"))->toDateString(), ]; } } diff --git a/database/migrations/2022_04_21_101027_add_slack_id_column_in_profiles_table.php b/database/migrations/2022_04_21_101027_add_slack_id_and_birthday_columns_in_profiles_table.php similarity index 78% rename from database/migrations/2022_04_21_101027_add_slack_id_column_in_profiles_table.php rename to database/migrations/2022_04_21_101027_add_slack_id_and_birthday_columns_in_profiles_table.php index 2e9e345..893b4ef 100644 --- a/database/migrations/2022_04_21_101027_add_slack_id_column_in_profiles_table.php +++ b/database/migrations/2022_04_21_101027_add_slack_id_and_birthday_columns_in_profiles_table.php @@ -11,13 +11,15 @@ return new class() extends Migration { { Schema::table("profiles", function (Blueprint $table): void { $table->string("slack_id")->nullable(); + $table->date("birthday")->nullable(); }); } public function down(): void { Schema::table("profiles", function (Blueprint $table): void { - $table->string("slack_id"); + $table->dropColumn("slack_id"); + $table->dropColumn("birthday"); }); } }; diff --git a/resources/js/Pages/Users/Create.vue b/resources/js/Pages/Users/Create.vue index 4959b47..9335a1d 100644 --- a/resources/js/Pages/Users/Create.vue +++ b/resources/js/Pages/Users/Create.vue @@ -234,6 +234,29 @@

+
+ +
+ +

+ {{ form.errors.birthday }} +

+
+
+
+ +
+ +

+ {{ form.errors.birthday }} +

+
+
form.value === props.user.employmentForm), employmentDate: props.user.employmentDate, + birthday: props.user.birthday, }) function editUser() { diff --git a/resources/lang/pl.json b/resources/lang/pl.json index d792d41..f755a40 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -56,7 +56,7 @@ "All rights reserved.": "Wszelkie prawa zastrzeżone", "Show vacation request": "Pokaż wniosek", "Vacation request :title has been created" : "Wniosek :title został utworzony", - "The vacation request :title has been created correctly in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title.", + "The vacation request :title from user :requester has been created sucessfully.": "Wniosek :title użytkownika :requester został utworzony pomyślnie.", "Vacation type: :type": "Rodzaj wniosku: :type", "From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)", "Click here for details": "Kliknij, aby zobaczyć szczegóły", @@ -67,7 +67,7 @@ "Vacation request :title has been :status": "Wniosek :title został :status", "The vacation request :title from user :requester has been :status.": "Wniosek urlopowy :title użytkownika :requester został :status.", "Vacation request :title has been created on your behalf": "Wniosek urlopowy :title został utworzony w Twoim imieniu", - "The vacation request :title has been created correctly by user :creator on your behalf in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title w Twoim imieniu przez użytkownika :creator.", + "The vacation request :title has been created successfully by user :creator on your behalf.": "Wniosek urlopowy :title został pomyślnie utworzony w Twoim imieniu przez użytkownika :creator.", "Key no :number has been created.": "Klucz nr :number został utworzony.", "Key no :number has been deleted.": "Klucz nr :number został usunięty.", "Key no :number has been taken from :user.": "Klucz nr :number został zabrany użytkownikowi :user.", diff --git a/tests/Unit/SendDailySummaryToSlackTest.php b/tests/Unit/SendDailySummaryToSlackTest.php new file mode 100644 index 0000000..68cac03 --- /dev/null +++ b/tests/Unit/SendDailySummaryToSlackTest.php @@ -0,0 +1,82 @@ +createCurrentYearPeriod(); + } + + public function testCommandSendsMessageToSlackIfWeekday(): void + { + $weekDay = Carbon::create(2022, 4, 22); + $this->assertTrue($weekDay->isWeekday()); + + $this->travelTo($weekDay); + + $this->artisan(SendDailySummaryToSlack::class) + ->execute(); + + Http::assertSentCount(1); + } + + public function testCommandDoesntSendIfIsWeekend(): void + { + $weekend = Carbon::create(2022, 4, 23); + $this->assertTrue($weekend->isWeekend()); + + $this->travelTo($weekend); + + $this->artisan(SendDailySummaryToSlack::class) + ->execute(); + + Http::assertNothingSent(); + } + + public function testCommandDoesntSendIfIsHoliday(): void + { + $holiday = Holiday::factory(["date" => Carbon::create(2022, 4, 22)])->create(); + + $this->assertDatabaseHas("holidays", [ + "date" => $holiday->date->toDateString(), + ]); + + $this->travelTo(Carbon::create(2022, 4, 22)); + + $this->artisan(SendDailySummaryToSlack::class) + ->execute(); + + Http::assertNothingSent(); + } + + public function testCommandForceSendsMessageEvenIsWeekendOrHoliday(): void + { + $weekend = Carbon::create(2022, 4, 23); + $this->assertTrue($weekend->isWeekend()); + + $this->travelTo($weekend); + + $this->artisan(SendDailySummaryToSlack::class, ["--force" => true]) + ->execute(); + + Http::assertSentCount(1); + } +} \ No newline at end of file