Merge branch 'main' into #134-fill-users-data-for-resume
# Conflicts: # .eslintrc.js # composer.lock # package-lock.json # package.json
This commit is contained in:
		| @@ -60,3 +60,8 @@ GOOGLE_CLIENT_SECRET= | |||||||
| GOOGLE_REDIRECT=http://localhost/login/google/end | GOOGLE_REDIRECT=http://localhost/login/google/end | ||||||
| GOOGLE_CALENDAR_ID= | GOOGLE_CALENDAR_ID= | ||||||
| LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= | LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= | ||||||
|  |  | ||||||
|  | SLACK_URL=https://slack.com/api | ||||||
|  | SLACK_CLIENT_TOKEN= | ||||||
|  | SLACK_SIGNING_SECRET= | ||||||
|  | SLACK_DEFAULT_CHANNEL="#general" | ||||||
|   | |||||||
| @@ -15,5 +15,6 @@ module.exports = { | |||||||
|     'comma-dangle': ['error', 'always-multiline'], |     'comma-dangle': ['error', 'always-multiline'], | ||||||
|     'object-curly-spacing': ['error', 'always'], |     'object-curly-spacing': ['error', 'always'], | ||||||
|     'vue/require-default-prop': 0, |     'vue/require-default-prop': 0, | ||||||
|  |     'vue/multi-word-component-names': 0, | ||||||
|   }, |   }, | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | name: Deploy | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     tags: | ||||||
|  |       - v* | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   deploy: | ||||||
|  |     runs-on: ubuntu-20.04 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: akhileshns/heroku-deploy@v3.12.12 | ||||||
|  |         with: | ||||||
|  |           heroku_api_key: ${{secrets.HEROKU_API_KEY}} | ||||||
|  |           heroku_app_name: ${{secrets.HEROKU_APP_NAME}} | ||||||
|  |           heroku_email: ${{secrets.HEROKU_EMAIL}} | ||||||
| @@ -4,13 +4,24 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Architecture\Providers; | namespace Toby\Architecture\Providers; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Foundation\Application; | ||||||
|  | use Illuminate\Notifications\ChannelManager; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Facades\Notification; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
|  | use Toby\Infrastructure\Slack\Channels\SlackApiChannel; | ||||||
|  |  | ||||||
| class AppServiceProvider extends ServiceProvider | class AppServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|  |     public function register(): void | ||||||
|  |     { | ||||||
|  |         Notification::resolved(function (ChannelManager $service): void { | ||||||
|  |             $service->extend("slack", fn(Application $app): SlackApiChannel => $app->make(SlackApiChannel::class)); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
|     { |     { | ||||||
|         Carbon::macro("toDisplayString", fn() => $this->translatedFormat("d.m.Y")); |         Carbon::macro("toDisplayString", fn(): string => $this->translatedFormat("d.m.Y")); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -30,10 +30,10 @@ class AuthServiceProvider extends ServiceProvider | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         Gate::define("manageUsers", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageUsers", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("manageHolidays", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageHolidays", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("manageVacationLimits", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageVacationLimits", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("generateTimesheet", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("generateTimesheet", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("listMonthlyUsage", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("listMonthlyUsage", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,6 +28,6 @@ class RouteServiceProvider extends ServiceProvider | |||||||
|  |  | ||||||
|     protected function configureRateLimiting(): void |     protected function configureRateLimiting(): void | ||||||
|     { |     { | ||||||
|         RateLimiter::for("api", fn(Request $request) => Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip())); |         RateLimiter::for("api", fn(Request $request): Limit => Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip())); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,10 +6,10 @@ namespace Toby\Domain\Actions\VacationRequest; | |||||||
|  |  | ||||||
| use Illuminate\Validation\ValidationException; | use Illuminate\Validation\ValidationException; | ||||||
| use Toby\Domain\Notifications\VacationRequestCreatedNotification; | use Toby\Domain\Notifications\VacationRequestCreatedNotification; | ||||||
| use Toby\Domain\VacationDaysCalculator; |  | ||||||
| use Toby\Domain\VacationRequestStateManager; | use Toby\Domain\VacationRequestStateManager; | ||||||
| use Toby\Domain\VacationTypeConfigRetriever; | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
| use Toby\Domain\Validation\VacationRequestValidator; | use Toby\Domain\Validation\VacationRequestValidator; | ||||||
|  | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| @@ -19,7 +19,7 @@ class CreateAction | |||||||
|         protected VacationRequestStateManager $stateManager, |         protected VacationRequestStateManager $stateManager, | ||||||
|         protected VacationRequestValidator $vacationRequestValidator, |         protected VacationRequestValidator $vacationRequestValidator, | ||||||
|         protected VacationTypeConfigRetriever $configRetriever, |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|         protected VacationDaysCalculator $vacationDaysCalculator, |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|         protected WaitForTechApprovalAction $waitForTechApprovalAction, |         protected WaitForTechApprovalAction $waitForTechApprovalAction, | ||||||
|         protected WaitForAdminApprovalAction $waitForAdminApprovalAction, |         protected WaitForAdminApprovalAction $waitForAdminApprovalAction, | ||||||
|         protected ApproveAction $approveAction, |         protected ApproveAction $approveAction, | ||||||
| @@ -52,11 +52,7 @@ class CreateAction | |||||||
|  |  | ||||||
|         $vacationRequest->save(); |         $vacationRequest->save(); | ||||||
|  |  | ||||||
|         $days = $this->vacationDaysCalculator->calculateDays( |         $days = $this->workDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); | ||||||
|             $vacationRequest->yearPeriod, |  | ||||||
|             $vacationRequest->from, |  | ||||||
|             $vacationRequest->to, |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         foreach ($days as $day) { |         foreach ($days as $day) { | ||||||
|             $vacationRequest->vacations()->create([ |             $vacationRequest->vacations()->create([ | ||||||
|   | |||||||
| @@ -57,6 +57,6 @@ class CalendarGenerator | |||||||
|             ->approved() |             ->approved() | ||||||
|             ->with("vacationRequest") |             ->with("vacationRequest") | ||||||
|             ->get() |             ->get() | ||||||
|             ->groupBy(fn(Vacation $vacation) => $vacation->date->toDateString()); |             ->groupBy(fn(Vacation $vacation): string => $vacation->date->toDateString()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								app/Domain/DailySummaryRetriever.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								app/Domain/DailySummaryRetriever.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
|  |  | ||||||
|  | class DailySummaryRetriever | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function getAbsences(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return Vacation::query() | ||||||
|  |             ->with(["user", "vacationRequest"]) | ||||||
|  |             ->whereDate("date", $date) | ||||||
|  |             ->approved() | ||||||
|  |             ->whereTypes( | ||||||
|  |                 VacationType::all()->filter(fn(VacationType $type): bool => $this->configRetriever->isVacation($type)), | ||||||
|  |             ) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getRemoteDays(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return Vacation::query() | ||||||
|  |             ->with(["user", "vacationRequest"]) | ||||||
|  |             ->whereDate("date", $date) | ||||||
|  |             ->approved() | ||||||
|  |             ->whereTypes( | ||||||
|  |                 VacationType::all()->filter(fn(VacationType $type): bool => !$this->configRetriever->isVacation($type)), | ||||||
|  |             ) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getBirthdays(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return User::query() | ||||||
|  |             ->whereRelation("profile", "birthday", $date) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -21,7 +21,7 @@ enum EmploymentForm: string | |||||||
|         $cases = collect(EmploymentForm::cases()); |         $cases = collect(EmploymentForm::cases()); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(EmploymentForm $enum) => [ |             fn(EmploymentForm $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ enum Role: string | |||||||
|         $cases = collect(Role::cases()); |         $cases = collect(Role::cases()); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(Role $enum) => [ |             fn(Role $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ enum VacationType: string | |||||||
|         $cases = VacationType::all(); |         $cases = VacationType::all(); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(VacationType $enum) => [ |             fn(VacationType $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								app/Domain/Notifications/Channels.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Domain/Notifications/Channels.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | class Channels | ||||||
|  | { | ||||||
|  |     public const MAIL = "mail"; | ||||||
|  |     public const SLACK = "slack"; | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected User $sender, | ||||||
|  |         protected User $recipient, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(Notifiable $notifiable): SlackMessage | ||||||
|  |     { | ||||||
|  |         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 | ||||||
|  |     { | ||||||
|  |         if ($user->profile->slack_id !== null) { | ||||||
|  |             return "<@{$user->profile->slack_id}>"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->profile->full_name; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected User $recipient, | ||||||
|  |         protected User $sender, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(Notifiable $notifiable): SlackMessage | ||||||
|  |     { | ||||||
|  |         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 | ||||||
|  |     { | ||||||
|  |         if ($user->profile->slack_id !== null) { | ||||||
|  |             return "<@{$user->profile->slack_id}>"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->profile->full_name; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								app/Domain/Notifications/Notifiable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/Notifications/Notifiable.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | interface Notifiable | ||||||
|  | { | ||||||
|  |     public function notify($instance); | ||||||
|  | } | ||||||
| @@ -9,6 +9,7 @@ use Illuminate\Notifications\Messages\MailMessage; | |||||||
| use Illuminate\Notifications\Notification; | use Illuminate\Notifications\Notification; | ||||||
| use InvalidArgumentException; | use InvalidArgumentException; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
| class VacationRequestCreatedNotification extends Notification | class VacationRequestCreatedNotification extends Notification | ||||||
| { | { | ||||||
| @@ -20,7 +21,15 @@ class VacationRequestCreatedNotification extends Notification | |||||||
|  |  | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |  | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -46,19 +55,25 @@ class VacationRequestCreatedNotification extends Notification | |||||||
|         $days = $this->vacationRequest->vacations()->count(); |         $days = $this->vacationRequest->vacations()->count(); | ||||||
|  |  | ||||||
|         return (new MailMessage()) |         return (new MailMessage()) | ||||||
|             ->greeting(__("Hi :user!", [ |             ->greeting( | ||||||
|                 "user" => $user, |                 __("Hi :user!", [ | ||||||
|             ])) |                     "user" => $user, | ||||||
|  |                 ]), | ||||||
|  |             ) | ||||||
|             ->subject($this->buildSubject()) |             ->subject($this->buildSubject()) | ||||||
|             ->line($this->buildDescription()) |             ->line($this->buildDescription()) | ||||||
|             ->line(__("Vacation type: :type", [ |             ->line( | ||||||
|                 "type" => $type, |                 __("Vacation type: :type", [ | ||||||
|             ])) |                     "type" => $type, | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |                 ]), | ||||||
|                 "from" => $from, |             ) | ||||||
|                 "to" => $to, |             ->line( | ||||||
|                 "days" => $days, |                 __("From :from to :to (number of days: :days)", [ | ||||||
|             ])) |                     "from" => $from, | ||||||
|  |                     "to" => $to, | ||||||
|  |                     "days" => $days, | ||||||
|  |                 ]), | ||||||
|  |             ) | ||||||
|             ->action(__("Click here for details"), $url); |             ->action(__("Click here for details"), $url); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -80,18 +95,16 @@ class VacationRequestCreatedNotification extends Notification | |||||||
|     protected function buildDescription(): string |     protected function buildDescription(): string | ||||||
|     { |     { | ||||||
|         $name = $this->vacationRequest->name; |         $name = $this->vacationRequest->name; | ||||||
|         $appName = config("app.name"); |  | ||||||
|  |  | ||||||
|         if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) { |         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 from user :user has been created successfully.", [ | ||||||
|  |                 "user" => $this->vacationRequest->user->profile->full_name, | ||||||
|                 "title" => $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, |             "title" => $this->vacationRequest->name, | ||||||
|             "appName" => $appName, |  | ||||||
|             "creator" => $this->vacationRequest->creator->profile->full_name, |             "creator" => $this->vacationRequest->creator->profile->full_name, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ use Illuminate\Notifications\Notification; | |||||||
| use InvalidArgumentException; | use InvalidArgumentException; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
| class VacationRequestStatusChangedNotification extends Notification | class VacationRequestStatusChangedNotification extends Notification | ||||||
| { | { | ||||||
| @@ -22,7 +23,15 @@ class VacationRequestStatusChangedNotification extends Notification | |||||||
|  |  | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |  | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -43,27 +52,17 @@ class VacationRequestStatusChangedNotification extends Notification | |||||||
|     protected function buildMailMessage(string $url): MailMessage |     protected function buildMailMessage(string $url): MailMessage | ||||||
|     { |     { | ||||||
|         $user = $this->user->profile->first_name; |         $user = $this->user->profile->first_name; | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |         $type = $this->vacationRequest->type->label(); | ||||||
|         $status = $this->vacationRequest->state->label(); |  | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |         $from = $this->vacationRequest->from->toDisplayString(); | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |         $to = $this->vacationRequest->to->toDisplayString(); | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |         $days = $this->vacationRequest->vacations()->count(); | ||||||
|         $requester = $this->vacationRequest->user->profile->full_name; |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |         return (new MailMessage()) | ||||||
|             ->greeting(__("Hi :user!", [ |             ->greeting(__("Hi :user!", [ | ||||||
|                 "user" => $user, |                 "user" => $user, | ||||||
|             ])) |             ])) | ||||||
|             ->subject(__("Vacation request :title has been :status", [ |             ->subject($this->buildSubject()) | ||||||
|                 "title" => $title, |             ->line($this->buildDescription()) | ||||||
|                 "status" => $status, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title from user :requester has been :status.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|                 "status" => $status, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |             ->line(__("Vacation type: :type", [ | ||||||
|                 "type" => $type, |                 "type" => $type, | ||||||
|             ])) |             ])) | ||||||
| @@ -74,4 +73,21 @@ class VacationRequestStatusChangedNotification extends Notification | |||||||
|             ])) |             ])) | ||||||
|             ->action(__("Click here for details"), $url); |             ->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(), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ use InvalidArgumentException; | |||||||
| use Toby\Domain\States\VacationRequest\WaitingForTechnical; | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
| class VacationRequestWaitsForApprovalNotification extends Notification | class VacationRequestWaitsForApprovalNotification extends Notification | ||||||
| { | { | ||||||
| @@ -23,7 +24,15 @@ class VacationRequestWaitsForApprovalNotification extends Notification | |||||||
|  |  | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |  | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Notifications\Messages\MailMessage; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\VacationRequestsAttachment; | ||||||
|  |  | ||||||
|  | class VacationRequestsSummaryNotification extends Notification | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected Carbon $day, | ||||||
|  |         protected Collection $vacationRequests, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -26,7 +26,7 @@ class PolishHolidaysRetriever | |||||||
|  |  | ||||||
|     protected function prepareHolidays(array $holidays): Collection |     protected function prepareHolidays(array $holidays): Collection | ||||||
|     { |     { | ||||||
|         return collect($holidays)->map(fn(Holiday $holiday) => [ |         return collect($holidays)->map(fn(Holiday $holiday): array => [ | ||||||
|             "name" => $holiday->getName([static::LANG_KEY]), |             "name" => $holiday->getName([static::LANG_KEY]), | ||||||
|             "date" => Carbon::createFromTimestamp($holiday->getTimestamp()), |             "date" => Carbon::createFromTimestamp($holiday->getTimestamp()), | ||||||
|         ])->values(); |         ])->values(); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ class TimesheetExport implements WithMultipleSheets | |||||||
|     public function sheets(): array |     public function sheets(): array | ||||||
|     { |     { | ||||||
|         return $this->users |         return $this->users | ||||||
|             ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month, $this->types)) |             ->map(fn(User $user): TimesheetPerUserSheet => new TimesheetPerUserSheet($user, $this->month, $this->types)) | ||||||
|             ->toArray(); |             ->toArray(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -193,8 +193,8 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With | |||||||
|             ->get() |             ->get() | ||||||
|             ->groupBy( |             ->groupBy( | ||||||
|                 [ |                 [ | ||||||
|                     fn(Vacation $vacation) => $vacation->date->toDateString(), |                     fn(Vacation $vacation): string => $vacation->date->toDateString(), | ||||||
|                     fn(Vacation $vacation) => $vacation->vacationRequest->type->value, |                     fn(Vacation $vacation): string => $vacation->vacationRequest->type->value, | ||||||
|                 ], |                 ], | ||||||
|             ); |             ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -42,8 +42,8 @@ class UserVacationStatsRetriever | |||||||
|                     ->states(VacationRequestStatesRetriever::successStates()), |                     ->states(VacationRequestStatesRetriever::successStates()), | ||||||
|             ) |             ) | ||||||
|             ->get() |             ->get() | ||||||
|             ->groupBy(fn(Vacation $vacation) => strtolower($vacation->date->englishMonth)) |             ->groupBy(fn(Vacation $vacation): string => strtolower($vacation->date->englishMonth)) | ||||||
|             ->map(fn(Collection $items) => $items->count()); |             ->map(fn(Collection $items): int => $items->count()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int |     public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int | ||||||
| @@ -107,13 +107,13 @@ class UserVacationStatsRetriever | |||||||
|     { |     { | ||||||
|         $types = VacationType::all(); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function getNotLimitableVacationTypes(): Collection |     protected function getNotLimitableVacationTypes(): Collection | ||||||
|     { |     { | ||||||
|         $types = VacationType::all(); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => !$this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => !$this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,9 +7,9 @@ namespace Toby\Domain\Validation\Rules; | |||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
| use Toby\Domain\VacationDaysCalculator; |  | ||||||
| use Toby\Domain\VacationRequestStatesRetriever; | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
| use Toby\Domain\VacationTypeConfigRetriever; | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| @@ -18,7 +18,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected VacationTypeConfigRetriever $configRetriever, |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|         protected VacationDaysCalculator $vacationDaysCalculator, |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function check(VacationRequest $vacationRequest): bool |     public function check(VacationRequest $vacationRequest): bool | ||||||
| @@ -29,7 +29,9 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
|  |  | ||||||
|         $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); |         $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); | ||||||
|         $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); |         $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); | ||||||
|         $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count(); |         $estimatedDays = $this->workDaysCalculator | ||||||
|  |             ->calculateDays($vacationRequest->from, $vacationRequest->to) | ||||||
|  |             ->count(); | ||||||
|  |  | ||||||
|         return $limit >= ($vacationDays + $estimatedDays); |         return $limit >= ($vacationDays + $estimatedDays); | ||||||
|     } |     } | ||||||
| @@ -64,6 +66,6 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
|     { |     { | ||||||
|         $types = VacationType::all(); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,19 +4,19 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class MinimumOneVacationDayRule implements VacationRequestRule | class MinimumOneVacationDayRule implements VacationRequestRule | ||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected VacationDaysCalculator $vacationDaysCalculator, |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function check(VacationRequest $vacationRequest): bool |     public function check(VacationRequest $vacationRequest): bool | ||||||
|     { |     { | ||||||
|         return $this->vacationDaysCalculator |         return $this->workDaysCalculator | ||||||
|             ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) |             ->calculateDays($vacationRequest->from, $vacationRequest->to) | ||||||
|             ->isNotEmpty(); |             ->isNotEmpty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ class VacationTypeCanBeSelected implements VacationRequestRule | |||||||
|         $employmentForm = $vacationRequest->user->profile->employment_form; |         $employmentForm = $vacationRequest->user->profile->employment_form; | ||||||
|  |  | ||||||
|         $availableTypes = VacationType::all() |         $availableTypes = VacationType::all() | ||||||
|             ->filter(fn(VacationType $type) => $this->configRetriever->isAvailableFor($type, $employmentForm)); |             ->filter(fn(VacationType $type): bool => $this->configRetriever->isAvailableFor($type, $employmentForm)); | ||||||
|  |  | ||||||
|         return $availableTypes->contains($vacationRequest->type); |         return $availableTypes->contains($vacationRequest->type); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -9,11 +9,12 @@ use Carbon\CarbonPeriod; | |||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| 
 | 
 | ||||||
| class VacationDaysCalculator | class WorkDaysCalculator | ||||||
| { | { | ||||||
|     public function calculateDays(YearPeriod $yearPeriod, CarbonInterface $from, CarbonInterface $to): Collection |     public function calculateDays(CarbonInterface $from, CarbonInterface $to): Collection | ||||||
|     { |     { | ||||||
|         $period = CarbonPeriod::create($from, $to); |         $period = CarbonPeriod::create($from, $to); | ||||||
|  |         $yearPeriod = YearPeriod::findByYear($from->year); | ||||||
|         $holidays = $yearPeriod->holidays()->pluck("date"); |         $holidays = $yearPeriod->holidays()->pluck("date"); | ||||||
| 
 | 
 | ||||||
|         $validDays = new Collection(); |         $validDays = new Collection(); | ||||||
| @@ -35,7 +35,7 @@ class YearPeriodRetriever | |||||||
|  |  | ||||||
|         $years = YearPeriod::all(); |         $years = YearPeriod::all(); | ||||||
|  |  | ||||||
|         $navigation = $years->map(fn(YearPeriod $yearPeriod) => $this->toNavigation($yearPeriod)); |         $navigation = $years->map(fn(YearPeriod $yearPeriod): array => $this->toNavigation($yearPeriod)); | ||||||
|  |  | ||||||
|         return [ |         return [ | ||||||
|             "current" => $this->toNavigation($current), |             "current" => $this->toNavigation($current), | ||||||
|   | |||||||
| @@ -8,14 +8,17 @@ use Database\Factories\KeyFactory; | |||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | use Toby\Domain\Notifications\Notifiable as NotifiableInterface; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
|  * @property User $user |  * @property User $user | ||||||
|  */ |  */ | ||||||
| class Key extends Model | class Key extends Model implements NotifiableInterface | ||||||
| { | { | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
|  |     use Notifiable; | ||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
| @@ -24,6 +27,11 @@ class Key extends Model | |||||||
|         return $this->belongsTo(User::class); |         return $this->belongsTo(User::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function routeNotificationForSlack(): string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.default_channel"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): KeyFactory |     protected static function newFactory(): KeyFactory | ||||||
|     { |     { | ||||||
|         return KeyFactory::new(); |         return KeyFactory::new(); | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ use Toby\Eloquent\Helpers\ColorGenerator; | |||||||
|  * @property string $position |  * @property string $position | ||||||
|  * @property EmploymentForm $employment_form |  * @property EmploymentForm $employment_form | ||||||
|  * @property Carbon $employment_date |  * @property Carbon $employment_date | ||||||
|  |  * @property Carbon $birthday | ||||||
|  */ |  */ | ||||||
| class Profile extends Model | class Profile extends Model | ||||||
| { | { | ||||||
| @@ -30,6 +31,7 @@ class Profile extends Model | |||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         "employment_form" => EmploymentForm::class, |         "employment_form" => EmploymentForm::class, | ||||||
|         "employment_date" => "date", |         "employment_date" => "date", | ||||||
|  |         "birthday" => "date", | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     public function user(): BelongsTo |     public function user(): BelongsTo | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ use Illuminate\Notifications\Notifiable; | |||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Domain\Enums\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
| use Toby\Domain\Enums\Role; | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\Notifiable as NotifiableInterface; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
| @@ -26,7 +27,7 @@ use Toby\Domain\Enums\Role; | |||||||
|  * @property Collection $vacationRequests |  * @property Collection $vacationRequests | ||||||
|  * @property Collection $vacations |  * @property Collection $vacations | ||||||
|  */ |  */ | ||||||
| class User extends Authenticatable | class User extends Authenticatable implements NotifiableInterface | ||||||
| { | { | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
|     use Notifiable; |     use Notifiable; | ||||||
| @@ -99,7 +100,7 @@ class User extends Authenticatable | |||||||
|             ->where("email", "ILIKE", "%{$text}%") |             ->where("email", "ILIKE", "%{$text}%") | ||||||
|             ->orWhereRelation( |             ->orWhereRelation( | ||||||
|                 "profile", |                 "profile", | ||||||
|                 fn(Builder $query) => $query |                 fn(Builder $query): Builder => $query | ||||||
|                     ->where("first_name", "ILIKE", "%{$text}%") |                     ->where("first_name", "ILIKE", "%{$text}%") | ||||||
|                     ->orWhere("last_name", "ILIKE", "%{$text}%"), |                     ->orWhere("last_name", "ILIKE", "%{$text}%"), | ||||||
|             ); |             ); | ||||||
| @@ -122,6 +123,11 @@ class User extends Authenticatable | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function routeNotificationForSlack() | ||||||
|  |     { | ||||||
|  |         return $this->profile->slack_id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): UserFactory |     protected static function newFactory(): UserFactory | ||||||
|     { |     { | ||||||
|         return UserFactory::new(); |         return UserFactory::new(); | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; | |||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
|  | use Illuminate\Support\Arr; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Spatie\ModelStates\HasStates; | use Spatie\ModelStates\HasStates; | ||||||
| @@ -84,6 +85,13 @@ class VacationRequest extends Model | |||||||
|         return $query->whereNotState("state", $states); |         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 |     public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder | ||||||
|     { |     { | ||||||
|         return $query->where("from", "<=", $vacationRequest->to) |         return $query->where("from", "<=", $vacationRequest->to) | ||||||
|   | |||||||
| @@ -0,0 +1,79 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Carbon\CarbonInterface; | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
|  | use Toby\Domain\DailySummaryRetriever; | ||||||
|  | use Toby\Eloquent\Models\Holiday; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\AbsencesAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\BirthdaysAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\RemotesAttachment; | ||||||
|  |  | ||||||
|  | class SendDailySummaryToSlack extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:slack:daily-summary {--f|force}"; | ||||||
|  |     protected $description = "Sent daily summary to slack"; | ||||||
|  |  | ||||||
|  |     public function handle(DailySummaryRetriever $dailySummaryRetriever): void | ||||||
|  |     { | ||||||
|  |         $now = Carbon::today(); | ||||||
|  |  | ||||||
|  |         if (!$this->option("force") && !$this->shouldHandle($now)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $attachments = new Collection([ | ||||||
|  |             new AbsencesAttachment($dailySummaryRetriever->getAbsences($now)), | ||||||
|  |             new RemotesAttachment($dailySummaryRetriever->getRemoteDays($now)), | ||||||
|  |             new BirthdaysAttachment($dailySummaryRetriever->getBirthdays($now)), | ||||||
|  |         ]); | ||||||
|  |  | ||||||
|  |         Http::withToken($this->getSlackClientToken()) | ||||||
|  |             ->post($this->getUrl(), [ | ||||||
|  |                 "channel" => $this->getSlackChannel(), | ||||||
|  |                 "text" => "Podsumowanie dla dnia {$now->toDisplayString()}", | ||||||
|  |                 "attachments" => $attachments, | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getUrl(): string | ||||||
|  |     { | ||||||
|  |         return "{$this->getSlackBaseUrl()}/chat.postMessage"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackBaseUrl(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.url"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackClientToken(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.client_token"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackChannel(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.default_channel"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestsSummaryNotification; | ||||||
|  | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class SendVacationRequestSummariesToApprovers extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:send-vacation-request-reminders"; | ||||||
|  |     protected $description = "Sends vacation request reminders to approvers if they didn't approve"; | ||||||
|  |  | ||||||
|  |     public function handle(): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->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)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,16 +6,16 @@ namespace Toby\Infrastructure\Http\Controllers\Api; | |||||||
|  |  | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Infrastructure\Http\Controllers\Controller; | use Toby\Infrastructure\Http\Controllers\Controller; | ||||||
| use Toby\Infrastructure\Http\Requests\Api\CalculateVacationDaysRequest; | use Toby\Infrastructure\Http\Requests\Api\CalculateVacationDaysRequest; | ||||||
|  |  | ||||||
| class CalculateVacationDaysController extends Controller | class CalculateVacationDaysController extends Controller | ||||||
| { | { | ||||||
|     public function __invoke(CalculateVacationDaysRequest $request, VacationDaysCalculator $calculator): JsonResponse |     public function __invoke(CalculateVacationDaysRequest $request, WorkDaysCalculator $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()); |         return new JsonResponse($days->map(fn(Carbon $day): string => $day->toDateString())->all()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ class GetAvailableVacationTypesController extends Controller | |||||||
|         $user = User::query()->find($request->get("user")); |         $user = User::query()->find($request->get("user")); | ||||||
|  |  | ||||||
|         $types = VacationType::all() |         $types = VacationType::all() | ||||||
|             ->filter(fn(VacationType $type) => $configRetriever->isAvailableFor($type, $user->profile->employment_form)) |             ->filter(fn(VacationType $type): bool => $configRetriever->isAvailableFor($type, $user->profile->employment_form)) | ||||||
|             ->map(fn(VacationType $type) => [ |             ->map(fn(VacationType $type): array => [ | ||||||
|                 "label" => $type->label(), |                 "label" => $type->label(), | ||||||
|                 "value" => $type->value, |                 "value" => $type->value, | ||||||
|             ]) |             ]) | ||||||
|   | |||||||
| @@ -7,12 +7,11 @@ namespace Toby\Infrastructure\Http\Controllers; | |||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\DailySummaryRetriever; | ||||||
| use Toby\Domain\UserVacationStatsRetriever; | use Toby\Domain\UserVacationStatsRetriever; | ||||||
| use Toby\Domain\VacationRequestStatesRetriever; | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
| use Toby\Domain\VacationTypeConfigRetriever; | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; | use Toby\Eloquent\Helpers\YearPeriodRetriever; | ||||||
| use Toby\Eloquent\Models\Vacation; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
| use Toby\Infrastructure\Http\Resources\HolidayResource; | use Toby\Infrastructure\Http\Resources\HolidayResource; | ||||||
| use Toby\Infrastructure\Http\Resources\VacationRequestResource; | use Toby\Infrastructure\Http\Resources\VacationRequestResource; | ||||||
| @@ -25,24 +24,14 @@ class DashboardController extends Controller | |||||||
|         YearPeriodRetriever $yearPeriodRetriever, |         YearPeriodRetriever $yearPeriodRetriever, | ||||||
|         UserVacationStatsRetriever $vacationStatsRetriever, |         UserVacationStatsRetriever $vacationStatsRetriever, | ||||||
|         VacationTypeConfigRetriever $configRetriever, |         VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         DailySummaryRetriever $dailySummaryRetriever, | ||||||
|     ): Response { |     ): Response { | ||||||
|         $user = $request->user(); |         $user = $request->user(); | ||||||
|         $now = Carbon::now(); |         $now = Carbon::now(); | ||||||
|         $yearPeriod = $yearPeriodRetriever->selected(); |         $yearPeriod = $yearPeriodRetriever->selected(); | ||||||
|  |  | ||||||
|         $absences = Vacation::query() |         $absences = $dailySummaryRetriever->getAbsences($now); | ||||||
|             ->with(["user", "vacationRequest"]) |         $remoteDays = $dailySummaryRetriever->getRemoteDays($now); | ||||||
|             ->whereDate("date", $now) |  | ||||||
|             ->approved() |  | ||||||
|             ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))) |  | ||||||
|             ->get(); |  | ||||||
|  |  | ||||||
|         $remoteDays = Vacation::query() |  | ||||||
|             ->with(["user", "vacationRequest"]) |  | ||||||
|             ->whereDate("date", $now) |  | ||||||
|             ->approved() |  | ||||||
|             ->whereTypes(VacationType::all()->filter(fn(VacationType $type) => !$configRetriever->isVacation($type))) |  | ||||||
|             ->get(); |  | ||||||
|  |  | ||||||
|         if ($user->can("listAll", VacationRequest::class)) { |         if ($user->can("listAll", VacationRequest::class)) { | ||||||
|             $vacationRequests = $yearPeriod->vacationRequests() |             $vacationRequests = $yearPeriod->vacationRequests() | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ use Illuminate\Auth\Access\AuthorizationException; | |||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Symfony\Component\HttpFoundation\RedirectResponse; | use Symfony\Component\HttpFoundation\RedirectResponse; | ||||||
|  | use Toby\Domain\Notifications\KeyHasBeenGivenNotification; | ||||||
|  | use Toby\Domain\Notifications\KeyHasBeenTakenNotification; | ||||||
| use Toby\Eloquent\Models\Key; | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Infrastructure\Http\Requests\GiveKeyRequest; | use Toby\Infrastructure\Http\Requests\GiveKeyRequest; | ||||||
| @@ -60,6 +62,8 @@ class KeysController extends Controller | |||||||
|  |  | ||||||
|         $key->save(); |         $key->save(); | ||||||
|  |  | ||||||
|  |         $key->notify(new KeyHasBeenTakenNotification($request->user(), $previousUser)); | ||||||
|  |  | ||||||
|         return redirect() |         return redirect() | ||||||
|             ->back() |             ->back() | ||||||
|             ->with("success", __("Key no :number has been taken from :user.", [ |             ->with("success", __("Key no :number has been taken from :user.", [ | ||||||
| @@ -81,6 +85,8 @@ class KeysController extends Controller | |||||||
|  |  | ||||||
|         $key->save(); |         $key->save(); | ||||||
|  |  | ||||||
|  |         $key->notify(new KeyHasBeenGivenNotification($request->user(), $recipient)); | ||||||
|  |  | ||||||
|         return redirect() |         return redirect() | ||||||
|             ->back() |             ->back() | ||||||
|             ->with("success", __("Key no :number has been given to :user.", [ |             ->with("success", __("Key no :number has been given to :user.", [ | ||||||
|   | |||||||
| @@ -35,8 +35,10 @@ class TimesheetController extends Controller | |||||||
|  |  | ||||||
|         $types = VacationType::all() |         $types = VacationType::all() | ||||||
|             ->filter( |             ->filter( | ||||||
|                 fn(VacationType $type) => $configRetriever->isAvailableFor($type, EmploymentForm::EmploymentContract) |                 fn(VacationType $type): bool => $configRetriever->isAvailableFor( | ||||||
|                     && $configRetriever->isVacation($type), |                     $type, | ||||||
|  |                     EmploymentForm::EmploymentContract, | ||||||
|  |                 ) && $configRetriever->isVacation($type), | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|         $filename = "{$carbonMonth->translatedFormat("F Y")}.xlsx"; |         $filename = "{$carbonMonth->translatedFormat("F Y")}.xlsx"; | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ class VacationLimitController extends Controller | |||||||
|             ->sortBy(fn(VacationLimit $limit): string => "{$limit->user->profile->last_name} {$limit->user->profile->first_name}") |             ->sortBy(fn(VacationLimit $limit): string => "{$limit->user->profile->last_name} {$limit->user->profile->first_name}") | ||||||
|             ->values(); |             ->values(); | ||||||
|  |  | ||||||
|         $limitsResource = $limits->map(fn(VacationLimit $limit) => [ |         $limitsResource = $limits->map(fn(VacationLimit $limit): array => [ | ||||||
|             "id" => $limit->id, |             "id" => $limit->id, | ||||||
|             "user" => new UserResource($limit->user), |             "user" => new UserResource($limit->user), | ||||||
|             "hasVacation" => $limit->hasVacation(), |             "hasVacation" => $limit->hasVacation(), | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ class HandleInertiaRequests extends Middleware | |||||||
|     { |     { | ||||||
|         $user = $request->user(); |         $user = $request->user(); | ||||||
|  |  | ||||||
|         return fn() => [ |         return fn(): array => [ | ||||||
|             "user" => $user ? new UserResource($user) : null, |             "user" => $user ? new UserResource($user) : null, | ||||||
|             "can" => [ |             "can" => [ | ||||||
|                 "manageVacationLimits" => $user ? $user->can("manageVacationLimits") : false, |                 "manageVacationLimits" => $user ? $user->can("manageVacationLimits") : false, | ||||||
| @@ -45,7 +45,7 @@ class HandleInertiaRequests extends Middleware | |||||||
|  |  | ||||||
|     protected function getFlashData(Request $request): Closure |     protected function getFlashData(Request $request): Closure | ||||||
|     { |     { | ||||||
|         return fn() => [ |         return fn(): array => [ | ||||||
|             "success" => $request->session()->get("success"), |             "success" => $request->session()->get("success"), | ||||||
|             "error" => $request->session()->get("error"), |             "error" => $request->session()->get("error"), | ||||||
|             "info" => $request->session()->get("info"), |             "info" => $request->session()->get("info"), | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ class UserRequest extends FormRequest | |||||||
|             "position" => ["required"], |             "position" => ["required"], | ||||||
|             "employmentForm" => ["required", new Enum(EmploymentForm::class)], |             "employmentForm" => ["required", new Enum(EmploymentForm::class)], | ||||||
|             "employmentDate" => ["required", "date_format:Y-m-d"], |             "employmentDate" => ["required", "date_format:Y-m-d"], | ||||||
|  |             "birthday" => ["nullable", "date_format:Y-m-d"], | ||||||
|  |             "slackId" => [], | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -41,6 +43,8 @@ class UserRequest extends FormRequest | |||||||
|             "position" => $this->get("position"), |             "position" => $this->get("position"), | ||||||
|             "employment_form" => $this->get("employmentForm"), |             "employment_form" => $this->get("employmentForm"), | ||||||
|             "employment_date" => $this->get("employmentDate"), |             "employment_date" => $this->get("employmentDate"), | ||||||
|  |             "birthday" => $this->get("birthday"), | ||||||
|  |             "slack_id" => $this->get("slackId"), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class HolidayResource extends JsonResource | |||||||
|             "id" => $this->id, |             "id" => $this->id, | ||||||
|             "name" => $this->name, |             "name" => $this->name, | ||||||
|             "date" => $this->date->toDateString(), |             "date" => $this->date->toDateString(), | ||||||
|             "isPast" => $this->date->isPast(), |             "isPast" => $this->date->endOfDay()->isPast(), | ||||||
|             "displayDate" => $this->date->toDisplayString(), |             "displayDate" => $this->date->toDisplayString(), | ||||||
|             "dayOfWeek" => $this->date->dayName, |             "dayOfWeek" => $this->date->dayName, | ||||||
|         ]; |         ]; | ||||||
|   | |||||||
| @@ -21,6 +21,8 @@ class UserFormDataResource extends JsonResource | |||||||
|             "position" => $this->profile->position, |             "position" => $this->profile->position, | ||||||
|             "employmentForm" => $this->profile->employment_form, |             "employmentForm" => $this->profile->employment_form, | ||||||
|             "employmentDate" => $this->profile->employment_date->toDateString(), |             "employmentDate" => $this->profile->employment_date->toDateString(), | ||||||
|  |             "birthday" => $this->profile->birthday?->toDateString(), | ||||||
|  |             "slackId" => $this->profile->slack_id, | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								app/Infrastructure/Slack/Channels/SlackApiChannel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/Infrastructure/Slack/Channels/SlackApiChannel.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Channels; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\Client\Response; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
|  | use Toby\Domain\Notifications\Notifiable; | ||||||
|  |  | ||||||
|  | class SlackApiChannel | ||||||
|  | { | ||||||
|  |     public function send(Notifiable $notifiable, Notification $notification): Response | ||||||
|  |     { | ||||||
|  |         $baseUrl = $this->getBaseUrl(); | ||||||
|  |         $url = "{$baseUrl}/chat.postMessage"; | ||||||
|  |         $channel = $notifiable->routeNotificationFor("slack", $notification); | ||||||
|  |  | ||||||
|  |         $message = $notification->toSlack($notifiable); | ||||||
|  |  | ||||||
|  |         return Http::withToken($this->getClientToken()) | ||||||
|  |             ->post($url, array_merge($message->getPayload(), [ | ||||||
|  |                 "channel" => $channel, | ||||||
|  |             ])); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getClientToken(): string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.client_token"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getBaseUrl(): string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.url"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								app/Infrastructure/Slack/Controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/Infrastructure/Slack/Controller.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack; | ||||||
|  |  | ||||||
|  | use Exception; | ||||||
|  | use Illuminate\Http\Request as IlluminateRequest; | ||||||
|  | use Illuminate\Http\Response as IlluminateResponse; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Illuminate\Validation\ValidationException; | ||||||
|  | use Spatie\SlashCommand\Attachment; | ||||||
|  | use Spatie\SlashCommand\Controller as SlackController; | ||||||
|  | use Spatie\SlashCommand\Exceptions\InvalidRequest; | ||||||
|  | use Spatie\SlashCommand\Exceptions\RequestCouldNotBeHandled; | ||||||
|  | use Spatie\SlashCommand\Exceptions\SlackSlashCommandException; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  |  | ||||||
|  | class Controller extends SlackController | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * @throws InvalidRequest|RequestCouldNotBeHandled | ||||||
|  |      */ | ||||||
|  |     public function getResponse(IlluminateRequest $request): IlluminateResponse | ||||||
|  |     { | ||||||
|  |         $this->verifyWithSigning($request); | ||||||
|  |  | ||||||
|  |         $handler = $this->determineHandler(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             $response = $handler->handle($this->request); | ||||||
|  |         } catch (SlackSlashCommandException $exception) { | ||||||
|  |             $response = $exception->getResponse($this->request); | ||||||
|  |         } catch (ValidationException $exception) { | ||||||
|  |             $response = $this->prepareValidationResponse($exception); | ||||||
|  |         } catch (Exception $exception) { | ||||||
|  |             $response = $this->convertToResponse($exception); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $response->getIlluminateResponse(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function prepareValidationResponse(ValidationException $exception): Response | ||||||
|  |     { | ||||||
|  |         $errors = (new Collection($exception->errors())) | ||||||
|  |             ->map( | ||||||
|  |                 fn(array $message): Attachment => Attachment::create() | ||||||
|  |                     ->setColor("danger") | ||||||
|  |                     ->setText($message[0]), | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |         return Response::create($this->request) | ||||||
|  |             ->withText(":x: Polecenie `/{$this->request->command} {$this->request->text}` jest niepoprawne:") | ||||||
|  |             ->withAttachments($errors->all()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								app/Infrastructure/Slack/Elements/AbsencesAttachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/Infrastructure/Slack/Elements/AbsencesAttachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
|  |  | ||||||
|  | class AbsencesAttachment extends ListAttachment | ||||||
|  | { | ||||||
|  |     public function __construct(Collection $absences) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         $this | ||||||
|  |             ->setTitle("Nieobecności :palm_tree:") | ||||||
|  |             ->setColor("#eab308") | ||||||
|  |             ->setItems($absences->map(fn(Vacation $vacation): string => $vacation->user->profile->full_name)) | ||||||
|  |             ->setEmptyText("Wszyscy dzisiaj pracują :muscle:"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								app/Infrastructure/Slack/Elements/Attachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/Infrastructure/Slack/Elements/Attachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Support\Arrayable; | ||||||
|  | use Spatie\SlashCommand\Attachment as BaseAttachment; | ||||||
|  |  | ||||||
|  | class Attachment extends BaseAttachment implements Arrayable | ||||||
|  | { | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								app/Infrastructure/Slack/Elements/BirthdaysAttachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/Infrastructure/Slack/Elements/BirthdaysAttachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class BirthdaysAttachment extends ListAttachment | ||||||
|  | { | ||||||
|  |     public function __construct(Collection $birthdays) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         $this | ||||||
|  |             ->setTitle("Urodziny :birthday:") | ||||||
|  |             ->setColor("#3c5f97") | ||||||
|  |             ->setItems($birthdays->map(fn(User $user): string => $user->profile->full_name)) | ||||||
|  |             ->setEmptyText("Dzisiaj nikt nie ma urodzin :cry:"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								app/Infrastructure/Slack/Elements/KeysAttachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/Infrastructure/Slack/Elements/KeysAttachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  |  | ||||||
|  | class KeysAttachment extends ListAttachment | ||||||
|  | { | ||||||
|  |     public function __construct(Collection $keys) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         $this | ||||||
|  |             ->setColor("#3c5f97") | ||||||
|  |             ->setItems($keys->map(fn(Key $key): string => "Klucz nr {$key->id} - <@{$key->user->profile->slack_id}>")) | ||||||
|  |             ->setEmptyText("Nie ma żadnych kluczy w tobym"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								app/Infrastructure/Slack/Elements/ListAttachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Infrastructure/Slack/Elements/ListAttachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  |  | ||||||
|  | class ListAttachment extends Attachment | ||||||
|  | { | ||||||
|  |     protected Collection $items; | ||||||
|  |     protected string $emptyText = ""; | ||||||
|  |  | ||||||
|  |     public function setItems(Collection $items): static | ||||||
|  |     { | ||||||
|  |         $this->items = $items; | ||||||
|  |  | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function setEmptyText(string $emptyText): static | ||||||
|  |     { | ||||||
|  |         $this->emptyText = $emptyText; | ||||||
|  |  | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toArray(): array | ||||||
|  |     { | ||||||
|  |         $fields = parent::toArray(); | ||||||
|  |  | ||||||
|  |         return array_merge($fields, [ | ||||||
|  |             "text" => $this->items->isNotEmpty() ? $this->items->implode("\n") : $this->emptyText, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								app/Infrastructure/Slack/Elements/RemotesAttachment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/Infrastructure/Slack/Elements/RemotesAttachment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
|  |  | ||||||
|  | class RemotesAttachment extends ListAttachment | ||||||
|  | { | ||||||
|  |     public function __construct(Collection $remoteDays) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         $this | ||||||
|  |             ->setTitle("Praca zdalna :house_with_garden:") | ||||||
|  |             ->setColor("#527aba") | ||||||
|  |             ->setItems($remoteDays->map(fn(Vacation $vacation): string => $vacation->user->profile->full_name)) | ||||||
|  |             ->setEmptyText("Wszyscy dzisiaj są w biurze :boom:"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								app/Infrastructure/Slack/Elements/SlackMessage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/Infrastructure/Slack/Elements/SlackMessage.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  |  | ||||||
|  | class SlackMessage | ||||||
|  | { | ||||||
|  |     protected string $text = ""; | ||||||
|  |     protected Collection $attachments; | ||||||
|  |  | ||||||
|  |     public function __construct() | ||||||
|  |     { | ||||||
|  |         $this->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(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Elements; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestsAttachment extends ListAttachment | ||||||
|  | { | ||||||
|  |     public function __construct(Collection $vacationRequests) | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         $this | ||||||
|  |             ->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})"; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Exceptions; | ||||||
|  |  | ||||||
|  | use Spatie\SlashCommand\Exceptions\SlackSlashCommandException; | ||||||
|  |  | ||||||
|  | class UserNotFoundException extends SlackSlashCommandException | ||||||
|  | { | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								app/Infrastructure/Slack/Handlers/CatchAll.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Infrastructure/Slack/Handlers/CatchAll.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Spatie\SlashCommand\Handlers\BaseHandler; | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\Attachment; | ||||||
|  | use Toby\Infrastructure\Slack\Traits\ListsHandlers; | ||||||
|  |  | ||||||
|  | class CatchAll extends BaseHandler | ||||||
|  | { | ||||||
|  |     use ListsHandlers; | ||||||
|  |  | ||||||
|  |     public function canHandle(Request $request): bool | ||||||
|  |     { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $handlers = $this->findAvailableHandlers(); | ||||||
|  |         $attachmentFields = $this->mapHandlersToAttachments($handlers); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack(":x: Nie rozpoznaję polecenia. Lista wszystkich poleceń:") | ||||||
|  |             ->withAttachment( | ||||||
|  |                 Attachment::create() | ||||||
|  |                     ->setColor("danger") | ||||||
|  |                     ->useMarkdown() | ||||||
|  |                     ->setFields($attachmentFields), | ||||||
|  |             ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								app/Infrastructure/Slack/Handlers/DailySummary.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Infrastructure/Slack/Handlers/DailySummary.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Domain\DailySummaryRetriever; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\AbsencesAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\BirthdaysAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\RemotesAttachment; | ||||||
|  |  | ||||||
|  | class DailySummary extends SignatureHandler | ||||||
|  | { | ||||||
|  |     protected $signature = "toby dzisiaj"; | ||||||
|  |     protected $description = "Codzienne podsumowanie"; | ||||||
|  |  | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $dailySummaryRetriever = app()->make(DailySummaryRetriever::class); | ||||||
|  |  | ||||||
|  |         $now = Carbon::today(); | ||||||
|  |  | ||||||
|  |         $attachments = new Collection([ | ||||||
|  |             new AbsencesAttachment($dailySummaryRetriever->getAbsences($now)), | ||||||
|  |             new RemotesAttachment($dailySummaryRetriever->getRemoteDays($now)), | ||||||
|  |             new BirthdaysAttachment($dailySummaryRetriever->getBirthdays($now)), | ||||||
|  |         ]); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack("Podsumowanie dla dnia {$now->toDisplayString()}") | ||||||
|  |             ->withAttachments($attachments->all()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								app/Infrastructure/Slack/Handlers/GiveKeysTo.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/Infrastructure/Slack/Handlers/GiveKeysTo.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Illuminate\Validation\ValidationException; | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Domain\Notifications\KeyHasBeenGivenNotification; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Infrastructure\Slack\Exceptions\UserNotFoundException; | ||||||
|  | use Toby\Infrastructure\Slack\Rules\SlackUserExistsRule; | ||||||
|  | use Toby\Infrastructure\Slack\Traits\FindsUserBySlackId; | ||||||
|  |  | ||||||
|  | class GiveKeysTo extends SignatureHandler | ||||||
|  | { | ||||||
|  |     use FindsUserBySlackId; | ||||||
|  |  | ||||||
|  |     protected $signature = "toby klucze:dla {user}"; | ||||||
|  |     protected $description = "Przekaż klucze wskazanemu użytkownikowi"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws UserNotFoundException | ||||||
|  |      * @throws ValidationException | ||||||
|  |      */ | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         ["user" => $from] = $this->validate(); | ||||||
|  |  | ||||||
|  |         $authUser = $this->findUserBySlackIdOrFail($request->userId); | ||||||
|  |         $user = $this->findUserBySlackId($from); | ||||||
|  |  | ||||||
|  |         /** @var Key $key */ | ||||||
|  |         $key = $authUser->keys()->first(); | ||||||
|  |  | ||||||
|  |         if (!$key) { | ||||||
|  |             throw ValidationException::withMessages(["key" => "Nie masz żadnego klucza do przekazania"]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($user->is($authUser)) { | ||||||
|  |             throw ValidationException::withMessages([ | ||||||
|  |                 "key" => "Nie możesz przekazać sobie kluczy :dzban:", | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $key->user()->associate($user); | ||||||
|  |  | ||||||
|  |         $key->save(); | ||||||
|  |  | ||||||
|  |         $key->notify(new KeyHasBeenGivenNotification($authUser, $user)); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack( | ||||||
|  |             ":white_check_mark: Klucz nr {$key->id} został przekazany użytkownikowi <@{$user->profile->slack_id}>", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getRules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "user" => ["required", new SlackUserExistsRule()], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getMessages(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "user.required" => "Musisz podać użytkownika, któremu chcesz przekazać klucze", | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								app/Infrastructure/Slack/Handlers/Help.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/Infrastructure/Slack/Handlers/Help.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\Attachment; | ||||||
|  | use Toby\Infrastructure\Slack\Traits\ListsHandlers; | ||||||
|  |  | ||||||
|  | class Help extends SignatureHandler | ||||||
|  | { | ||||||
|  |     use ListsHandlers; | ||||||
|  |  | ||||||
|  |     protected $signature = "toby pomoc"; | ||||||
|  |     protected $description = "Wyświetl wszystkie dostępne polecenia"; | ||||||
|  |  | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $handlers = $this->findAvailableHandlers(); | ||||||
|  |  | ||||||
|  |         $attachmentFields = $this->mapHandlersToAttachments($handlers); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack("Dostępne polecenia:") | ||||||
|  |             ->withAttachment( | ||||||
|  |                 Attachment::create() | ||||||
|  |                     ->setColor("good") | ||||||
|  |                     ->useMarkdown() | ||||||
|  |                     ->setFields($attachmentFields), | ||||||
|  |             ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								app/Infrastructure/Slack/Handlers/HomeOffice.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/Infrastructure/Slack/Handlers/HomeOffice.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Domain\Actions\VacationRequest\CreateAction; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  | use Toby\Infrastructure\Slack\Traits\FindsUserBySlackId; | ||||||
|  |  | ||||||
|  | class HomeOffice extends SignatureHandler | ||||||
|  | { | ||||||
|  |     use FindsUserBySlackId; | ||||||
|  |  | ||||||
|  |     protected $signature = "toby zdalnie"; | ||||||
|  |     protected $description = "Pracuj dzisiaj zdalnie"; | ||||||
|  |  | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $user = $this->findUserBySlackId($request->userId); | ||||||
|  |  | ||||||
|  |         $this->createRemoteday($user, Carbon::today()); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack(":white_check_mark: Pracujesz dzisiaj zdalnie"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function createRemoteday(User $user, Carbon $date): void | ||||||
|  |     { | ||||||
|  |         $yearPeriod = YearPeriod::findByYear($date->year); | ||||||
|  |  | ||||||
|  |         app(CreateAction::class)->execute([ | ||||||
|  |             "user_id" => $user->id, | ||||||
|  |             "type" => VacationType::HomeOffice, | ||||||
|  |             "from" => $date, | ||||||
|  |             "to" => $date, | ||||||
|  |             "year_period_id" => $yearPeriod->id, | ||||||
|  |             "flow_skipped" => false, | ||||||
|  |         ], $user); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								app/Infrastructure/Slack/Handlers/KeyList.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Infrastructure/Slack/Handlers/KeyList.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\KeysAttachment; | ||||||
|  |  | ||||||
|  | class KeyList extends SignatureHandler | ||||||
|  | { | ||||||
|  |     protected $signature = "toby klucze"; | ||||||
|  |     protected $description = "Lista wszystkich kluczy"; | ||||||
|  |  | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $keys = Key::query() | ||||||
|  |             ->orderBy("id") | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack("Lista kluczy :key:") | ||||||
|  |             ->withAttachment(new KeysAttachment($keys)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								app/Infrastructure/Slack/Handlers/SignatureHandler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Infrastructure/Slack/Handlers/SignatureHandler.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Facades\Validator; | ||||||
|  | use Spatie\SlashCommand\Handlers\SignatureHandler as BaseSignatureHandler; | ||||||
|  |  | ||||||
|  | abstract class SignatureHandler extends BaseSignatureHandler | ||||||
|  | { | ||||||
|  |     public function validate() | ||||||
|  |     { | ||||||
|  |         return Validator::validate($this->getArguments(), $this->getRules(), $this->getMessages()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getRules(): array | ||||||
|  |     { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getMessages(): array | ||||||
|  |     { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								app/Infrastructure/Slack/Handlers/TakeKeysFrom.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								app/Infrastructure/Slack/Handlers/TakeKeysFrom.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Handlers; | ||||||
|  |  | ||||||
|  | use Illuminate\Validation\ValidationException; | ||||||
|  | use Spatie\SlashCommand\Request; | ||||||
|  | use Spatie\SlashCommand\Response; | ||||||
|  | use Toby\Domain\Notifications\KeyHasBeenTakenNotification; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Infrastructure\Slack\Exceptions\UserNotFoundException; | ||||||
|  | use Toby\Infrastructure\Slack\Rules\SlackUserExistsRule; | ||||||
|  | use Toby\Infrastructure\Slack\Traits\FindsUserBySlackId; | ||||||
|  |  | ||||||
|  | class TakeKeysFrom extends SignatureHandler | ||||||
|  | { | ||||||
|  |     use FindsUserBySlackId; | ||||||
|  |  | ||||||
|  |     protected $signature = "toby klucze:od {user}"; | ||||||
|  |     protected $description = "Zabierz klucze wskazanemu użytkownikowi"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws UserNotFoundException|ValidationException | ||||||
|  |      */ | ||||||
|  |     public function handle(Request $request): Response | ||||||
|  |     { | ||||||
|  |         ["user" => $from] = $this->validate(); | ||||||
|  |  | ||||||
|  |         $authUser = $this->findUserBySlackIdOrFail($request->userId); | ||||||
|  |         $user = $this->findUserBySlackId($from); | ||||||
|  |  | ||||||
|  |         /** @var Key $key */ | ||||||
|  |         $key = $user->keys()->first(); | ||||||
|  |  | ||||||
|  |         if (!$key) { | ||||||
|  |             throw ValidationException::withMessages([ | ||||||
|  |                 "key" => "Użytkownik <@{$user->profile->slack_id}> nie ma żadnych kluczy", | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($key->user()->is($authUser)) { | ||||||
|  |             throw ValidationException::withMessages([ | ||||||
|  |                 "key" => "Nie możesz zabrać sobie kluczy :dzban:", | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $key->user()->associate($authUser); | ||||||
|  |  | ||||||
|  |         $key->save(); | ||||||
|  |  | ||||||
|  |         $key->notify(new KeyHasBeenTakenNotification($authUser, $user)); | ||||||
|  |  | ||||||
|  |         return $this->respondToSlack(":white_check_mark: Klucz nr {$key->id} został zabrany użytkownikowi <@{$user->profile->slack_id}>"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getRules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "user" => ["required", new SlackUserExistsRule()], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getMessages(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "user.required" => "Musisz podać użytkownika, któremu chcesz zabrać klucze", | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								app/Infrastructure/Slack/Rules/SlackUserExistsRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Infrastructure/Slack/Rules/SlackUserExistsRule.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Rules; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Validation\Rule; | ||||||
|  | use Illuminate\Support\Str; | ||||||
|  | use Toby\Eloquent\Models\Profile; | ||||||
|  |  | ||||||
|  | class SlackUserExistsRule implements Rule | ||||||
|  | { | ||||||
|  |     public function passes($attribute, $value): bool | ||||||
|  |     { | ||||||
|  |         $slackId = Str::between($value, "<@", "|"); | ||||||
|  |  | ||||||
|  |         return Profile::query()->where("slack_id", $slackId)->exists(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function message(): string | ||||||
|  |     { | ||||||
|  |         return "Użytkownik :input nie istnieje w tobym"; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								app/Infrastructure/Slack/Traits/FindsUserBySlackId.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/Infrastructure/Slack/Traits/FindsUserBySlackId.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Traits; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Str; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Infrastructure\Slack\Exceptions\UserNotFoundException; | ||||||
|  |  | ||||||
|  | trait FindsUserBySlackId | ||||||
|  | { | ||||||
|  |     protected function findUserBySlackId(string $slackId): ?User | ||||||
|  |     { | ||||||
|  |         $id = $this->prepareSlackIdFromString($slackId); | ||||||
|  |  | ||||||
|  |         /** @var User $user */ | ||||||
|  |         $user = User::query() | ||||||
|  |             ->whereRelation("profile", "slack_id", $id) | ||||||
|  |             ->first(); | ||||||
|  |  | ||||||
|  |         return $user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws UserNotFoundException | ||||||
|  |      */ | ||||||
|  |     protected function findUserBySlackIdOrFail(string $slackId): ?User | ||||||
|  |     { | ||||||
|  |         $user = $this->findUserBySlackId($slackId); | ||||||
|  |  | ||||||
|  |         if (!$user) { | ||||||
|  |             throw new UserNotFoundException("Użytkownik {$slackId} nie istnieje w tobym"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function prepareSlackIdFromString(string $slackId): string | ||||||
|  |     { | ||||||
|  |         return Str::between($slackId, "<@", "|"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								app/Infrastructure/Slack/Traits/ListsHandlers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Infrastructure/Slack/Traits/ListsHandlers.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Slack\Traits; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Illuminate\Support\Str; | ||||||
|  | use Spatie\SlashCommand\AttachmentField; | ||||||
|  | use Spatie\SlashCommand\Handlers\BaseHandler; | ||||||
|  | use Spatie\SlashCommand\Handlers\SignatureHandler; | ||||||
|  | use Spatie\SlashCommand\Handlers\SignatureParts; | ||||||
|  | use Spatie\SlashCommand\HandlesSlashCommand; | ||||||
|  |  | ||||||
|  | trait ListsHandlers | ||||||
|  | { | ||||||
|  |     protected function findAvailableHandlers(): Collection | ||||||
|  |     { | ||||||
|  |         return collect(config("laravel-slack-slash-command.handlers")) | ||||||
|  |             ->map(fn(string $handlerClassName): BaseHandler => new $handlerClassName($this->request)) | ||||||
|  |             ->filter(fn(HandlesSlashCommand $handler): bool => $handler instanceof SignatureHandler) | ||||||
|  |             ->filter(function (SignatureHandler $handler) { | ||||||
|  |                 $signatureParts = new SignatureParts($handler->getSignature()); | ||||||
|  |  | ||||||
|  |                 return Str::is($signatureParts->getSlashCommandName(), $this->request->command); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function mapHandlersToAttachments(Collection $handlers): array | ||||||
|  |     { | ||||||
|  |         return $handlers | ||||||
|  |             ->sort( | ||||||
|  |                 fn(SignatureHandler $handlerA, SignatureHandler $handlerB): int => strcmp( | ||||||
|  |                     $handlerA->getFullCommand(), | ||||||
|  |                     $handlerB->getFullCommand(), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |             ->map( | ||||||
|  |                 fn(SignatureHandler $handler): AttachmentField => AttachmentField::create( | ||||||
|  |                     $handler->getDescription(), | ||||||
|  |                     "`/{$handler->getSignature()}`", | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |             ->all(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -10,7 +10,7 @@ | |||||||
|         "ext-redis": "*", |         "ext-redis": "*", | ||||||
|         "azuyalabs/yasumi": "^2.4", |         "azuyalabs/yasumi": "^2.4", | ||||||
|         "barryvdh/laravel-dompdf": "^1.0", |         "barryvdh/laravel-dompdf": "^1.0", | ||||||
|         "fruitcake/laravel-cors": "^2.0", |         "fruitcake/laravel-cors": "^3.0", | ||||||
|         "guzzlehttp/guzzle": "^7.0.1", |         "guzzlehttp/guzzle": "^7.0.1", | ||||||
|         "inertiajs/inertia-laravel": "^0.5.1", |         "inertiajs/inertia-laravel": "^0.5.1", | ||||||
|         "laravel/framework": "^9.7", |         "laravel/framework": "^9.7", | ||||||
| @@ -22,10 +22,11 @@ | |||||||
|         "phpoffice/phpword": "^0.18.3", |         "phpoffice/phpword": "^0.18.3", | ||||||
|         "rackbeat/laravel-ui-avatars": "^1.0", |         "rackbeat/laravel-ui-avatars": "^1.0", | ||||||
|         "spatie/laravel-google-calendar": "^3.5", |         "spatie/laravel-google-calendar": "^3.5", | ||||||
|         "spatie/laravel-model-states": "^2.1" |         "spatie/laravel-model-states": "^2.1", | ||||||
|  |         "spatie/laravel-slack-slash-command": "^1.11" | ||||||
|     }, |     }, | ||||||
|     "require-dev": { |     "require-dev": { | ||||||
|         "blumilksoftware/codestyle": "^1.0.0", |         "blumilksoftware/codestyle": "^1.2.0", | ||||||
|         "fakerphp/faker": "^1.19", |         "fakerphp/faker": "^1.19", | ||||||
|         "laravel/dusk": "^6.21", |         "laravel/dusk": "^6.21", | ||||||
|         "mockery/mockery": "^1.4.4", |         "mockery/mockery": "^1.4.4", | ||||||
| @@ -62,7 +63,8 @@ | |||||||
|     "extra": { |     "extra": { | ||||||
|         "laravel": { |         "laravel": { | ||||||
|             "dont-discover": [ |             "dont-discover": [ | ||||||
|                 "laravel/telescope" |                 "laravel/telescope", | ||||||
|  |                 "spatie/laravel-slack-slash-command" | ||||||
|             ] |             ] | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|   | |||||||
							
								
								
									
										696
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										696
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -37,12 +37,12 @@ return [ | |||||||
|         Illuminate\Translation\TranslationServiceProvider::class, |         Illuminate\Translation\TranslationServiceProvider::class, | ||||||
|         Illuminate\Validation\ValidationServiceProvider::class, |         Illuminate\Validation\ValidationServiceProvider::class, | ||||||
|         Illuminate\View\ViewServiceProvider::class, |         Illuminate\View\ViewServiceProvider::class, | ||||||
|  |         Barryvdh\DomPDF\ServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\AppServiceProvider::class, |         Toby\Architecture\Providers\AppServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\AuthServiceProvider::class, |         Toby\Architecture\Providers\AuthServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\EventServiceProvider::class, |         Toby\Architecture\Providers\EventServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\RouteServiceProvider::class, |         Toby\Architecture\Providers\RouteServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\TelescopeServiceProvider::class, |         Toby\Architecture\Providers\TelescopeServiceProvider::class, | ||||||
|         Toby\Architecture\Providers\ObserverServiceProvider::class, |         Toby\Architecture\Providers\ObserverServiceProvider::class, | ||||||
|         Barryvdh\DomPDF\ServiceProvider::class, |  | ||||||
|     ], |     ], | ||||||
| ]; | ]; | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								config/laravel-slack-slash-command.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								config/laravel-slack-slash-command.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\CatchAll; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\DailySummary; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\GiveKeysTo; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\Help; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\HomeOffice; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\KeyList; | ||||||
|  | use Toby\Infrastructure\Slack\Handlers\TakeKeysFrom; | ||||||
|  |  | ||||||
|  | return [ | ||||||
|  |     "signing_secret" => env("SLACK_SIGNING_SECRET"), | ||||||
|  |     "handlers" => [ | ||||||
|  |         TakeKeysFrom::class, | ||||||
|  |         GiveKeysTo::class, | ||||||
|  |         KeyList::class, | ||||||
|  |         HomeOffice::class, | ||||||
|  |         DailySummary::class, | ||||||
|  |         Help::class, | ||||||
|  |         CatchAll::class, | ||||||
|  |     ], | ||||||
|  | ]; | ||||||
| @@ -8,4 +8,9 @@ return [ | |||||||
|         "client_secret" => env("GOOGLE_CLIENT_SECRET"), |         "client_secret" => env("GOOGLE_CLIENT_SECRET"), | ||||||
|         "redirect" => env("GOOGLE_REDIRECT"), |         "redirect" => env("GOOGLE_REDIRECT"), | ||||||
|     ], |     ], | ||||||
|  |     "slack" => [ | ||||||
|  |         "url" => "https://slack.com/api", | ||||||
|  |         "client_token" => env("SLACK_CLIENT_TOKEN"), | ||||||
|  |         "default_channel" => env("SLACK_DEFAULT_CHANNEL"), | ||||||
|  |     ], | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ class ProfileFactory extends Factory | |||||||
|             "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), |             "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), | ||||||
|             "position" => $this->faker->jobTitle(), |             "position" => $this->faker->jobTitle(), | ||||||
|             "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), |             "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), | ||||||
|  |             "birthday" => Carbon::createFromInterface($this->faker->dateTimeBetween("1970-01-01", "1998-01-01"))->toDateString(), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  |  | ||||||
|  | return new class() extends Migration { | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         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->dropColumn("slack_id"); | ||||||
|  |             $table->dropColumn("birthday"); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -7,7 +7,7 @@ namespace Database\Seeders; | |||||||
| use Illuminate\Database\Seeder; | use Illuminate\Database\Seeder; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Toby\Domain\PolishHolidaysRetriever; | use Toby\Domain\PolishHolidaysRetriever; | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\Key; | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationLimit; | use Toby\Eloquent\Models\VacationLimit; | ||||||
| @@ -70,8 +70,7 @@ class DatabaseSeeder extends Seeder | |||||||
|                     "year_period_id" => $yearPeriods->random()->id, |                     "year_period_id" => $yearPeriods->random()->id, | ||||||
|                 ]) |                 ]) | ||||||
|                 ->afterCreating(function (VacationRequest $vacationRequest): void { |                 ->afterCreating(function (VacationRequest $vacationRequest): void { | ||||||
|                     $days = app(VacationDaysCalculator::class)->calculateDays( |                     $days = app(WorkDaysCalculator::class)->calculateDays( | ||||||
|                         $vacationRequest->yearPeriod, |  | ||||||
|                         $vacationRequest->from, |                         $vacationRequest->from, | ||||||
|                         $vacationRequest->to, |                         $vacationRequest->to, | ||||||
|                     ); |                     ); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ use Toby\Domain\States\VacationRequest\Created; | |||||||
| use Toby\Domain\States\VacationRequest\Rejected; | use Toby\Domain\States\VacationRequest\Rejected; | ||||||
| use Toby\Domain\States\VacationRequest\WaitingForAdministrative; | use Toby\Domain\States\VacationRequest\WaitingForAdministrative; | ||||||
| use Toby\Domain\States\VacationRequest\WaitingForTechnical; | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\Key; | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\Resume; | use Toby\Eloquent\Models\Resume; | ||||||
| use Toby\Eloquent\Models\Technology; | use Toby\Eloquent\Models\Technology; | ||||||
| @@ -166,8 +166,7 @@ class DemoSeeder extends Seeder | |||||||
|             ->for($user, "creator") |             ->for($user, "creator") | ||||||
|             ->for($currentYearPeriod) |             ->for($currentYearPeriod) | ||||||
|             ->afterCreating(function (VacationRequest $vacationRequest): void { |             ->afterCreating(function (VacationRequest $vacationRequest): void { | ||||||
|                 $days = app(VacationDaysCalculator::class)->calculateDays( |                 $days = app(WorkDaysCalculator::class)->calculateDays( | ||||||
|                     $vacationRequest->yearPeriod, |  | ||||||
|                     $vacationRequest->from, |                     $vacationRequest->from, | ||||||
|                     $vacationRequest->to, |                     $vacationRequest->to, | ||||||
|                 ); |                 ); | ||||||
| @@ -236,8 +235,7 @@ class DemoSeeder extends Seeder | |||||||
|             ->for($user, "creator") |             ->for($user, "creator") | ||||||
|             ->for($currentYearPeriod) |             ->for($currentYearPeriod) | ||||||
|             ->afterCreating(function (VacationRequest $vacationRequest): void { |             ->afterCreating(function (VacationRequest $vacationRequest): void { | ||||||
|                 $days = app(VacationDaysCalculator::class)->calculateDays( |                 $days = app(WorkDaysCalculator::class)->calculateDays( | ||||||
|                     $vacationRequest->yearPeriod, |  | ||||||
|                     $vacationRequest->from, |                     $vacationRequest->from, | ||||||
|                     $vacationRequest->to, |                     $vacationRequest->to, | ||||||
|                 ); |                 ); | ||||||
| @@ -293,8 +291,7 @@ class DemoSeeder extends Seeder | |||||||
|             ->for($user, "creator") |             ->for($user, "creator") | ||||||
|             ->for($currentYearPeriod) |             ->for($currentYearPeriod) | ||||||
|             ->afterCreating(function (VacationRequest $vacationRequest): void { |             ->afterCreating(function (VacationRequest $vacationRequest): void { | ||||||
|                 $days = app(VacationDaysCalculator::class)->calculateDays( |                 $days = app(WorkDaysCalculator::class)->calculateDays( | ||||||
|                     $vacationRequest->yearPeriod, |  | ||||||
|                     $vacationRequest->from, |                     $vacationRequest->from, | ||||||
|                     $vacationRequest->to, |                     $vacationRequest->to, | ||||||
|                 ); |                 ); | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ services: | |||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|  |  | ||||||
|   database: |   database: | ||||||
|     image: postgres:13 |     image: postgres:14 | ||||||
|     container_name: toby-db-dev |     container_name: toby-db-dev | ||||||
|     environment: |     environment: | ||||||
|       - PGPASSWORD=${DOCKER_DEV_DB_ROOT_PASSWORD} |       - PGPASSWORD=${DOCKER_DEV_DB_ROOT_PASSWORD} | ||||||
| @@ -51,7 +51,7 @@ services: | |||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|  |  | ||||||
|   database-test: |   database-test: | ||||||
|     image: postgres:13 |     image: postgres:14 | ||||||
|     container_name: toby-db-test |     container_name: toby-db-test | ||||||
|     environment: |     environment: | ||||||
|       - PGPASSWORD=${DOCKER_TEST_DB_ROOT_PASSWORD} |       - PGPASSWORD=${DOCKER_TEST_DB_ROOT_PASSWORD} | ||||||
| @@ -65,7 +65,7 @@ services: | |||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|  |  | ||||||
|   redis: |   redis: | ||||||
|     image: redis:6 |     image: redis:7 | ||||||
|     container_name: toby-redis |     container_name: toby-redis | ||||||
|     ports: |     ports: | ||||||
|       - ${FORWARD_REDIS_PORT:-6379}:6379 |       - ${FORWARD_REDIS_PORT:-6379}:6379 | ||||||
| @@ -76,7 +76,7 @@ services: | |||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|  |  | ||||||
|   node: |   node: | ||||||
|     image: node:17.2.0-alpine3.14 |     image: node:18.1.0-alpine3.14 | ||||||
|     container_name: toby-node |     container_name: toby-node | ||||||
|     working_dir: /application |     working_dir: /application | ||||||
|     volumes: |     volumes: | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| FROM ghcr.io/blumilksoftware/php:8.1 | FROM ghcr.io/blumilksoftware/php:8.1 | ||||||
|  |  | ||||||
| ARG XDEBUG_VERSION=3.1.2 | ARG XDEBUG_VERSION=3.1.4 | ||||||
| ARG INSTALL_XDEBUG=false | ARG INSTALL_XDEBUG=false | ||||||
|  |  | ||||||
| RUN if [ ${INSTALL_XDEBUG} = true ]; then \ | RUN if [ ${INSTALL_XDEBUG} = true ]; then \ | ||||||
|   | |||||||
							
								
								
									
										2094
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2094
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										29
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								package.json
									
									
									
									
									
								
							| @@ -13,28 +13,28 @@ | |||||||
|         "postinstall": "npm run prod" |         "postinstall": "npm run prod" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@headlessui/vue": "^1.6.0", |         "@headlessui/vue": "^1.6.1", | ||||||
|         "@heroicons/vue": "^1.0.6", |         "@heroicons/vue": "^1.0.6", | ||||||
|         "@inertiajs/inertia": "^0.11.0", |         "@inertiajs/inertia": "^0.11.0", | ||||||
|         "@inertiajs/inertia-vue3": "^0.6.0", |         "@inertiajs/inertia-vue3": "^0.6.0", | ||||||
|         "@inertiajs/progress": "^0.2.7", |         "@inertiajs/progress": "^0.2.7", | ||||||
|         "@tailwindcss/forms": "^0.5.0", |         "@tailwindcss/forms": "^0.5.1", | ||||||
|         "@tailwindcss/line-clamp": "^0.3.1", |         "@tailwindcss/line-clamp": "^0.4.0", | ||||||
|         "@tailwindcss/typography": "^0.5.2", |         "@tailwindcss/typography": "^0.5.2", | ||||||
|         "@vue/compiler-sfc": "^3.2.31", |         "@vue/compiler-sfc": "^3.2.33", | ||||||
|         "autoprefixer": "^10.4.4", |         "autoprefixer": "^10.4.7", | ||||||
|         "axios": "^0.26.1", |         "axios": "^0.27.2", | ||||||
|         "echarts": "^5.3.2", |         "echarts": "^5.3.2", | ||||||
|         "eslit": "^6.0.0", |         "eslit": "^6.0.0", | ||||||
|         "flatpickr": "^4.6.11", |         "flatpickr": "^4.6.13", | ||||||
|         "laravel-mix": "^6.0.43", |         "laravel-mix": "^6.0.43", | ||||||
|         "lodash": "^4.17.21", |         "lodash": "^4.17.21", | ||||||
|         "luxon": "^2.3.1", |         "luxon": "^2.3.2", | ||||||
|         "postcss": "^8.4.12", |         "postcss": "^8.4.13", | ||||||
|         "tailwindcss": "^3.0.23", |         "tailwindcss": "^3.0.24", | ||||||
|         "vue": "^3.2.31", |         "vue": "^3.2.33", | ||||||
|         "vue-echarts": "^6.0.2", |         "vue-echarts": "^6.0.2", | ||||||
|         "vue-flatpickr-component": "^9.0.5", |         "vue-flatpickr-component": "^9.0.6", | ||||||
|         "vue-loader": "^17.0.0", |         "vue-loader": "^17.0.0", | ||||||
|         "vue-material-design-icons": "^5.0.0", |         "vue-material-design-icons": "^5.0.0", | ||||||
|         "vue-toastification": "^2.0.0-rc.5", |         "vue-toastification": "^2.0.0-rc.5", | ||||||
| @@ -42,8 +42,7 @@ | |||||||
|         "vuedraggable": "^4.1.0" |         "vuedraggable": "^4.1.0" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "eslint": "^8.12.0", |         "eslint": "^8.14.0", | ||||||
|         "eslint-plugin-tailwindcss": "^3.5.0", |         "eslint-plugin-vue": "^8.7.1" | ||||||
|         "eslint-plugin-vue": "^8.5.0" |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import HandHeartOutlineIcon from 'vue-material-design-icons/HandHeartOutline.vue | |||||||
| import CalendarCheckIcon from 'vue-material-design-icons/CalendarCheck.vue' | import CalendarCheckIcon from 'vue-material-design-icons/CalendarCheck.vue' | ||||||
| import MedicalBagIcon from 'vue-material-design-icons/MedicalBag.vue' | import MedicalBagIcon from 'vue-material-design-icons/MedicalBag.vue' | ||||||
| import CalendarRemoveIcon from 'vue-material-design-icons/CalendarRemove.vue' | import CalendarRemoveIcon from 'vue-material-design-icons/CalendarRemove.vue' | ||||||
| import LaptopIcon from 'vue-material-design-icons/Laptop.vue' | import HomeCityIcon from 'vue-material-design-icons/HomeCity.vue' | ||||||
|  |  | ||||||
| const types = [ | const types = [ | ||||||
|   { |   { | ||||||
| @@ -43,8 +43,8 @@ const types = [ | |||||||
|     text: 'Urlop szkoleniowy', |     text: 'Urlop szkoleniowy', | ||||||
|     value: 'training_vacation', |     value: 'training_vacation', | ||||||
|     icon: HumanMaleBoardIcon, |     icon: HumanMaleBoardIcon, | ||||||
|     color: 'text-blumilk-500', |     color: 'text-indigo-500', | ||||||
|     border: 'border-blumilk-500', |     border: 'border-indigo-500', | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     text: 'Urlop bezpłatny', |     text: 'Urlop bezpłatny', | ||||||
| @@ -84,9 +84,9 @@ const types = [ | |||||||
|   { |   { | ||||||
|     text: 'Praca zdalna', |     text: 'Praca zdalna', | ||||||
|     value: 'home_office', |     value: 'home_office', | ||||||
|     icon: LaptopIcon, |     icon: HomeCityIcon, | ||||||
|     color: 'text-fuchsia-500', |     color: 'text-lime-500', | ||||||
|     border: 'border-fuchsia-500', |     border: 'border-lime-500', | ||||||
|   }, |   }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ | |||||||
|         :remote-days="remoteDays.data" |         :remote-days="remoteDays.data" | ||||||
|       /> |       /> | ||||||
|       <UpcomingHolidays |       <UpcomingHolidays | ||||||
|         v-if="years.current.year === years.selected.year" |         v-if="years.current.year === years.selected.year && holidays.data.length" | ||||||
|         :holidays="holidays.data" |         :holidays="holidays.data" | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <InertiaHead title="Dodaj dzień wolny" /> |   <InertiaHead title="Dodaj dzień wolny" /> | ||||||
|   <div class="bg-white shadow-md"> |   <div class="mx-auto w-full max-w-7xl bg-white shadow-md"> | ||||||
|     <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|       <h2 class="text-lg font-medium leading-6 text-gray-900"> |       <h2 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|         Dodaj dzień wolny |         Dodaj dzień wolny | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <InertiaHead title="Dodawanie użytkownika" /> |   <InertiaHead title="Dodawanie użytkownika" /> | ||||||
|   <div class="bg-white shadow-md"> |   <div class="mx-auto w-full max-w-7xl bg-white shadow-md"> | ||||||
|     <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|       <h2 class="text-lg font-medium leading-6 text-gray-900"> |       <h2 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|         Dodaj użytkownika |         Dodaj użytkownika | ||||||
| @@ -234,6 +234,52 @@ | |||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |       <div class="items-center py-4 sm:grid sm:grid-cols-3"> | ||||||
|  |         <label | ||||||
|  |           for="slackId" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Slack ID | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:col-span-2 sm:mt-0"> | ||||||
|  |           <input | ||||||
|  |             id="position" | ||||||
|  |             v-model="form.slackId" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg rounded-md shadow-sm sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.slackId, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.slackId }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.slackId" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.slackId }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="items-center py-4 sm:grid sm:grid-cols-3"> | ||||||
|  |         <label | ||||||
|  |           for="birthday" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data urodzenia | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:col-span-2 sm:mt-0"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="birthday" | ||||||
|  |             v-model="form.birthday" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg rounded-md shadow-sm sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.birthday, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.birthday }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.birthday" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.birthday }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|       <div class="flex justify-end py-3"> |       <div class="flex justify-end py-3"> | ||||||
|         <div class="space-x-3"> |         <div class="space-x-3"> | ||||||
|           <InertiaLink |           <InertiaLink | ||||||
| @@ -274,6 +320,8 @@ const form = useForm({ | |||||||
|   role: props.roles[0], |   role: props.roles[0], | ||||||
|   position: null, |   position: null, | ||||||
|   employmentDate: null, |   employmentDate: null, | ||||||
|  |   birthday: null, | ||||||
|  |   slackId: null, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| function createUser() { | function createUser() { | ||||||
|   | |||||||
| @@ -241,6 +241,52 @@ | |||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |       <div class="items-center py-4 sm:grid sm:grid-cols-3"> | ||||||
|  |         <label | ||||||
|  |           for="birthday" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data urodzenia | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:col-span-2 sm:mt-0"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="birthday" | ||||||
|  |             v-model="form.birthday" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg rounded-md shadow-sm sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.birthday, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.birthday }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.birthday" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.birthday }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="items-center py-4 sm:grid sm:grid-cols-3"> | ||||||
|  |         <label | ||||||
|  |           for="slackId" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Slack ID | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:col-span-2 sm:mt-0"> | ||||||
|  |           <input | ||||||
|  |             id="position" | ||||||
|  |             v-model="form.slackId" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg rounded-md shadow-sm sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.slackId, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.slackId }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.slackId" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.slackId }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|       <div class="flex justify-end py-3"> |       <div class="flex justify-end py-3"> | ||||||
|         <div class="space-x-3"> |         <div class="space-x-3"> | ||||||
|           <InertiaLink |           <InertiaLink | ||||||
| @@ -282,6 +328,8 @@ const form = useForm({ | |||||||
|   position: props.user.position, |   position: props.user.position, | ||||||
|   employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), |   employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), | ||||||
|   employmentDate: props.user.employmentDate, |   employmentDate: props.user.employmentDate, | ||||||
|  |   birthday: props.user.birthday, | ||||||
|  |   slackId: props.user.slackId, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| function editUser() { | function editUser() { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <InertiaHead title="Złóż wniosek" /> |   <InertiaHead title="Złóż wniosek" /> | ||||||
|   <div class="grid grid-cols-1 gap-4 items-start xl:grid-cols-3 xl:gap-8"> |   <div :class="[stats.limit > 0 ? ' grid grid-cols-1 gap-4 items-start xl:grid-cols-3 xl:gap-8' : 'mx-auto w-full max-w-7xl']"> | ||||||
|     <div class="flex flex-col h-full bg-white shadow-md xl:col-span-2"> |     <div class="flex flex-col h-full bg-white shadow-md xl:col-span-2"> | ||||||
|       <div class="p-4 sm:px-6"> |       <div class="p-4 sm:px-6"> | ||||||
|         <h2 class="text-lg font-medium leading-6 text-gray-900"> |         <h2 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
| @@ -306,7 +306,10 @@ | |||||||
|         </div> |         </div> | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|     <div class="h-full bg-white shadow-md"> |     <div | ||||||
|  |       v-if="stats.limit > 0 " | ||||||
|  |       class="h-full bg-white shadow-md" | ||||||
|  |     > | ||||||
|       <div class="p-4 sm:px-6"> |       <div class="p-4 sm:px-6"> | ||||||
|         <h2 class="text-lg font-medium leading-6 text-gray-900"> |         <h2 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|           <span v-if="auth.user.id !== form.user.id"> |           <span v-if="auth.user.id !== form.user.id"> | ||||||
|   | |||||||
| @@ -132,9 +132,11 @@ | |||||||
|           </tr> |           </tr> | ||||||
|         </thead> |         </thead> | ||||||
|         <tbody class="bg-white divide-y divide-gray-100"> |         <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|           <tr |           <InertiaLink | ||||||
|             v-for="request in requests.data" |             v-for="request in requests.data" | ||||||
|             :key="request.id" |             :key="request.id" | ||||||
|  |             :href="`/vacation/requests/${request.id}`" | ||||||
|  |             as="tr" | ||||||
|             class="relative hover:bg-blumilk-25" |             class="relative hover:bg-blumilk-25" | ||||||
|           > |           > | ||||||
|             <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |             <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||||
| @@ -167,12 +169,8 @@ | |||||||
|               > |               > | ||||||
|                 <ChevronRightIcon class="block w-6 h-6 fill-blumilk-500" /> |                 <ChevronRightIcon class="block w-6 h-6 fill-blumilk-500" /> | ||||||
|               </InertiaLink> |               </InertiaLink> | ||||||
|               <InertiaLink |  | ||||||
|                 :href="`/vacation/requests/${request.id}`" |  | ||||||
|                 class="absolute inset-0 focus:outline-blumilk-500" |  | ||||||
|               /> |  | ||||||
|             </td> |             </td> | ||||||
|           </tr> |           </InertiaLink> | ||||||
|           <tr v-if="! requests.data.length"> |           <tr v-if="! requests.data.length"> | ||||||
|             <td |             <td | ||||||
|               colspan="100%" |               colspan="100%" | ||||||
|   | |||||||
| @@ -211,10 +211,12 @@ | |||||||
|           </tr> |           </tr> | ||||||
|         </thead> |         </thead> | ||||||
|         <tbody class="bg-white divide-y divide-gray-100"> |         <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|           <tr |           <InertiaLink | ||||||
|             v-for="request in requests.data" |             v-for="request in requests.data" | ||||||
|             :key="request.id" |             :key="request.id" | ||||||
|             class="relative hover:bg-blumilk-25" |             as="tr" | ||||||
|  |             :href="`/vacation/requests/${request.id}`" | ||||||
|  |             class="relative hover:bg-blumilk-25 hover:cursor-pointer" | ||||||
|           > |           > | ||||||
|             <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |             <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||||
|               <InertiaLink |               <InertiaLink | ||||||
| @@ -261,12 +263,8 @@ | |||||||
|               > |               > | ||||||
|                 <ChevronRightIcon class="block w-6 h-6 fill-blumilk-500" /> |                 <ChevronRightIcon class="block w-6 h-6 fill-blumilk-500" /> | ||||||
|               </InertiaLink> |               </InertiaLink> | ||||||
|               <InertiaLink |  | ||||||
|                 :href="`/vacation/requests/${request.id}`" |  | ||||||
|                 class="absolute inset-0 focus:outline-blumilk-500" |  | ||||||
|               /> |  | ||||||
|             </td> |             </td> | ||||||
|           </tr> |           </InertiaLink> | ||||||
|           <tr v-if="! requests.data.length"> |           <tr v-if="! requests.data.length"> | ||||||
|             <td |             <td | ||||||
|               colspan="100%" |               colspan="100%" | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ | |||||||
|               </dt> |               </dt> | ||||||
|               <dd |               <dd | ||||||
|                 v-if="request.comment != null" |                 v-if="request.comment != null" | ||||||
|                 class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0" |                 class="mt-1 text-sm text-gray-900 break-all sm:col-span-2 sm:mt-0" | ||||||
|               > |               > | ||||||
|                 {{ request.comment }} |                 {{ request.comment }} | ||||||
|               </dd> |               </dd> | ||||||
| @@ -89,7 +89,7 @@ | |||||||
|               </dd> |               </dd> | ||||||
|             </div> |             </div> | ||||||
|             <div |             <div | ||||||
|               v-if="VacationType.isVacation" |               v-if="request.isVacation" | ||||||
|               class="py-5 px-4 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6" |               class="py-5 px-4 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6" | ||||||
|             > |             > | ||||||
|               <dt class="flex items-center text-sm font-medium text-gray-500"> |               <dt class="flex items-center text-sm font-medium text-gray-500"> | ||||||
| @@ -100,7 +100,7 @@ | |||||||
|                   <li class="flex justify-between items-center py-3 pr-4 pl-3 text-sm"> |                   <li class="flex justify-between items-center py-3 pr-4 pl-3 text-sm"> | ||||||
|                     <div class="flex flex-1 items-center w-0"> |                     <div class="flex flex-1 items-center w-0"> | ||||||
|                       <PaperClipIcon class="shrink-0 w-5 h-5 text-gray-400" /> |                       <PaperClipIcon class="shrink-0 w-5 h-5 text-gray-400" /> | ||||||
|                       <span class="flex-1 ml-2 w-0 truncate"> wniosek_urlopowy.pdf </span> |                       <span class="flex-1 ml-2 w-0 truncate">wniosek.pdf</span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="shrink-0 ml-4"> |                     <div class="shrink-0 ml-4"> | ||||||
|                       <a |                       <a | ||||||
|   | |||||||
| @@ -14,18 +14,24 @@ | |||||||
|             class="py-5" |             class="py-5" | ||||||
|           > |           > | ||||||
|             <div class="relative focus-within:ring-2 focus-within:ring-blumilk-500"> |             <div class="relative focus-within:ring-2 focus-within:ring-blumilk-500"> | ||||||
|               <h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500"> |               <div class="flex flex-row"> | ||||||
|                 <InertiaLink |                 <h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500"> | ||||||
|                   :href="`/vacation/requests/${request.id}`" |                   <InertiaLink | ||||||
|                   class="hover:underline focus:outline-none" |                     :href="`/vacation/requests/${request.id}`" | ||||||
|                 > |                     class="hover:underline focus:outline-none" | ||||||
|                   <span class="absolute inset-0" /> |                   > | ||||||
|                   Wniosek o {{ findType(request.type).text.toLowerCase() }} |                     <span class="absolute inset-0" /> | ||||||
|                   [{{ request.name }}] |                     Wniosek [{{ request.name }}] | ||||||
|                 </InertiaLink> |                   </InertiaLink> | ||||||
|               </h3> |                 </h3> | ||||||
|               <p class="mt-1 text-sm text-gray-600"> |                 <div> | ||||||
|                 {{ request.from }} - {{ request.to }} |                   <div class="ml-2 text-sm text-gray-600"> | ||||||
|  |                     {{ request.from }} - {{ request.to }} | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <p class="mt-2 text-sm text-gray-600"> | ||||||
|  |                 <VacationType :type="request.type" /> | ||||||
|               </p> |               </p> | ||||||
|               <div class="mt-3 text-sm text-gray-600"> |               <div class="mt-3 text-sm text-gray-600"> | ||||||
|                 <div class="flex"> |                 <div class="flex"> | ||||||
| @@ -66,11 +72,10 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup> | <script setup> | ||||||
| import useVacationTypeInfo from '@/Composables/vacationTypeInfo' | import VacationType from '@/Shared/VacationType' | ||||||
|  |  | ||||||
| defineProps({ | defineProps({ | ||||||
|   requests: Object, |   requests: Object, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const { findType } = useVacationTypeInfo() |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ | |||||||
|               {{ holiday.name }} |               {{ holiday.name }} | ||||||
|             </p> |             </p> | ||||||
|             <p class="text-sm text-gray-500"> |             <p class="text-sm text-gray-500"> | ||||||
|               {{ holiday.displayDate }} |               {{ holiday.displayDate }} ({{ holiday.dayOfWeek }}) | ||||||
|             </p> |             </p> | ||||||
|           </div> |           </div> | ||||||
|         </li> |         </li> | ||||||
|   | |||||||
| @@ -14,18 +14,22 @@ | |||||||
|             class="py-5" |             class="py-5" | ||||||
|           > |           > | ||||||
|             <div class="relative focus-within:ring-2 focus-within:ring-blumilk-500"> |             <div class="relative focus-within:ring-2 focus-within:ring-blumilk-500"> | ||||||
|               <h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500"> |               <div class="flex flex-row"> | ||||||
|                 <InertiaLink |                 <h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500"> | ||||||
|                   :href="`/vacation/requests/${request.id}`" |                   <InertiaLink | ||||||
|                   class="hover:underline focus:outline-none" |                     :href="`/vacation/requests/${request.id}`" | ||||||
|                 > |                     class="hover:underline focus:outline-none" | ||||||
|                   <span class="absolute inset-0" /> |                   > | ||||||
|                   Wniosek o {{ findType(request.type).text.toLowerCase() }} |                     <span class="absolute inset-0" /> | ||||||
|                   [{{ request.name }}] |                     Wniosek [{{ request.name }}] | ||||||
|                 </InertiaLink> |                   </InertiaLink> | ||||||
|               </h3> |                 </h3> | ||||||
|               <p class="mt-1 text-sm text-gray-600"> |                 <div class="ml-2 text-sm text-gray-600"> | ||||||
|                 {{ request.from }} - {{ request.to }} |                   {{ request.from }} - {{ request.to }} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <p class="mt-2 text-sm text-gray-600"> | ||||||
|  |                 <VacationType :type="request.type" /> | ||||||
|               </p> |               </p> | ||||||
|               <p class="mt-2 text-sm text-gray-600"> |               <p class="mt-2 text-sm text-gray-600"> | ||||||
|                 <Status :status="request.state" /> |                 <Status :status="request.state" /> | ||||||
| @@ -52,12 +56,11 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup> | <script setup> | ||||||
| import useVacationTypeInfo from '@/Composables/vacationTypeInfo' |  | ||||||
| import Status from '@/Shared/Status' | import Status from '@/Shared/Status' | ||||||
|  | import VacationType from '@/Shared/VacationType' | ||||||
|  |  | ||||||
| defineProps({ | defineProps({ | ||||||
|   requests: Object, |   requests: Object, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const { findType } = useVacationTypeInfo() |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ | |||||||
|           </dt> |           </dt> | ||||||
|         </div> |         </div> | ||||||
|         <div class="py-5 px-4 bg-white shadow-md sm:p-6"> |         <div class="py-5 px-4 bg-white shadow-md sm:p-6"> | ||||||
|           <dt class="mt-1 text-4xl font-semibold text-fuchsia-700"> |           <dt class="mt-1 text-4xl font-semibold text-lime-500"> | ||||||
|             {{ stats.homeOffice }} |             {{ stats.homeOffice }} | ||||||
|           </dt> |           </dt> | ||||||
|           <dd class="font-medium text-gray-700 truncate text-md"> |           <dd class="font-medium text-gray-700 truncate text-md"> | ||||||
|   | |||||||
| @@ -23,11 +23,11 @@ | |||||||
|   "cancelled": "anulowany", |   "cancelled": "anulowany", | ||||||
|   "rejected": "odrzucony", |   "rejected": "odrzucony", | ||||||
|   "approved": "zatwierdzony", |   "approved": "zatwierdzony", | ||||||
|   "You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.", |   "You have pending vacation request in this range.": "Masz oczekujący wniosek w tym zakresie dat.", | ||||||
|   "You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.", |   "You have approved vacation request in this range.": "Masz zaakceptowany wniosek w tym zakresie dat.", | ||||||
|   "Vacation limit has been exceeded.": "Limit urlopu został przekroczony.", |   "Vacation limit has been exceeded.": "Limit urlopu został przekroczony.", | ||||||
|   "Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.", |   "Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.", | ||||||
|   "The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku.", |   "The vacation request cannot be created at the turn of the year.": "Wniosek nie może zostać złożony na przełomie roku.", | ||||||
|   "User has been created.": "Użytkownik został utworzony.", |   "User has been created.": "Użytkownik został utworzony.", | ||||||
|   "User has been updated.": "Użytkownik został zaktualizowany.", |   "User has been updated.": "Użytkownik został zaktualizowany.", | ||||||
|   "User has been deleted.": "Użytkownik został usunięty.", |   "User has been deleted.": "Użytkownik został usunięty.", | ||||||
| @@ -37,11 +37,11 @@ | |||||||
|   "Holiday has been deleted.": "Dzień wolny został usunięty.", |   "Holiday has been deleted.": "Dzień wolny został usunięty.", | ||||||
|   "Selected year period has been changed.": "Wybrany rok został zmieniony.", |   "Selected year period has been changed.": "Wybrany rok został zmieniony.", | ||||||
|   "Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.", |   "Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.", | ||||||
|   "Vacation request has been created.": "Wniosek urlopowy został utworzony.", |   "Vacation request has been created.": "Wniosek został utworzony.", | ||||||
|   "Vacation request has been accepted.": "Wniosek urlopowy został zaakceptowany.", |   "Vacation request has been accepted.": "Wniosek został zaakceptowany.", | ||||||
|   "Vacation request has been approved.": "Wniosek urlopowy został zatwierdzony.", |   "Vacation request has been approved.": "Wniosek został zatwierdzony.", | ||||||
|   "Vacation request has been rejected.": "Wniosek urlopowy został odrzucony.", |   "Vacation request has been rejected.": "Wniosek został odrzucony.", | ||||||
|   "Vacation request has been cancelled.": "Wniosek urlopowy został anulowany.", |   "Vacation request has been cancelled.": "Wniosek został anulowany.", | ||||||
|   "Sum:": "Suma:", |   "Sum:": "Suma:", | ||||||
|   "Date": "Data", |   "Date": "Data", | ||||||
|   "Day of week": "Dzień tygodnia", |   "Day of week": "Dzień tygodnia", | ||||||
| @@ -56,7 +56,7 @@ | |||||||
|   "All rights reserved.": "Wszelkie prawa zastrzeżone", |   "All rights reserved.": "Wszelkie prawa zastrzeżone", | ||||||
|   "Show vacation request": "Pokaż wniosek", |   "Show vacation request": "Pokaż wniosek", | ||||||
|   "Vacation request :title has been created" : "Wniosek :title został utworzony", |   "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 successfully.": "Wniosek :title użytkownika :requester został utworzony pomyślnie.", | ||||||
|   "Vacation type: :type": "Rodzaj wniosku: :type", |   "Vacation type: :type": "Rodzaj wniosku: :type", | ||||||
|   "From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)", |   "From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)", | ||||||
|   "Click here for details": "Kliknij, aby zobaczyć szczegóły", |   "Click here for details": "Kliknij, aby zobaczyć szczegóły", | ||||||
| @@ -67,9 +67,11 @@ | |||||||
|   "Vacation request :title has been :status": "Wniosek :title został :status", |   "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.", |   "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", |   "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 created.": "Klucz nr :number został utworzony.", | ||||||
|   "Key no :number has been deleted.": "Klucz nr :number został usunięty.", |   "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.", |   "Key no :number has been taken from :user.": "Klucz nr :number został zabrany użytkownikowi :user.", | ||||||
|   "Key no :number has been given to :user.": "Klucz nr :number został przekazany użytkownikowi :user." |   "Key no :number has been given to :user.": "Klucz nr :number został przekazany użytkownikowi :user.", | ||||||
|  |   ":sender gives key no :key to :recipient": ":sender przekazuje klucz nr :key :recipient", | ||||||
|  |   ":recipient takes key no :key from :sender": ":recipient zabiera klucz nr :key :sender" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ | |||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div class="main"> |     <div class="main"> | ||||||
|         <h2>Wniosek o urlop</h2> |         <h2>Wniosek</h2> | ||||||
|         <p class="content"> |         <p class="content"> | ||||||
|             Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }} |             Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }} | ||||||
|             do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. {{ $vacationRequest->vacations()->count() }} dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}. |             do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. {{ $vacationRequest->vacations()->count() }} dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}. | ||||||
|   | |||||||
| @@ -7,6 +7,9 @@ use Toby\Infrastructure\Http\Controllers\Api\CalculateUserUnavailableDaysControl | |||||||
| use Toby\Infrastructure\Http\Controllers\Api\CalculateUserVacationStatsController; | use Toby\Infrastructure\Http\Controllers\Api\CalculateUserVacationStatsController; | ||||||
| use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController; | use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController; | ||||||
| use Toby\Infrastructure\Http\Controllers\Api\GetAvailableVacationTypesController; | use Toby\Infrastructure\Http\Controllers\Api\GetAvailableVacationTypesController; | ||||||
|  | use Toby\Infrastructure\Slack\Controller as SlackCommandController; | ||||||
|  |  | ||||||
|  | Route::post("slack", [SlackCommandController::class, "getResponse"]); | ||||||
|  |  | ||||||
| Route::middleware("auth:sanctum")->group(function (): void { | Route::middleware("auth:sanctum")->group(function (): void { | ||||||
|     Route::post("vacation/calculate-days", CalculateVacationDaysController::class); |     Route::post("vacation/calculate-days", CalculateVacationDaysController::class); | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ declare(strict_types=1); | |||||||
| namespace Tests\Feature; | namespace Tests\Feature; | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Testing\DatabaseMigrations; | use Illuminate\Foundation\Testing\DatabaseMigrations; | ||||||
|  | use Illuminate\Support\Facades\Notification; | ||||||
| use Inertia\Testing\AssertableInertia as Assert; | use Inertia\Testing\AssertableInertia as Assert; | ||||||
| use Tests\FeatureTestCase; | use Tests\FeatureTestCase; | ||||||
| use Toby\Eloquent\Models\Key; | use Toby\Eloquent\Models\Key; | ||||||
| @@ -14,6 +15,13 @@ class KeyTest extends FeatureTestCase | |||||||
| { | { | ||||||
|     use DatabaseMigrations; |     use DatabaseMigrations; | ||||||
|  |  | ||||||
|  |     protected function setUp(): void | ||||||
|  |     { | ||||||
|  |         parent::setUp(); | ||||||
|  |  | ||||||
|  |         Notification::fake(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function testUserCanSeeKeyList(): void |     public function testUserCanSeeKeyList(): void | ||||||
|     { |     { | ||||||
|         Key::factory()->count(10)->create(); |         Key::factory()->count(10)->create(); | ||||||
|   | |||||||
							
								
								
									
										82
									
								
								tests/Unit/SendDailySummaryToSlackTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/Unit/SendDailySummaryToSlackTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Tests\Unit; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Testing\RefreshDatabase; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
|  | use Tests\TestCase; | ||||||
|  | use Tests\Traits\InteractsWithYearPeriods; | ||||||
|  | use Toby\Eloquent\Models\Holiday; | ||||||
|  | use Toby\Infrastructure\Console\Commands\SendDailySummaryToSlack; | ||||||
|  |  | ||||||
|  | class SendDailySummaryToSlackTest extends TestCase | ||||||
|  | { | ||||||
|  |     use RefreshDatabase; | ||||||
|  |     use InteractsWithYearPeriods; | ||||||
|  |  | ||||||
|  |     protected function setUp(): void | ||||||
|  |     { | ||||||
|  |         parent::setUp(); | ||||||
|  |  | ||||||
|  |         Http::fake(); | ||||||
|  |         $this->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 testCommandDoesntSendMessageIfWeekend(): void | ||||||
|  |     { | ||||||
|  |         $weekend = Carbon::create(2022, 4, 23); | ||||||
|  |         $this->assertTrue($weekend->isWeekend()); | ||||||
|  |  | ||||||
|  |         $this->travelTo($weekend); | ||||||
|  |  | ||||||
|  |         $this->artisan(SendDailySummaryToSlack::class) | ||||||
|  |             ->execute(); | ||||||
|  |  | ||||||
|  |         Http::assertNothingSent(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function testCommandDoesntSendMessageIfHoliday(): 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										130
									
								
								tests/Unit/SendVacationRequestSummariesTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								tests/Unit/SendVacationRequestSummariesTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Tests\Unit; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Testing\RefreshDatabase; | ||||||
|  | use Illuminate\Support\Facades\Notification; | ||||||
|  | use Tests\TestCase; | ||||||
|  | use Tests\Traits\InteractsWithYearPeriods; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestsSummaryNotification; | ||||||
|  | use Toby\Domain\States\VacationRequest\Approved; | ||||||
|  | use Toby\Domain\States\VacationRequest\Cancelled; | ||||||
|  | use Toby\Domain\States\VacationRequest\Created; | ||||||
|  | use Toby\Domain\States\VacationRequest\Rejected; | ||||||
|  | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  | use Toby\Infrastructure\Console\Commands\SendVacationRequestSummariesToApprovers; | ||||||
|  |  | ||||||
|  | class SendVacationRequestSummariesTest extends TestCase | ||||||
|  | { | ||||||
|  |     use RefreshDatabase; | ||||||
|  |     use InteractsWithYearPeriods; | ||||||
|  |  | ||||||
|  |     protected function setUp(): void | ||||||
|  |     { | ||||||
|  |         parent::setUp(); | ||||||
|  |  | ||||||
|  |         Notification::fake(); | ||||||
|  |         $this->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); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user