From 39e69731a6fc1d6b933d5ec17f26b451fd3c274a Mon Sep 17 00:00:00 2001 From: EwelinaSkrzypacz Date: Thu, 7 Sep 2023 11:47:18 +0200 Subject: [PATCH] #282 - wip --- ...ionRequestWaitsForApprovalNotification.php | 39 ++++++ .../Slack/Elements/KeysAttachment.php | 14 +- .../Slack/SlackActionController.php | 120 ++++++++++++++++++ ...troller.php => SlackCommandController.php} | 7 +- lang/pl.json | 16 ++- routes/api.php | 6 +- 6 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 app/Infrastructure/Slack/SlackActionController.php rename app/Infrastructure/Slack/{Controller.php => SlackCommandController.php} (87%) diff --git a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php index 73935bd2..f390ec98 100644 --- a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php +++ b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php @@ -4,10 +4,49 @@ namespace Toby\Domain\Notifications; +use Spatie\SlashCommand\AttachmentAction; +use Toby\Domain\States\VacationRequest\WaitingForAdministrative; use Toby\Domain\States\VacationRequest\WaitingForTechnical; +use Toby\Infrastructure\Slack\Elements\Attachment; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class VacationRequestWaitsForApprovalNotification extends VacationRequestNotification { + public function toSlack(): SlackMessage + { + $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); + $seeDetails = __("See details"); + + if ($this->vacationRequest->state->equals(WaitingForTechnical::class)) { + $actions = [ + AttachmentAction::create("technical_approval", __("Approve"), "button") + ->setValue("technical_approval") + ->setStyle("primary"), + AttachmentAction::create("reject", __("Reject"), "button") + ->setValue("reject") + ->setStyle("danger"), + ]; + } elseif ($this->vacationRequest->state->equals(WaitingForAdministrative::class)) { + $actions = [ + AttachmentAction::create("administrative_approval", __("Approve"), "button") + ->setValue("administrative_approval") + ->setStyle("primary"), + AttachmentAction::create("reject", __("Reject"), "button") + ->setValue("reject") + ->setStyle("danger"), + ]; + } + + $attachment = Attachment::create() + ->setCallbackId((string)$this->vacationRequest->id) + ->setText(__("Available actions:")) + ->setActions($actions); + + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <{$url}|{$seeDetails}>") + ->withAttachment($attachment); + } + protected function buildDescription(): string { $title = $this->vacationRequest->name; diff --git a/app/Infrastructure/Slack/Elements/KeysAttachment.php b/app/Infrastructure/Slack/Elements/KeysAttachment.php index ef62d990..079a62ca 100644 --- a/app/Infrastructure/Slack/Elements/KeysAttachment.php +++ b/app/Infrastructure/Slack/Elements/KeysAttachment.php @@ -17,14 +17,22 @@ public function __construct(Collection $keys) ->setColor("#3c5f97") ->setItems($keys->map(function (Key $key): string { if ($key->user === null) { - return "Klucz nr {$key->id} - jest w biurze"; + return __("Key no. :number - is in the office.", [ + "number" => $key->id, + ]); } if ($key->user->profile->slack_id === null) { - return "Klucz nr {$key->id} - {$key->user->profile->full_name}"; + return __("Key no. :number - :user", [ + "number" => $key->id, + "user" => $key->user->profile->full_name, + ]); } - return "Klucz nr {$key->id} - <@{$key->user->profile->slack_id}>"; + return __("Key no. :number - :user", [ + "number" => $key->id, + "user" => "<@{$key->user->profile->slack_id}>", + ]); })) ->setEmptyText(__("There are no keys in toby")); } diff --git a/app/Infrastructure/Slack/SlackActionController.php b/app/Infrastructure/Slack/SlackActionController.php new file mode 100644 index 00000000..64caef70 --- /dev/null +++ b/app/Infrastructure/Slack/SlackActionController.php @@ -0,0 +1,120 @@ +verifyWithSigning($request); + + $payload = json_decode($request->payload, true); + + $userSlackId = $payload["user"]["id"]; + + $user = $this->findUserBySlackId($userSlackId); + + $vacationRequestId = $payload["callback_id"]; + + $action = $payload["actions"][0]["value"]; + + $vacationRequest = VacationRequest::query()->findOrFail($vacationRequestId); + + switch ($action) { + case "technical_approval": + if ($user->cannot("acceptAsTechApprover", $vacationRequest)) { + return $this->prepareAuthorizationError(); + } + + if (!$vacationRequest->state->equals(WaitingForTechnical::class)) { + return $this->prepareActionError($vacationRequest); + } + $acceptAsTechnical->execute($vacationRequest, $user); + $responseText = __("The request :title from user :requester has been approved by you as a technical approver.", [ + "title" => $vacationRequest->name, + "requester" => $vacationRequest->user->profile->full_name, + "technical" => $user->profile->full_name, + ]); + + break; + case "administrative_approval": + if ($user->cannot("acceptAsAdminApprover", $vacationRequest)) { + return $this->prepareAuthorizationError(); + } + + if (!$vacationRequest->state->equals(WaitingForAdministrative::class)) { + return $this->prepareActionError($vacationRequest); + } + $acceptAsAdministrative->execute($vacationRequest, $user); + $responseText = __("The request :title from user :requester has been approved by you as an administrative approver.", [ + "title" => $vacationRequest->name, + "requester" => $vacationRequest->user->profile->full_name, + ]); + + break; + case "reject": + if ($user->cannot("reject", $vacationRequest)) { + return $this->prepareAuthorizationError(); + } + + if (!$vacationRequest->state->equals(WaitingForTechnical::class, WaitingForAdministrative::class)) { + return $this->prepareActionError($vacationRequest); + } + $reject->execute($vacationRequest, $user); + $responseText = __("The request :title from user :requester has been rejected by you.", [ + "title" => $vacationRequest->name, + "requester" => $vacationRequest->user->profile->full_name, + ]); + + break; + default: + return $this->prepareUnrecognizedActionError(); + } + + return response()->json([ + "text" => $responseText, + ]); + } + + protected function prepareAuthorizationError(): JsonResponse + { + return response()->json([ + "text" => __("You do not have permission to perform this action."), + ]); + } + + protected function prepareActionError(VacationRequest $vacationRequest): JsonResponse + { + return response()->json([ + "text" => __("You cannot perform this action because the current status of the request :title by user :requester is :status.", [ + "title" => $vacationRequest->name, + "requester" => $vacationRequest->user->profile->full_name, + "status" => $vacationRequest->state->label(), + ]), + ]); + } + + protected function prepareUnrecognizedActionError(): JsonResponse + { + return response()->json([ + "text" => __("Unrecognized action."), + ]); + } +} diff --git a/app/Infrastructure/Slack/Controller.php b/app/Infrastructure/Slack/SlackCommandController.php similarity index 87% rename from app/Infrastructure/Slack/Controller.php rename to app/Infrastructure/Slack/SlackCommandController.php index a3467bbe..87e94b81 100644 --- a/app/Infrastructure/Slack/Controller.php +++ b/app/Infrastructure/Slack/SlackCommandController.php @@ -16,7 +16,7 @@ use Spatie\SlashCommand\Exceptions\SlackSlashCommandException; use Spatie\SlashCommand\Response; -class Controller extends SlackController +class SlackCommandController extends SlackController { /** * @throws InvalidRequest|RequestCouldNotBeHandled @@ -50,7 +50,10 @@ protected function prepareValidationResponse(ValidationException $exception): Re ); return Response::create($this->request) - ->withText(":x: Polecenie `/{$this->request->command} {$this->request->text}` jest niepoprawne:") + ->withText(__(":x: Command /:command :text is incorrect:", [ + "command" => $this->request->command, + "text" => $this->request->text, + ])) ->withAttachments($errors->all()); } } diff --git a/lang/pl.json b/lang/pl.json index 6e76379f..c03ca6d8 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -143,5 +143,19 @@ "The deadline for OHS training for some employees is about to expire.": "Niedługo mija termin szkolenia BHP dla części pracowników.", "Below is a list of employees with upcoming OHS training:": "Poniżej znajduje się lista pracowników ze zbliżającym się terminem szkolenia BHP:", "The deadline for OHS training for some employees has passed.": "Termin szkolenia BHP dla części pracowników minął.", - "Below is a list of employees with overdue OHS training:": "Poniżej znajduje się lista pracowników z przeterminowanym szkoleniem BHP:" + "Below is a list of employees with overdue OHS training:": "Poniżej znajduje się lista pracowników z przeterminowanym szkoleniem BHP:", + "Approve": "Zaakceptuj", + "Reject": "Odrzuć", + "Available actions:": "Dostępne akcje:", + "The request :title from user :requester has been approved by you as a technical approver.": "Wniosek :title użytkownika :requester został zaakceptowany przez Ciebie jako przełożonego technicznego.", + "The request :title from user :requester has been approved by you as an administrative approver.": "Wniosek :title użytkownika :requester został zaakceptowany przez Ciebie jako przełożonego administracyjnego.", + "The request :title from user :requester has been rejected by you.": "Wniosek :title użytkownika :requester został odrzucony przez Ciebie.", + "Unrecognized action": "Nieznana akcja", + ":x: Command /:command :text is incorrect:": ":x: Polecenie /:command :text jest niepoprawne:", + "Key no. :number - is in the office.": "Klucz nr :number - jest w biurze", + "Key no. :number - :user": "Klucz nr :number - :user", + "waiting_for_administrative": "czeka na akceptację od przełożonego administracyjnego", + "You do not have permission to perform this action.": "Nie masz uprawnień, aby wykonać tę akcję.", + "waiting_for_technical": "czeka na akceptację od przełożonego technicznego", + "You cannot perform this action because the current status of the request :title by user :requester is :status.": "Nie możesz wykonać tej akcji, ponieważ aktualny status wniosku :title użytkownika :requester to :status" } diff --git a/routes/api.php b/routes/api.php index 7fa9ef11..af3ce033 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,9 +7,11 @@ 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 SlackCommandController; +use Toby\Infrastructure\Slack\SlackActionController; +use Toby\Infrastructure\Slack\SlackCommandController; -Route::post("slack", [SlackCommandController::class, "getResponse"]); +Route::post("slack/commands", [SlackCommandController::class, "getResponse"]); +Route::post("slack/actions", [SlackActionController::class, "handleVacationRequestAction"]); Route::middleware("auth:sanctum")->group(function (): void { Route::post("vacation/calculate-days", CalculateVacationDaysController::class);