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 @@
+
+
+ Data urodzenia
+
+
+
+
+ {{ form.errors.birthday }}
+
+
+
+
+
+ Data urodzenia
+
+
+
+
+ {{ 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